diff --git a/.fuze/manifest.json b/.fuze/manifest.json index 7b95887..aca38d8 100644 --- a/.fuze/manifest.json +++ b/.fuze/manifest.json @@ -1,7 +1,7 @@ { "identity": { "namespace": "agent", - "note": "Prefix namespace for this product's entity types (governance/identifier-standard.md \u00a72). DECLARED here rather than derived from the repo directory name: a rename would otherwise silently orphan every id already issued, and that surfaces much later as unresolvable references with no obvious cause. Reserved spine prefixes (usr/org/prt/app, billing, messaging) belong to FuzeFront; this product's own types namespace as agent_. Enforced by `gate_identifier.py --namespace`.", + "note": "Prefix namespace for this product's entity types (governance/identifier-standard.md §2). DECLARED here rather than derived from the repo directory name: a rename would otherwise silently orphan every id already issued, and that surfaces much later as unresolvable references with no obvious cause. Reserved spine prefixes (usr/org/prt/app, billing, messaging) belong to FuzeFront; this product's own types namespace as agent_. Enforced by `gate_identifier.py --namespace`.", "packages": { "node": "@izzywdev/fuzefront-identity", "python": "fuzefront-identity" @@ -35,7 +35,7 @@ "hardening": { "ruleset": true, "requireSignatures": true, - "deployOnPush": false + "deployOnPush": true }, "a2a": { "enabled": true, @@ -114,7 +114,7 @@ "min_score": 80, "at_width": 375 }, - "The standalone URL loads with no portal chrome \u2014 it is what the APK wraps." + "The standalone URL loads with no portal chrome — it is what the APK wraps." ] }, "toolchain": { diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 4783c94..59b5058 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -22,8 +22,32 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + # deploy-on-push repos are NEVER bot-merged (.fuze manifest schema, + # governance/repo-manifest.schema.json in izzywdev/FuzeSDLC: "If true, + # never bot-merge; merge in a deploy window"). Read the flag from + # .fuze/manifest.json and gate the enable step on it here, so the + # declaration is ENFORCED on the merge path rather than only honored by + # convention — a manifest value nothing on the merge path reads is a + # declaration, not a control. See .fuze/manifest.json for the evidence + # this repo deploys on push (workflow + job cited in the PR that set + # hardening.deployOnPush). + - name: Read deploy-on-push flag + id: hardening + run: | + if grep -qE '"deployOnPush"[[:space:]]*:[[:space:]]*true' .fuze/manifest.json 2>/dev/null; then + echo "deploy_on_push=true" >> "$GITHUB_OUTPUT" + echo "::notice::hardening.deployOnPush=true" + else + echo "deploy_on_push=false" >> "$GITHUB_OUTPUT" + echo "::notice::hardening.deployOnPush=false" + fi + + - name: Deploy-on-push repo — auto-merge withheld + if: steps.hardening.outputs.deploy_on_push == 'true' + run: echo "::notice title=Auto-merge withheld::hardening.deployOnPush=true — merging this PR deploys to production, so it must be merged by a human in a deploy window. This job is green because withholding the merge IS the correct outcome here, not a failure." + - name: Enable auto-merge - if: github.event.pull_request.user.login == github.repository_owner || contains(github.event.pull_request.labels.*.name, 'auto-merge') + if: steps.hardening.outputs.deploy_on_push != 'true' && (github.event.pull_request.user.login == github.repository_owner || contains(github.event.pull_request.labels.*.name, 'auto-merge')) continue-on-error: true run: | gh pr merge ${{ github.event.pull_request.number }} --auto --squash --delete-branch