name: Canonical Governance on: push: branches: - main pull_request: branches: - main workflow_dispatch: permissions: contents: read jobs: governance: runs-on: ubuntu-24.04 # The release-control audits resolve the workspace layout from the # local checkout: the repo directory must be named exactly "pulse" # (the canonical repo id) and the evidence repos must be checked out # as siblings, mirroring /repos/ on dev machines. defaults: run: working-directory: repos/pulse steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false fetch-depth: 0 path: repos/pulse - name: Checkout pulse-pro evidence repo uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: rcourtman/pulse-pro token: ${{ secrets.WORKFLOW_PAT }} persist-credentials: false fetch-depth: 1 path: repos/pulse-pro - name: Checkout pulse-enterprise evidence repo uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: rcourtman/pulse-enterprise token: ${{ secrets.WORKFLOW_PAT }} persist-credentials: false fetch-depth: 1 path: repos/pulse-enterprise - name: Resolve pulse-mobile evidence ref id: pulse-mobile-ref shell: bash env: CANDIDATE_REF: ${{ github.head_ref }} GH_TOKEN: ${{ secrets.WORKFLOW_PAT }} run: | set -euo pipefail resolved_ref="main" if [ -n "${CANDIDATE_REF}" ]; then matching_ref="$(gh api \ "repos/rcourtman/pulse-mobile/git/matching-refs/heads/${CANDIDATE_REF}" \ --jq '.[].ref' 2>/dev/null \ | grep --fixed-strings --line-regexp -- "refs/heads/${CANDIDATE_REF}" \ || true)" if [ -n "${matching_ref}" ]; then resolved_ref="${CANDIDATE_REF}" fi fi echo "ref=${resolved_ref}" >> "${GITHUB_OUTPUT}" echo "Using pulse-mobile ref: ${resolved_ref}" - name: Checkout pulse-mobile evidence repo uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: rcourtman/pulse-mobile ref: ${{ steps.pulse-mobile-ref.outputs.ref }} token: ${{ secrets.WORKFLOW_PAT }} persist-credentials: false fetch-depth: 1 path: repos/pulse-mobile - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: repos/pulse/go.mod cache: true cache-dependency-path: repos/pulse/go.sum - name: Set up Node.js for frontend governance uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' cache: npm cache-dependency-path: repos/pulse/frontend-modern/package-lock.json - name: Install locked frontend governance dependencies working-directory: repos/pulse/frontend-modern env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' run: npm ci --ignore-scripts --no-audit --no-fund - name: Stub embedded frontend assets for Go tests run: bash scripts/ensure_test_assets.sh - name: Check Pulse Mobile compatibility contract run: | python3 scripts/release_control/generate_mobile_compatibility_test.py python3 scripts/release_control/mobile_compatibility_test.py python3 scripts/release_control/generate_mobile_compatibility.py --check --mobile-repo ../pulse-mobile python3 scripts/release_control/mobile_compatibility.py \ --mobile-repo ../pulse-mobile \ --evidence-out "${RUNNER_TEMP}/pulse-mobile-compatibility.json" \ --pretty go test ./internal/api \ -run 'Test(RelayMobileRuntimeRouteInventory|RelayMobileAccessScopeAllowsGovernedMobileRuntimeEndpoints|RelayMobileAccessScopeDeniesAdjacentAIRoutes|Contract_PatrolAttentionRoutesAcceptMobileRelayCapability)$' \ -count=1 go test ./internal/relay \ -run 'Test(CanonicalActionOutcomeNotificationMatrix|NewPatrolFindingNotification|NewApprovalRequestNotification|NewActionDecisionNotificationUsesCanonicalActionIdentity|NewFixCompletedNotification|NewActionOutcomeNotificationIsHonestAboutVerification)$' \ -count=1 - name: Upload Pulse Mobile compatibility evidence uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pulse-mobile-compatibility path: ${{ runner.temp }}/pulse-mobile-compatibility.json if-no-files-found: error - name: Determine governance diff range id: diff shell: bash run: | set -euo pipefail if [ "${{ github.event_name }}" = "pull_request" ]; then range="${{ github.event.pull_request.base.sha }}...${{ github.sha }}" elif [ "${{ github.event_name }}" = "push" ]; then range="${{ github.event.before }}...${{ github.sha }}" elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then range="HEAD^...HEAD" else range="" fi echo "range=${range}" >> "$GITHUB_OUTPUT" - name: Run canonical completion guard against changed commits shell: bash run: | set -euo pipefail range="${{ steps.diff.outputs.range }}" if [ -z "${range}" ]; then printf '' | python3 scripts/release_control/canonical_completion_guard.py --files-from-stdin exit 0 fi base="${range%%...*}" head_sha="${range##*...}" # Evaluate each commit the way the commit-time hook did: its own # file list against its own parent. A range-wide evaluation # cannot honor per-commit Contract-Neutral trailers (recorded by # .husky/prepare-commit-msg when the author sets # PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT), so bypassed commits that # passed locally would fail here. if ! commits=$(git rev-list --reverse --no-merges "${base}..${head_sha}" 2>/dev/null); then echo "Range base ${base} unavailable (force push?); evaluating ${head_sha} against its parent." commits="${head_sha}" fi if [ -z "${commits}" ]; then echo "No commits to evaluate in range ${range}." exit 0 fi status=0 for commit in ${commits}; do if ! git rev-parse --verify --quiet "${commit}^" >/dev/null; then echo "Skipping root commit ${commit} (no parent to diff against)." continue fi reason=$(git log -1 --format='%(trailers:key=Contract-Neutral,valueonly,separator=; )' "${commit}" | tr '\n' ' ') echo "::group::canonical completion guard @ ${commit}" # Pass the parent as the diff base so the guard compares # contract texts parent-vs-commit; the CI index equals HEAD, # so the default index comparison would misreport contract # updates in earlier commits as insubstantial. if ! git diff-tree --no-commit-id --name-only -r "${commit}" \ | PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT="${reason}" python3 scripts/release_control/canonical_completion_guard.py \ --files-from-stdin --diff-base "${commit}^"; then echo "Canonical completion guard failed for commit ${commit}." status=1 fi if ! git diff-tree --no-commit-id --name-only -r "${commit}" \ | python3 scripts/release_control/browser_verification_guard.py \ --files-from-stdin --commit "${commit}"; then echo "Browser verification guard failed for commit ${commit}." status=1 fi echo "::endgroup::" done exit ${status} - name: Run status audit env: PULSE_REPO_ROOT_PULSE: ${{ github.workspace }}/repos/pulse PULSE_REPO_ROOT_PULSE_PRO: ${{ github.workspace }}/repos/pulse-pro PULSE_REPO_ROOT_PULSE_ENTERPRISE: ${{ github.workspace }}/repos/pulse-enterprise PULSE_REPO_ROOT_PULSE_MOBILE: ${{ github.workspace }}/repos/pulse-mobile run: python3 scripts/release_control/status_audit.py --check - name: Validate Pulse Intelligence release-gate schema run: python3 scripts/release_control/pulse_intelligence_gate.py --validate-only --matrix docs/release-control/v6/internal/pulse-intelligence-release-gate.json - name: Run Pulse Intelligence release-gate unit tests run: python3 scripts/release_control/pulse_intelligence_gate_test.py - name: Run control plane audit env: PULSE_REPO_ROOT_PULSE: ${{ github.workspace }}/repos/pulse PULSE_REPO_ROOT_PULSE_PRO: ${{ github.workspace }}/repos/pulse-pro PULSE_REPO_ROOT_PULSE_ENTERPRISE: ${{ github.workspace }}/repos/pulse-enterprise PULSE_REPO_ROOT_PULSE_MOBILE: ${{ github.workspace }}/repos/pulse-mobile run: python3 scripts/release_control/control_plane_audit.py --check - name: Run registry audit run: python3 scripts/release_control/registry_audit.py --check - name: Run canonical mutation registry audits run: go test ./internal/mutationregistry ./internal/ai/tools -run 'Test(EveryRegisteredMutationHasDisposition|InfrastructureAPIRoutesResolveToRegistry|TransportCommandCatalogsResolveToRegistry|PatrolJobRegistrationResolvesToRegistry|RuntimeCandidateAuditNegativeFixtures|ActionRouteMethodAuthorityIsExactAndLookalikesFailClosed|NonAdmittingTransportMessagesCannotCarryDispatchAuthority|UnknownTransportLookalikeFailsClosed|RegisteredModelMutationSchemasResolveToClosedRegistry|RetiredMutationAliasesCannotShadowExtensions)' -count=1 - name: Run contract audit run: python3 scripts/release_control/contract_audit.py --check - name: Run canonical completion guard unit tests run: python3 scripts/release_control/canonical_completion_guard_test.py - name: Run browser verification guard unit tests run: python3 scripts/release_control/browser_verification_guard_test.py - name: Run control plane audit unit tests run: python3 scripts/release_control/control_plane_audit_test.py - name: Run contract audit unit tests run: python3 scripts/release_control/contract_audit_test.py - name: Run staged Go formatter unit tests run: python3 scripts/release_control/format_staged_go_test.py - name: Run governance stage guard unit tests run: python3 scripts/release_control/governance_stage_guard_test.py - name: Run registry audit unit tests run: python3 scripts/release_control/registry_audit_test.py - name: Run repo file IO unit tests run: python3 scripts/release_control/repo_file_io_test.py - name: Run release promotion policy unit tests run: python3 scripts/release_control/release_promotion_policy_test.py - name: Run release container identity verifier unit tests run: python3 scripts/release_control/verify_release_container_images_test.py - name: Run stable container alias verifier unit tests run: python3 scripts/release_control/verify_stable_container_aliases_test.py - name: Run immutable release integrity unit tests run: python3 scripts/release_control/verify_github_release_integrity_test.py - name: Run immutable release setting unit tests run: python3 scripts/release_control/check_github_release_immutability_test.py - name: Run status audit unit tests run: python3 scripts/release_control/status_audit_test.py - name: Run subsystem contract helper unit tests run: python3 scripts/release_control/subsystem_contracts_test.py - name: Run subsystem lookup unit tests run: python3 scripts/release_control/subsystem_lookup_test.py - name: Run repo governance guardrail tests env: PULSE_REPO_ROOT_PULSE: ${{ github.workspace }}/repos/pulse PULSE_REPO_ROOT_PULSE_PRO: ${{ github.workspace }}/repos/pulse-pro PULSE_REPO_ROOT_PULSE_ENTERPRISE: ${{ github.workspace }}/repos/pulse-enterprise PULSE_REPO_ROOT_PULSE_MOBILE: ${{ github.workspace }}/repos/pulse-mobile run: go test ./internal/repoctl -count=1 - name: Run active-target automated readiness assertion proofs run: python3 scripts/release_control/readiness_assertion_guard.py --active-target --proof-type automated - name: Run active-target hybrid readiness assertion proofs run: python3 scripts/release_control/readiness_assertion_guard.py --active-target --proof-type hybrid - name: Run readiness assertion guard unit tests run: python3 scripts/release_control/readiness_assertion_guard_test.py