diff --git a/.github/rulesets/Immutable-Tags.json b/.github/rulesets/Immutable-Tags.json new file mode 100644 index 00000000..53739afe --- /dev/null +++ b/.github/rulesets/Immutable-Tags.json @@ -0,0 +1,19 @@ +{ + "name": "Immutable-Tags", + "target": "tag", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["~ALL"], + "exclude": [] + } + }, + "bypass_actors": [], + "rules": [ + {"type": "creation"}, + {"type": "deletion"}, + {"type": "non_fast_forward"}, + {"type": "update"}, + {"type": "required_signatures"} + ] +} diff --git a/.github/rulesets/Optimus-Branch.json b/.github/rulesets/Optimus-Branch.json new file mode 100644 index 00000000..03ad4882 --- /dev/null +++ b/.github/rulesets/Optimus-Branch.json @@ -0,0 +1,44 @@ +{ + "name": "Optimus-Branch", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "bypass_actors": [], + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "required_signatures" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 2, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_review_thread_resolution": true, + "require_extra_approval_for_unattributed_changes": true, + "required_reviewers": [], + "allowed_merge_methods": [] + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [] + } + } + ] +} diff --git a/.github/rulesets/tag-protection.json b/.github/rulesets/tag-protection.json deleted file mode 100755 index fb1ca507..00000000 --- a/.github/rulesets/tag-protection.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "Immutable Release Tags", - "target": "tag", - "enforcement": "active", - "bypass_actors": [ - { - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "always" - } - ], - "conditions": { - "ref_name": { - "exclude": [], - "include": [ - "refs/tags/*" - ] - } - }, - "rules": [ - { - "type": "creation" - }, - { - "type": "update" - }, - { - "type": "deletion" - }, - { - "type": "required_signatures" - } - ] -} diff --git a/.github/settings.yml b/.github/settings.yml index e915efc0..094206a2 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -158,19 +158,3 @@ labels: # increased"), which is why every workflow here fails with zero steps while the # estate's public repos run normally. Repopulate this list โ€” one context at a # time, each pinned to a job id, each verified green โ€” once Actions can run. -branches: - - name: "main" - protection: - required_pull_request_reviews: - required_approving_review_count: 1 - dismiss_stale_reviews: true - require_code_owner_reviews: true - required_conversation_resolution: true - required_status_checks: - strict: true - contexts: ["estate-audit"] - enforce_admins: true - required_signatures: true - restrictions: null - allow_force_pushes: false - allow_deletions: false diff --git a/.github/workflows/a2ml-validate.yml b/.github/workflows/a2ml-validate.yml new file mode 100644 index 00000000..720030a7 --- /dev/null +++ b/.github/workflows/a2ml-validate.yml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: MPL-2.0 +name: "๐ŸŸก CHECK: A2ML Manifest Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + a2ml-validate: + name: Validate A2ML manifests + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for A2ML files + id: detect + run: | + COUNT=$(find . -name '*.a2ml' -not -path './.git/*' | wc -l) + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ]; then + echo "::warning::No .a2ml manifest files found. Every RSR repo should have 0-AI-MANIFEST.a2ml" + fi + + - name: "๐ŸŸก CHECK: Validate A2ML manifests" + if: steps.detect.outputs.count > 0 + uses: hyperpolymath/deed-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + A2ML_COUNT="${{ steps.detect.outputs.count }}" + if [ "$A2ML_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## A2ML Validation + + :warning: **No .a2ml files found.** Every RSR-compliant repo should have at least `0-AI-MANIFEST.a2ml`. + + Create one with: `a2mliser init` or copy from [rsr-template-repo](https://github.com/hyperpolymath/rsr-template-repo). + EOF + else + echo "## A2ML Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Scanned **${A2ML_COUNT}** .a2ml file(s). See step output for details." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/boj-build.yml b/.github/workflows/build-notification.yml similarity index 97% rename from .github/workflows/boj-build.yml rename to .github/workflows/build-notification.yml index 13b1c232..1884f584 100644 --- a/.github/workflows/boj-build.yml +++ b/.github/workflows/build-notification.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c761e2b6..62069c69 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -34,13 +35,15 @@ jobs: build-mode: none steps: - name: Checkout - uses: actions/checkout@v7.0.1 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.8 + uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.37.8 + uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v3 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index f6622a59..d064cf0e 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -44,7 +45,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false permissions: - contents: write # needed to enable auto-merge + contents: read # needed to enable auto-merge pull-requests: write # needed to approve # NB: keep narrow โ€” do NOT add secrets: read or id-token: write here. jobs: diff --git a/.github/workflows/container-build.yml b/.github/workflows/docker-build.yml similarity index 97% rename from .github/workflows/container-build.yml rename to .github/workflows/docker-build.yml index 81fd885b..5440e784 100644 --- a/.github/workflows/container-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 8afe053f..0fe5a56f 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/dogfood-summary.yml b/.github/workflows/dogfood-summary.yml new file mode 100644 index 00000000..8b52296c --- /dev/null +++ b/.github/workflows/dogfood-summary.yml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: MPL-2.0 +name: "โ„น๏ธ ADVISORY: Dogfooding Compliance Scorecard" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + dogfood-summary: + name: "Dogfooding compliance summary" + runs-on: ubuntu-latest + timeout-minutes: 15 + if: always() + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Generate dogfooding scorecard + run: | + SCORE=0 + MAX=6 + + # A2ML manifest present? + if find . -name '*.a2ml' -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + A2ML_STATUS=":white_check_mark:" + else + A2ML_STATUS=":x:" + fi + + # K9 contracts present? + if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + K9_STATUS=":white_check_mark:" + else + K9_STATUS=":x:" + fi + + # .editorconfig present? + if [ -f ".editorconfig" ]; then + SCORE=$((SCORE + 1)) + EC_STATUS=":white_check_mark:" + else + EC_STATUS=":x:" + fi + + # Groove manifest or code? + if [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + GROOVE_STATUS=":white_check_mark:" + else + GROOVE_STATUS=":ballot_box_with_check:" + fi + + # VeriSimDB integration? + if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + VSDB_STATUS=":white_check_mark:" + else + VSDB_STATUS=":ballot_box_with_check:" + fi + + # eclexiaiser energy tracking? + if [ -f "eclexiaiser.toml" ]; then + SCORE=$((SCORE + 1)) + ECLEX_STATUS=":white_check_mark:" + else + ECLEX_STATUS=":ballot_box_with_check:" + fi + + cat <> "$GITHUB_STEP_SUMMARY" + ## Dogfooding Scorecard + + **Score: ${SCORE}/${MAX}** + + | Tool/Format | Status | Notes | + |-------------|--------|-------| + | A2ML manifest (0-AI-MANIFEST.a2ml) | ${A2ML_STATUS} | Required for all RSR repos | + | K9 contracts | ${K9_STATUS} | Required for repos with config files | + | .editorconfig | ${EC_STATUS} | Required for all repos | + | Groove endpoint | ${GROOVE_STATUS} | Required for service repos | + | VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos | + | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | + + --- + *Generated by the Dogfooding Compliance workflow.* + *Dogfooding is guinea pig fooding โ€” we test our tools on ourselves.* + EOF diff --git a/.github/workflows/wellknown-enforcement.yml b/.github/workflows/dot-wellknown-enforcement.yml similarity index 98% rename from .github/workflows/wellknown-enforcement.yml rename to .github/workflows/dot-wellknown-enforcement.yml index 3166cb4c..fd681267 100644 --- a/.github/workflows/wellknown-enforcement.yml +++ b/.github/workflows/dot-wellknown-enforcement.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/eclexiaiser-validate.yml b/.github/workflows/eclexiaiser-validate.yml new file mode 100644 index 00000000..9b2017f4 --- /dev/null +++ b/.github/workflows/eclexiaiser-validate.yml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: MPL-2.0 +name: "๐ŸŸก CHECK: Eclexiaiser Manifest Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + eclexiaiser-validate: + name: "๐ŸŸก CHECK: Validate eclexiaiser manifest" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check and validate eclexiaiser manifest + id: eclex + run: | + if [ ! -f "eclexiaiser.toml" ]; then + # Check if repo has a Containerfile โ€” if so, recommend eclexiaiser + if [ -f "Containerfile" ]; then + echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets." + fi + echo "has_manifest=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "has_manifest=true" >> "$GITHUB_OUTPUT" + + # Validate eclexiaiser.toml structure (bash + grep; NO Python per estate policy). + # Structural presence checks only โ€” deep schema validation is eclexiaiser's own job. + err=0 + grep -qE '^[[:space:]]*\[project\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::[project] section is required"; err=1; } + grep -qE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]+"' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::a non-empty name is required"; err=1; } + grep -qE '^[[:space:]]*\[\[(functions)\]\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::at least one [[functions]] entry is required"; err=1; } + if [ "$err" -ne 0 ]; then + exit 1 + fi + fns=$(grep -cE '^[[:space:]]*\[\[(functions)\]\]' eclexiaiser.toml) + echo "Valid: eclexiaiser.toml structure present (${fns} function block(s))" + + - name: "Write summary" + run: | + if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/empty-linter.yml b/.github/workflows/empty-linter.yml new file mode 100644 index 00000000..95a1bfd7 --- /dev/null +++ b/.github/workflows/empty-linter.yml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: MPL-2.0 +name: "๐Ÿ”ด GATE: Invisible Character Detection" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + empty-lint: + name: "๐Ÿ”ด GATE: Empty-linter (invisible characters)" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Scan for invisible characters + id: lint + run: | + RESULTS_FILE="$RUNNER_TEMP/empty-lint-results.bin" + BLOCKING_FILE="$RUNNER_TEMP/empty-lint-blocking-results.bin" + if ! scripts/check-invisible-characters.sh \ + "$GITHUB_WORKSPACE" "$RESULTS_FILE" "$BLOCKING_FILE"; then + echo "::error::Invisible-character scanner failed; refusing a partial pass" + exit 2 + fi + + FINDINGS=0 + while IFS= read -r -d '' filepath; do + FINDINGS=$((FINDINGS + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::warning file=${SAFE_PATH}::Invisible Unicode or C0 characters detected" + done < "$RESULTS_FILE" + + BLOCKING=0 + while IFS= read -r -d '' filepath; do + BLOCKING=$((BLOCKING + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::error file=${SAFE_PATH}::C0 control character or NUL byte detected" + done < "$BLOCKING_FILE" + + echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" + echo "blocking=$BLOCKING" >> "$GITHUB_OUTPUT" + echo "ready=true" >> "$GITHUB_OUTPUT" + + if [ "$BLOCKING" -gt 0 ]; then + echo "## Empty-linter: BLOCKED โ€” $BLOCKING file(s) contain C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + + - name: "Write summary" + run: | + if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then + FINDINGS="${{ steps.lint.outputs.findings }}" + if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY" + fi + else + echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/estate-rules.yml b/.github/workflows/estate-rules.yml index c8dede39..3fd46094 100644 --- a/.github/workflows/estate-rules.yml +++ b/.github/workflows/estate-rules.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -67,3 +68,18 @@ jobs: # Was written but wired to nothing, so it had never run in CI. - name: V-language gate self-test run: bash tests/workflows/check_no_vlang_test.sh + - name: Install verified Nickel 1.17.0 + run: | + mkdir -p "$RUNNER_TEMP/nickel-bin" + curl --proto '=https' --proto-redir '=https' --fail --silent --show-error --location \ + https://github.com/nickel-lang/nickel/releases/download/1.17.0/nickel-x86_64-linux \ + --output "$RUNNER_TEMP/nickel-bin/nickel" + echo "afcdfa6e0fff31760cf229e85997456c02c00b8b3b84ff38f897ac7b3f39ae34 $RUNNER_TEMP/nickel-bin/nickel" | sha256sum --check --strict + chmod +x "$RUNNER_TEMP/nickel-bin/nickel" + echo "$RUNNER_TEMP/nickel-bin" >> "$GITHUB_PATH" + - name: Evaluate session contracts + run: bash scripts/validate-session-contracts.sh + - name: Verify session envelope controls + run: bash tests/workflows/session_contracts_test.sh + - name: Verify mint cleanup controls + run: bash tests/workflows/mint_cleanup_test.sh diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 2f18507a..57cca5f8 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -19,4 +20,4 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@8f31a5a4ba591d544b65f91f6d78b136e07756f0 diff --git a/.github/workflows/groove-check.yml b/.github/workflows/groove-check.yml new file mode 100644 index 00000000..bb20ec9c --- /dev/null +++ b/.github/workflows/groove-check.yml @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: MPL-2.0 +name: "๐ŸŸก CHECK: Groove Protocol Compliance" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + groove-check: + name: "๐ŸŸก CHECK: Groove manifest check" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for Groove manifest + id: groove + run: | + # Check for static or dynamic Groove endpoints + HAS_MANIFEST="false" + HAS_GROOVE_CODE="false" + + if [ -f ".well-known/groove/manifest.json" ]; then + HAS_MANIFEST="true" + # Validate the manifest JSON + if ! jq empty .well-known/groove/manifest.json 2>/dev/null; then + echo "::error file=.well-known/groove/manifest.json::Invalid JSON in Groove manifest" + else + SVC_ID=$(jq -r '.service_id // "unknown"' .well-known/groove/manifest.json) + echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" + fi + fi + + # Check for Groove endpoint code (Rust, Elixir, Zig, V) + if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then + HAS_GROOVE_CODE="true" + fi + + # Check if this repo likely serves HTTP in production. Key on + # production HTTP-server framework markers only. A bare `TcpListener` + # is deliberately NOT a signal: it is dominated by test/utility use + # (e.g. a #[cfg(test)] loopback fake), so matching it produced a + # spurious groove nudge on client-only apps. A real hand-rolled Rust + # server still pairs the listener with `axum::serve`/`hyper::Server`, + # which are matched here. + HAS_SERVER="false" + if grep -rl 'Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web\|hyper::Server\|rocket::build\|warp::serve' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then + HAS_SERVER="true" + fi + + echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT" + echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT" + echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" + + if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then + echo "::warning::This repo has server code but no Groove endpoint. Add .well-known/groove/manifest.json for service discovery." + fi + + - name: "Write summary" + run: | + echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" + echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" + echo "| Static manifest (.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/guix-policy.yml b/.github/workflows/guix-policy.yml index b956ed43..0a414262 100644 --- a/.github/workflows/guix-policy.yml +++ b/.github/workflows/guix-policy.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index dfa571c3..9e337ac0 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -28,4 +29,4 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@cc58c0cb23f73fc2019ce85a56a468e5248a93b3 diff --git a/.github/workflows/k9-validate.yml b/.github/workflows/k9-validate.yml new file mode 100644 index 00000000..8706a291 --- /dev/null +++ b/.github/workflows/k9-validate.yml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: MPL-2.0 +name: "๐ŸŸก CHECK: K9 Contract Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + k9-validate: + name: "๐ŸŸก CHECK: Validate K9 contracts" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for K9 files + id: detect + run: | + COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) + CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ + -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ + -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) + echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" + echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then + echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." + fi + + - name: "๐ŸŸก CHECK: Validate K9 contracts" + if: steps.detect.outputs.k9_count > 0 + uses: hyperpolymath/k9-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + K9_COUNT="${{ steps.detect.outputs.k9_count }}" + CFG_COUNT="${{ steps.detect.outputs.config_count }}" + if [ "$K9_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## K9 Contract Validation + + :warning: **No K9 contract files found.** Repos with configuration files should have K9 contracts. + + Generate contracts with: `k9iser generate .` + EOF + else + echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/label-triage.yml b/.github/workflows/label-triage.yml index 9886e920..814a1924 100644 --- a/.github/workflows/label-triage.yml +++ b/.github/workflows/label-triage.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Label Triage diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index c80b676c..83ab941a 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Labels diff --git a/.github/workflows/main-estate-audit.yml b/.github/workflows/main-estate-audit.yml new file mode 100644 index 00000000..9ca1f932 --- /dev/null +++ b/.github/workflows/main-estate-audit.yml @@ -0,0 +1,12 @@ +name: Central Estate CI/CD Audit + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_call: + +jobs: + call-estate-audit: + uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@feat/cicd-workflow-call diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index fe60874a..f5c02a3a 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/openssf-compliance.yml b/.github/workflows/ossf-best-practices.yml similarity index 98% rename from .github/workflows/openssf-compliance.yml rename to .github/workflows/ossf-best-practices.yml index a590351d..dc0124cf 100644 --- a/.github/workflows/openssf-compliance.yml +++ b/.github/workflows/ossf-best-practices.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 14343b13..4514b9f9 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -89,4 +90,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v5.0.0 + uses: actions/deploy-pages@v5.0.1 diff --git a/.github/workflows/push-email-notify.yml b/.github/workflows/push-email-notify.yml index 9e133d7c..35befdc2 100644 --- a/.github/workflows/push-email-notify.yml +++ b/.github/workflows/push-email-notify.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # Dormant push-email notification. ARMED by setting the repo variable @@ -40,7 +41,7 @@ jobs: timeout-minutes: 5 steps: - name: Send push notification email - uses: hyperpolymath/smtp-notify-action@v0.2.0 # NOSONAR โ€” pin authority is actions.lock (sha1-ede1191ef6ff3ac02c4f4d9efdf837ee517e11d7) + uses: hyperpolymath/smtp-notify-action@v0.3.0 # NOSONAR โ€” pin authority is actions.lock (sha1-ede1191ef6ff3ac02c4f4d9efdf837ee517e11d7) with: server_address: ${{ secrets.SMTP_HOST }} server_port: ${{ secrets.SMTP_PORT }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 48f8a7c1..3ba7fe87 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -57,7 +58,7 @@ jobs: run: | find . -type f -size +1M -not -path "./.git/*" | head -10 || echo "No large files" - name: EditorConfig check - uses: editorconfig-checker/action-editorconfig-checker@v2.2.0 + uses: editorconfig-checker/action-editorconfig-checker@v3.0.0 continue-on-error: true docs: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31b9a436..01950f87 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -134,7 +135,7 @@ jobs: # name: release-artifacts # path: artifacts/ - name: Create GitHub Release - uses: softprops/action-gh-release@v3.0.2 + uses: softprops/action-gh-release@v3.0.3 with: body: ${{ needs.changelog.outputs.changelog }} draft: false diff --git a/.github/workflows/rhodibot.yml b/.github/workflows/rsr-compliance-canary.yml similarity index 99% rename from .github/workflows/rhodibot.yml rename to .github/workflows/rsr-compliance-canary.yml index 628df642..707d6f1d 100644 --- a/.github/workflows/rhodibot.yml +++ b/.github/workflows/rsr-compliance-canary.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/runtime-policy.yml b/.github/workflows/runtime-policy.yml index 44e1a72b..d65d9d6b 100644 --- a/.github/workflows/runtime-policy.yml +++ b/.github/workflows/runtime-policy.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 551e2b5c..77a1f35f 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a7ed8b62..94c97ebf 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -17,7 +18,7 @@ permissions: jobs: scorecard: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@8750b94ac1bbe8c51ad13fe106669b13478f0b62 # Reusable called-workflow permissions are CAPPED by the caller's grants; # without security-events: write here the scorecard SARIF upload fails with # startup_failure (hypatia WF018). id-token: write enables OIDC publish. diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index 34f0e339..d47ea14c 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/security-policy.yml b/.github/workflows/security-policy.yml index 6644bae2..0793bece 100644 --- a/.github/workflows/security-policy.yml +++ b/.github/workflows/security-policy.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 4ef5edea..30797447 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/static-analysis-gate.yml b/.github/workflows/static-analysis-gate.yml index 3a369bd7..8ccf6cfb 100644 --- a/.github/workflows/static-analysis-gate.yml +++ b/.github/workflows/static-analysis-gate.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index c491ee6a..1d8348a2 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. @@ -37,13 +38,15 @@ jobs: failed=0 for file in .github/workflows/*.yml .github/workflows/*.yaml; do [ -f "$file" ] || continue - if ! head -1 "$file" | grep -q "^# SPDX-License-Identifier:"; then + # actions-lock may prepend its own comment. Require the licence + # in the leading comment block, before the workflow body. + if ! awk '/^# SPDX-License-Identifier:/ { found=1 } /^[^#[:space:]]/ { exit } END { exit !found }' "$file"; then echo "ERROR: $file missing SPDX header" failed=1 fi done if [ $failed -eq 1 ]; then - echo "Add '# SPDX-License-Identifier: MPL-2.0' as first line" + echo "Add '# SPDX-License-Identifier: MPL-2.0' to the leading comment block" exit 1 fi echo "All workflows have SPDX headers" diff --git a/CLAUDE.md b/CLAUDE.md index 4ae30f7f..6e5fd12a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,9 +51,9 @@ The descriptive family (working name *descriptiles*) describes what-is; the **co - **Before you invent, rename, or consolidate anything: STOP and check the map + IS-NOT.** ## Estate language policy (overridable per-repo via AGENTIC) -Deny: **Nix, Python, Go, AGPL**. (Guix, not Nix.) -JS/TS runtimes in order: **Bun** (default; runs .ts directly) > Deno (grandfathered) > pnpm > npm (last resort, permitted). -TypeScript is permitted under Bun. The "use ReScript" rule is retired โ€” ReScript is no longer used in this estate. Do not migrate Bun to Deno. +Deny: **Nix, Python, Go, TypeScript, AGPL**. (Guix, not Nix.) +JavaScript tooling order: **Bun** (default) > Deno (grandfathered) > pnpm > npm (last resort, permitted). +Use plain JavaScript when this tooling is needed. The "use ReScript" rule is retired โ€” ReScript is no longer used in this estate. Do not migrate Bun to Deno. --- diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc new file mode 100644 index 00000000..c0b5f31c --- /dev/null +++ b/CONTRIBUTING.adoc @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Contributing + +Contributions are welcome. The complete contribution workflow, including setup, +issue-reporting guidance, branch naming and review expectations, is maintained in +link:.github/CONTRIBUTING.md[the contribution guide]. + +Before contributing, read the +link:.github/CODE_OF_CONDUCT.md[Code of Conduct] and follow the repository's +language policy in `CLAUDE.md`. Run the checks relevant to your change before +opening a pull request. + +Report security vulnerabilities privately through +link:https://github.com/hyperpolymath/rsr-template-repo/security/advisories/new[GitHub Security Advisories], +as described in link:.github/SECURITY.md[the security policy], not through a +public issue. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..417391d2 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,8 @@ +# Pointer + +This repository has no `AGENTS.md` yet. Until it does, the instructions +for every coding agent live in **[CLAUDE.md](./CLAUDE.md)**. Read that +file, and skip anything in it that is specific to Claude Code tooling. +Do not duplicate rules here. + +When `AGENTS.md` lands in this repository, retarget this pointer at it. diff --git a/coordination.k9 b/coordination.k9 deleted file mode 100644 index d40f3eb8..00000000 --- a/coordination.k9 +++ /dev/null @@ -1,56 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Thin coordination bindings for central session-management standards - -pedigree = { - schema_version = "1.0.0", - metadata = { - name = "coordination", - version = "1.0.0", - }, - security = { - leash = 'Kennel, - }, -} - -session_management: - source_of_truth: "standards/session-management-standards" - canonical_commands: - - "intake repo " - - "checkpoint change " - - "verify maintenance " - - "verify substantial " - - "verify release " - - "close planned " - - "close urgent " - - "recover repo " - - "handover full " - - "handover split " - - "handover model " - - "handover human " - -signals: - - name: "session.intake" - command: "intake repo " - - name: "session.checkpoint" - command: "checkpoint change " - - name: "session.verify.maintenance" - command: "verify maintenance " - - name: "session.verify.substantial" - command: "verify substantial " - - name: "session.verify.release" - command: "verify release " - - name: "session.close.planned" - command: "close planned " - - name: "session.close.urgent" - command: "close urgent " - - name: "session.recover" - command: "recover repo " - - name: "session.handover.full" - command: "handover full " - - name: "session.handover.split" - command: "handover split " - - name: "session.handover.model" - command: "handover model " - - name: "session.handover.human" - command: "handover human " diff --git a/coordination.k9.ncl b/coordination.k9.ncl new file mode 100644 index 00000000..44ce7c6a --- /dev/null +++ b/coordination.k9.ncl @@ -0,0 +1,49 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Thin coordination bindings for central session-management standards + +{ + pedigree = { + schema_version = "1.0.0", + metadata = { + name = "session-coordination", + version = "0.1.0", + }, + security = { + leash = 'Kennel, + }, + }, + + session_management = { + source_of_truth = "standards/session-management-standards", + canonical_commands = [ + "intake repo ", + "checkpoint change ", + "verify maintenance ", + "verify substantial ", + "verify release ", + "close planned ", + "close urgent ", + "recover repo ", + "handover full ", + "handover split ", + "handover model ", + "handover human ", + ], + }, + + signals = [ + { name = "session.intake", command = "intake repo " }, + { name = "session.checkpoint", command = "checkpoint change " }, + { name = "session.verify.maintenance", command = "verify maintenance " }, + { name = "session.verify.substantial", command = "verify substantial " }, + { name = "session.verify.release", command = "verify release " }, + { name = "session.close.planned", command = "close planned " }, + { name = "session.close.urgent", command = "close urgent " }, + { name = "session.recover", command = "recover repo " }, + { name = "session.handover.full", command = "handover full " }, + { name = "session.handover.split", command = "handover split " }, + { name = "session.handover.model", command = "handover model " }, + { name = "session.handover.human", command = "handover human " }, + ], +} diff --git a/docs/architecture/REPOSITORY-MAP.adoc b/docs/architecture/REPOSITORY-MAP.adoc index 4e0f6068..179044c4 100644 --- a/docs/architecture/REPOSITORY-MAP.adoc +++ b/docs/architecture/REPOSITORY-MAP.adoc @@ -93,6 +93,10 @@ predecessors did. | yes | generated arrival pack; the generator, pre-commit and Claude Code all read it from the ROOT (do not hand-edit; edit the a2ml source) +| `CONTRIBUTING.adoc` +| optional +| AsciiDoc entry point to the canonical .github/ contribution guide + | `Justfile` | yes | thin; delegates phases to build/just/*.just @@ -129,7 +133,7 @@ predecessors did. | yes | .gitlab-ci.yml + .pre-commit-config.yaml; ci/README.adoc records the out-of-band GitLab project setting these require -| `coordination.k9` +| `coordination.k9.ncl` | yes | repo-local session binding (template-mandated) @@ -163,7 +167,7 @@ predecessors did. | `session/` | yes -| dispatch.sh, custom-checks.k9, local-hooks.sh +| dispatch.sh, custom-checks.k9.ncl, local-hooks.sh | `sonar-project.properties` | optional diff --git a/machine-readable/arrival-pack/arrival-pack.ncl b/machine-readable/arrival-pack/arrival-pack.ncl index e712f301..964a51a3 100644 --- a/machine-readable/arrival-pack/arrival-pack.ncl +++ b/machine-readable/arrival-pack/arrival-pack.ncl @@ -71,9 +71,9 @@ The descriptive family (working name *descriptiles*) describes what-is; the **co - **Before you invent, rename, or consolidate anything: STOP and check the map + IS-NOT.** ## Estate language policy (overridable per-repo via AGENTIC) -Deny: **Nix, Python, Go, AGPL**. (Guix, not Nix.) -JS/TS runtimes in order: **Bun** (default; runs .ts directly) > Deno (grandfathered) > pnpm > npm (last resort, permitted). -TypeScript is permitted under Bun. The "use ReScript" rule is retired โ€” ReScript is no longer used in this estate. Do not migrate Bun to Deno. +Deny: **Nix, Python, Go, TypeScript, AGPL**. (Guix, not Nix.) +JavaScript tooling order: **Bun** (default) > Deno (grandfathered) > pnpm > npm (last resort, permitted). +Use plain JavaScript when this tooling is needed. The "use ReScript" rule is retired โ€” ReScript is no longer used in this estate. Do not migrate Bun to Deno. --- diff --git a/machine-readable/contractiles/README.adoc b/machine-readable/contractiles/README.adoc index 07503f4b..31f2503e 100644 --- a/machine-readable/contractiles/README.adoc +++ b/machine-readable/contractiles/README.adoc @@ -19,10 +19,8 @@ format formerly called A2ML is now *DEED* (`.deed`); files here still carry `.a2ml` pending a single atomic estate-wide rename, so do not hand-convert them. Verb-file naming is tracked separately. -Two in-tree citations below are known to be unresolved and are left as -written rather than silently repaired: `docs/CONTRACTILE-SPEC.adoc` does not -exist in this repository, and the "contractile CLI" has never been built -- -enforcement currently rides on the CI gates, not on that tool. +The "contractile CLI" has never been built -- enforcement currently rides on +the CI gates, not on that tool. ==== Each verb directory holds a *trident* of files: @@ -44,7 +42,7 @@ All verb runners import `_base.ncl` (shared pedigree + run-defaults + probe-schema). The `INDEX.a2ml` registry catalogues every verb; consumers (CI scripts, the contractile CLI, Hypatia rules) SHOULD read it to discover available verbs rather than hard-coding the list. See -`docs/CONTRACTILE-SPEC.adoc` for the normative specification. +https://github.com/hyperpolymath/standards/blob/main/docs/CONTRACTILE-SPEC.adoc[the normative specification]. == Verbs (6 + k9 exception) @@ -135,7 +133,7 @@ declare a `paired_xfile` pointing to a specific contractile xfile (e.g. `../must/Mustfile.a2ml`). Floating k9 components with no paired xfile are non-conformant. -See `docs/CONTRACTILE-SPEC.adoc ยงk9-exception` for the normative statement. +See https://github.com/hyperpolymath/standards/blob/main/docs/CONTRACTILE-SPEC.adoc#k9-exception[the normative statement]. == Fill-In Instructions diff --git a/machine-readable/root-allow.txt b/machine-readable/root-allow.txt index 45a0e56c..e70d4e2e 100644 --- a/machine-readable/root-allow.txt +++ b/machine-readable/root-allow.txt @@ -34,8 +34,9 @@ CHANGELOG.adoc # AsciiDoc is the estate-standard documentation forma CITATION.cff # citation metadata; GitHub reads it from the root of the default branch only 0-AI-MANIFEST.a2ml # universal AI entry point CLAUDE.md # generated arrival pack; the generator, pre-commit and Claude Code all read it from the ROOT (do not hand-edit; edit the a2ml source) +?GEMINI.md # pointer to CLAUDE.md for repos without AGENTS.md yet Justfile # thin; delegates phases to build/just/*.just -coordination.k9 # repo-local session binding (template-mandated) +coordination.k9.ncl # repo-local session binding (template-mandated) # โ”€โ”€โ”€ Conventional dotfiles (tool-required at root) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ .editorconfig @@ -50,6 +51,7 @@ mise.toml # pinned toolchains .clinerules # AI editor rules. Cline/Cursor/Windsurf read these from the project .cursorrules # ROOT, so the copies formerly under machine-readable/ai/ were inert. .windsurfrules +?.claude/ # Claude Code project state (checkpoints, history) โ€” created by the tool itself # โ”€โ”€โ”€ Directories โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ .git/ @@ -61,7 +63,7 @@ docs/ # human documentation build/ # build orchestration: just/ phase modules, container/, docs-seed/, templates/ ci/ # .gitlab-ci.yml + .pre-commit-config.yaml; ci/README.adoc records the out-of-band GitLab project setting these require scripts/ # repo helper scripts -session/ # dispatch.sh, custom-checks.k9, local-hooks.sh +session/ # dispatch.sh, custom-checks.k9.ncl, local-hooks.sh src/ tests/ @@ -76,5 +78,6 @@ tests/ ?features/ # optional feature packs ?verification/ # proofs; only where formal-proofs is declared ?CONTRIBUTING.md # accepted at root OR .github/; .github/ is the canonical estate location +?CONTRIBUTING.adoc # AsciiDoc entry point to the canonical .github/ contribution guide ?SECURITY.md # accepted at root OR .github/; likewise ?REQUIRES_INITIALISATION.md # written by the minting process, removed once `just repo-init` runs diff --git a/scripts/prune-dependabot-ecosystems.py b/scripts/prune-dependabot-ecosystems.py deleted file mode 100755 index 6c195fb8..00000000 --- a/scripts/prune-dependabot-ecosystems.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: MPL-2.0 -"""Prune .github/dependabot.yml to the ecosystems this project actually uses. - -The template ships every common ecosystem under the header "Covers common -ecosystems - remove unused ones for your project". Nothing removed them. -Measured 2026-08-04 across 423 estate checkouts โ€” declared vs. manifest present -at the repo root: - - pip 206 declared / 206 with no Python manifest (100%) - npm 231 declared / 222 with no package.json ( 96%) - mix 207 declared / 197 with no mix.exs ( 95%) - cargo 251 declared / 166 with no Cargo.toml ( 66%) - -Each of those fails on every scheduled run with "Dependabot encountered an error -performing the update". Same shape as the instruction block: the template -documents a manual step and nothing performs it. - -Separately and much worse, 209 repos declare `package-ecosystem: "nix"`, which -is not a valid Dependabot ecosystem value at all. An invalid enum makes GitHub -reject the WHOLE config, so in those repos the github-actions and cargo entries -beside it are inert too โ€” Dependabot has been silently doing nothing. The -template's own dependabot.yml already carries a comment forbidding `nix`; this -script drops it if a stale copy reintroduces one. - -A freshly minted repo has no manifests yet, so pruning on file presence would -delete everything. Pass the ecosystems to keep explicitly โ€” the operator's -archetype choice is better evidence than an empty tree. - -Usage: prune-dependabot-ecosystems.py - prune-dependabot-ecosystems.py .github/dependabot.yml github-actions cargo -""" -import re -import sys -from pathlib import Path - -# Never valid for Dependabot, and rejects the entire file if present. -ALWAYS_DROP = {"nix"} - - -def main() -> int: - if len(sys.argv) < 3: - sys.exit(__doc__) - path = Path(sys.argv[1]) - keep = set(sys.argv[2:]) - ALWAYS_DROP - if not path.is_file(): - print(f" dependabot: {path} absent, nothing to prune") - return 0 - - text = path.read_text(encoding="utf-8") - # An entry runs from its `- package-ecosystem:` line to just before the - # next one (or EOF), so trailing comments travel with their own entry. - parts = re.split(r"(?m)^(?=[ \t]*-[ \t]*package-ecosystem:)", text) - head, entries = parts[0], parts[1:] - if not entries: - print(" dependabot: no ecosystem entries found") - return 0 - - kept, dropped = [], [] - for entry in entries: - m = re.search(r"package-ecosystem:[ \t]*[\"']?([A-Za-z-]+)", entry) - name = m.group(1) if m else "?" - (kept if name in keep else dropped).append((name, entry)) - - if not kept: - # Refuse to write a config with an empty `updates:` list โ€” that is - # invalid and would reject the file just as surely as a bad enum. - print(" dependabot: refusing to prune every entry; left unchanged") - return 0 - if not dropped: - print(" dependabot: nothing to prune") - return 0 - - path.write_text(head + "".join(e for _, e in kept), encoding="utf-8") - print(" dependabot: kept " + ", ".join(n for n, _ in kept) - + " / dropped " + ", ".join(n for n, _ in dropped)) - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/prune-dependabot-ecosystems.rb b/scripts/prune-dependabot-ecosystems.rb new file mode 100644 index 00000000..3bc45743 --- /dev/null +++ b/scripts/prune-dependabot-ecosystems.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby +# SPDX-License-Identifier: MPL-2.0 +# Keep only explicitly selected Dependabot ecosystems after project minting. +# Nix is not a valid Dependabot ecosystem. Refuse to empty the updates list. +abort 'Usage: prune-dependabot-ecosystems.rb ' if ARGV.length < 2 +path = ARGV.shift +keep = ARGV - ['nix'] +unless File.file?(path) + puts " dependabot: #{path} absent, nothing to prune" + exit 0 +end +text = File.read(path, encoding: 'UTF-8') +parts = text.split(/^(?=[ \t]*-[ \t]*package-ecosystem:)/) +head = parts.shift || '' +entries = parts.map { |entry| [entry[/package-ecosystem:[ \t]*["']?([A-Za-z-]+)/, 1] || '?', entry] } +kept, dropped = entries.partition { |name, _entry| keep.include?(name) } +if entries.empty? + puts ' dependabot: no ecosystem entries found' +elsif kept.empty? + puts ' dependabot: refusing to prune every entry; left unchanged' +elsif dropped.empty? + puts ' dependabot: nothing to prune' +else + File.write(path, head + kept.map(&:last).join) + puts " dependabot: kept #{kept.map(&:first).join(', ')} / dropped #{dropped.map(&:first).join(', ')}" +end diff --git a/scripts/strip-instruction-blocks.py b/scripts/strip-instruction-blocks.py deleted file mode 100755 index deec73c3..00000000 --- a/scripts/strip-instruction-blocks.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: MPL-2.0 -"""Delete the RSR template's "TEMPLATE INSTRUCTIONS" comment blocks. - -The community-health templates open with an HTML comment headed -"TEMPLATE INSTRUCTIONS (delete this block before publishing)". Nothing in the -mint ever deleted it. Measured 2026-08-04 across 423 estate checkouts: 211 -repos still carry it โ€” 206 in CODE_OF_CONDUCT.md, 106 in SECURITY.md. - -It is self-detonating, which is why substitution cannot save you. The block's -own first line says to replace all values using a LITERAL doubled-brace token. -That is metasyntax, not a real token, so the mint's sed pass has nothing to -match and leaves it intact. Every placeholder gate in the estate then greps a -doubled-brace SHOUTY_SNAKE pattern and finds it, so a repo whose every genuine -token was substituted correctly still trips its own gate. That is the mechanism -behind metadatastician/688-attack-hub#11 and f19-stealth-glider#12. - -Worse, the block carries the DONOR's substituted values. squisher-corpus filled -its own name into the instruction table instead of deleting the block, and its -root CODE_OF_CONDUCT.md was then copied outward: 58 repos now name "Squisher -Corpus" as the project they protect and route conduct reports to the wrong repo. - -Run AFTER substitution: any real token the operator supplied is already filled -in, so only the instructions go. - -Usage: strip-instruction-blocks.py [root] (default ".") -Exit 0 always; prints one line per file changed. Idempotent. -""" -import re -import sys -from pathlib import Path - -MARKER = "TEMPLATE INSTRUCTIONS" - -# `(?:(?!-->).)*?` is load-bearing: it cannot cross a comment terminator. -# Without it the match starts at the file's FIRST ``, and takes the licence header and the `# Code of Conduct` H1 -# with it. Caught on metadatastician/boj-server-mk2 while testing; it looks -# correct on any file whose SPDX header happens to be a single line. -# -# Anchoring on the comment rather than the line also means prose that merely -# MENTIONS the marker survives โ€” documentation about this defect would -# otherwise be deleted by the fix for it. -BLOCK = re.compile( - r").)*?" + re.escape(MARKER) + r"(?:(?!-->).)*?-->[ \t]*\n?", - re.S, -) - -SKIP_DIRS = {".git", "node_modules", ".venv", "target", "dist"} - - -def strip(path: Path) -> bool: - try: - text = path.read_text(encoding="utf-8") - except (UnicodeDecodeError, OSError): - return False - if MARKER not in text: - return False - new = BLOCK.sub("", text) - if new == text: - return False - new = re.sub(r"\n{3,}", "\n\n", new) # collapse the gap the deletion leaves - path.write_text(new, encoding="utf-8") - return True - - -def main() -> int: - root = Path(sys.argv[1] if len(sys.argv) > 1 else ".") - changed = 0 - for p in sorted(root.rglob("*")): - if not p.is_file() or p.is_symlink(): - continue - if SKIP_DIRS & set(p.parts): - continue - if strip(p): - print(f" instruction block: stripped from {p}") - changed += 1 - print(f" instruction blocks: {changed} file(s) cleaned" if changed - else " instruction blocks: none found") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/strip-instruction-blocks.rb b/scripts/strip-instruction-blocks.rb new file mode 100644 index 00000000..ae4f1def --- /dev/null +++ b/scripts/strip-instruction-blocks.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby +# SPDX-License-Identifier: MPL-2.0 +# Remove only HTML comments containing TEMPLATE INSTRUCTIONS after minting. +# The tempered match cannot cross a preceding SPDX comment's terminator. +require 'find' +root = ARGV.fetch(0, '.') +marker = 'TEMPLATE INSTRUCTIONS' +block = /).)*?#{Regexp.escape(marker)}(?:(?!-->).)*?-->[ \t]*\n?/m +skip_dirs = %w[.git node_modules .venv target dist] +changed = 0 +Find.find(root) do |path| + if File.directory?(path) && skip_dirs.include?(File.basename(path)) + Find.prune + end + next if File.symlink?(path) || !File.file?(path) + begin + text = File.read(path, encoding: 'UTF-8') + rescue SystemCallError + next + end + next unless text.valid_encoding? && text.include?(marker) + updated = text.gsub(block, '') + next if updated == text + File.write(path, updated.gsub(/\n{3,}/, "\n\n")) + puts " instruction block: stripped from #{path}" + changed += 1 +end +puts changed.positive? ? " instruction blocks: #{changed} file(s) cleaned" : ' instruction blocks: none found' diff --git a/scripts/validate-session-contracts.sh b/scripts/validate-session-contracts.sh new file mode 100644 index 00000000..c1f8fb67 --- /dev/null +++ b/scripts/validate-session-contracts.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Validate the two session policies with their actual Nickel evaluator. +set -euo pipefail +command -v nickel >/dev/null || { + echo "nickel is required to validate .k9.ncl session policies" >&2 + exit 2 +} +for file in coordination.k9.ncl session/custom-checks.k9.ncl; do + envelope_line=$(awk '/[^ ]/ { if ($0 == "K9!") print NR; exit }' "$file") + if [[ -z "$envelope_line" ]]; then + echo "$file: missing K9! envelope" >&2 + exit 1 + fi + # K9! is a transport envelope, not a Nickel expression. These standalone + # records have no imports; evaluation also exercises their field contracts. + sed "${envelope_line}d" "$file" | nickel export --format json >/dev/null + echo "$file: Nickel evaluation passed" +done diff --git a/session/README.adoc b/session/README.adoc index d14a6d75..80e329a7 100644 --- a/session/README.adoc +++ b/session/README.adoc @@ -11,9 +11,14 @@ Authoritative protocols live in: This repo keeps only thin bindings: * `+dispatch.sh+` maps canonical commands to central protocol paths. -* `+custom-checks.k9+` defines repo-local policy checks. +* `+custom-checks.k9.ncl+` defines repo-local policy checks as a Nickel record. * `+local-hooks.sh+` provides optional repo-specific hook behavior. +Run `bash scripts/validate-session-contracts.sh` from the repository root +with Nickel installed to evaluate both session records. The script removes +the `K9!` transport envelope before invoking the actual Nickel evaluator; +a missing evaluator or invalid expression fails validation. + === Canonical Commands * `+intake repo +` diff --git a/session/custom-checks.k9 b/session/custom-checks.k9 deleted file mode 100644 index 8ce6225f..00000000 --- a/session/custom-checks.k9 +++ /dev/null @@ -1,28 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Local repository session checks (thin policy layer) -version: "0.1" - -pedigree = { - schema_version = "1.0.0", - metadata = { - name = "session-custom-checks", - version = "0.1.0", - }, - security = { - leash = 'Kennel, - }, -} - -checks: - - id: "session-state-has-next-action" - applies_to: ["close planned", "close urgent", "handover full", "handover split", "handover model", "handover human"] - requirement: "LAST-CANONICAL-COMMAND.md contains next intended action" - - - id: "session-state-has-residual-risks" - applies_to: ["verify maintenance", "verify substantial", "verify release", "recover repo"] - requirement: "Residual risks field is not left blank" - - - id: "session-state-has-recommended-next-protocol" - applies_to: ["intake repo", "checkpoint change", "recover repo", "handover full"] - requirement: "Recommended next protocol is set" diff --git a/session/custom-checks.k9.ncl b/session/custom-checks.k9.ncl new file mode 100644 index 00000000..6f6f36b2 --- /dev/null +++ b/session/custom-checks.k9.ncl @@ -0,0 +1,51 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Local repository session checks (thin policy layer) + +{ + pedigree = { + schema_version = "1.0.0", + metadata = { + name = "custom-session-checks", + version = "0.1.0", + }, + security = { + leash = 'Kennel, + }, + }, + + checks = [ + { + id = "session-state-has-next-action", + applies_to = [ + "close planned", + "close urgent", + "handover full", + "handover split", + "handover model", + "handover human", + ], + requirement = "LAST-CANONICAL-COMMAND.md contains next intended action", + }, + { + id = "session-state-has-residual-risks", + applies_to = [ + "verify maintenance", + "verify substantial", + "verify release", + "recover repo", + ], + requirement = "Residual risks field is not left blank", + }, + { + id = "session-state-has-recommended-next-protocol", + applies_to = [ + "intake repo", + "checkpoint change", + "recover repo", + "handover full", + ], + requirement = "Recommended next protocol is set", + }, + ], +} diff --git a/tests/workflows/mint_cleanup_test.sh b/tests/workflows/mint_cleanup_test.sh new file mode 100644 index 00000000..614bec11 --- /dev/null +++ b/tests/workflows/mint_cleanup_test.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +set -euo pipefail +repo=$(git rev-parse --show-toplevel) +fixture=$(mktemp -d) +trap 'rm -rf "$fixture"' EXIT +cat > "$fixture/dependabot.yml" <<'YAML' +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + - package-ecosystem: nix + directory: / + - package-ecosystem: cargo + directory: / +YAML +ruby "$repo/scripts/prune-dependabot-ecosystems.rb" "$fixture/dependabot.yml" github-actions +grep -q 'github-actions' "$fixture/dependabot.yml" +if grep -qE 'nix|cargo' "$fixture/dependabot.yml"; then exit 1; fi +cp "$fixture/dependabot.yml" "$fixture/before" +ruby "$repo/scripts/prune-dependabot-ecosystems.rb" "$fixture/dependabot.yml" nix +cmp "$fixture/dependabot.yml" "$fixture/before" +cat > "$fixture/policy.md" <<'DOC' + +# Policy +Prose mentions TEMPLATE INSTRUCTIONS and must survive. + +Actual policy. +DOC +ruby "$repo/scripts/strip-instruction-blocks.rb" "$fixture" +grep -q SPDX "$fixture/policy.md" +grep -q '^# Policy' "$fixture/policy.md" +grep -q '^Prose mentions' "$fixture/policy.md" +if grep -q 'delete only this' "$fixture/policy.md"; then exit 1; fi +cp "$fixture/policy.md" "$fixture/before" +ruby "$repo/scripts/strip-instruction-blocks.rb" "$fixture" +cmp "$fixture/policy.md" "$fixture/before" +echo 'PASS: selected ecosystems, nonempty updates, comment boundaries, and idempotence' diff --git a/tests/workflows/session_contracts_test.sh b/tests/workflows/session_contracts_test.sh new file mode 100644 index 00000000..116fa2d4 --- /dev/null +++ b/tests/workflows/session_contracts_test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Exercise envelope handling using the actual Nickel evaluator. +set -euo pipefail +repo=$(git rev-parse --show-toplevel) +fixture=$(mktemp -d) +trap 'rm -rf "$fixture"' EXIT +mkdir -p "$fixture/session" +cp "$repo/coordination.k9.ncl" "$fixture/coordination.k9.ncl" +cp "$repo/session/custom-checks.k9.ncl" "$fixture/session/custom-checks.k9.ncl" +cd "$fixture" +bash "$repo/scripts/validate-session-contracts.sh" +{ printf '\n \n'; cat "$repo/coordination.k9.ncl"; } > coordination.k9.ncl +bash "$repo/scripts/validate-session-contracts.sh" +printf '\n{ value = 1 }\n' > coordination.k9.ncl +if bash "$repo/scripts/validate-session-contracts.sh"; then + echo 'FAIL: missing envelope accepted' >&2 + exit 1 +fi +printf '\nK9!\n{ invalid = }\n' > coordination.k9.ncl +if bash "$repo/scripts/validate-session-contracts.sh"; then + echo 'FAIL: invalid Nickel accepted' >&2 + exit 1 +fi +echo 'PASS: leading blanks, missing envelope, and invalid Nickel controls'