diff --git a/.github/actions/verify-native/action.yml b/.github/actions/verify-native/action.yml new file mode 100644 index 00000000..9df12275 --- /dev/null +++ b/.github/actions/verify-native/action.yml @@ -0,0 +1,99 @@ +name: Verify native owning candidate +description: Prepare the original disposable host and execute all owner prerequisites and existing component scenes. +runs: + using: composite + steps: + - run: npm prune --ignore-scripts --no-audit --no-fund --workspaces=false + shell: bash + - name: Fetch exact existing Reviewer runner + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + repository: treeseed-ai/reviewer + ref: 001329f305c79beb6c1c8dfb9b85a36c6360e3eb + path: .treeseed/tools/reviewer + - name: Install the executing Reviewer suite dependencies + run: npm ci --prefix .treeseed/tools/reviewer --ignore-scripts --no-audit --no-fund + shell: bash + - name: Measure the executing Reviewer prerequisite custody + id: reviewer-prerequisites + run: node --import ./.treeseed/tools/reviewer/node_modules/tsx/dist/loader.mjs .treeseed/tools/reviewer/scripts/scene-action-prerequisites.ts + shell: bash + - name: Install Reviewer native prerequisite toolchain + if: steps.reviewer-prerequisites.outputs.toolchain-ready != 'true' + uses: erlef/setup-beam@v1 + with: + otp-version: 27.3.4.18 + elixir-version: 1.17.3 + - name: Install the executing Reviewer exact SDK artifact + uses: treeseed-ai/sdk/.github/actions/install-exact-sdk@8702e0285622276d850250a5f8637b6d704318d1 + env: + NODE_ENV: production + with: + github-token: ${{ github.token }} + commit: ${{ steps.reviewer-prerequisites.outputs.sdk-commit }} + paths: .treeseed/tools/reviewer/node_modules/@treeseed/sdk + - name: Build the executing Reviewer archive assets + run: npm run --prefix .treeseed/tools/reviewer build:dist + shell: bash + - name: Reproduce executing Reviewer installed archives before full suites + working-directory: .treeseed/tools/reviewer + run: sudo --preserve-env=GITHUB_WORKSPACE,NPM_CONFIG_CACHE,PATH "$(command -v node)" node_modules/vitest/vitest.mjs run --config ./vitest.config.ts tests/integration/guarantees/installed-custody.test.ts -t '^executes actual production-installed Reviewer and owner archives and retains denied checkout imports and missing-helper failures before exact retry$' + shell: bash + - name: Build original capacity host installer + run: npm run build:deb + env: + TREESEED_APT_SUITE: development + TREESEED_INTEGRATION_RELEASES: https://raw.githubusercontent.com/treeseed-ai/platform/be36cd24299c0381a4e1c81c420823f6340ca136/config/integrations/stable.release.json,https://raw.githubusercontent.com/treeseed-ai/platform/be36cd24299c0381a4e1c81c420823f6340ca136/config/integrations/development.release.json + shell: bash + - name: Initialize disposable native capacity host + run: sudo --preserve-env=GITHUB_ACTIONS,RUNNER_ENVIRONMENT,GITHUB_REPOSITORY,GITHUB_WORKSPACE,TREESEED_PRIVILEGED_CACHE_TESTS,NPM_CONFIG_CACHE,PATH "$(command -v node)" --import tsx scripts/verify-native-host.ts + env: + TREESEED_PRIVILEGED_CACHE_TESTS: "1" + shell: bash + - name: Prepare native development image before timed tests + run: | + node node_modules/tsx/dist/cli.mjs --eval ' + import {execFileSync} from "node:child_process"; + import {resolveDevelopmentRuntimeImage} from "./src/supervisor/development-container.ts"; + const image="mirror.gcr.io/library/node@sha256:51b1100cc2a83d370c6a60952e3f2989c8a43159d0e38586e090f3b3326efefd"; + const docker=(args:readonly string[])=>execFileSync("/usr/bin/docker",[...args],{encoding:"utf8",stdio:["ignore","pipe","pipe"]}); + docker(["pull","--quiet",image]); + const expected=docker(["image","inspect",image,"--format","{{.Id}}"]).trim(); + docker(["tag",image,"node:24-bookworm-slim"]); + const resolved=resolveDevelopmentRuntimeImage((_executable:string,args:readonly string[])=>docker(args)); + if(resolved!==expected)throw new Error("Native development image readback differs from its held manifest."); + ' + shell: bash + - name: Reproduce public host inspection before the complete native suites + run: sudo --preserve-env=GITHUB_ACTIONS,GITHUB_WORKSPACE,TREESEED_PRIVILEGED_CACHE_TESTS,NPM_CONFIG_CACHE,PATH "$(command -v node)" node_modules/vitest/vitest.mjs run tests/sandbox-host-inspection-native.test.ts tests/warm-sandbox-pool.test.ts tests/sandbox-vm-custody.test.ts + env: + TREESEED_PRIVILEGED_CACHE_TESTS: "1" + shell: bash + - run: sudo --preserve-env=GITHUB_ACTIONS,GITHUB_WORKSPACE,TREESEED_PRIVILEGED_CACHE_TESTS,NPM_CONFIG_CACHE,PATH bash -euo pipefail -c 'npm run verify:direct' + env: + TREESEED_PRIVILEGED_CACHE_TESTS: "1" + shell: bash + - name: Execute coded sandbox component scenes + shell: bash + run: | + sudo --preserve-env=GITHUB_WORKSPACE,GITHUB_RUN_ID,GITHUB_RUN_ATTEMPT,TREESEED_PRIVILEGED_CACHE_TESTS,NPM_CONFIG_CACHE,PATH "$(command -v node)" --import ./.treeseed/tools/reviewer/node_modules/tsx/dist/loader.mjs \ + .treeseed/tools/reviewer/src/verifiers/guarantees/command.ts \ + --workspace "$GITHUB_WORKSPACE" --environment local --ids guarantee.deployment.golden.component-boundaries \ + --run-id "actions-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + env: + TREESEED_PRIVILEGED_CACHE_TESTS: "1" + - name: Retain coded scene evidence + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: scene-evidence-${{ github.sha }}-${{ github.run_attempt }} + path: .treeseed/guarantees/runs/actions-${{ github.run_id }}-${{ github.run_attempt }}/ + if-no-files-found: error + include-hidden-files: true + retention-days: 7 + - name: Return disposable npm cache to the packaging user + run: | + set -euo pipefail + test "$NPM_CONFIG_CACHE" = "$GITHUB_WORKSPACE/.treeseed/npm-cache" + sudo chown --no-dereference --recursive -- "$(id -u):$(id -g)" "$NPM_CONFIG_CACHE" + shell: bash diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 14d98b01..623f091c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,6 +28,7 @@ permissions: id-token: write attestations: write packages: write + actions: read concurrency: group: ${{ inputs.suite }}-apt-publication @@ -36,10 +37,11 @@ concurrency: jobs: publish: environment: ${{ inputs.suite == 'stable' && 'production' || 'staging' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 env: TREESEED_APT_SUITE: ${{ inputs.suite }} TREESEED_INTEGRATION_RELEASES: ${{ inputs.stable_lock_url }},${{ inputs.development_lock_url }} + NPM_CONFIG_CACHE: ${{ github.workspace }}/.treeseed/npm-cache steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: @@ -67,7 +69,7 @@ jobs: run: test -n "$APT_SIGNING_KEY" - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: - node-version: 24 + node-version: 24.12.0 cache: npm - run: npm ci - name: Restore exact release assets for APT-only resume @@ -115,8 +117,15 @@ jobs: else gh release upload "${{ inputs.tag }}" "${catalog[0]}" fi - - run: npm run verify:direct + - uses: treeseed-ai/sdk/.github/actions/install-exact-sdk@cbc03314871d271bce7275c5cee71d950397a0c3 + env: + NODE_ENV: production + with: + github-token: ${{ github.token }} + if: ${{ !inputs.resume_apt_only }} + - name: Verify native owning candidate if: ${{ !inputs.resume_apt_only }} + uses: ./.github/actions/verify-native - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 if: ${{ false }} - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index d49f9806..aecd4e70 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -3,7 +3,7 @@ name: Verify on: pull_request: push: - branches: [staging, main] + branches: [ staging, main ] permissions: contents: read @@ -64,89 +64,8 @@ jobs: NODE_ENV: production with: github-token: ${{ github.token }} - - run: npm prune --ignore-scripts --no-audit --no-fund --workspaces=false - - name: Fetch exact existing Reviewer runner - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: - repository: treeseed-ai/reviewer - ref: 001329f305c79beb6c1c8dfb9b85a36c6360e3eb - path: .treeseed/tools/reviewer - - name: Install the executing Reviewer suite dependencies - run: npm ci --prefix .treeseed/tools/reviewer --ignore-scripts --no-audit --no-fund - - name: Measure the executing Reviewer prerequisite custody - id: reviewer-prerequisites - run: node --import ./.treeseed/tools/reviewer/node_modules/tsx/dist/loader.mjs .treeseed/tools/reviewer/scripts/scene-action-prerequisites.ts - - name: Install Reviewer native prerequisite toolchain - if: steps.reviewer-prerequisites.outputs.toolchain-ready != 'true' - uses: erlef/setup-beam@v1 - with: - otp-version: "27.3.4.18" - elixir-version: "1.17.3" - - name: Install the executing Reviewer exact SDK artifact - uses: treeseed-ai/sdk/.github/actions/install-exact-sdk@8702e0285622276d850250a5f8637b6d704318d1 - env: - NODE_ENV: production - with: - github-token: ${{ github.token }} - commit: ${{ steps.reviewer-prerequisites.outputs.sdk-commit }} - paths: .treeseed/tools/reviewer/node_modules/@treeseed/sdk - - name: Build the executing Reviewer archive assets - run: npm run --prefix .treeseed/tools/reviewer build:dist - - name: Reproduce executing Reviewer installed archives before full suites - working-directory: .treeseed/tools/reviewer - run: sudo --preserve-env=GITHUB_WORKSPACE,NPM_CONFIG_CACHE,PATH "$(command -v node)" node_modules/vitest/vitest.mjs run --config ./vitest.config.ts tests/integration/guarantees/installed-custody.test.ts -t '^executes actual production-installed Reviewer and owner archives and retains denied checkout imports and missing-helper failures before exact retry$' - - name: Build original capacity host installer - run: npm run build:deb - env: - TREESEED_APT_SUITE: development - TREESEED_INTEGRATION_RELEASES: https://raw.githubusercontent.com/treeseed-ai/platform/be36cd24299c0381a4e1c81c420823f6340ca136/config/integrations/stable.release.json,https://raw.githubusercontent.com/treeseed-ai/platform/be36cd24299c0381a4e1c81c420823f6340ca136/config/integrations/development.release.json - - name: Initialize disposable native capacity host - run: sudo --preserve-env=GITHUB_ACTIONS,RUNNER_ENVIRONMENT,GITHUB_REPOSITORY,GITHUB_WORKSPACE,TREESEED_PRIVILEGED_CACHE_TESTS,NPM_CONFIG_CACHE,PATH "$(command -v node)" --import tsx scripts/verify-native-host.ts - env: - TREESEED_PRIVILEGED_CACHE_TESTS: '1' - - name: Prepare native development image before timed tests - run: | - node node_modules/tsx/dist/cli.mjs --eval ' - import {execFileSync} from "node:child_process"; - import {resolveDevelopmentRuntimeImage} from "./src/supervisor/development-container.ts"; - const image="mirror.gcr.io/library/node@sha256:51b1100cc2a83d370c6a60952e3f2989c8a43159d0e38586e090f3b3326efefd"; - const docker=(args:readonly string[])=>execFileSync("/usr/bin/docker",[...args],{encoding:"utf8",stdio:["ignore","pipe","pipe"]}); - docker(["pull","--quiet",image]); - const expected=docker(["image","inspect",image,"--format","{{.Id}}"]).trim(); - docker(["tag",image,"node:24-bookworm-slim"]); - const resolved=resolveDevelopmentRuntimeImage((_executable:string,args:readonly string[])=>docker(args)); - if(resolved!==expected)throw new Error("Native development image readback differs from its held manifest."); - ' - - name: Reproduce public host inspection before the complete native suites - run: sudo --preserve-env=GITHUB_ACTIONS,GITHUB_WORKSPACE,TREESEED_PRIVILEGED_CACHE_TESTS,NPM_CONFIG_CACHE,PATH "$(command -v node)" node_modules/vitest/vitest.mjs run tests/sandbox-host-inspection-native.test.ts tests/warm-sandbox-pool.test.ts tests/sandbox-vm-custody.test.ts - env: - TREESEED_PRIVILEGED_CACHE_TESTS: '1' - - run: sudo --preserve-env=GITHUB_ACTIONS,GITHUB_WORKSPACE,TREESEED_PRIVILEGED_CACHE_TESTS,NPM_CONFIG_CACHE,PATH bash -euo pipefail -c 'npm run verify:direct' - env: - TREESEED_PRIVILEGED_CACHE_TESTS: '1' - - name: Execute coded sandbox component scenes - shell: bash - run: | - sudo --preserve-env=GITHUB_WORKSPACE,GITHUB_RUN_ID,GITHUB_RUN_ATTEMPT,TREESEED_PRIVILEGED_CACHE_TESTS,NPM_CONFIG_CACHE,PATH "$(command -v node)" --import ./.treeseed/tools/reviewer/node_modules/tsx/dist/loader.mjs \ - .treeseed/tools/reviewer/src/verifiers/guarantees/command.ts \ - --workspace "$GITHUB_WORKSPACE" --environment local --ids guarantee.deployment.golden.component-boundaries \ - --run-id "actions-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - env: - TREESEED_PRIVILEGED_CACHE_TESTS: '1' - - name: Retain coded scene evidence - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: scene-evidence-${{ github.sha }}-${{ github.run_attempt }} - path: .treeseed/guarantees/runs/actions-${{ github.run_id }}-${{ github.run_attempt }}/ - if-no-files-found: error - include-hidden-files: true - retention-days: 7 - - name: Return disposable npm cache to the packaging user - run: | - set -euo pipefail - test "$NPM_CONFIG_CACHE" = "$GITHUB_WORKSPACE/.treeseed/npm-cache" - sudo chown --no-dereference --recursive -- "$(id -u):$(id -g)" "$NPM_CONFIG_CACHE" + - name: Verify native owning candidate + uses: ./.github/actions/verify-native - run: npm run verify:deb env: TREESEED_APT_SUITE: development diff --git a/guarantees/agent/golden/scenes/component-boundaries.scene.yaml b/guarantees/agent/golden/scenes/component-boundaries.scene.yaml index 9893aca1..cdb67ef2 100644 --- a/guarantees/agent/golden/scenes/component-boundaries.scene.yaml +++ b/guarantees/agent/golden/scenes/component-boundaries.scene.yaml @@ -847,3 +847,6 @@ workflow: - id: package-proxy-custody-native action: { verifier: deployment.golden.package-proxy-custody-native } expect: { status: passed } + - id: native-publication-shared-prerequisites-unit + action: { verifier: deployment.golden.native-publication-shared-prerequisites-unit } + expect: { status: passed } diff --git a/guarantees/verifiers/golden.verifiers.yaml b/guarantees/verifiers/golden.verifiers.yaml index 5f2c6337..787cc7d8 100644 --- a/guarantees/verifiers/golden.verifiers.yaml +++ b/guarantees/verifiers/golden.verifiers.yaml @@ -1390,3 +1390,9 @@ verifiers: ownerPackage: "@treeseed/deployment" testFile: tests/package-proxy-native.test.ts testName: "reads the real immutable release through guest authentication and denies foreign, missing and expired authority" + + deployment.golden.native-publication-shared-prerequisites-unit: + kind: vitestCase + ownerPackage: "@treeseed/deployment" + testFile: tests/native-publication-workflow.test.ts + testName: "shares the complete original native prerequisites suites and scenes before verification or release packaging" diff --git a/tests/acceptance-prerequisites-contract.test.ts b/tests/acceptance-prerequisites-contract.test.ts index 45fd12e2..f002b377 100644 --- a/tests/acceptance-prerequisites-contract.test.ts +++ b/tests/acceptance-prerequisites-contract.test.ts @@ -4,6 +4,7 @@ import { createRequire } from 'node:module'; import { tmpdir } from 'node:os'; import { expect, it, onTestFailed } from 'vitest'; import { parse } from 'yaml'; +import { expandNativeSteps } from './fixtures/native-verification-steps.js'; import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { assertDisposableNativeHost } from '../scripts/verify-native-host.js'; @@ -28,7 +29,7 @@ it('native denied host inspection retains its exact safe phase before resource s it('native development image preparation holds one exact official manifest before timed suites and verifies local image readback',()=>{ const path='.github/workflows/verify.yml',bytes=readFileSync(path); - const steps=parse(bytes.toString()).jobs.verify.steps as {name?:string;run?:string}[]; + const steps=expandNativeSteps(parse(bytes.toString()).jobs.verify.steps) as {name?:string;run?:string}[]; const preparation=steps.find(step=>step.name==='Prepare native development image before timed tests')!.run!; const payload=preparation.split("--eval '\n")[1]!.replace(/\n\s*'\s*$/u,''); expect(payload).not.toContain("'"); @@ -95,7 +96,7 @@ it('native original Vitest retains controlled phase criteria alongside real asse expect(rows[1]?.duration).toBeGreaterThanOrEqual(20); expect(rows[1]?.failureMessages.join('\n')).toContain('ACCEPTANCE_NATIVE_COLD_BUILD_WATCHDOG:'); const workflow=parse(readFileSync(new URL('../.github/workflows/verify.yml',import.meta.url),'utf8')); - const steps=workflow.jobs.verify.steps as {with?:Record}[]; + const steps=expandNativeSteps(workflow.jobs.verify.steps) as {with?:Record}[]; const checkout=steps.find(step=>step.with?.repository==='treeseed-ai/reviewer')!.with!; const reviewer=resolve(checkout.path!); const head=spawnSync('git',['-C',reviewer,'rev-parse','HEAD'],{encoding:'utf8',timeout:5000}); @@ -116,7 +117,7 @@ it('native original Vitest retains controlled phase criteria alongside real asse it('native pinned Reviewer retains bounded owning failure evidence after fresh complete prerequisites without exposing assertion values', () => { const workflow=parse(readFileSync('.github/workflows/verify.yml','utf8')); - const checkout=(workflow.jobs.verify.steps as {with?:Record}[]) + const checkout=(expandNativeSteps(workflow.jobs.verify.steps) as {with?:Record}[]) .find(step=>step.with?.repository==='treeseed-ai/reviewer')!.with!; const reviewer=resolve(checkout.path!),heldHead=spawnSync('git',['-C',reviewer,'rev-parse','HEAD'],{encoding:'utf8',timeout:5000}); expect(heldHead.status).toBe(0);expect(heldHead.stdout.trim()).toBe(checkout.ref); @@ -222,7 +223,7 @@ it('parses component metadata and binds every scene step to exactly one existing it('runs complete privileged owner prerequisites before coded scenes without a filtered substitute', () => { const workflow=parse(readFileSync(new URL('../.github/workflows/verify.yml',import.meta.url),'utf8')); - const steps=workflow.jobs.verify.steps as {name?:string;run?:string;env?:Record;uses?:string;with?:Record;'working-directory'?:string}[]; + const steps=expandNativeSteps(workflow.jobs.verify.steps) as {name?:string;run?:string;env?:Record;uses?:string;with?:Record;'working-directory'?:string}[]; expect(workflow.jobs.verify.env?.NPM_CONFIG_CACHE).toBe('${{ github.workspace }}/.treeseed/npm-cache'); for(const step of steps.filter(step=>step.run?.includes('sudo --preserve-env='))) expect(step.run).toMatch(/sudo --preserve-env=[^\s]*NPM_CONFIG_CACHE[^\s]* /u); @@ -272,7 +273,7 @@ it('runs complete privileged owner prerequisites before coded scenes without a f it('provisions only a disposable native Actions host through the original installer before complete privileged prerequisites', () => { const bytes=readFileSync('.github/workflows/verify.yml'); const workflow=parse(bytes.toString()),job=workflow.jobs.verify; - const steps=job.steps as {name?:string;run?:string;env?:Record}[]; + const steps=expandNativeSteps(job.steps) as {name?:string;run?:string;env?:Record}[]; expect(job['runs-on']).toBe('ubuntu-26.04'); const native=steps.find(step=>step.name==='Initialize disposable native capacity host'); expect(native).toBeDefined(); @@ -291,7 +292,7 @@ it('provisions only a disposable native Actions host through the original instal it('capacity execution packaging binds one exact SDK dependency to its original installer and every transitive consumer', () => { const inputs=new Map(['package.json','package-lock.json','.github/workflows/verify.yml'].map(path=>[path,readFileSync(path)])); const manifest=JSON.parse(inputs.get('package.json')!.toString()),lock=JSON.parse(inputs.get('package-lock.json')!.toString()); - const steps=parse(inputs.get('.github/workflows/verify.yml')!.toString()).jobs.verify.steps as {uses?:string;run?:string;env?:Record;with?:Record}[]; + const steps=expandNativeSteps(parse(inputs.get('.github/workflows/verify.yml')!.toString()).jobs.verify.steps) as {uses?:string;run?:string;env?:Record;with?:Record}[]; const installers=steps.filter(step=>step.uses?.startsWith('treeseed-ai/sdk/.github/actions/install-exact-sdk@')); expect(installers).toHaveLength(2); const owning=installers.filter(step=>step.with?.paths===undefined); @@ -319,21 +320,21 @@ it('every native execution workflow installs the same declared exact SDK before const dependency=JSON.parse(readFileSync('package.json','utf8')).dependencies['@treeseed/sdk'] as string; const commit=dependency.split('#').at(-1)!; expect(commit).toMatch(/^[a-f0-9]{40}$/u); - const workflows=['verify.yml','development-backup.yml','identity-acceptance.yml','postgres-transfer.yml']; + const workflows=['verify.yml','publish.yml','development-backup.yml','identity-acceptance.yml','postgres-transfer.yml']; for(const name of workflows){ const path=resolve('.github/workflows',name),bytes=readFileSync(path); const workflow=parse(bytes.toString()) as {jobs:Record}[]}>}; - const runtimes=Object.values(workflow.jobs).flatMap(job=>job.steps).filter(step=>step.uses?.startsWith('actions/setup-node@')); + const runtimes=Object.values(workflow.jobs).flatMap(job=>expandNativeSteps(job.steps)).filter(step=>step.uses?.startsWith('actions/setup-node@')); expect(runtimes.length,name).toBeGreaterThan(0); for(const runtime of runtimes)expect(runtime.with?.['node-version'],name).toBe('24.12.0'); - const installers=Object.values(workflow.jobs).flatMap(job=>job.steps) + const installers=Object.values(workflow.jobs).flatMap(job=>expandNativeSteps(job.steps)) .filter(step=>step.uses?.startsWith('treeseed-ai/sdk/.github/actions/install-exact-sdk@')); const owning=installers.filter(step=>step.with?.paths===undefined); expect(owning.length,name).toBeGreaterThan(0); expect(owning.map(step=>step.uses),name).toEqual(owning.map(()=>`treeseed-ai/sdk/.github/actions/install-exact-sdk@${commit}`)); for(const step of owning)expect(step.with?.commit,name).toBeUndefined(); const scoped=installers.filter(step=>step.with?.paths!==undefined); - expect(scoped,name).toEqual(name==='verify.yml'?[expect.objectContaining({ + expect(scoped,name).toEqual(['verify.yml','publish.yml'].includes(name)?[expect.objectContaining({ uses:'treeseed-ai/sdk/.github/actions/install-exact-sdk@8702e0285622276d850250a5f8637b6d704318d1', with:expect.objectContaining({commit:'${{ steps.reviewer-prerequisites.outputs.sdk-commit }}', paths:'.treeseed/tools/reviewer/node_modules/@treeseed/sdk'})})]:[]); @@ -363,7 +364,7 @@ it('native capacity execution package install retains exact held SDK bytes and r passed(run('npm',['ci','--ignore-scripts','--no-audit','--no-fund','--workspaces=false'])); const destination=resolve(root,'node_modules/@treeseed/sdk');rmSync(destination,{recursive:true});mkdirSync(destination); passed(run('tar',['-xzf',archive,'--strip-components=1','-C',destination])); - const steps=parse(inputs.get('.github/workflows/verify.yml')!.toString()).jobs.verify.steps as {uses?:string;run?:string;env?:Record}[]; + const steps=expandNativeSteps(parse(inputs.get('.github/workflows/verify.yml')!.toString()).jobs.verify.steps) as {uses?:string;run?:string;env?:Record}[]; const installer=steps.find(step=>step.uses?.startsWith('treeseed-ai/sdk/.github/actions/install-exact-sdk@')); expect(installer?.env?.NODE_ENV).toBe('production'); passed(run('npm',['install','--prefix',destination,'--ignore-scripts','--no-save','--package-lock=false','--no-audit','--no-fund'],root,{...process.env,NODE_ENV:installer!.env!.NODE_ENV})); @@ -408,7 +409,7 @@ it('native installer subprocess rejects a non Actions caller without touching he it('native pinned Reviewer checkout plans every original capacity component case without executing or rewriting candidate evidence', () => { const root=process.cwd(),workflow=parse(readFileSync('.github/workflows/verify.yml','utf8')); - const steps=workflow.jobs.verify.steps as {with?:Record}[]; + const steps=expandNativeSteps(workflow.jobs.verify.steps) as {with?:Record}[]; const checkout=steps.find(step=>step.with?.repository==='treeseed-ai/reviewer')!.with!; const reviewer=resolve(root,checkout.path!),command=resolve(reviewer,'src/verifiers/guarantees/command.ts'); const held=new Map(['.github/workflows/verify.yml','guarantees/verifiers/golden.verifiers.yaml', diff --git a/tests/debian-contract.test.ts b/tests/debian-contract.test.ts index 260a3efa..50c2ea18 100644 --- a/tests/debian-contract.test.ts +++ b/tests/debian-contract.test.ts @@ -5,6 +5,7 @@ import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { describe, expect, it } from 'vitest'; import { parse } from 'yaml'; +import { expandNativeSteps } from './fixtures/native-verification-steps.js'; describe('Debian and systemd contracts', () => { it.each(['publish.yml', 'publish-lab.yml'])('uses environment-scoped Docker Hub username/password in %s', (filename) => { @@ -252,8 +253,13 @@ console.log(JSON.stringify({response:JSON.parse(output), events})); const workflow = readFileSync('.github/workflows/publish.yml', 'utf8'); expect(publisher).toContain('release/apt/${suite}.fingerprint'); expect(publisher).toContain('does not match its published keyring'); - expect(workflow.indexOf('Require protected publication credentials before building')).toBeLessThan(workflow.indexOf('npm run verify:direct')); - expect(workflow.indexOf('Require the protected workflow ref for the selected suite')).toBeLessThan(workflow.indexOf('npm run verify:direct')); + const steps = expandNativeSteps(parse(workflow).jobs.publish.steps) as { name?: string; run?: string }[]; + const complete = steps.findIndex(step => step.run?.includes('npm run verify:direct')); + expect(complete).toBeGreaterThan(-1); + for (const name of ['Require protected publication credentials before building', 'Require the protected workflow ref for the selected suite']) { + const guard = steps.findIndex(step => step.name === name); + expect(guard).toBeGreaterThan(-1); expect(guard).toBeLessThan(complete); + } expect(workflow).toContain("refs/heads/main' || 'refs/heads/staging"); expect(workflow).toContain('Published release asset differs: $name'); expect(workflow).toContain('cmp --silent "$asset" "$existing_dir/$name"'); diff --git a/tests/fixtures/native-verification-steps.ts b/tests/fixtures/native-verification-steps.ts new file mode 100644 index 00000000..4f30d7bf --- /dev/null +++ b/tests/fixtures/native-verification-steps.ts @@ -0,0 +1,14 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { parse } from 'yaml'; + +/** GitHub expands this local composite; contract tests inspect the same effective steps. */ +export const nativeVerificationAction = './.github/actions/verify-native'; +export function expandNativeSteps(steps: T[]): T[] { + return steps.flatMap(step => { + if ((step as { uses?: string }).uses !== nativeVerificationAction) return [step]; + const action = parse(readFileSync('.github/actions/verify-native/action.yml', 'utf8')); + assert.equal(action.runs.using, 'composite'); assert(Array.isArray(action.runs.steps)); + return action.runs.steps as T[]; + }); +} diff --git a/tests/native-publication-workflow.test.ts b/tests/native-publication-workflow.test.ts new file mode 100644 index 00000000..f939b488 --- /dev/null +++ b/tests/native-publication-workflow.test.ts @@ -0,0 +1,42 @@ +import { readFileSync } from 'node:fs'; +import { expect, it } from 'vitest'; +import { parse } from 'yaml'; +import { expandNativeSteps, nativeVerificationAction } from './fixtures/native-verification-steps.js'; + +it('shares the complete original native prerequisites suites and scenes before verification or release packaging', () => { + const held = new Map(); + for (const [name, job] of [['verify', 'verify'], ['publish', 'publish']]) { + const path = `.github/workflows/${name}.yml`, bytes = readFileSync(path); held.set(path, bytes); + const workflow = parse(bytes.toString()), owner = workflow.jobs[job!]; + const declared = owner.steps as { uses?: string; if?: string }[]; + const shared = declared.filter(step => step.uses === nativeVerificationAction); + expect(shared, name).toHaveLength(1); + const directSdk = declared.filter(step => step.uses?.startsWith('treeseed-ai/sdk/.github/actions/install-exact-sdk@')); + expect(directSdk, name).toHaveLength(1); + expect(declared.indexOf(directSdk[0]!)).toBeLessThan(declared.indexOf(shared[0]!)); + expect(owner['runs-on']).toBe('ubuntu-26.04'); + expect(owner.env.NPM_CONFIG_CACHE).toBe('${{ github.workspace }}/.treeseed/npm-cache'); + if (name === 'publish') { + expect(shared[0]!.if).toBe('${{ !inputs.resume_apt_only }}'); + expect(workflow.permissions.actions).toBe('read'); + } + const steps = expandNativeSteps(owner.steps) as { name?: string; run?: string; env?: Record }[]; + const installed = steps.findIndex(step => step.name === 'Initialize disposable native capacity host'); + const complete = steps.findIndex(step => step.run?.includes('npm run verify:direct')); + const scenes = steps.findIndex(step => step.name === 'Execute coded sandbox component scenes'); + const packaging = steps.findIndex(step => step.run === 'npm run verify:deb'); + expect(installed).toBeGreaterThan(-1); expect(complete).toBeGreaterThan(installed); + expect(scenes).toBeGreaterThan(complete); expect(packaging).toBeGreaterThan(scenes); + expect(steps[complete]!.run).toContain('sudo --preserve-env='); + expect(steps[complete]!.env?.TREESEED_PRIVILEGED_CACHE_TESTS).toBe('1'); + expect(steps[scenes]!.run).toContain('guarantee.deployment.golden.component-boundaries'); + expect(steps.filter(step => step.run?.includes('npm run verify:direct'))).toHaveLength(1); + } + const path = '.github/actions/verify-native/action.yml', bytes = readFileSync(path); held.set(path, bytes); + const action = parse(bytes.toString()); expect(action.inputs).toBeUndefined(); + for (const step of action.runs.steps) if (step.run) expect(step.shell).toBe('bash'); + for (const step of action.runs.steps) if (step.uses?.startsWith('treeseed-ai/sdk/.github/actions/install-exact-sdk@')) { + expect(typeof step.with?.commit).toBe('string'); expect(step.with.commit.trim().length).toBeGreaterThan(0); + } + for (const [path, bytes] of held) expect(readFileSync(path)).toEqual(bytes); +});