release: stamp v0.15.0 publish state (#818) #3638
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Least-privilege: read-only by default. Jobs that need more elevate per-job. | |
| permissions: | |
| contents: read | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Change detection: determines which parallel lanes to run. | |
| # All lanes use job-level `if:` (NOT workflow-level path filters) so that | |
| # skipped jobs report `success` instead of staying `pending`. | |
| # --------------------------------------------------------------------------- | |
| detect-changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| outputs: | |
| core: ${{ steps.filter.outputs.core }} | |
| adapters: ${{ steps.filter.outputs.adapters }} | |
| examples: ${{ steps.filter.outputs.examples }} | |
| apps: ${{ steps.filter.outputs.apps }} | |
| published: ${{ steps.filter.outputs.published }} | |
| ci: ${{ steps.filter.outputs.ci }} | |
| root_config: ${{ steps.filter.outputs.root_config }} | |
| any_src: ${{ steps.filter.outputs.any_src }} | |
| stamp_any: ${{ steps.filter.outputs.stamp_any }} | |
| non_stamp: ${{ steps.filter.outputs.non_stamp }} | |
| security_config: ${{ steps.filter.outputs.security_config }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Detect changed paths | |
| id: filter | |
| uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 | |
| with: | |
| filters: | | |
| core: | |
| - 'packages/kernel/**' | |
| - 'packages/schema/**' | |
| - 'packages/crypto/**' | |
| - 'packages/protocol/**' | |
| - 'packages/control/**' | |
| - 'specs/**' | |
| adapters: | |
| - 'packages/adapters/**' | |
| - 'packages/mappings/**' | |
| - 'packages/rails/**' | |
| - 'packages/transport/**' | |
| - 'packages/net/**' | |
| - 'packages/middleware*/**' | |
| examples: | |
| - 'examples/**' | |
| apps: | |
| - 'apps/**' | |
| published: | |
| - 'packages/*/package.json' | |
| - 'packages/*/src/**' | |
| - 'packages/*/tsup.config.*' | |
| - 'scripts/publish-manifest.json' | |
| ci: | |
| - '.github/**' | |
| - 'scripts/**' | |
| # Root config: changes to build/test/runtime infrastructure | |
| # must trigger type-build, tests-core, and pack-smoke | |
| root_config: | |
| - '.node-version' | |
| - '.nvmrc' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'pnpm-workspace.yaml' | |
| - 'turbo.json' | |
| - 'tsconfig*.json' | |
| - 'vitest*.config.*' | |
| - '.eslintrc*' | |
| - 'eslint.config.*' | |
| - '.prettierrc*' | |
| - '.prettierignore' | |
| - 'prettier.config.*' | |
| - '.dependency-cruiser.json' | |
| - 'renovate.json' | |
| any_src: | |
| - 'packages/**' | |
| - 'specs/**' | |
| - 'examples/**' | |
| - 'apps/**' | |
| - 'scripts/**' | |
| # Stamp-only allowlist: release-state stamping artifacts that | |
| # are allowed to trigger the reduced stamp-PR CI profile. The | |
| # companion `non_stamp` filter uses negations to flag any | |
| # path outside this list; a PR is stamp-only when | |
| # stamp_any == true and non_stamp == false. | |
| stamp_any: | |
| - 'docs/releases/facts.json' | |
| - 'docs/releases/current.json' | |
| - 'REPO_SURFACE_STATUS.json' | |
| - 'docs/SURFACE_STATUS.md' | |
| - 'docs/PACKAGE_STATUS.md' | |
| non_stamp: | |
| - '!docs/releases/facts.json' | |
| - '!docs/releases/current.json' | |
| - '!REPO_SURFACE_STATUS.json' | |
| - '!docs/SURFACE_STATUS.md' | |
| - '!docs/PACKAGE_STATUS.md' | |
| - '**' | |
| # Security/audit gate: changes to audit configuration, allowlist, | |
| # surface package.json files, or audit gate tests. | |
| security_config: | |
| - 'security/**' | |
| - 'surfaces/**/package.json' | |
| - 'tests/scripts/**' | |
| # --------------------------------------------------------------------------- | |
| # Workflow lint: validates CI workflow files independently. | |
| # Does NOT use the local composite action (must not depend on what it validates). | |
| # Uses a pinned official release binary with checksum verification. | |
| # --------------------------------------------------------------------------- | |
| workflow-lint: | |
| name: Workflow Lint | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Install actionlint (pinned v1.7.11 with checksum) | |
| run: | | |
| ACTIONLINT_VERSION="1.7.11" | |
| ACTIONLINT_CHECKSUM="900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a" | |
| curl -fsSL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" -o /tmp/actionlint.tar.gz | |
| echo "${ACTIONLINT_CHECKSUM} /tmp/actionlint.tar.gz" | sha256sum -c - | |
| tar xzf /tmp/actionlint.tar.gz -C /tmp actionlint | |
| chmod +x /tmp/actionlint | |
| - name: Lint CI workflow | |
| run: /tmp/actionlint .github/workflows/ci.yml | |
| # --------------------------------------------------------------------------- | |
| # Stamp-only PR guard (defensive; runs only on stamp-only PRs) | |
| # Confirms that every changed path is in the release-state stamp allowlist. | |
| # The reduced stamp-PR CI profile is produced by the existing job-level | |
| # `if:` conditions that gate heavy lanes on core/any_src/ci/root_config; | |
| # stamp-only PRs touch none of those filters and naturally skip heavy | |
| # lanes. This guard is defense in depth: if path classification ever | |
| # misreports, the defensive check fails so a mislabelled stamp-profile | |
| # run cannot pass silently. | |
| # --------------------------------------------------------------------------- | |
| stamp-only-guard: | |
| name: Stamp-only PR guard | |
| needs: detect-changes | |
| if: >- | |
| needs.detect-changes.outputs.stamp_any == 'true' && | |
| needs.detect-changes.outputs.non_stamp == 'false' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Verify stamp-only allowlist | |
| run: | | |
| BASE_SHA="${{ github.event.pull_request.base.sha || github.event.before }}" | |
| HEAD_SHA="${{ github.event.pull_request.head.sha || github.sha }}" | |
| if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then | |
| BASE_SHA="origin/main" | |
| git fetch origin main | |
| fi | |
| bash scripts/ci/check-stamp-only-pr.sh "$BASE_SHA" "$HEAD_SHA" | |
| # --------------------------------------------------------------------------- | |
| # Lane 1: Fast guards (always runs) | |
| # Format, lint, security scans, forbidden strings, distribution checks | |
| # --------------------------------------------------------------------------- | |
| fast-guards: | |
| name: Fast Guards | |
| needs: detect-changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: ./.github/actions/setup-env | |
| - name: Format check | |
| run: pnpm format:check | |
| - name: Lint | |
| run: pnpm lint | |
| - name: Dependency architecture check | |
| run: pnpm lint:deps | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=4096' | |
| - name: Unicode security scan (Trojan Source) | |
| run: | | |
| git ls-files -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.mjs' '*.cjs' '*.json' '*.md' '*.yaml' '*.yml' \ | |
| | grep -vE '^(archive/|node_modules/)' \ | |
| | node scripts/find-invisible-unicode.mjs --stdin | |
| bash scripts/check-unicode.sh | |
| - name: Unicode targeted scan (previously-flagged files) | |
| run: | | |
| FILES=( | |
| packages/capture/core/src/plugin.ts | |
| packages/crypto/src/ed25519.ts | |
| packages/mcp-server/README.md | |
| packages/mcp-server/src/infra/path-safety.ts | |
| packages/schema/src/errors.ts | |
| security/audit-allowlist.json | |
| ) | |
| EXISTING=() | |
| for f in "${FILES[@]}"; do | |
| [ -f "$f" ] && EXISTING+=("$f") | |
| done | |
| if [ ${#EXISTING[@]} -gt 0 ]; then | |
| node scripts/find-invisible-unicode.mjs "${EXISTING[@]}" | |
| echo "OK: no invisible unicode in ${#EXISTING[@]} previously-flagged files" | |
| else | |
| echo "SKIP: none of the flagged files exist (renamed or removed)" | |
| fi | |
| - name: Domain guard | |
| run: bash -euxo pipefail scripts/guard.sh | |
| - name: Planning leak check | |
| run: | | |
| if [ -f scripts/check-planning-leak.sh ]; then | |
| bash scripts/check-planning-leak.sh | |
| else | |
| echo "SKIP: check-planning-leak.sh not present (local-only script)" | |
| fi | |
| - name: Forbidden strings check (ci:guards) | |
| run: bash scripts/ci/forbid-strings.sh | |
| - name: Worker surface typechecks (ci:surface) | |
| run: bash scripts/ci/surface-validator.sh | |
| - name: Distribution surface validation (DD-140) | |
| run: node scripts/verify-distribution.mjs | |
| - name: Package hygiene (drift, duplicates, private+publishConfig) | |
| run: bash scripts/check-publish-list.sh | |
| - name: Publish-manifest closure check | |
| run: npx tsx scripts/check-publish-closure.ts | |
| - name: Publish-manifest invariants (no overlaps, no duplicates, OIDC coverage) | |
| run: npx tsx scripts/check-manifest-invariants.ts | |
| - name: Protocol string verification (forbidden patterns) | |
| run: pnpm verify:protocol-strings | |
| - name: No network I/O in content-signal parser packages | |
| run: pnpm verify:no-network-in-parsers | |
| - name: No bidi / invisible Unicode in tracked files | |
| run: pnpm verify:no-bidi-controls | |
| - name: Spec drift verification (constants parity) | |
| run: pnpm verify:spec-drift | |
| - name: Surface status drift verification | |
| run: pnpm verify:surface-status | |
| - name: Public API contract drift verification | |
| run: pnpm verify:contracts:drift | |
| - name: OpenAPI drift verification (package spec vs app spec) | |
| run: pnpm verify:openapi:drift | |
| - name: Trust-artifact integrity (threat-model links, stability-contract rows) | |
| run: pnpm verify:trust-artifacts | |
| - name: Public surface-name verification (retired filenames and paths) | |
| run: pnpm verify:public-surface-names | |
| - name: Compliance-mapping integrity (ISO 42001 + EU AI Act Annex IV) | |
| run: pnpm verify:compliance-mappings | |
| - name: Privacy-boundary block lint (docs/privacy/*.md) | |
| run: pnpm verify:privacy-boundary | |
| - name: Release facts verification | |
| run: pnpm verify:release | |
| - name: Error code parity (advisory) | |
| continue-on-error: true | |
| run: bash scripts/check-error-codes.sh || true | |
| # --------------------------------------------------------------------------- | |
| # Lane 2: Type check + build + architecture | |
| # Gated: runs on main or when source/CI/root-config changed | |
| # --------------------------------------------------------------------------- | |
| type-build: | |
| name: Type, Build, Architecture | |
| needs: detect-changes | |
| if: >- | |
| github.ref == 'refs/heads/main' || | |
| needs.detect-changes.outputs.core == 'true' || | |
| needs.detect-changes.outputs.adapters == 'true' || | |
| needs.detect-changes.outputs.any_src == 'true' || | |
| needs.detect-changes.outputs.ci == 'true' || | |
| needs.detect-changes.outputs.root_config == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: ./.github/actions/setup-env | |
| - name: TypeScript check (core packages) | |
| run: pnpm typecheck:core | |
| - name: TypeScript check (legacy - advisory) | |
| continue-on-error: true | |
| run: pnpm typecheck:legacy || true | |
| - name: Build all packages | |
| run: pnpm build | |
| - name: TypeScript check (apps) | |
| run: pnpm typecheck:apps | |
| - name: Capture-core exports verification | |
| run: | | |
| pnpm build --filter @peac/capture-core | |
| node packages/capture/core/scripts/test-exports.mjs | |
| - name: Codegen drift check | |
| run: pnpm verify:codegen-drift | |
| - name: Error codes codegen drift check | |
| run: | | |
| pnpm codegen:errors | |
| pnpm exec prettier --write packages/kernel/src/errors.generated.ts packages/kernel/src/error-categories.generated.ts | |
| if ! git diff --exit-code packages/kernel/src/errors.generated.ts packages/kernel/src/error-categories.generated.ts specs/kernel/error-categories.json; then | |
| echo "FAIL: Generated files are out of sync with specs/kernel/errors.json" | |
| echo "Run: pnpm codegen:errors && pnpm exec prettier --write packages/kernel/src/errors.generated.ts packages/kernel/src/error-categories.generated.ts" | |
| exit 1 | |
| fi | |
| echo "OK: Error codes, categories, and JSON artifact are in sync" | |
| - name: Generated profiles drift check | |
| run: pnpm --filter @peac/policy-kit generate:profiles:check | |
| - name: Layer boundary enforcement | |
| run: bash scripts/check-layer-boundaries.sh | |
| - name: Version coherence check | |
| run: bash scripts/check-version-coherence.sh | |
| # --------------------------------------------------------------------------- | |
| # Lane 3: Core tests + conformance | |
| # Gated: runs on main or when source/CI/root-config changed | |
| # --------------------------------------------------------------------------- | |
| tests-core: | |
| name: Tests and Conformance | |
| needs: detect-changes | |
| if: >- | |
| github.ref == 'refs/heads/main' || | |
| needs.detect-changes.outputs.core == 'true' || | |
| needs.detect-changes.outputs.adapters == 'true' || | |
| needs.detect-changes.outputs.any_src == 'true' || | |
| needs.detect-changes.outputs.ci == 'true' || | |
| needs.detect-changes.outputs.root_config == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: ./.github/actions/setup-env | |
| - name: Build all packages | |
| run: pnpm build | |
| - name: Core tests | |
| run: pnpm test:core | |
| - name: Schema meta-validation | |
| run: node scripts/lint-schemas.mjs | |
| - name: Fixture integrity (per-fixture versioning) | |
| run: node scripts/validate-fixtures.mjs | |
| - name: Conformance tests (all suites) | |
| run: pnpm test:conformance | |
| - name: Bundle vectors sanity check | |
| run: | | |
| pnpm conformance:regen:bundle | |
| echo "OK: Bundle vector generator executed successfully" | |
| git checkout -- specs/conformance/fixtures/bundle/vectors/ 2>/dev/null || true | |
| - name: No working tree changes after tests | |
| run: | | |
| if [ -n "$(git status --porcelain)" ]; then | |
| echo "FAIL: Working tree has uncommitted changes after tests:" | |
| git status --short | |
| echo "" | |
| echo "If perf baselines changed, run: PEAC_PERF_UPDATE=1 pnpm test" | |
| exit 1 | |
| fi | |
| echo "OK: Working tree is clean after tests" | |
| # Performance gates: advisory in this workflow; blocking in nightly/release workflows | |
| - name: Performance SLO gate (advisory, Node 24 canonical) | |
| continue-on-error: true | |
| run: | | |
| PEAC_BENCH_JSON="tests/perf/ci-bench-result.json" \ | |
| pnpm exec vitest run tests/perf/wire02-slo.test.ts --reporter=dot | |
| - name: Extension regression gate (advisory, strict mode, Node 24 canonical) | |
| continue-on-error: true | |
| run: | | |
| PEAC_BENCH_JSON="tests/perf/ci-ext-bench-result.json" \ | |
| pnpm exec vitest run tests/perf/extension-regression.test.ts --reporter=dot | |
| - name: Upload benchmark artifacts | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: benchmark-results-${{ github.sha }} | |
| path: | | |
| tests/perf/ci-bench-result.json | |
| tests/perf/ci-ext-bench-result.json | |
| if-no-files-found: ignore | |
| retention-days: 90 | |
| # --------------------------------------------------------------------------- | |
| # Lane 3b: Protocol rollback-path matrix (release-class gate) | |
| # | |
| # Runs the @peac/protocol test suite twice, once with the internal | |
| # rollback-path flag PEAC_INTERNAL_LEGACY_PATH=0 (default) and once | |
| # with =1, across both supported LTS Node majors. Both halves green | |
| # verifies the flag is plumbed cleanly and no test silently depends | |
| # on a specific flag state. Both flag values currently use the same | |
| # protocol path, so the matrix asserts test-suite equivalence under | |
| # both, not universal byte-equivalence. | |
| # | |
| # Blocking matrix: Node 22 (Maintenance LTS) + Node 24 (Active LTS) | |
| # x legacy_path 0/1 = 4 cells. Programmatic-option coverage runs | |
| # inside the Vitest test suite itself | |
| # (packages/protocol/__tests__/_internal/legacy-path-flag.test.ts), | |
| # not as an extra CI matrix axis. | |
| # | |
| # Runs on every protocol-touching PR (detect-changes covers core), | |
| # on every release/* branch, and unconditionally on main, so the | |
| # release-prep gate inherits a hard tag-blocker. | |
| # --------------------------------------------------------------------------- | |
| protocol-rollback-path-matrix: | |
| name: Protocol Rollback-Path Matrix | |
| needs: detect-changes | |
| if: >- | |
| github.ref == 'refs/heads/main' || | |
| startsWith(github.ref, 'refs/heads/release/') || | |
| needs.detect-changes.outputs.core == 'true' || | |
| needs.detect-changes.outputs.ci == 'true' || | |
| needs.detect-changes.outputs.root_config == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| legacy_path: ['0', '1'] | |
| node-version: ['22', '24'] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: ./.github/actions/setup-env | |
| - name: Override Node to ${{ matrix.node-version }} | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: 'pnpm' | |
| - name: Build @peac/protocol and its workspace dependencies | |
| # Trailing `...` selects @peac/protocol AND every workspace | |
| # package it depends on (kernel, schema, crypto, ...). Targeted | |
| # build keeps the matrix cost lower than `pnpm build` while | |
| # ensuring tsc can resolve all `@peac/*` declarations. | |
| run: pnpm --filter '@peac/protocol...' build | |
| - name: Run @peac/protocol tests with PEAC_INTERNAL_LEGACY_PATH=${{ matrix.legacy_path }} | |
| env: | |
| PEAC_INTERNAL_LEGACY_PATH: ${{ matrix.legacy_path }} | |
| run: pnpm --filter @peac/protocol test | |
| # --------------------------------------------------------------------------- | |
| # Lane 3c: Protocol rollback-path matrix (advisory Current Node lane) | |
| # | |
| # Runs the same @peac/protocol test suite under both legacy-path | |
| # values on the latest Node Current line. Advisory only: | |
| # `continue-on-error: true` so a Current-line regression does not | |
| # block release. Required gating stays on the LTS lanes (Lane 3b). | |
| # --------------------------------------------------------------------------- | |
| protocol-rollback-path-matrix-current-node: | |
| name: Protocol Rollback-Path Matrix (Node Current, advisory) | |
| needs: detect-changes | |
| if: >- | |
| github.ref == 'refs/heads/main' || | |
| startsWith(github.ref, 'refs/heads/release/') || | |
| needs.detect-changes.outputs.core == 'true' || | |
| needs.detect-changes.outputs.ci == 'true' || | |
| needs.detect-changes.outputs.root_config == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| continue-on-error: true | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| legacy_path: ['0', '1'] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: ./.github/actions/setup-env | |
| - name: Setup Node Current | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| # `current` resolves to the latest released Node line. Verify | |
| # at maintenance time that this still maps to a supported | |
| # Current major (Node release schedule moves over time). | |
| node-version: 'current' | |
| cache: 'pnpm' | |
| - name: Build @peac/protocol and its workspace dependencies | |
| run: pnpm --filter '@peac/protocol...' build | |
| - name: Run @peac/protocol tests with PEAC_INTERNAL_LEGACY_PATH=${{ matrix.legacy_path }} | |
| env: | |
| PEAC_INTERNAL_LEGACY_PATH: ${{ matrix.legacy_path }} | |
| run: pnpm --filter @peac/protocol test | |
| # --------------------------------------------------------------------------- | |
| # Lane 4: Examples + Apps (only when relevant paths changed) | |
| # --------------------------------------------------------------------------- | |
| examples-apps: | |
| name: Examples and Apps | |
| needs: detect-changes | |
| if: >- | |
| needs.detect-changes.outputs.examples == 'true' || | |
| needs.detect-changes.outputs.apps == 'true' || | |
| needs.detect-changes.outputs.core == 'true' || | |
| needs.detect-changes.outputs.adapters == 'true' || | |
| needs.detect-changes.outputs.root_config == 'true' || | |
| github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: ./.github/actions/setup-env | |
| - name: Build all packages | |
| run: pnpm build | |
| - name: Examples typecheck | |
| run: pnpm examples:check | |
| - name: Quickstart demo (issue + verify) | |
| run: pnpm --filter @peac/example-quickstart demo | |
| - name: DID verification example | |
| run: pnpm --filter @peac/example-did-verification verify | |
| - name: ERC-8004 mapping conformance | |
| run: pnpm --filter @peac/example-erc8004-feedback verify | |
| - name: No X-PEAC headers in examples | |
| run: | | |
| if rg -i "x-peac" examples/ --glob '!*.md' 2>/dev/null | grep -q .; then | |
| echo "FAIL: Found X-PEAC headers in examples (use PEAC-Receipt instead)" | |
| exit 1 | |
| fi | |
| echo "OK: No X-PEAC headers found" | |
| - name: App builds (sandbox-issuer, verifier, api) | |
| run: pnpm build:apps | |
| - name: App tests (sandbox-issuer, verifier, api) | |
| run: pnpm test:apps | |
| - name: Sandbox issuer health smoke | |
| run: | | |
| node apps/sandbox-issuer/dist/node.js & | |
| SERVER_PID=$! | |
| sleep 2 | |
| STATUS=$(curl -sf http://127.0.0.1:3100/health | node -e "process.stdin.setEncoding('utf8');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).status))") | |
| kill $SERVER_PID 2>/dev/null || true | |
| if [ "$STATUS" != "ok" ]; then | |
| echo "FAIL: Sandbox issuer /health returned '$STATUS', expected 'ok'" | |
| exit 1 | |
| fi | |
| echo "OK: Sandbox issuer health check passed" | |
| # --------------------------------------------------------------------------- | |
| # Lane 5: Pack-install smoke (only when published package surfaces changed) | |
| # --------------------------------------------------------------------------- | |
| pack-smoke: | |
| name: Pack and Install Smoke | |
| needs: detect-changes | |
| if: >- | |
| needs.detect-changes.outputs.published == 'true' || | |
| needs.detect-changes.outputs.root_config == 'true' || | |
| github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 8 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: ./.github/actions/setup-env | |
| - name: Build all packages | |
| run: pnpm build | |
| - name: Pack and install gate | |
| run: bash scripts/pack-and-install.sh | |
| - name: OTel pack-and-import smoke test | |
| run: bash scripts/otel-smoke.sh | |
| # --------------------------------------------------------------------------- | |
| # Aggregator: evaluates all lane results. | |
| # Uses `if: always()` so it runs even when optional lanes skip. | |
| # | |
| # Branch-protection continuity: this job preserves the required check name | |
| # "Build, Lint, Test" (job id: ci). No branch-protection rename is needed. | |
| # The old single-job `ci` is replaced internally by this aggregator over | |
| # parallel lane jobs. ci-windows, node-compat, and scope-guard remain | |
| # separate required checks. | |
| # --------------------------------------------------------------------------- | |
| ci: | |
| name: Build, Lint, Test | |
| needs: [workflow-lint, fast-guards, type-build, tests-core, examples-apps, pack-smoke] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - name: Evaluate lane results | |
| run: | | |
| echo "workflow-lint: ${{ needs.workflow-lint.result }}" | |
| echo "fast-guards: ${{ needs.fast-guards.result }}" | |
| echo "type-build: ${{ needs.type-build.result }}" | |
| echo "tests-core: ${{ needs.tests-core.result }}" | |
| echo "examples-apps: ${{ needs.examples-apps.result }}" | |
| echo "pack-smoke: ${{ needs.pack-smoke.result }}" | |
| # workflow-lint: always required, must succeed | |
| if [[ "${{ needs.workflow-lint.result }}" != "success" ]]; then | |
| echo "FAIL: workflow-lint did not succeed"; exit 1 | |
| fi | |
| # fast-guards: always required, must succeed | |
| if [[ "${{ needs.fast-guards.result }}" != "success" ]]; then | |
| echo "FAIL: fast-guards did not succeed"; exit 1 | |
| fi | |
| # type-build: required when it runs, OK when skipped | |
| if [[ "${{ needs.type-build.result }}" == "failure" || "${{ needs.type-build.result }}" == "cancelled" ]]; then | |
| echo "FAIL: type-build failed"; exit 1 | |
| fi | |
| # tests-core: required when it runs, OK when skipped | |
| if [[ "${{ needs.tests-core.result }}" == "failure" || "${{ needs.tests-core.result }}" == "cancelled" ]]; then | |
| echo "FAIL: tests-core failed"; exit 1 | |
| fi | |
| # examples-apps: optional (success or skipped OK, failure blocks) | |
| if [[ "${{ needs.examples-apps.result }}" == "failure" || "${{ needs.examples-apps.result }}" == "cancelled" ]]; then | |
| echo "FAIL: examples-apps failed"; exit 1 | |
| fi | |
| # pack-smoke: optional (success or skipped OK, failure blocks) | |
| if [[ "${{ needs.pack-smoke.result }}" == "failure" || "${{ needs.pack-smoke.result }}" == "cancelled" ]]; then | |
| echo "FAIL: pack-smoke failed"; exit 1 | |
| fi | |
| echo "All lanes passed" | |
| # --------------------------------------------------------------------------- | |
| # Windows audit gate (unchanged) | |
| # --------------------------------------------------------------------------- | |
| ci-windows: | |
| name: Audit Gate (Windows) | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| needs: detect-changes | |
| if: | | |
| needs.detect-changes.outputs.ci == 'true' || | |
| needs.detect-changes.outputs.root_config == 'true' || | |
| needs.detect-changes.outputs.any_src == 'true' || | |
| needs.detect-changes.outputs.security_config == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version-file: '.node-version' | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Audit gate (default mode) | |
| run: node scripts/audit-gate.mjs | |
| - name: Audit gate (strict mode) | |
| run: node scripts/audit-gate.mjs | |
| env: | |
| AUDIT_STRICT: '1' | |
| - name: Audit gate tests | |
| run: pnpm exec vitest run tests/scripts/audit-gate.test.ts | |
| # --------------------------------------------------------------------------- | |
| # Node.js compatibility matrix (unchanged except pinned pnpm action) | |
| # --------------------------------------------------------------------------- | |
| node-compat: | |
| name: Node ${{ matrix.node-version }} (compat) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| node-version: ['22', '25'] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 | |
| - name: Setup Node.js ${{ matrix.node-version }} | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build core packages | |
| run: pnpm --filter @peac/kernel --filter @peac/schema --filter @peac/crypto --filter @peac/protocol build | |
| - name: Test core packages | |
| run: pnpm --filter @peac/kernel --filter @peac/schema --filter @peac/crypto --filter @peac/protocol test | |
| - name: Extension regression (Node ${{ matrix.node-version }}, advisory) | |
| continue-on-error: true | |
| run: | | |
| PEAC_BENCH_JSON="tests/perf/ci-compat-${{ matrix.node-version }}.json" \ | |
| pnpm exec vitest run tests/perf/extension-regression.test.ts --reporter=dot | |
| - name: Upload compat benchmark artifacts | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: compat-bench-node-${{ matrix.node-version }}-${{ github.sha }} | |
| path: tests/perf/ci-compat-*.json | |
| if-no-files-found: ignore | |
| retention-days: 90 | |
| # --------------------------------------------------------------------------- | |
| # PR scope guard (unchanged) | |
| # --------------------------------------------------------------------------- | |
| scope-guard: | |
| name: PR Scope Guard | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Check PR scope | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: bash scripts/ci/scope-guard.sh | |
| # --------------------------------------------------------------------------- | |
| # Opt-in shadow-mode lane. | |
| # | |
| # Runs the @peac/protocol test suite under PEAC_INTERNAL_SHADOW_CORE=1 | |
| # only when the PR carries the `run-shadow-lane` label. The nightly | |
| # workflow runs the same body unconditionally; the opt-in lane lets a | |
| # contributor pull that coverage onto a specific PR branch when the | |
| # change is plausibly observable through the shadow path. | |
| # --------------------------------------------------------------------------- | |
| shadow-lane-pr: | |
| name: Shadow-mode test suite (opt-in) | |
| if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-shadow-lane') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| PM="$(node -p "require(require('path').join(process.env.GITHUB_WORKSPACE||process.cwd(),'package.json')).packageManager")" | |
| corepack prepare "$PM" --activate | |
| pnpm --version | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version-file: '.node-version' | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build @peac/protocol | |
| run: pnpm --filter @peac/protocol build | |
| - name: Run @peac/protocol test suite under shadow flag | |
| env: | |
| PEAC_INTERNAL_SHADOW_CORE: '1' | |
| run: pnpm --filter @peac/protocol test | |
| - name: Verify shadow-log redaction (any persisted artifacts) | |
| run: node scripts/verify-shadow-redaction.mjs |