From 65298717030a8cc23fecb05cdbc53a39795c0340 Mon Sep 17 00:00:00 2001 From: Felipe Rosa Date: Tue, 1 Sep 2026 00:21:28 +0000 Subject: [PATCH 1/3] chore(release): delete build.js/sync.js and add version --check `scripts/build.js` compiled hook executables and regenerated `plugins/genie/package.json`; both compile targets left with the hook runtime and the plugin manifest is now a reviewed, committed Orca-payload file. `scripts/sync.js` copied the plugin into `~/.claude/plugins/genie`, which no longer exists. Their `build:plugin`, `sync` and `build-and-sync` npm scripts and the `scripts/build.js` build-tarballs path filter go with them. `scripts/version.ts` gains a read-only `--check` mode. The bare command read no argv at all and always performed a real bump, so the three-file version set had no runnable verification; `--check` reports the targets and their bump-readiness and exits 0 without writing, and any other argv is rejected before any mutation. CLAUDE.md's version-file sentence follows the set down to three. --- .github/workflows/build-tarballs.yml | 1 - CLAUDE.md | 2 +- package.json | 3 - scripts/build.js | 75 -------------- scripts/release-docs.test.ts | 1 - scripts/sync.js | 144 --------------------------- scripts/version-format.test.ts | 65 ++++++++---- scripts/version.ts | 72 ++++++++++++-- 8 files changed, 115 insertions(+), 248 deletions(-) delete mode 100644 scripts/build.js delete mode 100644 scripts/sync.js diff --git a/.github/workflows/build-tarballs.yml b/.github/workflows/build-tarballs.yml index d21f97bdc..4baea079e 100644 --- a/.github/workflows/build-tarballs.yml +++ b/.github/workflows/build-tarballs.yml @@ -56,7 +56,6 @@ on: - 'bunfig.toml' - 'tsconfig.json' - 'scripts/build-binary.sh' - - 'scripts/build.js' - 'scripts/json-top-level-string.js' - 'scripts/orca-bundle-parity.ts' - 'scripts/fresh-install-smoke.ts' diff --git a/CLAUDE.md b/CLAUDE.md index e194a0e09..c6332cd18 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -182,7 +182,7 @@ Biome's `noExcessiveCognitiveComplexity` is set to `maxAllowedComplexity: 25` (w - **Hook dispatch is provider-aware and fail-closed** — the plugin-local Codex launcher selects only the canonical `$GENIE_HOME/bin/genie`, preserves stdin/stdout/signals, bounds child time/output, validates event-specific JSON, and converts launch/timeout/schema failures into a reasoned deny. Claude dispatch remains a short-lived in-process `genie hook dispatch` fork. Neither path is a daemon. - **`AskUserQuestion` is the one PreToolUse carve-out** — it is in `NON_INTERCEPTABLE_PRE_TOOL_USE_TOOLS` and MUST get an EMPTY response, not the neutral `{ decision: 'block' }` block form. Empirically CC consumes any additionalContext as the synthesized answer, so a fail-closed block would corrupt the inline picker. The fail-closed envelope special-cases this tool. - **The product MCP server and its launchers are retired** — `genie mcp` is a stub that writes a stable diagnostic to stderr and exits 1; no plugin ships an MCP declaration or launcher, and `genie init` only retires proven Genie-owned historical routes (in `.mcp.json` it retires only the dead `genie mcp` entry — a genie binary with args exactly `["mcp"]` — backing the file up first and preserving every other server byte-for-byte; `genie doctor` warns while that entry is still present). The UI-owned `genie ui-bridge` is retired too — there is no Genie UI any more, the Orca integration is the only UI surface — so it is the same shape of stub (stable stderr diagnostic, exit 1), and its private transport (`mcp-server.ts`), tool registry (`mcp-tools.ts`), and change watcher (`bridge-watcher.ts`) are deleted. -- **The Orca plugin ships as a tree-only subtree ref, never from the repo root** — Orca installs a plugin from a git URL+ref (or a local folder) whose ROOT holds `orca-plugin.json`, and its loader rejects any tree containing a symlink ("unsafe file path or symlink") and caps an install at 2000 files / 50 MB. The genie root can therefore never be the install tree (`docs -> .docs-vendor/genie`; ~14k files in a dev checkout), and a re-rooted root `orca-plugin.json` does NOT fix that — do not reintroduce one, and do not add it to `scripts/version.ts`, the `version.yml` JSON_FILES list (still five version files), or `release-guard.sh`. `plugins/genie` alone is symlink-free, ~132 files, ~1.3 MB, and holds the manifest at its root, so `.github/workflows/orca-plugin-ref.yml` force-pushes `git commit-tree HEAD:plugins/genie` (a parentless, history-free commit) to `refs/heads/orca-plugin` from main and `refs/heads/orca-plugin-dev` from dev, skipping when the tree hash already matches. Those refs are never merged back. The only repo-root Orca file is `orca-marketplace.json` — a source-only, versionless index pointing `automagik.genie` at ref `orca-plugin`, copied into no tarball. `scripts/orca-manifest-parity.test.ts` is the drift guard and also asserts `plugins/genie` stays symlink-free and inside the file cap. `genie setup --orchestration-mode orca` selects authority only; it never registers the plugin with Orca. +- **The Orca plugin ships as a tree-only subtree ref, never from the repo root** — Orca installs a plugin from a git URL+ref (or a local folder) whose ROOT holds `orca-plugin.json`, and its loader rejects any tree containing a symlink ("unsafe file path or symlink") and caps an install at 2000 files / 50 MB. The genie root can therefore never be the install tree (`docs -> .docs-vendor/genie`; ~14k files in a dev checkout), and a re-rooted root `orca-plugin.json` does NOT fix that — do not reintroduce one, and do not add it to `scripts/version.ts`, the `version.yml` JSON_FILES list (still three version files — `package.json`, `plugins/genie/package.json`, `plugins/genie/orca-plugin.json`; read them without bumping via `bun scripts/version.ts --check`), or `release-guard.sh`. `plugins/genie` alone is symlink-free, ~132 files, ~1.3 MB, and holds the manifest at its root, so `.github/workflows/orca-plugin-ref.yml` force-pushes `git commit-tree HEAD:plugins/genie` (a parentless, history-free commit) to `refs/heads/orca-plugin` from main and `refs/heads/orca-plugin-dev` from dev, skipping when the tree hash already matches. Those refs are never merged back. The only repo-root Orca file is `orca-marketplace.json` — a source-only, versionless index pointing `automagik.genie` at ref `orca-plugin`, copied into no tarball. `scripts/orca-manifest-parity.test.ts` is the drift guard and also asserts `plugins/genie` stays symlink-free and inside the file cap. `genie setup --orchestration-mode orca` selects authority only; it never registers the plugin with Orca. - **Lifecycle authority is an explicit mode, never inferred** — `standalone` is the default (including when `orchestration.mode` is absent); installing or opening Orca changes nothing. `genie setup --orchestration-mode orca` probes the shipped plugin payload and a compatible Orca runtime before atomically switching, after which Genie refuses local `genie.db` lifecycle reads/writes and roadmap writes/syncs/exports. Switching back with `--orchestration-mode standalone` imports no Orca state. There is no fallback database in either direction. - **Two `genie.db` files, never cross-import** — per-repo `.genie/genie.db` (`genie-db.ts`, task/board/wish) and global `~/.genie/genie.db` (`global-db.ts`, omni queue + inbox) are independent databases with their own schemas and `user_version`. `global-db.ts` shares only `sqlite-open.ts` with the per-repo one — do not reach across for path constants. - **Codex integration health is native state, not OTel** — the old Genie exporter at `127.0.0.1:14318` has no relay and is removed by an exact-match, backup-first migration. Preserve unrelated OTel settings and `disable_paste_burst`. diff --git a/package.json b/package.json index a227b7218..9b68ee2ed 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,6 @@ "version": "bun run scripts/version.ts", "build": "bun build src/genie.ts --outdir dist --target bun --minify-syntax --minify-whitespace --external bun && chmod +x dist/*.js", "build:binary": "bash scripts/build-binary.sh", - "build:plugin": "node scripts/build.js", - "sync": "node scripts/sync.js", - "build-and-sync": "npm run build:plugin && npm run sync", "lint": "biome check .", "lint:fix": "biome check --write .", "lint:docs-links": "markdown-link-check --config .github/markdown-link-check.json SECURITY.md && markdown-link-check --config .github/markdown-link-check.json docs/incident-response/canisterworm.mdx", diff --git a/scripts/build.js b/scripts/build.js deleted file mode 100644 index d632b9823..000000000 --- a/scripts/build.js +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env node - -/** - * Build script for the genie plugin payload. - * - * The generated hook executables this script used to bundle left with the hook - * runtime; what remains is the plugin package manifest generator plus the - * fresh-install smoke that gates it. - */ - -import { execFileSync } from 'node:child_process'; -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { replaceTopLevelStringProperty } from './json-top-level-string.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const rootDir = path.join(__dirname, '..'); - -export function updateManifestVersion(filePath, version) { - const source = fs.readFileSync(filePath, 'utf-8'); - const parsed = JSON.parse(source); - if (typeof parsed.version !== 'string') throw new Error(`manifest has no string version: ${filePath}`); - const updated = replaceTopLevelStringProperty(source, 'version', version); - fs.writeFileSync(filePath, updated); -} - -export function pluginPackageManifest(version) { - return { - name: 'genie-plugin', - version, - private: true, - description: 'Runtime dependencies for genie bundled CLIs', - license: 'MIT', - type: 'module', - dependencies: {}, - engines: { - node: '>=18.0.0', - bun: '>=1.0.0', - }, - }; -} - -export async function buildPlugin() { - console.log('Building genie plugin...\n'); - - try { - execFileSync('bun', [path.join(rootDir, 'scripts/fresh-install-smoke.ts')], { stdio: 'inherit' }); - - // Read version from package.json - const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8')); - const version = packageJson.version; - console.log(`Version: ${version}`); - - // Generate plugin/package.json for dependency installation - console.log('\nGenerating plugin package.json...'); - const pluginPackageJson = pluginPackageManifest(version); - fs.writeFileSync( - path.join(rootDir, 'plugins/genie/package.json'), - `${JSON.stringify(pluginPackageJson, null, 2)}\n`, - ); - console.log('plugins/genie/package.json generated'); - - console.log('\nBuild complete!'); - } catch (error) { - console.error('\nBuild failed:', error.message); - if (error.errors) { - console.error('\nBuild errors:'); - error.errors.forEach((err) => console.error(` - ${err.text}`)); - } - process.exit(1); - } -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) buildPlugin(); diff --git a/scripts/release-docs.test.ts b/scripts/release-docs.test.ts index 93812d936..112898fab 100644 --- a/scripts/release-docs.test.ts +++ b/scripts/release-docs.test.ts @@ -473,7 +473,6 @@ describe('Group E release and documentation contracts', () => { "'bunfig.toml'", "'tsconfig.json'", "'scripts/build-binary.sh'", - "'scripts/build.js'", "'scripts/json-top-level-string.js'", "'scripts/orca-bundle-parity.ts'", "'scripts/fresh-install-smoke.ts'", diff --git a/scripts/sync.js b/scripts/sync.js deleted file mode 100644 index 580612237..000000000 --- a/scripts/sync.js +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env node -/** - * Sync script for genie - * - * @deprecated Use `term sync` instead. This script uses copy mode which is slower - * and doesn't update the plugin registry. `term sync` uses symlinks for instant - * feedback and marks the plugin with devMode in installed_plugins.json. - * - * Deploys the built plugin to the install target: - * ~/.claude/plugins/genie/ - * - * Uses pure Node.js - no rsync dependency. - * Also triggers worker restart after sync. - */ - -import { execSync } from 'node:child_process'; -import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from 'node:fs'; -import http from 'node:http'; -import os from 'node:os'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const rootDir = path.join(__dirname, '..'); -const pluginDir = path.join(rootDir, 'plugins', 'genie'); -const INSTALLED_PATH = path.join(os.homedir(), '.claude', 'plugins', 'genie'); -const WORKER_PORT = 48888; - -/** - * Recursively copy directory contents - */ -function copyDir(src, dest) { - mkdirSync(dest, { recursive: true }); - - const entries = readdirSync(src, { withFileTypes: true }); - - for (const entry of entries) { - const srcPath = path.join(src, entry.name); - const destPath = path.join(dest, entry.name); - - if (entry.isDirectory()) { - copyDir(srcPath, destPath); - } else { - copyFileSync(srcPath, destPath); - } - } -} - -/** - * Clean directory but preserve .git if it exists - */ -function cleanDir(dir) { - if (!existsSync(dir)) return; - - const entries = readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - // Preserve .git directory for git-based updates - if (entry.name === '.git') continue; - - const fullPath = path.join(dir, entry.name); - rmSync(fullPath, { recursive: true, force: true }); - } -} - -function getPluginVersion() { - try { - const pluginJsonPath = path.join(pluginDir, '.claude-plugin', 'plugin.json'); - const pluginJson = JSON.parse(readFileSync(pluginJsonPath, 'utf-8')); - return pluginJson.version; - } catch (error) { - console.error('Failed to read plugin version:', error.message); - return null; - } -} - -function triggerWorkerRestart() { - return new Promise((resolve) => { - console.log('\nTriggering worker restart...'); - const req = http.request( - { - hostname: '127.0.0.1', - port: WORKER_PORT, - path: '/api/admin/restart', - method: 'POST', - timeout: 2000, - }, - (res) => { - if (res.statusCode === 200) { - console.log('Worker restart triggered'); - } else { - console.log(`Worker restart returned status ${res.statusCode}`); - } - resolve(); - }, - ); - req.on('error', () => { - console.log('Worker not running, will start on next hook'); - resolve(); - }); - req.on('timeout', () => { - req.destroy(); - console.log('Worker restart timed out'); - resolve(); - }); - req.end(); - }); -} - -async function main() { - console.warn( - '\x1b[33mWarning: scripts/sync.js is deprecated. Use `term sync` instead for symlink-based sync with registry updates.\x1b[0m\n', - ); - - const version = getPluginVersion(); - console.log(`Syncing genie ${version || 'unknown'} to ${INSTALLED_PATH}...`); - - try { - // Ensure target directory exists - mkdirSync(INSTALLED_PATH, { recursive: true }); - - // Clean existing files (preserving .git) - console.log('Cleaning target directory...'); - cleanDir(INSTALLED_PATH); - - // Copy plugin files - console.log('Copying plugin files...'); - copyDir(pluginDir, INSTALLED_PATH); - - // Run bun install in target - console.log('\nRunning bun install in target...'); - execSync('bun install', { cwd: INSTALLED_PATH, stdio: 'inherit' }); - - console.log('\nSync complete!'); - - // Trigger worker restart - await triggerWorkerRestart(); - } catch (error) { - console.error('Sync failed:', error.message); - process.exit(1); - } -} - -main(); diff --git a/scripts/version-format.test.ts b/scripts/version-format.test.ts index 7d7c91ec0..b36c91cee 100644 --- a/scripts/version-format.test.ts +++ b/scripts/version-format.test.ts @@ -2,8 +2,7 @@ import { afterEach, describe, expect, test } from 'bun:test'; import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; -import { pluginPackageManifest, updateManifestVersion } from './build.js'; -import { synchronizeVersionFiles, updateJsonVersion } from './version.ts'; +import { synchronizeVersionFiles, updateJsonVersion, versionCheckReport } from './version.ts'; describe('manifest version formatting', () => { const roots: string[] = []; @@ -51,12 +50,6 @@ describe('manifest version formatting', () => { expect(readFileSync(path, 'utf8')).toBe(original.replace('5.260710.14', '5.260711.1')); }); - test('build.js changes only the version token', () => { - const { path, original } = fixture(); - updateManifestVersion(path, '5.260711.2'); - expect(readFileSync(path, 'utf8')).toBe(original.replace('5.260710.14', '5.260711.2')); - }); - test('version stampers target the top-level key when a nested version appears first', async () => { const root = mkdtempSync(join(tmpdir(), 'genie-version-nested-')); roots.push(root); @@ -67,19 +60,57 @@ describe('manifest version formatting', () => { await updateJsonVersion(path, '5.260711.7'); expect(readFileSync(path, 'utf8')).toBe(original.replace('"5.0.0"', '"5.260711.7"')); expect(JSON.parse(readFileSync(path, 'utf8')).metadata.version).toBe('nested'); - - writeFileSync(path, original); - updateManifestVersion(path, '5.260711.8'); - expect(readFileSync(path, 'utf8')).toBe(original.replace('"5.0.0"', '"5.260711.8"')); - expect(JSON.parse(readFileSync(path, 'utf8')).metadata.version).toBe('nested'); }); - test('plugin package generator preserves reviewed MIT metadata', () => { + /** + * `plugins/genie/package.json` used to be regenerated by the deleted + * `scripts/build.js`; it is now a reviewed, committed Orca-payload manifest. + * Its shape is asserted directly so a hand edit cannot silently drop the MIT + * license or add a runtime dependency to the shipped payload. + */ + test('the committed plugin package manifest keeps its reviewed MIT metadata', () => { const source = readFileSync(join(import.meta.dir, '..', 'plugins', 'genie', 'package.json'), 'utf8'); const tracked = JSON.parse(source); - expect(pluginPackageManifest(tracked.version)).toEqual(tracked); - expect(pluginPackageManifest(tracked.version).license).toBe('MIT'); - expect(`${JSON.stringify(pluginPackageManifest(tracked.version), null, 2)}\n`).toBe(source); + expect(tracked).toEqual({ + name: 'genie-plugin', + version: tracked.version, + private: true, + description: 'Runtime dependencies for genie bundled CLIs', + license: 'MIT', + type: 'module', + dependencies: {}, + engines: { node: '>=18.0.0', bun: '>=1.0.0' }, + }); + expect(`${JSON.stringify(tracked, null, 2)}\n`).toBe(source); + }); + + test('--check reports exactly the three stamped version files and writes nothing', async () => { + const root = synchronizationFixture(); + const before = ['package.json', 'plugins/genie/orca-plugin.json', 'plugins/genie/package.json'].map((path) => + readFileSync(join(root, path), 'utf8'), + ); + const report = await versionCheckReport(root); + expect(report.targets).toEqual([ + join(root, 'package.json'), + join(root, 'plugins/genie/orca-plugin.json'), + join(root, 'plugins/genie/package.json'), + ]); + expect(report.failures).toEqual([]); + const after = ['package.json', 'plugins/genie/orca-plugin.json', 'plugins/genie/package.json'].map((path) => + readFileSync(join(root, path), 'utf8'), + ); + expect(after).toEqual(before); + }); + + test('--check reports a malformed or missing target instead of rewriting it', async () => { + const root = synchronizationFixture(); + writeFileSync(join(root, 'plugins/genie/orca-plugin.json'), '{"name":"genie"}\n'); + rmSync(join(root, 'plugins/genie/package.json')); + const report = await versionCheckReport(root); + expect(report.targets).toHaveLength(3); + expect(report.failures).toHaveLength(2); + expect(report.failures.join('\n')).toContain('top-level version must be a string'); + expect(report.failures.join('\n')).toContain('file is missing'); }); test('synchronization updates every required file or rejects the run', async () => { diff --git a/scripts/version.ts b/scripts/version.ts index 864e2302b..c45fd7506 100644 --- a/scripts/version.ts +++ b/scripts/version.ts @@ -15,6 +15,10 @@ * - plugins/genie/orca-plugin.json (Orca) * - plugins/genie/package.json (runtime payload metadata) * + * `--check` is the read-only mode: it reports that exact target set and whether + * each file is bump-ready, then exits 0 without writing. The BARE command always + * performs a real bump, so `--check` is the only form safe to run as validation. + * * CI staging (GITHUB_ACTIONS only): after rewriting, this script `git add`s every * file it actually touched. This exists because the release workflow's own * `git add -A '*.json' 'src/lib/version.ts'` list re-guesses the version-carrying @@ -104,13 +108,46 @@ function stageRewrittenFilesInCi(rootDir: string, paths: string[]): void { } } +/** + * The authoritative version-file set, in stamping order. Three files since wish + * `skills-everywhere-b` retired the Codex/Claude/Kimi/Hermes/pi manifests; the + * same list is enumerated by `scripts/release-payload-version.ts`, + * `scripts/release-guard.sh` and `.github/workflows/version.yml`, which is why + * `--check` exists: it is the only way to read this set without bumping. + */ +export const VERSION_FILES = ['package.json', 'plugins/genie/orca-plugin.json', 'plugins/genie/package.json'] as const; + +export function versionFilePaths(rootDir: string): string[] { + return VERSION_FILES.map((relativePath) => join(rootDir, relativePath)); +} + +export interface VersionCheckReport { + targets: string[]; + failures: string[]; +} + +/** + * Read-only companion to `synchronizeVersionFiles`: report the exact bump + * targets and whether each is shaped so a bump could stamp it, WITHOUT writing + * anything and without deriving a new version. The bare command performs a real + * bump, so this is the only runnable verification of the version-file set. + */ +export async function versionCheckReport(rootDir: string): Promise { + const targets = versionFilePaths(rootDir); + const failures: string[] = []; + for (const path of targets) { + try { + await assertVersionFileShape(path); + } catch (error) { + failures.push(`${path}: ${error instanceof Error ? error.message : String(error)}`); + } + } + return { targets, failures }; +} + /** Update every authoritative version file and fail the command on any partial write. */ export async function synchronizeVersionFiles(rootDir: string, version: string): Promise { - const paths = [ - join(rootDir, 'package.json'), - join(rootDir, 'plugins/genie/orca-plugin.json'), - join(rootDir, 'plugins/genie/package.json'), - ]; + const paths = versionFilePaths(rootDir); const preflightFailures: string[] = []; for (const path of paths) { try { @@ -136,9 +173,32 @@ export async function synchronizeVersionFiles(rootDir: string, version: string): if (failed.length > 0) throw new Error(`version synchronization failed for: ${failed.join(', ')}`); } +async function runCheck(rootDir: string): Promise { + const report = await versionCheckReport(rootDir); + console.log(`version --check: ${report.targets.length} version file(s), no writes performed`); + for (const relativePath of VERSION_FILES) console.log(` • ${relativePath}`); + if (report.failures.length > 0) { + throw new Error(`version files are not bump-ready:\n ${report.failures.join('\n ')}`); + } + console.log('\n✅ Every version file is present and bump-ready'); +} + async function main() { - const version = generateVersion(); const rootDir = join(dirname(import.meta.path), '..'); + const argv = process.argv.slice(2); + + // Argument handling is deliberately strict and comes BEFORE any mutation: the + // bare command performs a real, irreversible bump, so an unrecognized flag + // must never degrade into one. + if (argv.length > 0) { + if (argv.length === 1 && argv[0] === '--check') { + await runCheck(rootDir); + return; + } + throw new Error(`usage: bun scripts/version.ts [--check] (got: ${argv.join(' ')})`); + } + + const version = generateVersion(); console.log(`Version: ${version}`); console.log('Updating files:'); From 3052dc5f1edf74a8445a5d24b7d42eb0c5b324bd Mon Sep 17 00:00:00 2001 From: Felipe Rosa Date: Tue, 1 Sep 2026 00:26:07 +0000 Subject: [PATCH 2/3] chore(release): delete the codex dogfood matrix from release-publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the G3 release freeze. `codex-native-dogfood` and its `codex-dogfood-completeness` roll-up exercised `genie setup --codex`, the activation-pending update terminal and the doctor integrationSummary — all deleted in G3 — so both jobs were guaranteed red on every head carrying G3. They leave together with `tests/support/codex-dogfood-{harness,harness.test,entry-runner}.ts`, `scripts/validate-live-dogfood-evidence.ts` and `scripts/validate-dogfood-matrix-evidence.ts` (with their tests): 3,889 lines. `publish.needs` drops exactly one edge, `codex-dogfood-completeness`, with its matching if-guard; `admit`, `attest-delivery-evidence`, `delivery-evidence-compatibility`, `skills-install-smoke`, `release-update-path-smoke` and `stable-release-security-gate` all remain with their guards byte-intact, and `release-update-path-smoke` keeps depending on `genie update --publish-local-delivery`. `scripts/candidate-dogfood-matrix.ts` is KEPT: `prepare-delivery-evidence` derives the platform inventory and the update-path projection from it, and `stable-release-security-gate` — itself a `publish.needs` edge — re-derives and `cmp`s it. Only the `dogfood_matrix` job output, whose sole consumer was the deleted matrix, goes with them; the JSON artifact it projected is still built, uploaded and consumed. `scripts/run-musl-dogfood.sh` and `.github/workflows/musl-adapter-smoke.yml` are byte-unchanged against origin/dev. --- .github/workflows/release-publish.yml | 289 +-- scripts/release-docs.test.ts | 84 +- .../validate-dogfood-matrix-evidence.test.ts | 177 -- scripts/validate-dogfood-matrix-evidence.ts | 321 --- .../validate-live-dogfood-evidence.test.ts | 511 ----- scripts/validate-live-dogfood-evidence.ts | 891 -------- scripts/workflow-yaml-parse.test.ts | 106 +- src/lib/delivery-evidence-verify.ts | 2 +- tests/support/codex-dogfood-entry-runner.ts | 89 - tests/support/codex-dogfood-harness.test.ts | 75 - tests/support/codex-dogfood-harness.ts | 1825 ----------------- 11 files changed, 137 insertions(+), 4233 deletions(-) delete mode 100644 scripts/validate-dogfood-matrix-evidence.test.ts delete mode 100644 scripts/validate-dogfood-matrix-evidence.ts delete mode 100644 scripts/validate-live-dogfood-evidence.test.ts delete mode 100644 scripts/validate-live-dogfood-evidence.ts delete mode 100644 tests/support/codex-dogfood-entry-runner.ts delete mode 100644 tests/support/codex-dogfood-harness.test.ts delete mode 100644 tests/support/codex-dogfood-harness.ts diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 6224ed3b4..553423723 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -219,7 +219,6 @@ jobs: attestations: read outputs: matrix: ${{ steps.build.outputs.matrix }} - dogfood_matrix: ${{ steps.build.outputs.dogfood_matrix }} update_path_matrix: ${{ steps.build.outputs.update_path_matrix }} candidate_manifest_name: ${{ steps.build.outputs.candidate_manifest_name }} candidate_manifest_sha256: ${{ steps.build.outputs.candidate_manifest_sha256 }} @@ -289,7 +288,6 @@ jobs: --manifest "$SELECTED_MANIFEST" \ --artifact-dir dist \ --output candidate-dogfood-matrix.json - DOGFOOD_MATRIX="$(tr -d '\n' < candidate-dogfood-matrix.json)" # release-update-path-smoke runs the two Linux legs of the SAME # manifest-derived inventory. It is a projection of # candidate-dogfood-matrix.json, never a hand-written platform list: @@ -397,7 +395,6 @@ jobs: done { echo "matrix={\"include\":${MATRIX}}" - echo "dogfood_matrix=${DOGFOOD_MATRIX}" echo "update_path_matrix=${UPDATE_PATH_MATRIX}" echo "candidate_manifest_name=${CANDIDATE_MANIFEST_NAME}" echo "candidate_manifest_sha256=${CANDIDATE_MANIFEST_SHA256}" @@ -559,268 +556,16 @@ jobs: --manifest "$manifest" done - codex-native-dogfood: - name: Codex standalone task/board dogfood / ${{ matrix.platform }} - needs: [prepare-delivery-evidence, attest-delivery-evidence, delivery-evidence-compatibility] - runs-on: ${{ matrix.runner }} - timeout-minutes: 30 - permissions: - contents: read - attestations: read - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.prepare-delivery-evidence.outputs.dogfood_matrix) }} - steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - with: - # The harness and validators are trusted release controls. The exact - # candidate from source_sha is consumed only as a signed archive. - ref: ${{ github.sha }} - persist-credentials: false - - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: 1.3.11 - - - name: Install trusted harness dependencies - run: bun install --frozen-lockfile --ignore-scripts - - - name: Install pinned real Codex app-server - shell: bash - run: | - set -euo pipefail - npm install -g @openai/codex@0.144.1 - CODEX_PATH="$(node -e 'console.log(require("node:fs").realpathSync(process.argv[1]))' "$(command -v codex)")" - [[ "$("$CODEX_PATH" --version)" == "codex-cli 0.144.1" ]] - echo "GENIE_DOGFOOD_REAL_CODEX=${CODEX_PATH}" >> "$GITHUB_ENV" - - - name: Install cosign for previous-stable verification - uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3 - with: - cosign-release: 'v2.4.1' - - # The upstream installer action bootstraps through a linux-amd64 binary, - # so it can never run on this job's arm64 runners (darwin-arm64 ENOEXEC, - # ubuntu-24.04-arm exit 2). Install the platform-correct release asset - # directly, pinned to the upstream SHA256SUM.md ledger for v2.7.1. - - name: Install slsa-verifier for previous-stable verification - shell: bash - run: | - set -euo pipefail - case "$(uname -s)-$(uname -m)" in - Linux-x86_64) ASSET=slsa-verifier-linux-amd64 SHA256=946dbec729094195e88ef78e1734324a27869f03e2c6bd2f61cbc06bd5350339 ;; - Linux-aarch64) ASSET=slsa-verifier-linux-arm64 SHA256=5d3b2349ede7bfec19e7a21569f18b9f7410145ad12e9584b175370669e14061 ;; - Darwin-arm64) ASSET=slsa-verifier-darwin-arm64 SHA256=39abfcf5f1d690c3e889ce3d2d6a8b87711424d83368511868d414e8f8bcb05c ;; - *) - echo "::error::unsupported runner platform for slsa-verifier: $(uname -s)-$(uname -m)" - exit 1 - ;; - esac - mkdir -p "${RUNNER_TEMP}/slsa-verifier-bin" - curl -fsSL --retry 3 -o "${RUNNER_TEMP}/slsa-verifier-bin/slsa-verifier" \ - "https://github.com/slsa-framework/slsa-verifier/releases/download/v2.7.1/${ASSET}" - echo "${SHA256} ${RUNNER_TEMP}/slsa-verifier-bin/slsa-verifier" | shasum -a 256 -c - - chmod +x "${RUNNER_TEMP}/slsa-verifier-bin/slsa-verifier" - echo "${RUNNER_TEMP}/slsa-verifier-bin" >> "$GITHUB_PATH" - - # Ubuntu 24.04 images permit creating unprivileged user namespaces but - # AppArmor denies capability use inside them, so the real Codex sandbox - # dies at loopback setup ("bwrap: loopback: Failed RTM_NEWADDR"). Lift the - # restriction so the dogfood exercises Codex's actual bubblewrap sandbox - # instead of disabling it. - - name: Allow the Codex bubblewrap sandbox to configure its network namespace - if: runner.os == 'Linux' - run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - - # The harness fails a capability probe on ANY stderr output, and the - # adapter's first `docker run` of an unpulled image streams pull progress - # to stderr. Pull the exact pinned digest up front so probe stderr stays - # clean. Image pin must match scripts/run-musl-dogfood.sh. - - name: Pre-pull the pinned Alpine image for the musl execution adapter - if: matrix.execution == 'alpine-container' - run: docker pull -q 'alpine:3.19@sha256:6baf43584bcb78f2e5847d1de515f23499913ac9f12bdf834811a3145eb11ca1' - - - name: Download exact verified previous-stable inputs - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: codex-dogfood-previous-release - path: dogfood-entry/inputs/previous - - - name: Download exact candidate signed triplet - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: genie-${{ matrix.version }}-${{ matrix.platform }}-signed - path: dogfood-entry/inputs/candidate - - - name: Download exact candidate manifest - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: delivery-candidate-manifests - path: dogfood-entry/inputs/candidate/manifests - - - name: Download canonical candidate descriptors - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: delivery-descriptors - path: dogfood-entry/inputs/candidate/descriptors - - - name: Download exact candidate delivery endorsement - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: delivery-endorsement-${{ matrix.channel }}-${{ matrix.platform }} - path: dogfood-entry/inputs/candidate/descriptors - - - name: Resolve native execution adapter - id: adapter - shell: bash - env: - EXECUTION_KIND: ${{ matrix.execution }} - run: | - set -euo pipefail - case "$EXECUTION_KIND" in - host-native) - echo 'path=' >> "$GITHUB_OUTPUT" - ;; - alpine-container) - ADAPTER="${GITHUB_WORKSPACE}/scripts/run-musl-dogfood.sh" - [[ -f "$ADAPTER" && ! -L "$ADAPTER" && -x "$ADAPTER" ]] || { - echo "::error::pinned musl execution adapter is unavailable" - exit 1 - } - echo "path=${ADAPTER}" >> "$GITHUB_OUTPUT" - ;; - *) - echo "::error::manifest matrix selected unknown execution kind ${EXECUTION_KIND}" - exit 1 - ;; - esac - - - name: Run exact N to T lifecycle and emit reusable evidence - shell: bash - env: - PLATFORM: ${{ matrix.platform }} - PREVIOUS_VERSION: ${{ needs.prepare-delivery-evidence.outputs.previous_version }} - CANDIDATE_VERSION: ${{ matrix.version }} - CANDIDATE_CHANNEL: ${{ matrix.channel }} - CANDIDATE_MANIFEST_NAME: ${{ matrix.manifest }} - EXECUTION_ADAPTER: ${{ steps.adapter.outputs.path }} - run: | - set -euo pipefail - PREVIOUS_ARTIFACT="dogfood-entry/inputs/previous/genie-${PREVIOUS_VERSION}-${PLATFORM}.tar.gz" - CANDIDATE_ARTIFACT="dogfood-entry/inputs/candidate/genie-${CANDIDATE_VERSION}-${PLATFORM}.tar.gz" - CANDIDATE_DESCRIPTOR="dogfood-entry/inputs/candidate/descriptors/genie-${CANDIDATE_VERSION}-${PLATFORM}.tar.gz.${CANDIDATE_CHANNEL}.delivery.json" - EVIDENCE="dogfood-entry/codex-dogfood-${CANDIDATE_VERSION}-${PLATFORM}.md" - RUNNER_ARGS=( - --previous-artifact "$PREVIOUS_ARTIFACT" - --previous-manifest dogfood-entry/inputs/previous/manifest.json - --previous-bundle "${PREVIOUS_ARTIFACT}.bundle" - --previous-provenance "${PREVIOUS_ARTIFACT}.intoto.jsonl" - --candidate-artifact "$CANDIDATE_ARTIFACT" - --candidate-manifest "dogfood-entry/inputs/candidate/manifests/${CANDIDATE_MANIFEST_NAME}" - --candidate-descriptor "$CANDIDATE_DESCRIPTOR" - --candidate-bundle "${CANDIDATE_DESCRIPTOR}.sigstore.json" - --platform-id "$PLATFORM" - --output-evidence "$EVIDENCE" - ) - if [[ -n "$EXECUTION_ADAPTER" ]]; then - RUNNER_ARGS+=(--execution-adapter "$EXECUTION_ADAPTER") - fi - bun tests/support/codex-dogfood-entry-runner.ts "${RUNNER_ARGS[@]}" - bun scripts/validate-live-dogfood-evidence.ts \ - --file "$EVIDENCE" \ - --inputs-root dogfood-entry - - - name: Upload exact per-entry evidence and referenced inputs - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: codex-dogfood-evidence-${{ matrix.platform }} - path: dogfood-entry - if-no-files-found: error - retention-days: 30 - - codex-dogfood-completeness: - name: Require complete Codex standalone dogfood matrix - needs: [prepare-delivery-evidence, codex-native-dogfood] - if: ${{ always() }} - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - name: Reject failed, skipped, or unavailable standalone entries - shell: bash - env: - PREPARE_RESULT: ${{ needs.prepare-delivery-evidence.result }} - STANDALONE_RESULT: ${{ needs.codex-native-dogfood.result }} - EXPECTED_MATRIX: ${{ needs.prepare-delivery-evidence.outputs.dogfood_matrix }} - EXPECTED_MANIFEST_SHA256: ${{ needs.prepare-delivery-evidence.outputs.candidate_manifest_sha256 }} - run: | - set -euo pipefail - [[ "$PREPARE_RESULT" == success && "$STANDALONE_RESULT" == success ]] || { - echo "::error::Codex dogfood is incomplete (prepare=${PREPARE_RESULT}, standalone=${STANDALONE_RESULT})" - exit 1 - } - [[ -n "$EXPECTED_MATRIX" && -n "$EXPECTED_MANIFEST_SHA256" ]] || { - echo "::error::Codex dogfood candidate identity is unavailable" - exit 1 - } - - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - with: - ref: ${{ github.sha }} - persist-credentials: false - - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: 1.3.11 - - - name: Download manifest-derived candidate matrix - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: codex-dogfood-candidate-matrix - path: aggregate - - - name: Download every native entry - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - pattern: codex-dogfood-evidence-* - path: aggregate/entries - - - name: Validate one-to-one evidence and persist candidate identity - shell: bash - env: - EXPECTED_VERSION: ${{ inputs.version }} - EXPECTED_CHANNEL: ${{ inputs.channel }} - EXPECTED_SOURCE_SHA: ${{ inputs.source_sha }} - EXPECTED_MATRIX: ${{ needs.prepare-delivery-evidence.outputs.dogfood_matrix }} - EXPECTED_MANIFEST_SHA256: ${{ needs.prepare-delivery-evidence.outputs.candidate_manifest_sha256 }} - run: | - set -euo pipefail - [[ "$(tr -d '\n' < aggregate/candidate-dogfood-matrix.json)" == "$EXPECTED_MATRIX" ]] || { - echo "::error::downloaded candidate matrix differs from the matrix used to schedule native jobs" - exit 1 - } - bun scripts/validate-dogfood-matrix-evidence.ts \ - --matrix aggregate/candidate-dogfood-matrix.json \ - --evidence-dir aggregate/entries \ - --version "$EXPECTED_VERSION" \ - --channel "$EXPECTED_CHANNEL" \ - --source-sha "$EXPECTED_SOURCE_SHA" \ - --candidate-manifest-sha256 "$EXPECTED_MANIFEST_SHA256" \ - --output aggregate/codex-dogfood-completeness.json - - - name: Upload complete Codex dogfood evidence - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: codex-dogfood-completeness - path: aggregate/codex-dogfood-completeness.json - if-no-files-found: error - retention-days: 90 - # --------------------------------------------------------------------------- - # Wish `skills-everywhere`, C5 — two candidate-facing smokes added ALONGSIDE - # the Codex dogfood matrix above (which is unchanged) and required by - # `publish` below. + # Wish `skills-everywhere`, C5 — two candidate-facing smokes required by + # `publish` below. Wish `skills-everywhere-b`, G6: they are now the ONLY + # candidate-execution gates. The Codex standalone task/board dogfood matrix + # and its completeness roll-up that used to sit here were deleted with the + # Codex plugin subsystem they exercised; `publish.needs` therefore carries + # six edges, not seven. `prepare-delivery-evidence` keeps + # deriving `candidate-dogfood-matrix.json` and the verified previous-stable + # inputs — `release-update-path-smoke` and `stable-release-security-gate` + # both consume them. # --------------------------------------------------------------------------- # The skills.sh channel is the ONE supported way genie product skills reach an @@ -866,8 +611,8 @@ jobs: with: node-version: '22' - # Same pin as codex-native-dogfood's app-server install and ci.yml's - # codex-smoke, so all three agree on exactly one Codex generation. + # Pinned to exactly one Codex generation. This job's live `codex exec` + # is the only remaining Codex execution in the release workflow. - name: Install pinned real Codex CLI run: npm install -g @openai/codex@0.144.1 @@ -981,8 +726,8 @@ jobs: # Ubuntu 24.04 images permit unprivileged user namespaces but AppArmor # denies capability use inside them, so the real Codex sandbox dies at - # loopback setup. Same lift as codex-native-dogfood; only needed for the - # live `codex exec` below, so it is gated identically. + # loopback setup. Only needed for the live `codex exec` below, so it is + # gated identically. - name: Allow the Codex bubblewrap sandbox to configure its network namespace if: ${{ env.HAS_OPENAI_KEY == 'true' }} run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 @@ -1045,7 +790,7 @@ jobs: # capability report, the local delivery publication, and standalone # task/board on both a repository seeded under N and an untouched one. # - # The step sequence is copied from codex-native-dogfood (checkout of the + # The step sequence is the standard candidate-execution preamble (checkout of the # admitted control commit, pinned bun, trusted dependencies, cosign, the # platform-correct slsa-verifier, the digest-pinned Alpine pre-pull, the # artifact downloads and the execution-adapter resolution); only the @@ -1081,8 +826,8 @@ jobs: with: cosign-release: 'v2.4.1' - # Same direct, ledger-pinned install codex-native-dogfood uses: the - # upstream installer action bootstraps through a linux-amd64 binary. + # Direct, ledger-pinned install: the upstream installer action + # bootstraps through a linux-amd64 binary, which cannot run on arm64. - name: Install slsa-verifier for release verification shell: bash run: | @@ -1617,7 +1362,6 @@ jobs: - admit - attest-delivery-evidence - delivery-evidence-compatibility - - codex-dogfood-completeness - skills-install-smoke - release-update-path-smoke - stable-release-security-gate @@ -1627,7 +1371,6 @@ jobs: needs.admit.result == 'success' && needs.attest-delivery-evidence.result == 'success' && needs.delivery-evidence-compatibility.result == 'success' && - needs.codex-dogfood-completeness.result == 'success' && needs.skills-install-smoke.result == 'success' && needs.release-update-path-smoke.result == 'success' && needs.stable-release-security-gate.result == 'success' diff --git a/scripts/release-docs.test.ts b/scripts/release-docs.test.ts index 112898fab..7b4d88f45 100644 --- a/scripts/release-docs.test.ts +++ b/scripts/release-docs.test.ts @@ -204,7 +204,7 @@ describe('Group E release and documentation contracts', () => { const materialize = publish.indexOf('bash scripts/materialize-release-subjects.sh'); const descriptorBuild = publish.indexOf('bun scripts/build-delivery-evidence.ts'); const compatibilityJob = - publish.split('\n delivery-evidence-compatibility:')[1]?.split('\n codex-native-dogfood:')[0] ?? ''; + publish.split('\n delivery-evidence-compatibility:')[1]?.split('\n skills-install-smoke:')[0] ?? ''; const publishJob = publish.split('\n publish:')[1]?.split('\n manifests:')[0] ?? ''; const releaseUpload = publish.indexOf('bash scripts/reconcile-release-assets.sh'); const manifestsJob = publish.split('\n manifests:')[1]?.split('\n finalize:')[0] ?? ''; @@ -222,9 +222,8 @@ describe('Group E release and documentation contracts', () => { expect(compatibilityJob).toContain('bun install --frozen-lockfile --ignore-scripts'); expect(compatibilityJob).toContain('bun scripts/verify-delivery-evidence-pack.ts'); expect(compatibilityJob).toContain('name: delivery-candidate-manifests'); - expect(publishJob).toContain('- codex-dogfood-completeness'); + expect(publishJob).not.toContain('- codex-dogfood-completeness'); expect(publishJob).toContain('- stable-release-security-gate'); - expect(publishJob).toContain("needs.codex-dogfood-completeness.result == 'success'"); expect(publishJob).toContain("needs.stable-release-security-gate.result == 'success'"); expect(publishJob).toContain('name: delivery-candidate-manifests'); expect(manifestsJob).toContain('needs: finalize'); @@ -243,16 +242,34 @@ describe('Group E release and documentation contracts', () => { expect(assetReconciliation).toContain('.releaseManifestSha256 == $manifest_sha'); }); - test('manifest-derived native dogfood and the independent security gate jointly block publication', () => { + /** + * Wish `skills-everywhere-b`, G6: the `Codex standalone task/board dogfood` + * matrix and its completeness roll-up were deleted with the Codex plugin + * subsystem they exercised. What survives is the manifest-derived inventory + * they were built on — `scripts/candidate-dogfood-matrix.ts` is DELIBERATELY + * kept, because `prepare-delivery-evidence` derives the platform list and the + * update-path projection from it and `stable-release-security-gate` + * (a `publish.needs` edge) re-derives and `cmp`s it — plus the independent + * security gate. This test now pins both halves and the removal itself. + */ + test('the manifest-derived inventory and the independent security gate block publication', () => { const workflow = read('.github/workflows/release-publish.yml'); const prepare = workflow.split('\n prepare-delivery-evidence:')[1]?.split('\n attest-delivery-evidence:')[0] ?? ''; - const native = workflow.split('\n codex-native-dogfood:')[1]?.split('\n codex-dogfood-completeness:')[0] ?? ''; - const completeness = - workflow.split('\n codex-dogfood-completeness:')[1]?.split('\n stable-release-security-gate:')[0] ?? ''; const security = workflow.split('\n stable-release-security-gate:')[1]?.split('\n publish:')[0] ?? ''; const publish = workflow.split('\n publish:')[1]?.split('\n manifests:')[0] ?? ''; + // The two retired jobs are gone, and nothing depends on them. + expect(workflow).not.toContain('\n codex-native-dogfood:'); + expect(workflow).not.toContain('\n codex-dogfood-completeness:'); + expect(workflow).not.toContain('needs.codex-native-dogfood'); + expect(workflow).not.toContain('needs.codex-dogfood-completeness'); + expect(workflow).not.toContain('- codex-dogfood-completeness'); + // ...and so are the three release controls deleted with them. + expect(workflow).not.toContain('tests/support/codex-dogfood-entry-runner.ts'); + expect(workflow).not.toContain('scripts/validate-live-dogfood-evidence.ts'); + expect(workflow).not.toContain('scripts/validate-dogfood-matrix-evidence.ts'); + // The selected candidate manifest is the sole platform inventory. A // hand-written representative matrix cannot become promotion evidence. expect(prepare).toContain('bun scripts/candidate-dogfood-matrix.ts'); @@ -260,41 +277,15 @@ describe('Group E release and documentation contracts', () => { expect(prepare).toContain('mapfile -t MANIFEST_PLATFORMS'); expect(prepare).toContain('for platform in "${MANIFEST_PLATFORMS[@]}"'); expect(prepare).not.toContain('PLATFORMS=(linux-x64-glibc'); - expect(prepare).toContain('dogfood_matrix=${DOGFOOD_MATRIX}'); expect(prepare).toContain('candidate_manifest_sha256=${CANDIDATE_MANIFEST_SHA256}'); expect(prepare).toContain('name: codex-dogfood-candidate-matrix'); + // release-update-path-smoke downloads this exact artifact for its N-to-T leg. expect(prepare).toContain('name: codex-dogfood-previous-release'); expect(prepare).toContain('bash scripts/verify-release.sh --local'); expect(prepare).not.toContain('--previous-descriptor'); - expect(native).toContain('matrix: ${{ fromJSON(needs.prepare-delivery-evidence.outputs.dogfood_matrix) }}'); - expect(native).toContain('runs-on: ${{ matrix.runner }}'); - expect(native).toContain('ref: ${{ github.sha }}'); - expect(native).toContain('name: genie-${{ matrix.version }}-${{ matrix.platform }}-signed'); - expect(native).toContain('--previous-provenance "${PREVIOUS_ARTIFACT}.intoto.jsonl"'); - expect(native).toContain('--candidate-descriptor "$CANDIDATE_DESCRIPTOR"'); - expect(native).toContain('--candidate-bundle "${CANDIDATE_DESCRIPTOR}.sigstore.json"'); - expect(native).toContain('EXECUTION_KIND: ${{ matrix.execution }}'); - expect(native).toContain('scripts/run-musl-dogfood.sh'); - expect(native).toContain('--inputs-root dogfood-entry'); - expect(native).toContain('name: codex-dogfood-evidence-${{ matrix.platform }}'); - - // Missing, skipped, duplicated, stale, or identity-mismatched native - // entries fail the aggregate instead of degrading to representative proof. - expect(completeness).toContain('if: ${{ always() }}'); - expect(completeness).toContain('[[ "$PREPARE_RESULT" == success && "$STANDALONE_RESULT" == success ]]'); - expect(completeness).toContain('downloaded candidate matrix differs from the matrix used to schedule native jobs'); - expect(completeness).toContain('bun scripts/validate-dogfood-matrix-evidence.ts'); - expect(completeness).toContain('--evidence-dir aggregate/entries'); - expect(completeness).toContain('--candidate-manifest-sha256 "$EXPECTED_MANIFEST_SHA256"'); - const aggregate = read('scripts/validate-dogfood-matrix-evidence.ts'); - expect(aggregate).toContain("entry.evidenceKind !== 'host-native'"); - expect(aggregate).toContain('candidate.manifestSha256 !== options.candidateManifestSha256'); - expect(aggregate).toContain('candidate.artifactSha256 !== matrixEntry.artifactSha256'); - expect(aggregate).toContain("kind: 'codex-dogfood-completeness'"); - // The machine security proof is read-only, protected-control-derived, and - // independent of dogfood while binding the same exact candidate digest. + // binds the exact candidate digest. expect(security).toContain('if: ${{ always() }}'); expect(security).toContain('permissions:\n contents: read\n attestations: read'); expect(security).toContain('ref: ${{ github.sha }}'); @@ -317,17 +308,26 @@ describe('Group E release and documentation contracts', () => { expect(security).toContain( 'EXPECTED_MANIFEST_SHA256: ${{ needs.prepare-delivery-evidence.outputs.candidate_manifest_sha256 }}', ); - expect(security).not.toContain('needs.codex-native-dogfood'); + expect(security).toContain('bun scripts/candidate-dogfood-matrix.ts'); expect(security).not.toContain('contents: write'); expect(security).not.toContain('id-token: write'); - // All channels use both gates. An unavailable/skipped gate is never - // equivalent to success, and the write-capable publication job stays shut. + // All channels use every surviving gate. An unavailable/skipped gate is + // never equivalent to success, and the write-capable publication job stays + // shut. Exactly six edges — one fewer than before G6, and only that one. expect(publish).toContain('always() &&'); - expect(publish).toContain('- codex-dogfood-completeness'); - expect(publish).toContain('- stable-release-security-gate'); - expect(publish).toContain("needs.codex-dogfood-completeness.result == 'success'"); - expect(publish).toContain("needs.stable-release-security-gate.result == 'success'"); + for (const gate of [ + 'admit', + 'attest-delivery-evidence', + 'delivery-evidence-compatibility', + 'skills-install-smoke', + 'release-update-path-smoke', + 'stable-release-security-gate', + ]) { + expect(publish).toContain(`- ${gate}`); + expect(publish).toContain(`needs.${gate}.result == 'success'`); + } + expect(publish.split('\n').filter((line) => /^\s+- [a-z][a-z0-9-]*$/.test(line))).toHaveLength(6); expect(publish).not.toContain("inputs.channel == 'stable'"); }); diff --git a/scripts/validate-dogfood-matrix-evidence.test.ts b/scripts/validate-dogfood-matrix-evidence.test.ts deleted file mode 100644 index 14ecbb874..000000000 --- a/scripts/validate-dogfood-matrix-evidence.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { describe, expect, test } from 'bun:test'; -import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { type DogfoodEntryValidator, validateDogfoodMatrixEvidence } from './validate-dogfood-matrix-evidence.ts'; - -const SOURCE_SHA = 'a'.repeat(40); -const MANIFEST_SHA = 'b'.repeat(64); -const VERSION = '5.260723.9'; -const PLATFORMS = ['linux-x64-glibc', 'linux-x64-musl', 'linux-arm64', 'darwin-arm64']; - -function fixture(platforms = PLATFORMS): { - root: string; - matrix: string; - evidenceDir: string; -} { - const root = mkdtempSync(join(tmpdir(), 'genie-dogfood-aggregate-')); - const evidenceDir = join(root, 'evidence'); - mkdirSync(evidenceDir); - const matrix = join(root, 'matrix.json'); - writeFileSync( - matrix, - JSON.stringify({ - include: platforms.map((platform, index) => ({ - platform, - runner: - platform === 'linux-arm64' ? 'ubuntu-24.04-arm' : platform === 'darwin-arm64' ? 'macos-15' : 'ubuntu-latest', - execution: platform.endsWith('musl') ? 'alpine-container' : 'host-native', - version: VERSION, - channel: 'dev', - manifest: 'dev.json', - manifestSha256: MANIFEST_SHA, - artifact: `genie-${VERSION}-${platform}.tar.gz`, - artifactSha256: String(index + 1).repeat(64), - bundle: `genie-${VERSION}-${platform}.tar.gz.bundle`, - provenance: `genie-${VERSION}-${platform}.tar.gz.intoto.jsonl`, - })), - }), - ); - for (const [index, platform] of platforms.entries()) { - const directory = join(evidenceDir, platform); - mkdirSync(directory); - writeFileSync( - join(directory, `codex-dogfood-${VERSION}-${platform}.md`), - evidence(platform, String(index + 1).repeat(64)), - ); - } - return { root, matrix, evidenceDir }; -} - -function evidence(platform: string, artifactSha256: string): string { - const manifest = { - kind: 'live-dogfood-evidence', - schemaVersion: 2, - entry: { - platformId: platform, - evidenceKind: 'host-native', - availability: 'verified', - }, - lifecycle: { - previousVersion: '5.260720.10', - candidateVersion: VERSION, - channel: 'dev', - sourceCommit: SOURCE_SHA, - artifacts: { - previous: { channel: 'stable' }, - candidate: { manifestSha256: MANIFEST_SHA, artifactSha256 }, - }, - }, - }; - return `# evidence\n\n\`\`\`json\n${JSON.stringify(manifest, null, 2)}\n\`\`\`\n`; -} - -const accept: DogfoodEntryValidator = () => []; - -function validate(fx: ReturnType, validator = accept) { - return validateDogfoodMatrixEvidence( - { - matrixPath: fx.matrix, - evidenceDir: fx.evidenceDir, - version: VERSION, - channel: 'dev', - sourceSha: SOURCE_SHA, - candidateManifestSha256: MANIFEST_SHA, - }, - validator, - ); -} - -describe('native dogfood matrix evidence aggregate', () => { - test('accepts exactly one host-native result per manifest-derived entry', () => { - const summary = validate(fixture()); - expect(summary).toMatchObject({ - schemaVersion: 1, - kind: 'codex-dogfood-completeness', - evidenceSchemaVersion: 2, - version: VERSION, - sourceSha: SOURCE_SHA, - candidateManifestSha256: MANIFEST_SHA, - }); - expect(summary.entries.map((entry) => entry.platformId)).toEqual([ - 'darwin-arm64', - 'linux-arm64', - 'linux-x64-glibc', - 'linux-x64-musl', - ]); - expect(summary.entries.every((entry) => entry.previousVersion === '5.260720.10')).toBe(true); - }); - - test('rejects missing, extra, and duplicate native evidence', () => { - const missing = fixture(); - writeFileSync( - missing.matrix, - JSON.stringify({ include: JSON.parse(readText(missing.matrix)).include.slice(0, 1) }), - ); - expect(() => validate(missing)).toThrow(/native entries|count/); - - const extra = fixture(); - const extraDir = join(extra.evidenceDir, 'extra'); - mkdirSync(extraDir); - writeFileSync(join(extraDir, 'extra.md'), evidence('windows-x64', '1'.repeat(64))); - expect(() => validate(extra)).toThrow(/count|non-manifest/); - - const duplicate = fixture(); - const duplicateDir = join(duplicate.evidenceDir, 'duplicate'); - mkdirSync(duplicateDir); - writeFileSync(join(duplicateDir, 'duplicate.md'), evidence('linux-x64-glibc', '1'.repeat(64))); - expect(() => validate(duplicate)).toThrow(/count|duplicate/); - }); - - test('rejects candidate identity, digest, availability, and prior-channel drift', () => { - for (const mutate of [ - (value: Record) => { - (value.lifecycle as Record).sourceCommit = 'c'.repeat(40); - }, - (value: Record) => { - const lifecycle = value.lifecycle as Record; - const artifacts = lifecycle.artifacts as Record>; - artifacts.candidate.artifactSha256 = 'f'.repeat(64); - }, - (value: Record) => { - (value.entry as Record).availability = 'unavailable'; - }, - (value: Record) => { - const lifecycle = value.lifecycle as Record; - const artifacts = lifecycle.artifacts as Record>; - artifacts.previous.channel = 'dev'; - }, - (value: Record) => { - (value.lifecycle as Record).previousVersion = '5.260724.1'; - }, - ]) { - const fx = fixture(); - const file = join(fx.evidenceDir, 'linux-x64-glibc', `codex-dogfood-${VERSION}-linux-x64-glibc.md`); - const value = JSON.parse(evidenceJson(readText(file))) as Record; - mutate(value); - writeFileSync(file, `# evidence\n\n\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\`\n`); - expect(() => validate(fx)).toThrow(); - } - }); - - test('propagates deep per-entry validation failures', () => { - expect(() => validate(fixture(), () => ['referenced candidate artifact digest mismatch'])).toThrow( - /referenced candidate artifact digest mismatch/, - ); - }); -}); - -function readText(path: string): string { - return readFileSync(path, 'utf8'); -} - -function evidenceJson(markdown: string): string { - const match = markdown.match(/```json\s*\n([\s\S]*?)\n```/); - if (!match?.[1]) throw new Error('missing fixture manifest'); - return match[1]; -} diff --git a/scripts/validate-dogfood-matrix-evidence.ts b/scripts/validate-dogfood-matrix-evidence.ts deleted file mode 100644 index c78fd3cf6..000000000 --- a/scripts/validate-dogfood-matrix-evidence.ts +++ /dev/null @@ -1,321 +0,0 @@ -#!/usr/bin/env bun - -/** - * Aggregate the native Group F results for one exact candidate manifest. - * - * The per-entry validator owns the deep lifecycle and referenced-input checks. - * This layer proves the release-level relation: every manifest-derived matrix - * entry has exactly one host-native result, there are no extra results, and - * every result binds the same candidate version/channel/source/manifest bytes. - */ - -import { createHash } from 'node:crypto'; -import { type Dirent, type Stats, lstatSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs'; -import { basename, dirname, join, resolve, sep } from 'node:path'; -import { compareReleaseVersions, parseReleaseVersion } from '../src/lib/release-payload-proof.ts'; -import { NATIVE_DOGFOOD_TARGETS } from './candidate-dogfood-matrix.ts'; -import { LIVE_DOGFOOD_SCHEMA_VERSION, validateLiveDogfoodEvidenceFile } from './validate-live-dogfood-evidence.ts'; - -const SHA256 = /^[0-9a-f]{64}$/; -const SOURCE_SHA = /^[0-9a-f]{40}$/; -const VERSION = /^\d+\.\d{6}\.\d+$/; -const CHANNELS = new Set(['dev', 'stable']); - -type JsonRecord = Record; -export type DogfoodEntryValidator = (path: string, inputsRoot: string) => string[]; - -export interface DogfoodMatrixValidationOptions { - matrixPath: string; - evidenceDir: string; - version: string; - channel: string; - sourceSha: string; - candidateManifestSha256: string; -} - -export interface DogfoodMatrixEvidenceSummary { - schemaVersion: 1; - kind: 'codex-dogfood-completeness'; - evidenceSchemaVersion: typeof LIVE_DOGFOOD_SCHEMA_VERSION; - version: string; - channel: string; - sourceSha: string; - candidateManifestSha256: string; - entries: Array<{ - platformId: string; - artifactSha256: string; - evidenceSha256: string; - evidenceFile: string; - previousVersion: string; - }>; -} - -function isRecord(value: unknown): value is JsonRecord { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function readPhysical(path: string, label: string): Buffer { - let stat: Stats; - try { - stat = lstatSync(path); - } catch { - throw new Error(`${label} is unavailable: ${path}`); - } - if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} must be a physical regular file: ${path}`); - const bytes = readFileSync(path); - if (bytes.length === 0) throw new Error(`${label} must not be empty: ${path}`); - return bytes; -} - -function sha256(bytes: Uint8Array): string { - return createHash('sha256').update(bytes).digest('hex'); -} - -function parseJsonObject(bytes: Uint8Array, label: string): JsonRecord { - let parsed: unknown; - try { - parsed = JSON.parse(Buffer.from(bytes).toString('utf8')); - } catch { - throw new Error(`${label} is not valid JSON`); - } - if (!isRecord(parsed)) throw new Error(`${label} must contain a JSON object`); - return parsed; -} - -function collectEvidenceFiles(root: string): string[] { - const requestedRoot = resolve(root); - const requestedStat = lstatSync(requestedRoot); - if (!requestedStat.isDirectory() || requestedStat.isSymbolicLink()) { - throw new Error(`evidence root must be a physical directory: ${root}`); - } - const physicalRoot = realpathSync(requestedRoot); - const found: string[] = []; - const visit = (directory: string, depth: number): void => { - if (depth > 4) throw new Error(`evidence directory nesting exceeds four levels: ${directory}`); - const entries = readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name)); - if (entries.length > 128) throw new Error(`evidence directory has too many entries: ${directory}`); - for (const entry of entries) { - assertSafeEntry(entry, directory, physicalRoot); - const path = join(directory, entry.name); - if (entry.isDirectory()) visit(path, depth + 1); - else if (entry.isFile() && entry.name.endsWith('.md')) found.push(path); - } - }; - visit(physicalRoot, 0); - return found; -} - -function assertSafeEntry(entry: Dirent, directory: string, root: string): void { - const path = join(directory, entry.name); - const stat = lstatSync(path); - if (stat.isSymbolicLink() || (!stat.isFile() && !stat.isDirectory())) { - throw new Error(`evidence tree contains a non-physical entry: ${path}`); - } - const canonical = realpathSync(path); - if (canonical !== root && !canonical.startsWith(`${root}${sep}`)) { - throw new Error(`evidence tree entry escapes its root: ${path}`); - } -} - -function extractEvidenceManifest(markdown: string, path: string): JsonRecord { - const manifests: JsonRecord[] = []; - for (const match of markdown.matchAll(/```json\s*\n([\s\S]*?)\n```/g)) { - try { - const value: unknown = JSON.parse(match[1]); - if (isRecord(value) && value.kind === 'live-dogfood-evidence') manifests.push(value); - } catch { - // The per-entry validator reports malformed blocks with richer context. - } - } - if (manifests.length !== 1) { - throw new Error(`${path} must contain exactly one live-dogfood-evidence manifest (got ${manifests.length})`); - } - return manifests[0] as JsonRecord; -} - -function requiredRecord(parent: JsonRecord, key: string, label: string): JsonRecord { - const value = parent[key]; - if (!isRecord(value)) throw new Error(`${label}.${key} must be an object`); - return value; -} - -function requiredString(parent: JsonRecord, key: string, label: string): string { - const value = parent[key]; - if (typeof value !== 'string' || value === '') throw new Error(`${label}.${key} must be a non-empty string`); - return value; -} - -function parseMatrix(path: string): JsonRecord[] { - const matrix = parseJsonObject(readPhysical(path, 'candidate dogfood matrix'), 'candidate dogfood matrix'); - if (!Array.isArray(matrix.include) || matrix.include.length === 0) { - throw new Error('candidate dogfood matrix include must be a non-empty array'); - } - if (matrix.include.some((entry) => !isRecord(entry))) { - throw new Error('candidate dogfood matrix entries must be objects'); - } - return matrix.include as JsonRecord[]; -} - -export function validateDogfoodMatrixEvidence( - options: DogfoodMatrixValidationOptions, - validateEntry: DogfoodEntryValidator = validateLiveDogfoodEvidenceFile, -): DogfoodMatrixEvidenceSummary { - if (!VERSION.test(options.version)) throw new Error('candidate version is invalid'); - if (!CHANNELS.has(options.channel)) throw new Error('candidate channel is invalid'); - if (!SOURCE_SHA.test(options.sourceSha)) throw new Error('candidate source SHA is invalid'); - if (!SHA256.test(options.candidateManifestSha256)) throw new Error('candidate manifest SHA-256 is invalid'); - - const matrix = parseMatrix(resolve(options.matrixPath)); - const expectedPlatforms = Object.keys(NATIVE_DOGFOOD_TARGETS); - if (matrix.length !== expectedPlatforms.length) { - throw new Error(`candidate dogfood matrix must contain exactly ${expectedPlatforms.length} native entries`); - } - const byPlatform = new Map(); - for (const entry of matrix) { - const platform = requiredString(entry, 'platform', 'matrix entry'); - const target = NATIVE_DOGFOOD_TARGETS[platform as keyof typeof NATIVE_DOGFOOD_TARGETS]; - if (target === undefined) throw new Error(`candidate dogfood matrix contains unsupported platform ${platform}`); - if (byPlatform.has(platform)) throw new Error(`candidate dogfood matrix contains duplicate platform ${platform}`); - if (entry.runner !== target.runner || entry.execution !== target.execution) { - throw new Error(`candidate dogfood matrix runner mapping mismatch for ${platform}`); - } - if (entry.version !== options.version || entry.channel !== options.channel) { - throw new Error(`matrix entry ${platform} does not bind candidate ${options.version}/${options.channel}`); - } - if (entry.manifestSha256 !== options.candidateManifestSha256) { - throw new Error(`matrix entry ${platform} candidate manifest digest mismatch`); - } - if (typeof entry.artifactSha256 !== 'string' || !SHA256.test(entry.artifactSha256)) { - throw new Error(`matrix entry ${platform} artifact digest is invalid`); - } - byPlatform.set(platform, entry); - } - const missingMatrixPlatforms = expectedPlatforms.filter((platform) => !byPlatform.has(platform)); - if (missingMatrixPlatforms.length > 0) { - throw new Error(`candidate dogfood matrix is missing native platforms: ${missingMatrixPlatforms.join(', ')}`); - } - - const evidenceFiles = collectEvidenceFiles(options.evidenceDir); - if (evidenceFiles.length !== matrix.length) { - throw new Error(`native evidence count ${evidenceFiles.length} does not equal matrix count ${matrix.length}`); - } - const seen = new Set(); - const summaries: DogfoodMatrixEvidenceSummary['entries'] = []; - for (const file of evidenceFiles) { - const bytes = readPhysical(file, 'native dogfood evidence'); - const errors = validateEntry(file, dirname(file)); - if (errors.length > 0) throw new Error(`${file} failed entry validation:\n${errors.join('\n')}`); - const manifest = extractEvidenceManifest(bytes.toString('utf8'), file); - if (manifest.schemaVersion !== LIVE_DOGFOOD_SCHEMA_VERSION) { - throw new Error(`${file} has unsupported evidence schema ${String(manifest.schemaVersion)}`); - } - const entry = requiredRecord(manifest, 'entry', 'evidence'); - const lifecycle = requiredRecord(manifest, 'lifecycle', 'evidence'); - const artifacts = requiredRecord(lifecycle, 'artifacts', 'evidence.lifecycle'); - const previous = requiredRecord(artifacts, 'previous', 'evidence.lifecycle.artifacts'); - const candidate = requiredRecord(artifacts, 'candidate', 'evidence.lifecycle.artifacts'); - const platform = requiredString(entry, 'platformId', 'evidence.entry'); - if (seen.has(platform)) throw new Error(`native evidence contains duplicate platform ${platform}`); - seen.add(platform); - const matrixEntry = byPlatform.get(platform); - if (matrixEntry === undefined) throw new Error(`native evidence contains non-manifest platform ${platform}`); - if (entry.evidenceKind !== 'host-native' || entry.availability !== 'verified') { - throw new Error(`native evidence ${platform} is unavailable or not host-native`); - } - if ( - lifecycle.candidateVersion !== options.version || - lifecycle.channel !== options.channel || - lifecycle.sourceCommit !== options.sourceSha - ) { - throw new Error(`native evidence ${platform} candidate identity mismatch`); - } - if (candidate.manifestSha256 !== options.candidateManifestSha256) { - throw new Error(`native evidence ${platform} candidate manifest digest mismatch`); - } - if (candidate.artifactSha256 !== matrixEntry.artifactSha256) { - throw new Error(`native evidence ${platform} candidate artifact digest mismatch`); - } - if (previous.channel !== 'stable') throw new Error(`native evidence ${platform} previous generation is not stable`); - const previousVersion = requiredString(lifecycle, 'previousVersion', 'evidence.lifecycle'); - const parsedPrevious = parseReleaseVersion(previousVersion); - const parsedCandidate = parseReleaseVersion(options.version); - if ( - parsedPrevious === null || - parsedCandidate === null || - compareReleaseVersions(parsedPrevious, parsedCandidate) >= 0 - ) { - throw new Error(`native evidence ${platform} previous stable N is not older than candidate T`); - } - summaries.push({ - platformId: platform, - artifactSha256: String(matrixEntry.artifactSha256), - evidenceSha256: sha256(bytes), - evidenceFile: basename(file), - previousVersion, - }); - } - const missing = [...byPlatform.keys()].filter((platform) => !seen.has(platform)); - if (missing.length > 0) throw new Error(`native evidence is missing matrix platforms: ${missing.join(', ')}`); - const previousVersions = new Set(summaries.map((entry) => entry.previousVersion)); - if (previousVersions.size !== 1) throw new Error('native evidence entries do not share one previous stable version'); - return { - schemaVersion: 1, - kind: 'codex-dogfood-completeness', - evidenceSchemaVersion: LIVE_DOGFOOD_SCHEMA_VERSION, - version: options.version, - channel: options.channel, - sourceSha: options.sourceSha, - candidateManifestSha256: options.candidateManifestSha256, - entries: summaries.sort((a, b) => a.platformId.localeCompare(b.platformId)), - }; -} - -function parseArgs(argv: string[]): DogfoodMatrixValidationOptions & { output: string } { - const values = new Map(); - for (let index = 0; index < argv.length; index += 2) { - const flag = argv[index]; - const value = argv[index + 1]; - if (!flag?.startsWith('--') || !value || values.has(flag)) throw new Error(`invalid argument: ${flag ?? ''}`); - values.set(flag, value); - } - const required = (flag: string): string => { - const value = values.get(flag); - if (!value) throw new Error(`missing ${flag}`); - return value; - }; - const allowed = new Set([ - '--matrix', - '--evidence-dir', - '--version', - '--channel', - '--source-sha', - '--candidate-manifest-sha256', - '--output', - ]); - for (const flag of values.keys()) if (!allowed.has(flag)) throw new Error(`unknown argument: ${flag}`); - return { - matrixPath: required('--matrix'), - evidenceDir: required('--evidence-dir'), - version: required('--version'), - channel: required('--channel'), - sourceSha: required('--source-sha'), - candidateManifestSha256: required('--candidate-manifest-sha256'), - output: required('--output'), - }; -} - -function main(): void { - try { - const { output, ...options } = parseArgs(process.argv.slice(2)); - const summary = validateDogfoodMatrixEvidence(options); - writeFileSync(output, `${JSON.stringify(summary, null, 2)}\n`, { flag: 'wx', mode: 0o600 }); - process.stdout.write(`validate-dogfood-matrix-evidence: OK (${summary.entries.length} native entries)\n`); - } catch (error) { - process.stderr.write( - `validate-dogfood-matrix-evidence: FAIL — ${error instanceof Error ? error.message : String(error)}\n`, - ); - process.exit(1); - } -} - -if (import.meta.main) main(); diff --git a/scripts/validate-live-dogfood-evidence.test.ts b/scripts/validate-live-dogfood-evidence.test.ts deleted file mode 100644 index 363479460..000000000 --- a/scripts/validate-live-dogfood-evidence.test.ts +++ /dev/null @@ -1,511 +0,0 @@ -import { describe, expect, test } from 'bun:test'; -import { createHash } from 'node:crypto'; -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { - LIVE_DOGFOOD_SCHEMA_VERSION, - REQUIRED_STAGE_IDS, - validateLiveDogfoodEvidence, - validateLiveDogfoodEvidenceFile, -} from './validate-live-dogfood-evidence.ts'; - -const N = '5.260720.10'; -const T = '5.260723.7'; -const PLATFORM = 'darwin-arm64'; -const HEX = (character: string, size = 64) => character.repeat(size); - -function provenance(kind: 'release-tarball' | 'delivery-evidence', source: string) { - return { - kind, - repository: 'automagik-dev/genie', - predicateType: - kind === 'release-tarball' - ? 'https://slsa.dev/provenance/v0.2' - : 'https://github.com/automagik-dev/genie/delivery-evidence/v1', - workflowIdentity: - kind === 'release-tarball' - ? 'https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.1.0' - : 'https://github.com/automagik-dev/genie/.github/workflows/release-publish.yml@refs/heads/main', - oidcIssuer: 'https://token.actions.githubusercontent.com', - sourceCommit: source, - controlCommit: HEX('c', 40), - sourceBranch: 'main', - sourceCiRunId: '1234', - identitySha256: HEX(kind === 'release-tarball' ? '1' : '2'), - bundleSha256: HEX(kind === 'release-tarball' ? '3' : '4'), - }; -} - -function artifact(version: string, channel: string, digestChar: string, kind: 'release-tarball' | 'delivery-evidence') { - return { - version, - channel, - platformId: PLATFORM, - platformTriple: 'darwin-arm64', - releaseTag: `v${version}`, - releaseName: `genie-${version}-${PLATFORM}.tar.gz`, - manifestSha256: HEX(digestChar), - artifactSha256: HEX(String.fromCharCode(digestChar.charCodeAt(0) + 1)), - binarySha256: HEX(String.fromCharCode(digestChar.charCodeAt(0) + 2)), - payloadSha256: HEX(String.fromCharCode(digestChar.charCodeAt(0) + 3)), - evidenceDigest: HEX(String.fromCharCode(digestChar.charCodeAt(0) + 4)), - provenance: provenance(kind, kind === 'delivery-evidence' ? HEX('a', 40) : HEX('b', 40)), - }; -} - -function task(token: string, label: string) { - return { - wish: `dogfood-${label}-${token}`, - taskId: `t_${label}_${token}`, - title: `task-${label}-${token}`, - status: 'in_progress', - claimedBy: `worker-${label}`, - }; -} - -function repo(root: string, token: string, label: string, pid: number) { - const identity = task(token, label); - return { - root, - requestedCwd: root, - effectiveCwd: root, - cwdIdentity: `9:${pid}`, - childPid: pid, - sentinel: { token, expected: { ...identity }, observed: { ...identity }, boardCount: 1 }, - }; -} - -function stage(id: (typeof REQUIRED_STAGE_IDS)[number]) { - const candidateStages = REQUIRED_STAGE_IDS.slice(3); - const activeVersion = candidateStages.includes(id) ? T : N; - const states: Record = { - 'seed-repositories': [0, 'seeded', 'seeded', null], - 'n-parent-active': [0, 'current', 'current', null], - 't-delivery-repair': [ - 2, - 'activation-pending', - 'activation-pending', - { - schemaVersion: 1, - code: 'activation-pending', - deliveryComplete: true, - retry: false, - nextAction: 'retire tasks then setup and start a new task', - }, - ], - 'activation-consent': [0, 'activated', 'current', null], - 'assets-converged': [0, 'current', 'current', null], - 'untouched-b-before-init': [0, 'empty', 'empty', null], - 'untouched-b-after-init': [0, 'current', 'current', null], - 'new-thread-sentinel': [0, 'current', 'current', null], - 'doctor-current': [0, 'current', 'current', null], - }; - const [exit, humanState, jsonState, trailer] = states[id] as [number, string, string, unknown]; - const generation = id === 't-delivery-repair' || candidateStages.includes(id) ? 'candidate' : 'previous'; - const executable = `/tmp/dogfood/bin/${generation}`; - const standalone = ['untouched-b-before-init', 'untouched-b-after-init', 'new-thread-sentinel'].includes(id); - const argv = standalone ? ['task', 'list', '--json'] : [id]; - const command = { - executable, - executableSha256: generation === 'candidate' ? HEX('c') : HEX('7'), - candidateBinary: executable, - candidateBinarySha256: generation === 'candidate' ? HEX('c') : HEX('7'), - argv, - pid: 3000 + REQUIRED_STAGE_IDS.indexOf(id), - requestedCwd: '/tmp/dogfood/repo-a', - cwdIdentity: '9:2001', - exit, - stdout: standalone ? JSON.stringify({ tasks: [] }) : '', - stderr: '', - }; - const observation = { - schemaVersion: 1, - commands: standalone - ? [command, { ...command, argv: ['board', '--json'], stdout: JSON.stringify({ tasks: [] }) }] - : [command], - }; - return { - id, - command: observation.commands.map((entry) => [executable, ...entry.argv].join(' ')).join(' && '), - exit, - humanState, - jsonState, - activeVersion, - trailer, - observationPath: `observations/${id}.json`, - observationSha256: createHash('sha256') - .update(`${JSON.stringify(observation, null, 2)}\n`) - .digest('hex'), - observation, - }; -} - -function validManifest(): Record { - const previous = artifact(N, 'stable', '5', 'release-tarball'); - const candidate = artifact(T, 'stable', 'a', 'delivery-evidence'); - const a = repo('/tmp/dogfood/repo-a', HEX('7', 40), 'a', 2001); - const b = repo('/tmp/dogfood/repo-b', HEX('8', 40), 'b', 2002); - return { - kind: 'live-dogfood-evidence', - schemaVersion: LIVE_DOGFOOD_SCHEMA_VERSION, - entry: { - id: `${T}-${PLATFORM}`, - evidenceKind: 'verified-local-fixture', - availability: 'verified', - platformId: PLATFORM, - platformTriple: 'darwin-arm64', - artifactName: candidate.releaseName, - inputs: { - previous: { - artifact: `previous/${previous.releaseName}`, - manifest: 'previous/stable.json', - identity: `previous/${previous.releaseName}.intoto.jsonl`, - bundle: `previous/${previous.releaseName}.bundle`, - identityKind: 'slsa-provenance', - }, - candidate: { - artifact: `candidate/${candidate.releaseName}`, - manifest: 'candidate/stable.json', - identity: `candidate/${candidate.releaseName}.stable.delivery.json`, - bundle: `candidate/${candidate.releaseName}.stable.delivery.json.sigstore.json`, - identityKind: 'delivery-descriptor', - }, - }, - }, - lifecycle: { - previousVersion: N, - candidateVersion: T, - channel: 'stable', - sourceCommit: HEX('a', 40), - artifacts: { previous, candidate }, - delivery: { - schemaVersion: 2, - deliveryId: HEX('d', 32), - evidenceDigest: candidate.evidenceDigest, - root: '/tmp/dogfood/genie-home', - targetVersion: T, - platformId: PLATFORM, - platformTriple: 'darwin-arm64', - releaseTag: candidate.releaseTag, - releaseName: candidate.releaseName, - releaseManifestSha256: candidate.manifestSha256, - artifactSha256: candidate.artifactSha256, - installedBinarySha256: candidate.binarySha256, - canonicalPayloadSha256: candidate.payloadSha256, - }, - convergence: { - standalone: { - state: 'standalone', - command: '/tmp/dogfood/genie-home/bin/genie', - taskArgs: ['task', 'list', '--json'], - boardArgs: ['board', '--json'], - }, - roles: { expectedCount: 7, observedCount: 7, current: true, reviewerSha256: HEX('e') }, - }, - stages: REQUIRED_STAGE_IDS.map(stage), - }, - repositories: { - cacheRoot: '/tmp/dogfood/codex/plugins/cache/automagik/genie', - a, - b: { - root: b.root, - beforeInit: { - databaseState: 'absent', - result: 'empty', - returnedTasks: 0, - }, - afterInit: { ...b, evidenceMode: 'standalone' }, - }, - }, - }; -} - -const DOCTOR = { - ok: true, - checks: [{ name: 'Codex plugin lifecycle', status: 'pass' }], - integrationSummary: { - schemaVersion: 1, - codexPlugin: { - state: 'current', - installedVersion: T, - targetVersion: T, - actionRequired: false, - deliveryComplete: true, - }, - }, -}; - -function evidence(manifest = validManifest(), doctor: unknown = DOCTOR): string { - return `\`\`\`json\n${JSON.stringify(manifest, null, 2)}\n\`\`\`\n\n\`\`\`json\n${JSON.stringify( - doctor, - null, - 2, - )}\n\`\`\`\n`; -} - -const ADAPTER = '/tmp/dogfood/bin/run-musl-dogfood.sh'; - -/** - * Reshape every stage observation the way an execution adapter records it: - * the adapter is the executable, the candidate binary moves into adapterArgv, - * and argv stays the candidate's OWN arguments. Only linux-x64-musl runs this - * way, which is why the pre-fix binary-prefixed argv escaped every other leg. - */ -function adapterShaped(manifest: Record): void { - for (const stage of manifest.lifecycle.stages) { - for (const command of stage.observation.commands) { - command.adapterArgv = [command.candidateBinary, ...command.argv]; - command.executable = ADAPTER; - command.executableSha256 = HEX('9'); - } - reprojectStage(stage); - } -} - -function reprojectStage(stage: Record): void { - stage.command = stage.observation.commands - .map((entry: Record) => [entry.executable, ...(entry.adapterArgv ?? entry.argv)].join(' ')) - .join(' && '); - stage.observationSha256 = createHash('sha256') - .update(`${JSON.stringify(stage.observation, null, 2)}\n`) - .digest('hex'); -} - -function errorsAfter(mutator: (manifest: Record) => void): string[] { - const manifest = validManifest(); - mutator(manifest); - return validateLiveDogfoodEvidence(evidence(manifest)); -} - -describe('validate-live-dogfood-evidence schema v2', () => { - test('accepts the real nested doctor topology and complete bound entry', () => { - expect(validateLiveDogfoodEvidence(evidence())).toEqual([]); - }); - - test('accepts standalone task/board evidence and rejects retired MCP evidence', () => { - expect(validateLiveDogfoodEvidence(evidence())).toEqual([]); - expect( - errorsAfter((manifest) => { - const stage = manifest.lifecycle.stages.find((entry: { id: string }) => entry.id === 'new-thread-sentinel'); - stage.observation.commands[0].argv = ['mcpServer/tool/call', 'genie', 'genie_board']; - }).join('\n'), - ).toContain('must reject retired MCP evidence'); - }); - - test('accepts adapter-shaped observations and rejects binary-prefixed argv', () => { - expect(errorsAfter(adapterShaped)).toEqual([]); - // The linux-x64-musl regression: argv carried [, 'task', - // 'list', '--json'], so the standalone assertion could never match. - const prefixed = errorsAfter((manifest) => { - adapterShaped(manifest); - for (const stage of manifest.lifecycle.stages) { - for (const command of stage.observation.commands) command.argv = [...command.adapterArgv]; - reprojectStage(stage); - } - }); - for (const index of [5, 6, 7]) { - expect(prefixed).toContain( - `stages[${index}].standalone must contain standalone task list --json and board --json observations`, - ); - } - expect( - errorsAfter((manifest) => { - adapterShaped(manifest); - manifest.lifecycle.stages[0].observation.commands[0].adapterArgv = ['ok', 7]; - reprojectStage(manifest.lifecycle.stages[0]); - }).join('\n'), - ).toContain('commands[0].adapterArgv must be a string array'); - expect( - errorsAfter((manifest) => { - adapterShaped(manifest); - const stage = manifest.lifecycle.stages.find((entry: { id: string }) => entry.id === 'new-thread-sentinel'); - stage.observation.commands[0].adapterArgv.push('genie_board@mcp'); - reprojectStage(stage); - }).join('\n'), - ).toContain('must reject retired MCP evidence'); - }); - - test('rejects obsolete flat integrationSummary.state', () => { - const flat = { ok: true, checks: [{}], integrationSummary: { schemaVersion: 1, state: 'current' } }; - expect(validateLiveDogfoodEvidence(evidence(validManifest(), flat)).join('\n')).toContain( - 'integrationSummary.codexPlugin', - ); - }); - - test('rejects absent, duplicate, and out-of-order stages', () => { - expect(errorsAfter((m) => m.lifecycle.stages.pop()).join('\n')).toContain('exactly 9 ordered stages'); - expect( - errorsAfter((m) => { - [m.lifecycle.stages[0], m.lifecycle.stages[1]] = [m.lifecycle.stages[1], m.lifecycle.stages[0]]; - }).join('\n'), - ).toContain('stages[0].id'); - expect(errorsAfter((m) => m.lifecycle.stages.push(m.lifecycle.stages[0])).join('\n')).toContain( - 'exactly 9 ordered stages', - ); - }); - - test('rejects inconsistent human/json/trailer/exit and N retiring before consent', () => { - expect( - errorsAfter((m) => { - m.lifecycle.stages[2].exit = 0; - }).join('\n'), - ).toContain('.exit'); - expect( - errorsAfter((m) => { - m.lifecycle.stages[2].humanState = 'current'; - }).join('\n'), - ).toContain('humanState'); - expect( - errorsAfter((m) => { - m.lifecycle.stages[2].trailer.deliveryComplete = false; - }).join('\n'), - ).toContain('deliveryComplete'); - expect( - errorsAfter((m) => { - m.lifecycle.stages[2].activeVersion = T; - }).join('\n'), - ).toContain('activeVersion'); - }); - - test('rejects every tampered candidate delivery binding', () => { - for (const field of [ - 'evidenceDigest', - 'targetVersion', - 'platformId', - 'platformTriple', - 'releaseTag', - 'releaseName', - 'releaseManifestSha256', - 'artifactSha256', - 'installedBinarySha256', - 'canonicalPayloadSha256', - ]) { - const errors = errorsAfter((m) => { - m.lifecycle.delivery[field] = field.endsWith('Sha256') ? HEX('0') : 'bad'; - }); - expect(errors.length, field).toBeGreaterThan(0); - } - }); - - test('rejects unavailable matrix entry and malformed provenance', () => { - expect( - errorsAfter((m) => { - m.entry.availability = 'unavailable'; - }).join('\n'), - ).toContain('availability'); - expect( - errorsAfter((m) => { - m.lifecycle.artifacts.previous.provenance.controlCommit = 'bad'; - }).join('\n'), - ).toContain('controlCommit'); - expect( - errorsAfter((m) => { - m.lifecycle.artifacts.candidate.provenance.workflowIdentity = 'other'; - }).join('\n'), - ).toContain('workflowIdentity'); - }); - - test('requires previous stable N to be older than candidate T', () => { - expect( - errorsAfter((m) => { - m.lifecycle.previousVersion = '5.260724.1'; - m.lifecycle.artifacts.previous.version = '5.260724.1'; - m.lifecycle.artifacts.previous.releaseTag = 'v5.260724.1'; - m.lifecycle.artifacts.previous.releaseName = 'genie-5.260724.1-darwin-arm64.tar.gz'; - }).join('\n'), - ).toContain('previous stable N must be older than candidate T'); - }); - - test('rejects empty/cross-repo/cache-root/stale task and repeated child PID evidence', () => { - expect( - errorsAfter((m) => { - m.repositories.a.sentinel.boardCount = 0; - }).join('\n'), - ).toContain('boardCount'); - expect( - errorsAfter((m) => { - m.repositories.a.sentinel.observed = m.repositories.b.afterInit.sentinel.observed; - }).join('\n'), - ).toContain('exactly equal'); - expect( - errorsAfter((m) => { - m.repositories.a.effectiveCwd = m.repositories.cacheRoot; - }).join('\n'), - ).toContain('plugin cache'); - expect( - errorsAfter((m) => { - m.repositories.a.sentinel.observed.status = 'done'; - }).join('\n'), - ).toContain('in_progress'); - expect( - errorsAfter((m) => { - m.repositories.b.afterInit.childPid = m.repositories.a.childPid; - }).join('\n'), - ).toContain('distinct child'); - }); - - test('requires untouched B database absence before init and standalone evidence after', () => { - expect( - errorsAfter((m) => { - m.repositories.b.beforeInit.databaseState = 'present'; - }).join('\n'), - ).toContain('databaseState'); - expect( - errorsAfter((m) => { - m.repositories.b.afterInit.evidenceMode = 'mcp'; - }).join('\n'), - ).toContain('evidenceMode'); - }); -}); - -describe('referenced input verification', () => { - test('rehashes every staged input and rejects missing or changed bytes', () => { - const root = mkdtempSync(join(tmpdir(), 'dogfood-validator-')); - try { - const manifest = validManifest(); - for (const generation of ['previous', 'candidate']) { - mkdirSync(join(root, generation)); - const inputs = manifest.entry.inputs[generation]; - const artifact = manifest.lifecycle.artifacts[generation]; - const files = [ - ['artifact', 'artifactSha256'], - ['manifest', 'manifestSha256'], - ['identity', 'identitySha256'], - ['bundle', 'bundleSha256'], - ]; - for (const [inputKey, digestKey] of files) { - const bytes = `${generation}-${inputKey}`; - const path = join(root, inputs[inputKey]); - writeFileSync(path, bytes); - const digest = createHash('sha256').update(bytes).digest('hex'); - if (inputKey === 'identity' || inputKey === 'bundle') artifact.provenance[digestKey] = digest; - else artifact[digestKey] = digest; - } - } - const candidate = manifest.lifecycle.artifacts.candidate; - Object.assign(manifest.lifecycle.delivery, { - releaseManifestSha256: candidate.manifestSha256, - artifactSha256: candidate.artifactSha256, - installedBinarySha256: candidate.binarySha256, - canonicalPayloadSha256: candidate.payloadSha256, - }); - mkdirSync(join(root, 'observations')); - for (const stage of manifest.lifecycle.stages) { - writeFileSync(join(root, stage.observationPath), `${JSON.stringify(stage.observation, null, 2)}\n`); - } - const path = join(root, 'evidence.md'); - writeFileSync(path, evidence(manifest)); - expect(validateLiveDogfoodEvidenceFile(path, root)).toEqual([]); - writeFileSync(join(root, manifest.entry.inputs.candidate.artifact), 'tampered'); - expect(validateLiveDogfoodEvidenceFile(path, root).join('\n')).toContain('candidate artifact digest mismatch'); - rmSync(join(root, manifest.entry.inputs.previous.bundle)); - expect(validateLiveDogfoodEvidenceFile(path, root).join('\n')).toContain('previous bundle is unavailable'); - writeFileSync(join(root, manifest.lifecycle.stages[0].observationPath), '{"tampered":true}\n'); - expect(validateLiveDogfoodEvidenceFile(path, root).join('\n')).toContain('stage observation digest mismatch'); - expect(readFileSync(path, 'utf8')).toContain('"schemaVersion": 2'); - } finally { - rmSync(root, { recursive: true, force: true }); - } - }); -}); diff --git a/scripts/validate-live-dogfood-evidence.ts b/scripts/validate-live-dogfood-evidence.ts deleted file mode 100644 index e369191d6..000000000 --- a/scripts/validate-live-dogfood-evidence.ts +++ /dev/null @@ -1,891 +0,0 @@ -#!/usr/bin/env bun - -/** - * Fail-closed validator for one reusable Codex dogfood matrix entry. - * - * Schema v2 binds a real N -> T lifecycle to the exact release inputs, delivery - * record, two seeded repositories, child CWD identities, ordered command - * stages, and the production doctor JSON topology. The manifest and doctor - * payload are separate fenced JSON blocks in the evidence Markdown. - */ - -import { createHash } from 'node:crypto'; -import { existsSync, lstatSync, readFileSync } from 'node:fs'; -import { dirname, isAbsolute, resolve } from 'node:path'; -import { compareReleaseVersions, parseReleaseVersion } from '../src/lib/release-payload-proof.ts'; - -export const LIVE_DOGFOOD_SCHEMA_VERSION = 2 as const; -export const REQUIRED_STAGE_IDS = [ - 'seed-repositories', - 'n-parent-active', - 't-delivery-repair', - 'activation-consent', - 'assets-converged', - 'untouched-b-before-init', - 'untouched-b-after-init', - 'new-thread-sentinel', - 'doctor-current', -] as const; - -const RELEASE_VERSION = /^\d+\.\d{6}\.\d+$/; -const COMMIT_SHA = /^[0-9a-f]{40}$/; -const SHA256 = /^[0-9a-f]{64}$/; -const DELIVERY_ID = /^[0-9a-f]{32}$/; -const DIRECTORY_IDENTITY = /^\d+:\d+$/; -const SENTINEL_TOKEN = /^[0-9a-f]{32,}$/; -const PLATFORM_TRIPLES = { - 'linux-x64-glibc': 'linux-x64', - 'linux-x64-musl': 'linux-x64', - 'linux-arm64': 'linux-arm64', - 'darwin-arm64': 'darwin-arm64', -} as const; -const REPOSITORY = 'automagik-dev/genie'; -const PREDICATE_TYPE = 'https://github.com/automagik-dev/genie/delivery-evidence/v1'; -const WORKFLOW_IDENTITY = - 'https://github.com/automagik-dev/genie/.github/workflows/release-publish.yml@refs/heads/main'; -const OIDC_ISSUER = 'https://token.actions.githubusercontent.com'; -const LEGACY_PREDICATE_TYPE = 'https://slsa.dev/provenance/v0.2'; -const LEGACY_WORKFLOW_IDENTITY = - 'https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.1.0'; - -type JsonRecord = Record; -interface JsonBlock { - value: JsonRecord; -} - -function isRecord(value: unknown): value is JsonRecord { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function extractJsonBlocks(markdown: string): Array { - const blocks: Array = []; - const pattern = /```json\s*\n([\s\S]*?)\n```/g; - for (let match = pattern.exec(markdown); match !== null; match = pattern.exec(markdown)) { - try { - const parsed: unknown = JSON.parse(match[1]); - blocks.push(isRecord(parsed) ? { value: parsed } : null); - } catch { - blocks.push(null); - } - } - return blocks; -} - -function record(errors: string[], label: string, value: unknown): JsonRecord | null { - if (isRecord(value)) return value; - errors.push(`${label} must be an object`); - return null; -} - -function nonempty(errors: string[], label: string, value: unknown): value is string { - if (typeof value === 'string' && value.trim() !== '') return true; - errors.push(`${label} must be a non-empty string`); - return false; -} - -function matches( - errors: string[], - label: string, - value: unknown, - pattern: RegExp, - expectation: string, -): value is string { - if (typeof value === 'string' && pattern.test(value)) return true; - errors.push(`${label} must be ${expectation}`); - return false; -} - -function absolute(errors: string[], label: string, value: unknown): value is string { - if (typeof value === 'string' && isAbsolute(value) && !value.includes('\0')) return true; - errors.push(`${label} must be an absolute path`); - return false; -} - -function portableInput(errors: string[], label: string, value: unknown, generation: string): value is string { - if ( - typeof value === 'string' && - value.startsWith(`${generation}/`) && - !isAbsolute(value) && - !value.includes('\\') && - !value.split('/').includes('..') && - value.split('/').length === 2 && - value.split('/')[1] !== '' - ) { - return true; - } - errors.push(`${label} must be a portable ${generation}/ reference`); - return false; -} - -function portableObservation(errors: string[], label: string, value: unknown, stageId: string): value is string { - if (value === `observations/${stageId}.json`) return true; - errors.push(`${label} must be observations/${stageId}.json`); - return false; -} - -function same(errors: string[], label: string, actual: unknown, expected: unknown): void { - if (actual !== expected) - errors.push(`${label} must equal ${JSON.stringify(expected)} (got ${JSON.stringify(actual)})`); -} - -function validateEntry(errors: string[], value: unknown): JsonRecord | null { - const entry = record(errors, 'manifest.entry', value); - if (entry === null) return null; - nonempty(errors, 'entry.id', entry.id); - if (!['host-native', 'verified-local-fixture'].includes(String(entry.evidenceKind))) { - errors.push("entry.evidenceKind must be 'host-native' or 'verified-local-fixture'"); - } - same(errors, 'entry.availability', entry.availability, 'verified'); - const platformId = entry.platformId; - if (typeof platformId !== 'string' || !(platformId in PLATFORM_TRIPLES)) { - errors.push('entry.platformId must name a supported release platform'); - } else { - same( - errors, - 'entry.platformTriple', - entry.platformTriple, - PLATFORM_TRIPLES[platformId as keyof typeof PLATFORM_TRIPLES], - ); - } - nonempty(errors, 'entry.artifactName', entry.artifactName); - const inputs = record(errors, 'entry.inputs', entry.inputs); - for (const generation of ['previous', 'candidate'] as const) { - const input = record(errors, `entry.inputs.${generation}`, inputs?.[generation]); - same( - errors, - `entry.inputs.${generation}.identityKind`, - input?.identityKind, - generation === 'previous' ? 'slsa-provenance' : 'delivery-descriptor', - ); - for (const key of ['artifact', 'manifest', 'identity', 'bundle']) { - portableInput(errors, `entry.inputs.${generation}.${key}`, input?.[key], generation); - } - } - return entry; -} - -function validateProvenance(errors: string[], label: string, value: unknown, commit: unknown, channel: unknown): void { - const provenance = record(errors, label, value); - if (provenance === null) return; - same(errors, `${label}.repository`, provenance.repository, REPOSITORY); - if (provenance.kind === 'release-tarball') { - same(errors, `${label}.predicateType`, provenance.predicateType, LEGACY_PREDICATE_TYPE); - same(errors, `${label}.workflowIdentity`, provenance.workflowIdentity, LEGACY_WORKFLOW_IDENTITY); - } else if (provenance.kind === 'delivery-evidence') { - same(errors, `${label}.predicateType`, provenance.predicateType, PREDICATE_TYPE); - same(errors, `${label}.workflowIdentity`, provenance.workflowIdentity, WORKFLOW_IDENTITY); - } else { - errors.push(`${label}.kind must be 'release-tarball' or 'delivery-evidence'`); - } - same(errors, `${label}.oidcIssuer`, provenance.oidcIssuer, OIDC_ISSUER); - if (commit === undefined) { - matches( - errors, - `${label}.sourceCommit`, - provenance.sourceCommit, - COMMIT_SHA, - 'a 40-character lowercase commit sha', - ); - } else { - same(errors, `${label}.sourceCommit`, provenance.sourceCommit, commit); - } - matches( - errors, - `${label}.controlCommit`, - provenance.controlCommit, - COMMIT_SHA, - 'a 40-character lowercase commit sha', - ); - // Delivery-era provenance carries the release inputs the orchestrator - // admitted, so its branch is bound to its channel (`admit` accepts only - // stable:main / dev:dev). - // - // Legacy release-tarball provenance is the generic SLSA statement of an - // ALREADY-PUBLISHED release, so its branch records where that release was - // BUILT, not the channel it was later promoted into. A stable release - // promotes an already-published dev prerelease and preserves its immutable - // signed bytes, so the previous stable N legitimately carries sourceBranch - // "dev" while its manifest channel is "stable" — v5.260720.10, the currently - // pinned N, is exactly this. Demanding "main" there is unsatisfiable; accept - // any branch the release chain admits instead. - if (provenance.kind === 'release-tarball') { - if (!['main', 'dev'].includes(String(provenance.sourceBranch))) { - errors.push(`${label}.sourceBranch must be main or dev`); - } - } else if (channel === 'stable') { - same(errors, `${label}.sourceBranch`, provenance.sourceBranch, 'main'); - } else { - same(errors, `${label}.sourceBranch`, provenance.sourceBranch, channel); - } - if (typeof provenance.sourceCiRunId !== 'string' || !/^(?:0|[1-9]\d*)$/.test(provenance.sourceCiRunId)) { - errors.push(`${label}.sourceCiRunId must be an unsigned decimal string`); - } - for (const key of ['identitySha256', 'bundleSha256']) { - matches(errors, `${label}.${key}`, provenance[key], SHA256, '64 lowercase hex characters'); - } -} - -function validateArtifact( - errors: string[], - label: string, - value: unknown, - expectedVersion: unknown, - entry: JsonRecord | null, - commit: unknown, - channel: unknown, -): JsonRecord | null { - const artifact = record(errors, label, value); - if (artifact === null) return null; - same(errors, `${label}.version`, artifact.version, expectedVersion); - if (channel === undefined) { - if (!['stable', 'dev'].includes(String(artifact.channel))) { - errors.push(`${label}.channel must be stable or dev`); - } - } else { - same(errors, `${label}.channel`, artifact.channel, channel); - } - same(errors, `${label}.platformId`, artifact.platformId, entry?.platformId); - same(errors, `${label}.platformTriple`, artifact.platformTriple, entry?.platformTriple); - same(errors, `${label}.releaseTag`, artifact.releaseTag, `v${String(expectedVersion)}`); - same( - errors, - `${label}.releaseName`, - artifact.releaseName, - `genie-${String(expectedVersion)}-${String(entry?.platformId)}.tar.gz`, - ); - for (const key of ['manifestSha256', 'artifactSha256', 'binarySha256', 'payloadSha256', 'evidenceDigest']) { - matches(errors, `${label}.${key}`, artifact[key], SHA256, '64 lowercase hex characters'); - } - validateProvenance(errors, `${label}.provenance`, artifact.provenance, commit, artifact.channel); - return artifact; -} - -function validateDelivery( - errors: string[], - value: unknown, - lifecycle: JsonRecord, - entry: JsonRecord | null, - candidate: JsonRecord | null, -): void { - const delivery = record(errors, 'lifecycle.delivery', value); - if (delivery === null) return; - same(errors, 'delivery.schemaVersion', delivery.schemaVersion, 2); - matches(errors, 'delivery.deliveryId', delivery.deliveryId, DELIVERY_ID, '32 lowercase hex characters'); - matches(errors, 'delivery.evidenceDigest', delivery.evidenceDigest, SHA256, '64 lowercase hex characters'); - absolute(errors, 'delivery.root', delivery.root); - if (typeof delivery.root === 'string' && /[/\\]plugins[/\\]cache(?:[/\\]|$)/.test(delivery.root)) { - errors.push('delivery.root must not be a plugin cache root'); - } - same(errors, 'delivery.targetVersion', delivery.targetVersion, lifecycle.candidateVersion); - same(errors, 'delivery.platformId', delivery.platformId, entry?.platformId); - same(errors, 'delivery.platformTriple', delivery.platformTriple, entry?.platformTriple); - same(errors, 'delivery.releaseTag', delivery.releaseTag, candidate?.releaseTag); - same(errors, 'delivery.releaseName', delivery.releaseName, candidate?.releaseName); - same(errors, 'delivery.releaseManifestSha256', delivery.releaseManifestSha256, candidate?.manifestSha256); - same(errors, 'delivery.artifactSha256', delivery.artifactSha256, candidate?.artifactSha256); - same(errors, 'delivery.installedBinarySha256', delivery.installedBinarySha256, candidate?.binarySha256); - same(errors, 'delivery.canonicalPayloadSha256', delivery.canonicalPayloadSha256, candidate?.payloadSha256); - same(errors, 'delivery.evidenceDigest binding', delivery.evidenceDigest, candidate?.evidenceDigest); -} - -function validateConvergence(errors: string[], value: unknown): void { - const convergence = record(errors, 'lifecycle.convergence', value); - if (convergence === null) return; - const standalone = record(errors, 'lifecycle.convergence.standalone', convergence.standalone); - same(errors, 'convergence.standalone.state', standalone?.state, 'standalone'); - absolute(errors, 'convergence.standalone.command', standalone?.command); - same( - errors, - 'convergence.standalone.taskArgs', - JSON.stringify(standalone?.taskArgs), - JSON.stringify(['task', 'list', '--json']), - ); - same( - errors, - 'convergence.standalone.boardArgs', - JSON.stringify(standalone?.boardArgs), - JSON.stringify(['board', '--json']), - ); - const roles = record(errors, 'lifecycle.convergence.roles', convergence.roles); - const expected = roles?.expectedCount; - if (typeof expected !== 'number' || !Number.isInteger(expected) || expected <= 0) { - errors.push('convergence.roles.expectedCount must be a positive integer'); - } - same(errors, 'convergence.roles.observedCount', roles?.observedCount, expected); - same(errors, 'convergence.roles.current', roles?.current, true); - matches(errors, 'convergence.roles.reviewerSha256', roles?.reviewerSha256, SHA256, '64 lowercase hex characters'); -} - -function validateTaskSentinel(errors: string[], label: string, value: unknown): JsonRecord | null { - const sentinel = record(errors, label, value); - if (sentinel === null) return null; - matches(errors, `${label}.token`, sentinel.token, SENTINEL_TOKEN, 'at least 128 bits of lowercase hex'); - const expected = record(errors, `${label}.expected`, sentinel.expected); - const observed = record(errors, `${label}.observed`, sentinel.observed); - if (expected !== null && observed !== null && JSON.stringify(expected) !== JSON.stringify(observed)) { - errors.push(`${label}.observed must exactly equal the seeded expected task identity`); - } - for (const [side, task] of [ - ['expected', expected], - ['observed', observed], - ] as const) { - nonempty(errors, `${label}.${side}.wish`, task?.wish); - nonempty(errors, `${label}.${side}.taskId`, task?.taskId); - nonempty(errors, `${label}.${side}.title`, task?.title); - nonempty(errors, `${label}.${side}.claimedBy`, task?.claimedBy); - same(errors, `${label}.${side}.status`, task?.status, 'in_progress'); - if (typeof sentinel.token === 'string') { - if (typeof task?.wish !== 'string' || !task.wish.includes(sentinel.token)) { - errors.push(`${label}.${side}.wish must contain the unpredictable sentinel token`); - } - if (typeof task?.title !== 'string' || !task.title.includes(sentinel.token)) { - errors.push(`${label}.${side}.title must contain the unpredictable sentinel token`); - } - } - } - same(errors, `${label}.boardCount`, sentinel.boardCount, 1); - return sentinel; -} - -function validateRepoObservation( - errors: string[], - label: string, - value: unknown, - cacheRoot: unknown, -): JsonRecord | null { - const repo = record(errors, label, value); - if (repo === null) return null; - absolute(errors, `${label}.root`, repo.root); - same(errors, `${label}.requestedCwd`, repo.requestedCwd, repo.root); - same(errors, `${label}.effectiveCwd`, repo.effectiveCwd, repo.root); - matches( - errors, - `${label}.cwdIdentity`, - repo.cwdIdentity, - DIRECTORY_IDENTITY, - 'an OS directory identity in dev:ino form', - ); - if (typeof repo.childPid !== 'number' || !Number.isInteger(repo.childPid) || repo.childPid <= 0) { - errors.push(`${label}.childPid must be a positive integer`); - } - if ( - repo.effectiveCwd === cacheRoot || - (typeof repo.effectiveCwd === 'string' && /[/\\]plugins[/\\]cache/.test(repo.effectiveCwd)) - ) { - errors.push(`${label}.effectiveCwd must not be the plugin cache root`); - } - validateTaskSentinel(errors, `${label}.sentinel`, repo.sentinel); - return repo; -} - -function validateRepositories(errors: string[], value: unknown): void { - const repositories = record(errors, 'manifest.repositories', value); - if (repositories === null) return; - absolute(errors, 'repositories.cacheRoot', repositories.cacheRoot); - const repoA = validateRepoObservation(errors, 'repositories.a', repositories.a, repositories.cacheRoot); - const repoB = record(errors, 'repositories.b', repositories.b); - if (repoB === null) return; - absolute(errors, 'repositories.b.root', repoB.root); - const before = record(errors, 'repositories.b.beforeInit', repoB.beforeInit); - same(errors, 'repositories.b.beforeInit.databaseState', before?.databaseState, 'absent'); - same(errors, 'repositories.b.beforeInit.result', before?.result, 'empty'); - same(errors, 'repositories.b.beforeInit.returnedTasks', before?.returnedTasks, 0); - const after = validateRepoObservation(errors, 'repositories.b.afterInit', repoB.afterInit, repositories.cacheRoot); - same(errors, 'repositories.b.afterInit.evidenceMode', after?.evidenceMode, 'standalone'); - if (repoA?.root === repoB.root) errors.push('repositories A and B must be different roots'); - const aToken = isRecord(repoA?.sentinel) ? repoA.sentinel.token : null; - const bToken = isRecord(after?.sentinel) ? after.sentinel.token : null; - if (aToken === bToken) errors.push('repositories A and B must use different unpredictable sentinels'); - if (repoA?.childPid === after?.childPid) errors.push('repositories A and B must record distinct child processes'); -} - -interface StageExpectation { - exit: number; - human: string; - json: string; - generation: 'previous' | 'candidate'; - binary: 'previous' | 'candidate'; - trailer: 'activation-pending' | null; -} - -const STAGE_EXPECTATIONS: Record<(typeof REQUIRED_STAGE_IDS)[number], StageExpectation> = { - 'seed-repositories': { - exit: 0, - human: 'seeded', - json: 'seeded', - generation: 'previous', - binary: 'previous', - trailer: null, - }, - 'n-parent-active': { - exit: 0, - human: 'current', - json: 'current', - generation: 'previous', - binary: 'previous', - trailer: null, - }, - 't-delivery-repair': { - exit: 2, - human: 'activation-pending', - json: 'activation-pending', - generation: 'previous', - binary: 'candidate', - trailer: 'activation-pending', - }, - 'activation-consent': { - exit: 0, - human: 'activated', - json: 'current', - generation: 'candidate', - binary: 'candidate', - trailer: null, - }, - 'assets-converged': { - exit: 0, - human: 'current', - json: 'current', - generation: 'candidate', - binary: 'candidate', - trailer: null, - }, - 'untouched-b-before-init': { - exit: 0, - human: 'empty', - json: 'empty', - generation: 'candidate', - binary: 'candidate', - trailer: null, - }, - 'untouched-b-after-init': { - exit: 0, - human: 'current', - json: 'current', - generation: 'candidate', - binary: 'candidate', - trailer: null, - }, - 'new-thread-sentinel': { - exit: 0, - human: 'current', - json: 'current', - generation: 'candidate', - binary: 'candidate', - trailer: null, - }, - 'doctor-current': { - exit: 0, - human: 'current', - json: 'current', - generation: 'candidate', - binary: 'candidate', - trailer: null, - }, -}; - -/** The real spawned vector: the adapter's argv when one wrapped the candidate. */ -function spawnArgv(command: JsonRecord): unknown[] { - if (Array.isArray(command.adapterArgv)) return command.adapterArgv; - return Array.isArray(command.argv) ? command.argv : []; -} - -function validateCapturedCommand( - errors: string[], - label: string, - value: unknown, -): { record: JsonRecord; summary: string; exit: number | null; candidateSha256: unknown } | null { - const command = record(errors, label, value); - if (command === null) return null; - absolute(errors, `${label}.executable`, command.executable); - matches(errors, `${label}.executableSha256`, command.executableSha256, SHA256, '64 lowercase hex characters'); - const candidateIsNull = command.candidateBinary === null && command.candidateBinarySha256 === null; - if (!candidateIsNull) { - absolute(errors, `${label}.candidateBinary`, command.candidateBinary); - matches( - errors, - `${label}.candidateBinarySha256`, - command.candidateBinarySha256, - SHA256, - '64 lowercase hex characters', - ); - } - if (!Array.isArray(command.argv) || command.argv.some((arg) => typeof arg !== 'string')) { - errors.push(`${label}.argv must be a string array`); - } - // Optional: present only when an execution adapter (linux-x64-musl Alpine) - // spawned the candidate. `argv` stays the candidate's own arguments on every - // platform; `adapterArgv` records the real vector handed to `executable`. - if ( - command.adapterArgv !== undefined && - (!Array.isArray(command.adapterArgv) || command.adapterArgv.some((arg) => typeof arg !== 'string')) - ) { - errors.push(`${label}.adapterArgv must be a string array`); - } - if (typeof command.pid !== 'number' || !Number.isInteger(command.pid) || command.pid <= 0) { - errors.push(`${label}.pid must be a positive integer`); - } - absolute(errors, `${label}.requestedCwd`, command.requestedCwd); - matches(errors, `${label}.cwdIdentity`, command.cwdIdentity, DIRECTORY_IDENTITY, 'an OS directory identity'); - if (typeof command.exit !== 'number' || !Number.isInteger(command.exit)) { - errors.push(`${label}.exit must be an integer`); - } - if (typeof command.stdout !== 'string') errors.push(`${label}.stdout must be a string`); - if (typeof command.stderr !== 'string') errors.push(`${label}.stderr must be a string`); - return { - record: command, - summary: [String(command.executable), ...spawnArgv(command).map(String)].join(' '), - exit: typeof command.exit === 'number' && Number.isInteger(command.exit) ? command.exit : null, - candidateSha256: command.candidateBinarySha256, - }; -} - -const STANDALONE_STAGES = new Set<(typeof REQUIRED_STAGE_IDS)[number]>([ - 'untouched-b-before-init', - 'untouched-b-after-init', - 'new-thread-sentinel', -]); - -function validateStandaloneObservation( - errors: string[], - id: (typeof REQUIRED_STAGE_IDS)[number], - index: number, - _evidenceKind: unknown, - commands: Array<{ record: JsonRecord }>, -): void { - if (!STANDALONE_STAGES.has(id)) return; - const label = `stages[${index}].standalone`; - const argv = commands.map((value) => value.record.argv); - const hasTask = argv.some((args) => Array.isArray(args) && args.join(' ') === 'task list --json'); - const hasBoard = argv.some((args) => Array.isArray(args) && args.join(' ') === 'board --json'); - if (!hasTask || !hasBoard) - errors.push(`${label} must contain standalone task list --json and board --json observations`); - const vectors = commands.flatMap((value) => [value.record.argv, value.record.adapterArgv]); - if (vectors.some((args) => Array.isArray(args) && args.some((arg) => String(arg).toLowerCase().includes('mcp')))) { - errors.push(`${label} must reject retired MCP evidence`); - } -} - -function validateStageObservation( - errors: string[], - stage: JsonRecord, - index: number, - id: (typeof REQUIRED_STAGE_IDS)[number], - expectedExit: number, - expectedBinarySha256: unknown, - evidenceKind: unknown, -): void { - portableObservation(errors, `stages[${index}].observationPath`, stage.observationPath, id); - matches(errors, `stages[${index}].observationSha256`, stage.observationSha256, SHA256, '64 lowercase hex characters'); - const observation = record(errors, `stages[${index}].observation`, stage.observation); - same(errors, `stages[${index}].observation.schemaVersion`, observation?.schemaVersion, 1); - if (!Array.isArray(observation?.commands) || observation.commands.length === 0) { - errors.push(`stages[${index}].observation.commands must be a non-empty array`); - return; - } - const commands = observation.commands - .map((value, commandIndex) => - validateCapturedCommand(errors, `stages[${index}].observation.commands[${commandIndex}]`, value), - ) - .filter((value) => value !== null); - same( - errors, - `stages[${index}].command projection`, - stage.command, - commands.map((value) => value.summary).join(' && '), - ); - if (!commands.some((command) => command.exit === expectedExit)) { - errors.push(`stages[${index}].observation must contain the projected exit ${expectedExit}`); - } - if (!commands.some((command) => command.candidateSha256 === expectedBinarySha256)) { - errors.push(`stages[${index}].observation must bind the authenticated generation binary digest`); - } - validateStandaloneObservation(errors, id, index, evidenceKind, commands); - const serialized = `${JSON.stringify(observation, null, 2)}\n`; - const observedDigest = createHash('sha256').update(serialized).digest('hex'); - same(errors, `stages[${index}].observation digest`, stage.observationSha256, observedDigest); -} - -function validateStages( - errors: string[], - value: unknown, - previousVersion: unknown, - candidateVersion: unknown, - previousBinarySha256: unknown, - candidateBinarySha256: unknown, - evidenceKind: unknown, -): void { - if (!Array.isArray(value)) { - errors.push('manifest.stages must be an array'); - return; - } - if (value.length !== REQUIRED_STAGE_IDS.length) { - errors.push(`manifest.stages must have exactly ${REQUIRED_STAGE_IDS.length} ordered stages (got ${value.length})`); - } - REQUIRED_STAGE_IDS.forEach((id, index) => { - const stage = record(errors, `stages[${index}] (${id})`, value[index]); - if (stage === null) return; - same(errors, `stages[${index}].id`, stage.id, id); - nonempty(errors, `stages[${index}].command`, stage.command); - const expected = STAGE_EXPECTATIONS[id]; - same(errors, `stages[${index}] (${id}).exit`, stage.exit, expected.exit); - same(errors, `stages[${index}] (${id}).humanState`, stage.humanState, expected.human); - same(errors, `stages[${index}] (${id}).jsonState`, stage.jsonState, expected.json); - same( - errors, - `stages[${index}] (${id}).activeVersion`, - stage.activeVersion, - expected.generation === 'previous' ? previousVersion : candidateVersion, - ); - validateStageObservation( - errors, - stage, - index, - id, - expected.exit, - expected.binary === 'previous' ? previousBinarySha256 : candidateBinarySha256, - evidenceKind, - ); - if (expected.trailer === null) { - same(errors, `stages[${index}] (${id}).trailer`, stage.trailer, null); - return; - } - const trailer = record(errors, `stages[${index}] (${id}).trailer`, stage.trailer); - same(errors, `stages[${index}] (${id}).trailer.schemaVersion`, trailer?.schemaVersion, 1); - same(errors, `stages[${index}] (${id}).trailer.code`, trailer?.code, expected.trailer); - same(errors, `stages[${index}] (${id}).trailer.deliveryComplete`, trailer?.deliveryComplete, true); - same(errors, `stages[${index}] (${id}).trailer.retry`, trailer?.retry, false); - nonempty(errors, `stages[${index}] (${id}).trailer.nextAction`, trailer?.nextAction); - }); -} - -function validateLifecycle(errors: string[], value: unknown, entry: JsonRecord | null): JsonRecord | null { - const lifecycle = record(errors, 'manifest.lifecycle', value); - if (lifecycle === null) return null; - matches( - errors, - 'lifecycle.previousVersion', - lifecycle.previousVersion, - RELEASE_VERSION, - 'a release version MAJOR.YYMMDD.PATCH', - ); - matches( - errors, - 'lifecycle.candidateVersion', - lifecycle.candidateVersion, - RELEASE_VERSION, - 'a release version MAJOR.YYMMDD.PATCH', - ); - if (lifecycle.previousVersion === lifecycle.candidateVersion) { - errors.push('lifecycle previous and candidate versions must differ'); - } - const previousVersion = parseReleaseVersion(lifecycle.previousVersion); - const candidateVersion = parseReleaseVersion(lifecycle.candidateVersion); - if ( - previousVersion !== null && - candidateVersion !== null && - compareReleaseVersions(previousVersion, candidateVersion) >= 0 - ) { - errors.push('lifecycle previous stable N must be older than candidate T'); - } - if (!['stable', 'dev'].includes(String(lifecycle.channel))) { - errors.push('lifecycle.channel must be stable or dev'); - } - matches(errors, 'lifecycle.sourceCommit', lifecycle.sourceCommit, COMMIT_SHA, 'a 40-character lowercase commit sha'); - const artifacts = record(errors, 'lifecycle.artifacts', lifecycle.artifacts); - const previous = validateArtifact( - errors, - 'lifecycle.artifacts.previous', - artifacts?.previous, - lifecycle.previousVersion, - entry, - undefined, - undefined, - ); - const candidate = validateArtifact( - errors, - 'lifecycle.artifacts.candidate', - artifacts?.candidate, - lifecycle.candidateVersion, - entry, - lifecycle.sourceCommit, - lifecycle.channel, - ); - same(errors, 'entry.artifactName binding', entry?.artifactName, candidate?.releaseName); - validateDelivery(errors, lifecycle.delivery, lifecycle, entry, candidate); - validateConvergence(errors, lifecycle.convergence); - validateStages( - errors, - (lifecycle as JsonRecord).stages, - lifecycle.previousVersion, - lifecycle.candidateVersion, - previous?.binarySha256, - candidate?.binarySha256, - entry?.evidenceKind, - ); - // Keep the previous value live in the validation graph: both generations must - // carry independent artifact identities, not a copied candidate object. - if (previous?.artifactSha256 === candidate?.artifactSha256) { - errors.push('previous and candidate artifacts must have different digests'); - } - return lifecycle; -} - -function validateDoctor(errors: string[], blocks: Array, candidateVersion: unknown): void { - const doctors = blocks.filter((block) => block !== null && isRecord(block.value.integrationSummary)); - if (doctors.length !== 1) { - errors.push(`evidence must contain exactly one doctor JSON block (got ${doctors.length})`); - return; - } - const doctor = (doctors[0] as JsonBlock).value; - same(errors, 'doctor.ok', doctor.ok, true); - if (!Array.isArray(doctor.checks) || doctor.checks.length === 0) - errors.push('doctor.checks must be a non-empty array'); - const summary = doctor.integrationSummary as JsonRecord; - same(errors, 'doctor.integrationSummary.schemaVersion', summary.schemaVersion, 1); - if ('state' in summary) errors.push('obsolete flat doctor integrationSummary.state is forbidden'); - const plugin = record(errors, 'doctor.integrationSummary.codexPlugin', summary.codexPlugin); - same(errors, 'doctor.integrationSummary.codexPlugin.state', plugin?.state, 'current'); - same(errors, 'doctor.integrationSummary.codexPlugin.installedVersion', plugin?.installedVersion, candidateVersion); - same(errors, 'doctor.integrationSummary.codexPlugin.targetVersion', plugin?.targetVersion, candidateVersion); - same(errors, 'doctor.integrationSummary.codexPlugin.actionRequired', plugin?.actionRequired, false); - same(errors, 'doctor.integrationSummary.codexPlugin.deliveryComplete', plugin?.deliveryComplete, true); -} - -function manifestFromBlocks(blocks: Array): JsonRecord | null { - const manifests = blocks.filter((block) => block !== null && block.value.kind === 'live-dogfood-evidence'); - return manifests.length > 0 ? (manifests[0] as JsonBlock).value : null; -} - -/** Validate evidence structure and all cross-field bindings. Empty means valid. */ -export function validateLiveDogfoodEvidence(markdown: string): string[] { - const errors: string[] = []; - const blocks = extractJsonBlocks(markdown); - const manifests = blocks.filter((block) => block !== null && block.value.kind === 'live-dogfood-evidence'); - if (manifests.length !== 1) { - errors.push(`evidence must contain exactly one live-dogfood-evidence manifest block (got ${manifests.length})`); - validateDoctor(errors, blocks, undefined); - return errors; - } - const manifest = (manifests[0] as JsonBlock).value; - same(errors, 'manifest.schemaVersion', manifest.schemaVersion, LIVE_DOGFOOD_SCHEMA_VERSION); - const entry = validateEntry(errors, manifest.entry); - const lifecycle = validateLifecycle(errors, manifest.lifecycle, entry); - validateRepositories(errors, manifest.repositories); - validateDoctor(errors, blocks, lifecycle?.candidateVersion); - return errors; -} - -function validateReferencedFiles(manifest: JsonRecord, inputsRoot: string): string[] { - const errors: string[] = []; - const entry = isRecord(manifest.entry) ? manifest.entry : null; - const inputs = entry && isRecord(entry.inputs) ? entry.inputs : null; - const lifecycle = isRecord(manifest.lifecycle) ? manifest.lifecycle : null; - const artifacts = lifecycle && isRecord(lifecycle.artifacts) ? lifecycle.artifacts : null; - for (const generation of ['previous', 'candidate'] as const) { - const input = inputs && isRecord(inputs[generation]) ? inputs[generation] : null; - const artifact = artifacts && isRecord(artifacts[generation]) ? artifacts[generation] : null; - const provenance = artifact && isRecord(artifact.provenance) ? artifact.provenance : null; - for (const [key, digest] of [ - ['artifact', artifact?.artifactSha256], - ['manifest', artifact?.manifestSha256], - ['identity', provenance?.identitySha256], - ['bundle', provenance?.bundleSha256], - ] as const) { - const reference = input?.[key]; - const path = typeof reference === 'string' ? resolve(inputsRoot, reference) : ''; - if (typeof reference !== 'string' || !path.startsWith(`${resolve(inputsRoot)}/`) || !existsSync(path)) { - errors.push(`referenced ${generation} ${key} is unavailable: ${String(reference)}`); - continue; - } - try { - if (!lstatSync(path).isFile()) { - errors.push(`referenced ${generation} ${key} is not a regular file: ${path}`); - continue; - } - const observed = createHash('sha256').update(readFileSync(path)).digest('hex'); - if (observed !== digest) errors.push(`referenced ${generation} ${key} digest mismatch`); - } catch (error) { - errors.push(`referenced ${generation} ${key} could not be verified: ${errorText(error)}`); - } - } - } - const stages = lifecycle && Array.isArray(lifecycle.stages) ? lifecycle.stages : []; - for (const [index, value] of stages.entries()) { - const stage = isRecord(value) ? value : null; - const reference = stage?.observationPath; - const path = typeof reference === 'string' ? resolve(inputsRoot, reference) : ''; - if ( - typeof reference !== 'string' || - !reference.startsWith('observations/') || - !path.startsWith(`${resolve(inputsRoot)}/`) || - !existsSync(path) - ) { - errors.push(`referenced stage observation is unavailable: ${String(reference)}`); - continue; - } - try { - if (!lstatSync(path).isFile()) { - errors.push(`referenced stage observation is not a regular file: ${path}`); - continue; - } - const bytes = readFileSync(path); - const observed = createHash('sha256').update(bytes).digest('hex'); - if (observed !== stage?.observationSha256) { - errors.push(`referenced stage observation digest mismatch at stages[${index}]`); - } - const parsed = JSON.parse(bytes.toString('utf8')) as unknown; - if (JSON.stringify(parsed) !== JSON.stringify(stage?.observation)) { - errors.push(`referenced stage observation content mismatch at stages[${index}]`); - } - } catch (error) { - errors.push(`referenced stage observation could not be verified: ${errorText(error)}`); - } - } - return errors; -} - -export function validateLiveDogfoodEvidenceFile(path: string, inputsRoot = dirname(resolve(path))): string[] { - if (!existsSync(path)) return [`evidence file not found: ${path}`]; - const markdown = readFileSync(path, 'utf8'); - const errors = validateLiveDogfoodEvidence(markdown); - if (errors.length > 0) return errors; - const manifest = manifestFromBlocks(extractJsonBlocks(markdown)); - return manifest === null ? errors : [...errors, ...validateReferencedFiles(manifest, inputsRoot)]; -} - -function parseArgs(argv: string[]): { file: string; inputsRoot?: string } { - if ( - (argv.length !== 2 && argv.length !== 4) || - argv[0] !== '--file' || - !argv[1] || - (argv.length === 4 && (argv[2] !== '--inputs-root' || !argv[3])) - ) { - throw new Error( - 'usage: bun scripts/validate-live-dogfood-evidence.ts --file [--inputs-root ]', - ); - } - return { file: argv[1], inputsRoot: argv[3] }; -} - -function errorText(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} - -function main(): void { - let args: { file: string; inputsRoot?: string }; - try { - args = parseArgs(process.argv.slice(2)); - } catch (error) { - console.error(errorText(error)); - process.exit(2); - } - const errors = validateLiveDogfoodEvidenceFile(args.file, args.inputsRoot); - if (errors.length > 0) { - console.error(`validate-live-dogfood-evidence: FAIL (${errors.length}) — ${args.file}`); - for (const error of errors) console.error(` - ${error}`); - process.exit(1); - } - console.log(`validate-live-dogfood-evidence: OK — ${args.file}`); -} - -if (import.meta.main) main(); diff --git a/scripts/workflow-yaml-parse.test.ts b/scripts/workflow-yaml-parse.test.ts index e90d9c642..7263e25a5 100644 --- a/scripts/workflow-yaml-parse.test.ts +++ b/scripts/workflow-yaml-parse.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'bun:test'; -import { readFileSync } from 'node:fs'; +import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; /** @@ -9,8 +9,9 @@ import { join } from 'node:path'; * `publish.needs` is the only thing that makes a smoke a RELEASE GATE rather * than decoration, and a job block can be reindented or renamed by an unrelated * edit without any other test noticing. This parses both workflows as YAML — - * never by grep — and asserts the wiring, including that the pre-existing Codex - * dogfood matrix is still present and still required. + * never by grep — and asserts the wiring, including (since wish + * `skills-everywhere-b`, G6) that the retired Codex dogfood matrix is gone and + * that its removal cost `publish` exactly one edge. * * Parser: `Bun.YAML.parse`, so no python/PyYAML and no new dependency. It is a * Bun builtin (present on the 1.3.11 CI pin and the 1.3.14 dev machines); the @@ -54,12 +55,6 @@ function needsOf(definition: YamlRecord): string[] { return []; } -function stepNames(definition: YamlRecord): string[] { - const steps = definition.steps; - if (!Array.isArray(steps)) return []; - return steps.filter(isRecord).map((step) => String(step.name ?? step.uses ?? '')); -} - function runScripts(definition: YamlRecord): string { const steps = definition.steps; if (!Array.isArray(steps)) return ''; @@ -109,29 +104,76 @@ describe('release-publish.yml gates', () => { expect(condition).toContain("needs.release-update-path-smoke.result == 'success'"); }); - test('the Codex dogfood matrix is untouched and still required', () => { + test('the Codex dogfood matrix is gone and nothing needs it', () => { const names = Object.keys(jobs(workflow(RELEASE_PUBLISH))); - expect(names).toContain('codex-native-dogfood'); - expect(names).toContain('codex-dogfood-completeness'); + expect(names).not.toContain('codex-native-dogfood'); + expect(names).not.toContain('codex-dogfood-completeness'); + for (const id of names) { + expect(needsOf(job(workflow(RELEASE_PUBLISH), id))).not.toContain('codex-native-dogfood'); + expect(needsOf(job(workflow(RELEASE_PUBLISH), id))).not.toContain('codex-dogfood-completeness'); + } + }); - const dogfood = job(workflow(RELEASE_PUBLISH), 'codex-native-dogfood'); - expect(needsOf(dogfood)).toEqual([ - 'prepare-delivery-evidence', + /** + * The removal above cost `publish` EXACTLY one edge. Pinning the full set (and + * the matching `if:` guard for each survivor) is what makes a future accidental + * drop of another gate a test failure rather than a silently weaker release. + */ + test('publish requires exactly the six surviving gates, each with its if-guard', () => { + const publish = job(workflow(RELEASE_PUBLISH), 'publish'); + const needs = needsOf(publish); + expect(needs).toEqual([ + 'admit', 'attest-delivery-evidence', 'delivery-evidence-compatibility', + 'skills-install-smoke', + 'release-update-path-smoke', + 'stable-release-security-gate', ]); - expect(stepNames(dogfood)).toContain('Run exact N to T lifecycle and emit reusable evidence'); - - expect(needsOf(job(workflow(RELEASE_PUBLISH), 'codex-dogfood-completeness'))).toEqual([ - 'prepare-delivery-evidence', - 'codex-native-dogfood', - ]); + const condition = String(publish.if ?? ''); + expect(condition).toContain('always()'); + for (const gate of needs) { + expect(condition).toContain(`needs.${gate}.result == 'success'`); + } + }); - const needs = needsOf(job(workflow(RELEASE_PUBLISH), 'publish')); - expect(needs).toContain('codex-dogfood-completeness'); - expect(String(job(workflow(RELEASE_PUBLISH), 'publish').if ?? '')).toContain( - "needs.codex-dogfood-completeness.result == 'success'", + /** + * `scripts/candidate-dogfood-matrix.ts` outlives the dogfood matrix it was + * named for: `prepare-delivery-evidence` derives the platform inventory and + * the update-path projection from it, and `stable-release-security-gate` — + * itself a `publish.needs` edge — re-derives and `cmp`s it. Both consumers are + * pinned here so a later "it is only for the dogfood" cleanup fails loudly. + */ + test('candidate-dogfood-matrix.ts survives with both of its consuming jobs', () => { + expect(runScripts(job(workflow(RELEASE_PUBLISH), 'prepare-delivery-evidence'))).toContain( + 'bun scripts/candidate-dogfood-matrix.ts', + ); + expect(runScripts(job(workflow(RELEASE_PUBLISH), 'stable-release-security-gate'))).toContain( + 'bun scripts/candidate-dogfood-matrix.ts', ); + expect(needsOf(job(workflow(RELEASE_PUBLISH), 'publish'))).toContain('stable-release-security-gate'); + }); + + /** + * G6 deleted three executable release controls. A workflow that still invokes + * one of them is green locally and red only in a real release run, so every + * `run:` block in every workflow is checked against the deleted set. + */ + test('no workflow invokes a script deleted with the dogfood matrix', () => { + const deleted = [ + 'tests/support/codex-dogfood-entry-runner.ts', + 'tests/support/codex-dogfood-harness.ts', + 'scripts/validate-live-dogfood-evidence.ts', + 'scripts/validate-dogfood-matrix-evidence.ts', + 'scripts/build.js', + 'scripts/sync.js', + ]; + for (const name of readdirSync(join(REPO_ROOT, '.github', 'workflows')).filter((entry) => entry.endsWith('.yml'))) { + const source = readFileSync(join(REPO_ROOT, '.github', 'workflows', name), 'utf8'); + for (const path of deleted) { + expect(`${name}: ${source.includes(path)}`).toBe(`${name}: false`); + } + } }); test('skills-install-smoke waits on the prepared delivery evidence', () => { @@ -262,9 +304,17 @@ describe('pins agree across the workflows and the shipped source', () => { } }); - test('the new smoke reuses the Codex pin and the Alpine image digest already in use', () => { + /** + * Before wish `skills-everywhere-b` G6 there were two of each pin (the Codex + * dogfood matrix carried one, the skills/update-path smokes the other), and + * this test's job was to prove they agreed. With the matrix deleted the smokes + * hold the only copy, so the assertion becomes "still exactly one, and it is + * still the same value the musl adapter uses" — a second, divergent pin + * reintroduced anywhere still fails here. + */ + test('the surviving smokes keep a single Codex pin and the musl adapter Alpine digest', () => { const codexPins = [...releasePublishText.matchAll(/@openai\/codex@([\w.]+)/g)].map((match) => match[1]); - expect(codexPins.length).toBeGreaterThan(1); + expect(codexPins.length).toBeGreaterThan(0); expect(new Set(codexPins).size).toBe(1); const adapterImage = readFileSync(join(REPO_ROOT, 'scripts', 'run-musl-dogfood.sh'), 'utf8').match( @@ -274,7 +324,7 @@ describe('pins agree across the workflows and the shipped source', () => { const workflowImages = [...releasePublishText.matchAll(/alpine:[\w.]+@sha256:[0-9a-f]{64}/g)].map( (match) => match[0], ); - expect(workflowImages).toHaveLength(2); + expect(workflowImages).toHaveLength(1); expect(new Set(workflowImages)).toEqual(new Set([adapterImage as string])); }); }); diff --git a/src/lib/delivery-evidence-verify.ts b/src/lib/delivery-evidence-verify.ts index 1156b22ca..0a94efd5a 100644 --- a/src/lib/delivery-evidence-verify.ts +++ b/src/lib/delivery-evidence-verify.ts @@ -456,7 +456,7 @@ function sourceBranchAllowedForChannel(branch: unknown, channel: DeliveryEvidenc // EVIDENCE_CHANNELS=(stable dev) with SOURCE_BRANCH=main), so dev clients on a // stable-promoted version verify a {channel:'dev', sourceBranch:'main'} // descriptor. Narrowing this to "dev" aborts every dev update after a stable - // release. Same asymmetry documented in validate-live-dogfood-evidence.ts. + // release. return branch === 'main' || branch === 'dev'; } diff --git a/tests/support/codex-dogfood-entry-runner.ts b/tests/support/codex-dogfood-entry-runner.ts deleted file mode 100644 index cf731a7c7..000000000 --- a/tests/support/codex-dogfood-entry-runner.ts +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bun - -import type { DeliveryEvidencePlatformId } from '../../src/lib/delivery-evidence-verify.js'; -import { type DogfoodEntryInput, type GenerationInputPaths, runDogfoodEntry } from './codex-dogfood-harness.js'; - -const ENV_KEYS = { - previousArtifact: 'DOGFOOD_N_ARTIFACT', - previousManifest: 'DOGFOOD_N_MANIFEST', - previousProvenance: 'DOGFOOD_N_PROVENANCE', - previousBundle: 'DOGFOOD_N_BUNDLE', - candidateArtifact: 'DOGFOOD_T_ARTIFACT', - candidateManifest: 'DOGFOOD_T_MANIFEST', - candidateDescriptor: 'DOGFOOD_T_DESCRIPTOR', - candidateBundle: 'DOGFOOD_T_BUNDLE', - platformId: 'DOGFOOD_PLATFORM_ID', - outputEvidence: 'DOGFOOD_EVIDENCE_OUT', - executionAdapter: 'DOGFOOD_EXECUTION_ADAPTER', -} as const; - -const ARG_KEYS: Record = { - '--previous-artifact': 'previousArtifact', - '--previous-manifest': 'previousManifest', - '--previous-provenance': 'previousProvenance', - '--previous-bundle': 'previousBundle', - '--candidate-artifact': 'candidateArtifact', - '--candidate-manifest': 'candidateManifest', - '--candidate-descriptor': 'candidateDescriptor', - '--candidate-bundle': 'candidateBundle', - '--platform-id': 'platformId', - '--output-evidence': 'outputEvidence', - '--execution-adapter': 'executionAdapter', -}; - -function parseArgs(argv: string[]): Record { - const values = Object.fromEntries(Object.entries(ENV_KEYS).map(([key, env]) => [key, process.env[env]])) as Record< - keyof typeof ENV_KEYS, - string | undefined - >; - for (let index = 0; index < argv.length; index += 2) { - const flag = argv[index]; - const value = argv[index + 1]; - const key = flag === undefined ? undefined : ARG_KEYS[flag]; - if (key === undefined || value === undefined || value === '') throw new Error(`invalid argument: ${flag ?? ''}`); - values[key] = value; - } - return values; -} - -function required(values: Record, key: keyof typeof ENV_KEYS): string { - const value = values[key]; - if (value === undefined || value === '') { - throw new Error(`missing --${key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)} or ${ENV_KEYS[key]}`); - } - return value; -} - -function generation( - values: Record, - prefix: 'previous' | 'candidate', -): GenerationInputPaths { - return { - artifact: required(values, `${prefix}Artifact`), - manifest: required(values, `${prefix}Manifest`), - identity: prefix === 'previous' ? required(values, 'previousProvenance') : required(values, 'candidateDescriptor'), - bundle: required(values, `${prefix}Bundle`), - identityKind: prefix === 'previous' ? 'slsa-provenance' : 'delivery-descriptor', - }; -} - -async function main(): Promise { - const values = parseArgs(process.argv.slice(2)); - const input: DogfoodEntryInput = { - previous: generation(values, 'previous'), - candidate: generation(values, 'candidate'), - platformId: required(values, 'platformId') as DeliveryEvidencePlatformId, - outputEvidence: required(values, 'outputEvidence'), - executionAdapter: values.executionAdapter, - evidenceKind: 'host-native', - }; - const result = await runDogfoodEntry(input); - process.stdout.write(`codex-dogfood-entry: OK ${result.outputEvidence}\n`); -} - -try { - await main(); -} catch (error) { - process.stderr.write(`codex-dogfood-entry: FAIL — ${error instanceof Error ? error.message : String(error)}\n`); - process.exitCode = 1; -} diff --git a/tests/support/codex-dogfood-harness.test.ts b/tests/support/codex-dogfood-harness.test.ts deleted file mode 100644 index 51e17d40e..000000000 --- a/tests/support/codex-dogfood-harness.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { afterEach, describe, expect, test } from 'bun:test'; -import { chmodSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { captureCommand, resolveInvocation } from './codex-dogfood-harness.ts'; - -const roots: string[] = []; - -function fixture(): { root: string; binary: string; adapter: string } { - const root = realpathSync(mkdtempSync(join(tmpdir(), 'dogfood-harness-'))); - roots.push(root); - // Stands in for the authenticated candidate: it echoes the exact argv it was - // handed, so the evidence record can be checked against reality. - const binary = join(root, 'genie'); - writeFileSync(binary, '#!/usr/bin/env bash\nprintf \'%s\\n\' "$*"\n'); - chmodSync(binary, 0o755); - // Stands in for scripts/run-musl-dogfood.sh: takes the candidate binary as - // its first positional argument and execs it with the remaining argv. - const adapter = join(root, 'adapter.sh'); - writeFileSync(adapter, '#!/usr/bin/env bash\nset -eu\ncandidate=$1\nshift\nexec "$candidate" "$@"\n'); - chmodSync(adapter, 0o755); - return { root, binary, adapter }; -} - -afterEach(() => { - while (roots.length > 0) rmSync(roots.pop() as string, { recursive: true, force: true }); -}); - -describe('candidate invocation evidence', () => { - test('resolveInvocation keeps argv on the candidate and names the adapter vector separately', () => { - expect(resolveInvocation({ binary: '/bin/genie', args: ['board', '--json'] })).toEqual({ - command: '/bin/genie', - argv: ['board', '--json'], - spawnArgv: ['board', '--json'], - }); - expect( - resolveInvocation({ binary: '/bin/genie', args: ['board', '--json'], executionAdapter: '/bin/adapter' }), - ).toEqual({ - command: '/bin/adapter', - argv: ['board', '--json'], - adapterArgv: ['/bin/genie', 'board', '--json'], - spawnArgv: ['/bin/genie', 'board', '--json'], - }); - }); - - test('records the candidate argv unchanged with and without an execution adapter', () => { - const { root, binary, adapter } = fixture(); - const args = ['task', 'list', '--json']; - const direct = captureCommand({ root, binary, args, cwd: root, env: { PATH: process.env.PATH ?? '' } }); - expect(direct.exit).toBe(0); - expect(direct.executable).toBe(binary); - expect(direct.candidateBinary).toBe(binary); - expect(direct.argv).toEqual(args); - expect(direct.adapterArgv).toBeUndefined(); - expect(direct.stdout.trim()).toBe('task list --json'); - - const adapted = captureCommand({ - root, - binary, - args, - cwd: root, - env: { PATH: process.env.PATH ?? '' }, - executionAdapter: adapter, - }); - expect(adapted.exit).toBe(0); - // The evidence argv is the candidate's own arguments on BOTH legs: a - // linux-x64-musl run must be byte-comparable with every other platform. - expect(adapted.argv).toEqual(args); - expect(adapted.adapterArgv).toEqual([binary, ...args]); - expect(adapted.executable).toBe(adapter); - expect(adapted.candidateBinary).toBe(binary); - // The candidate really did receive exactly `args` through the adapter. - expect(adapted.stdout.trim()).toBe('task list --json'); - }); -}); diff --git a/tests/support/codex-dogfood-harness.ts b/tests/support/codex-dogfood-harness.ts deleted file mode 100644 index 90b1e4bb9..000000000 --- a/tests/support/codex-dogfood-harness.ts +++ /dev/null @@ -1,1825 +0,0 @@ -/** - * Parameterized Group-F dogfood harness. - * - * Release inputs cross the boundary only as explicit paths. Both evidence packs - * go through the production descriptor/manifest/bundle verifier, extracted - * payloads go through production physical-tree hashing and capability parsing, - * the candidate delivery is published by the production deep store, activation - * uses the real consent/permit/executor path, role agents use the production - * convergence function, and repository observations use standalone task/board JSON - * server. The only injectable seam is the cryptographic bundle verifier and is - * exposed to tests through this support module, never by the CLI runner. - */ - -import { execFileSync } from 'node:child_process'; -import { createHash, randomBytes } from 'node:crypto'; -import { - chmodSync, - copyFileSync, - cpSync, - existsSync, - lstatSync, - mkdirSync, - mkdtempSync, - readFileSync, - readdirSync, - realpathSync, - rmSync, - statSync, - writeFileSync, -} from 'node:fs'; -import { tmpdir } from 'node:os'; -import { basename, dirname, isAbsolute, join, resolve } from 'node:path'; -import { - LIVE_DOGFOOD_SCHEMA_VERSION, - type REQUIRED_STAGE_IDS, - validateLiveDogfoodEvidence, - validateLiveDogfoodEvidenceFile, -} from '../../scripts/validate-live-dogfood-evidence.js'; -import { - DELIVERY_EVIDENCE_OIDC_ISSUER, - DELIVERY_EVIDENCE_PREDICATE_TYPE, - DELIVERY_EVIDENCE_REPOSITORY, - DELIVERY_EVIDENCE_WORKFLOW_IDENTITY, - type DeliveryEvidenceDescriptor, - type DeliveryEvidencePlatformId, - type DeliveryEvidenceVerificationDependencies, - type VerifiedDeliveryEvidence, - verifiedDeliveryEvidenceFacts, - verifyDownloadedDeliveryEvidence, -} from '../../src/lib/delivery-evidence-verify.js'; -import { compareReleaseVersions, parseReleaseVersion, scanPhysicalTree } from '../../src/lib/release-payload-proof.js'; -import { parseUpdateCapabilityReport } from '../../src/lib/update-capabilities.js'; -const PLATFORM_TRIPLES: Readonly> = { - 'linux-x64-glibc': 'linux-x64', - 'linux-x64-musl': 'linux-x64', - 'linux-arm64': 'linux-arm64', - 'darwin-arm64': 'darwin-arm64', -}; - -export interface GenerationInputPaths { - artifact: string; - manifest: string; - identity: string; - bundle: string; - identityKind: 'slsa-provenance' | 'delivery-descriptor'; -} - -export interface DogfoodEntryInput { - previous: GenerationInputPaths; - candidate: GenerationInputPaths; - platformId: DeliveryEvidencePlatformId; - outputEvidence?: string; - /** Executable invoked as: adapter update --print-update-capabilities --json. */ - executionAdapter?: string; - evidenceKind?: 'host-native' | 'verified-local-fixture'; -} - -export interface DogfoodHarnessDependencies { - /** Test-only cryptographic seam. The CLI never supplies this. */ - deliveryEvidenceVerification?: DeliveryEvidenceVerificationDependencies; - /** Test-only substitute for external cosign+slsa verification of historical N. */ - verifyLegacyProvenance?: (input: { - artifact: string; - bundle: string; - provenance: string; - version: string; - root: string; - }) => LegacyProvenanceFacts; - /** Test-only substitute for exact-binary standalone task/board proof. */ - observeStandalone?: (input: StandaloneEvidenceInput) => Promise; - root?: string; -} - -export interface LegacyProvenanceFacts { - sourceCommit: string; - sourceBranch: string; - sourceCiRunId: string; - controlCommit: string; -} - -export interface VerifiedGeneration { - paths: GenerationInputPaths; - facts: GenerationFacts; - evidence: VerifiedDeliveryEvidence | null; - evidenceDigest: string; - manifestSha256: string; - artifactSha256: string; - binarySha256: string; - payloadSha256: string; - identitySha256: string; - bundleSha256: string; - releaseRoot: string; - binaryPath: string; - payloadPath: string; -} - -interface GenerationFacts { - version: string; - channel: DeliveryEvidenceDescriptor['channel']; - platformId: DeliveryEvidencePlatformId; - platformTriple: string; - releaseTag: string; - releaseName: string; - releaseManifestSha256: string; - artifactSha256: string; - installedBinarySha256: string; - canonicalPayloadSha256: string; - sourceSha: string; - sourceBranch: string; - sourceCiRunId: string; - controlSha: string; -} - -interface TaskIdentity { - wish: string; - taskId: string; - title: string; - status: 'in_progress'; - claimedBy: string; -} - -interface RepoObservation { - root: string; - requestedCwd: string; - effectiveCwd: string; - cwdIdentity: string; - childPid: number; - sentinel: { - token: string; - expected: TaskIdentity; - observed: TaskIdentity; - boardCount: 1; - }; - evidenceMode?: 'standalone'; -} - -export interface StandaloneEvidenceInput { - tag: string; - requestedCwd: string; - candidateBinary: string; - candidateBinarySha256: string; - executionAdapter?: string; - root: string; - env: Record; -} - -export interface StandaloneEvidence { - requestedCwd: string; - effectiveCwd: string; - cwdIdentity: string; - controlCwd: string; - controlCwdIdentity: string; - childPid: number; - threadId: string; - isError: boolean; - payload: Record; - raw: Record; - commands?: CapturedCommand[]; -} - -interface CapturedCommand { - executable: string; - executableSha256: string; - candidateBinary: string | null; - candidateBinarySha256: string | null; - /** - * The CANDIDATE's own arguments, never the adapter spawn vector. Evidence - * consumers assert on what the genie binary was asked to do, so this stays - * identical on every platform whether or not an execution adapter wraps it. - */ - argv: string[]; - /** - * Present only when an execution adapter ran the candidate (linux-x64-musl - * goes through scripts/run-musl-dogfood.sh): the real argv handed to - * `executable`, i.e. [candidateBinary, ...argv]. - */ - adapterArgv?: string[]; - pid: number; - requestedCwd: string; - cwdIdentity: string; - exit: number; - stdout: string; - stderr: string; -} - -interface StageProjection { - id: (typeof REQUIRED_STAGE_IDS)[number]; - command: string; - exit: number; - humanState: string; - jsonState: string; - activeVersion: string; - trailer: Record | null; - observationPath: string; - observationSha256: string; - observation: { - schemaVersion: 1; - commands: CapturedCommand[]; - }; -} - -export interface DogfoodRunResult { - evidence: string; - manifest: Record; - doctor: Record; - outputEvidence?: string; -} - -function isolatedDogfoodEnv(root: string, overrides: Record = {}): Record { - const home = join(root, 'process-home'); - const temp = join(root, 'tmp'); - const xdgConfig = join(home, '.config'); - const xdgCache = join(home, '.cache'); - const xdgData = join(home, '.local', 'share'); - const xdgState = join(home, '.local', 'state'); - const genieHome = join(root, 'genie-home'); - const codexHome = join(root, 'codex-home'); - const bin = join(root, 'bin'); - for (const path of [home, temp, xdgConfig, xdgCache, xdgData, xdgState, genieHome, codexHome, bin]) { - mkdirSync(path, { recursive: true }); - } - const env: Record = { - PATH: `${bin}:${process.env.PATH ?? '/usr/bin:/bin'}`, - HOME: home, - GENIE_HOME: genieHome, - CODEX_HOME: codexHome, - CLAUDE_CONFIG_DIR: join(home, '.claude'), - HERMES_HOME: join(home, '.hermes'), - GENIE_AGENTS_SKILLS_DIR: join(home, '.agents', 'skills'), - TMPDIR: temp, - XDG_CONFIG_HOME: xdgConfig, - XDG_CACHE_HOME: xdgCache, - XDG_DATA_HOME: xdgData, - XDG_STATE_HOME: xdgState, - BUN_INSTALL_CACHE_DIR: join(xdgCache, 'bun'), - NPM_CONFIG_CACHE: join(xdgCache, 'npm'), - GIT_CONFIG_GLOBAL: join(home, '.gitconfig'), - GIT_CONFIG_NOSYSTEM: '1', - GENIE_TEST_SKIP_PGSERVE: '1', - NO_COLOR: '1', - ...overrides, - }; - for (const key of ['LANG', 'LC_ALL', 'SSL_CERT_FILE', 'SSL_CERT_DIR', 'NODE_EXTRA_CA_CERTS'] as const) { - const value = process.env[key]; - if (value !== undefined) env[key] = value; - } - return env; -} - -export async function runDogfoodEntry( - rawInput: DogfoodEntryInput, - dependencies: DogfoodHarnessDependencies = {}, -): Promise { - const input = normalizeInput(rawInput); - const ownsRoot = dependencies.root === undefined; - const root = dependencies.root ?? mkdtempSync(join(tmpdir(), 'genie-dogfood-entry-')); - mkdirSync(root, { recursive: true }); - try { - const previous = verifyGeneration( - 'previous', - input.previous, - input.platformId, - input.executionAdapter, - root, - dependencies, - ); - const candidate = verifyGeneration( - 'candidate', - input.candidate, - input.platformId, - input.executionAdapter, - root, - dependencies, - ); - const previousVersion = parseReleaseVersion(previous.facts.version); - const candidateVersion = parseReleaseVersion(candidate.facts.version); - if ( - previousVersion === null || - candidateVersion === null || - compareReleaseVersions(previousVersion, candidateVersion) >= 0 - ) { - throw new Error('previous stable N must be older than candidate T'); - } - if (previous.artifactSha256 === candidate.artifactSha256) { - throw new Error('previous and candidate artifacts unexpectedly have the same digest'); - } - const expectedEvidenceName = `codex-dogfood-${candidate.facts.version}-${input.platformId}.md`; - if (input.outputEvidence !== undefined && basename(input.outputEvidence) !== expectedEvidenceName) { - throw new Error(`output evidence filename must be ${expectedEvidenceName}`); - } - - const lifecycle = await runLifecycle(root, input, previous, candidate, dependencies); - const repositories = lifecycle.repositories; - if (input.outputEvidence !== undefined) { - stageEvidenceInputs(dirname(input.outputEvidence), previous, candidate, lifecycle.stages); - } - const manifest = buildManifest(input, previous, candidate, lifecycle, repositories); - const doctor = lifecycle.doctor; - const evidence = renderEvidence(manifest, doctor); - const errors = validateLiveDogfoodEvidence(evidence); - if (errors.length > 0) throw new Error(`generated evidence failed validation:\n${errors.join('\n')}`); - if (input.outputEvidence !== undefined) { - mkdirSync(dirname(input.outputEvidence), { recursive: true }); - writeFileSync(input.outputEvidence, evidence); - const fileErrors = validateLiveDogfoodEvidenceFile(input.outputEvidence, dirname(input.outputEvidence)); - if (fileErrors.length > 0) throw new Error(`staged evidence failed validation:\n${fileErrors.join('\n')}`); - } - return { evidence, manifest, doctor, outputEvidence: input.outputEvidence }; - } finally { - if (ownsRoot) rmSync(root, { recursive: true, force: true }); - } -} - -function stageEvidenceInputs( - outputRoot: string, - previous: VerifiedGeneration, - candidate: VerifiedGeneration, - stages: StageProjection[], -): void { - mkdirSync(outputRoot, { recursive: true }); - for (const [label, generation] of [ - ['previous', previous], - ['candidate', candidate], - ] as const) { - const target = join(outputRoot, label); - mkdirSync(target, { recursive: true }); - for (const key of ['artifact', 'manifest', 'identity', 'bundle'] as const) { - const path = generation.paths[key]; - const destination = join(target, basename(path)); - if (realpathIfPresent(destination) === realpathSync(path)) continue; - copyFileSync(path, destination); - } - } - const observationsRoot = join(outputRoot, 'observations'); - mkdirSync(observationsRoot, { recursive: true }); - for (const value of stages) { - const destination = join(outputRoot, value.observationPath); - writeFileSync(destination, serializeStageObservation(value.observation)); - if (sha256File(destination) !== value.observationSha256) { - throw new Error(`staged ${value.id} observation digest drifted`); - } - } -} - -function realpathIfPresent(path: string): string | null { - try { - return realpathSync(path); - } catch { - return null; - } -} - -function normalizeInput(input: DogfoodEntryInput): DogfoodEntryInput { - const normalizeGeneration = (label: string, value: GenerationInputPaths): GenerationInputPaths => { - const out = {} as GenerationInputPaths; - for (const key of ['artifact', 'manifest', 'identity', 'bundle'] as const) { - const path = resolve(value[key]); - if (!isAbsolute(path) || !existsSync(path) || !lstatSync(path).isFile()) { - throw new Error(`${label} ${key} is unavailable or not a regular file: ${path}`); - } - out[key] = realpathSync(path); - } - out.identityKind = value.identityKind; - return out; - }; - if (!(input.platformId in PLATFORM_TRIPLES)) throw new Error(`unsupported platform id: ${input.platformId}`); - const executionAdapter = - input.executionAdapter === undefined ? undefined : realpathSync(resolve(input.executionAdapter)); - if (executionAdapter !== undefined && !lstatSync(executionAdapter).isFile()) { - throw new Error(`execution adapter is not a regular file: ${executionAdapter}`); - } - return { - ...input, - previous: normalizeGeneration('previous', input.previous), - candidate: normalizeGeneration('candidate', input.candidate), - outputEvidence: input.outputEvidence === undefined ? undefined : resolve(input.outputEvidence), - executionAdapter, - evidenceKind: input.evidenceKind ?? 'host-native', - }; -} - -function verifyGeneration( - label: 'previous' | 'candidate', - paths: GenerationInputPaths, - platformId: DeliveryEvidencePlatformId, - executionAdapter: string | undefined, - root: string, - dependencies: DogfoodHarnessDependencies, -): VerifiedGeneration { - const identityBytes = readFileSync(paths.identity); - const bundleBytes = readFileSync(paths.bundle); - const manifestBytes = readFileSync(paths.manifest); - const artifactSha256 = sha256File(paths.artifact); - const manifest = parseReleaseManifest(label, manifestBytes, platformId); - const releaseName = `genie-${manifest.version}-${platformId}.tar.gz`; - if (basename(paths.artifact) !== releaseName) throw new Error(`${label} artifact name is not manifest-derived`); - - const authenticated = authenticateGenerationInputs({ - label, - paths, - platformId, - manifest, - manifestBytes, - identityBytes, - bundleBytes, - artifactSha256, - releaseName, - root, - dependencies, - }); - - const extractRoot = join(root, `${label}-extract`); - mkdirSync(extractRoot, { recursive: true }); - execFileSync('tar', ['-xzf', paths.artifact, '-C', extractRoot], { - env: isolatedDogfoodEnv(root), - stdio: 'pipe', - }); - const releaseRoot = findReleaseRoot(extractRoot); - const binaryPath = join(releaseRoot, 'genie'); - const payloadPath = join(releaseRoot, 'plugins', 'genie'); - if (!existsSync(binaryPath) || !lstatSync(binaryPath).isFile()) - throw new Error(`${label} extracted binary unavailable`); - const tree = scanPhysicalTree(payloadPath); - if (tree.status !== 'ok' || tree.digest === undefined) throw new Error(`${label} extracted payload is invalid`); - const binarySha256 = sha256File(binaryPath); - // `genie update --print-update-capabilities` is part of the delivery-era CLI - // surface, so only a generation authenticated by a delivery descriptor is - // required to expose it. The previous stable N is authenticated by legacy - // SLSA provenance precisely because it predates that surface: N is pinned to - // `.well-known/latest.json` (v5.260720.10), which is older than the commit - // that added the flag, and N only advances on a stable release that must - // itself pass this gate. Probing N therefore deadlocks every channel. - // N still has to execute natively — runLifecycle runs its binary and - // requires it to report N's own version as the active parent. - if (paths.identityKind === 'delivery-descriptor') { - verifyCapability(binaryPath, manifest.version, binarySha256, executionAdapter, root); - } - - const bound = bindExtractedGeneration({ - label, - authenticated, - manifest, - manifestBytes, - identityBytes, - bundleBytes, - artifactSha256, - releaseName, - platformId, - binarySha256, - payloadSha256: tree.digest, - }); - return { - paths, - facts: bound.facts, - evidence: bound.evidence, - evidenceDigest: bound.evidenceDigest, - manifestSha256: sha256Bytes(manifestBytes), - artifactSha256, - binarySha256, - payloadSha256: tree.digest, - identitySha256: sha256Bytes(identityBytes), - bundleSha256: sha256Bytes(bundleBytes), - releaseRoot, - binaryPath, - payloadPath, - }; -} - -type ParsedReleaseManifest = ReturnType; -type AuthenticatedGeneration = - | { kind: 'delivery-descriptor'; descriptor: DeliveryEvidenceDescriptor; evidence: VerifiedDeliveryEvidence } - | { kind: 'slsa-provenance'; provenance: LegacyProvenanceFacts }; - -function authenticateGenerationInputs(input: { - label: 'previous' | 'candidate'; - paths: GenerationInputPaths; - platformId: DeliveryEvidencePlatformId; - manifest: ParsedReleaseManifest; - manifestBytes: Buffer; - identityBytes: Buffer; - bundleBytes: Buffer; - artifactSha256: string; - releaseName: string; - root: string; - dependencies: DogfoodHarnessDependencies; -}): AuthenticatedGeneration { - if (input.paths.identityKind === 'slsa-provenance') { - const provenance = (input.dependencies.verifyLegacyProvenance ?? verifyLegacyReleaseProvenance)({ - artifact: input.paths.artifact, - bundle: input.paths.bundle, - provenance: input.paths.identity, - version: input.manifest.version, - root: input.root, - }); - return { kind: 'slsa-provenance', provenance }; - } - const descriptor = JSON.parse(input.identityBytes.toString('utf8')) as DeliveryEvidenceDescriptor; - if (descriptor.platformId !== input.platformId || descriptor.platformTriple !== PLATFORM_TRIPLES[input.platformId]) { - throw new Error(`${input.label} descriptor platform identity is inconsistent`); - } - if ( - descriptor.version !== input.manifest.version || - descriptor.channel !== input.manifest.channel || - descriptor.releaseName !== input.releaseName || - descriptor.artifactSha256 !== input.artifactSha256 - ) { - throw new Error(`${input.label} delivery descriptor does not bind the authenticated artifact`); - } - const evidence = verifyDownloadedDeliveryEvidence( - { - descriptorBytes: input.identityBytes, - bundleBytes: input.bundleBytes, - manifestBytes: input.manifestBytes, - targetVersion: descriptor.version, - channel: descriptor.channel, - platformId: descriptor.platformId, - platformTriple: descriptor.platformTriple, - releaseTag: descriptor.releaseTag, - releaseName: descriptor.releaseName, - artifactSha256: descriptor.artifactSha256, - installedBinarySha256: descriptor.installedBinarySha256, - canonicalPayloadSha256: descriptor.canonicalPayloadSha256, - }, - input.dependencies.deliveryEvidenceVerification, - ); - return { kind: 'delivery-descriptor', descriptor, evidence }; -} - -function bindExtractedGeneration(input: { - label: 'previous' | 'candidate'; - authenticated: AuthenticatedGeneration; - manifest: ParsedReleaseManifest; - manifestBytes: Buffer; - identityBytes: Buffer; - bundleBytes: Buffer; - artifactSha256: string; - releaseName: string; - platformId: DeliveryEvidencePlatformId; - binarySha256: string; - payloadSha256: string; -}): Pick { - if (input.authenticated.kind === 'delivery-descriptor') { - const { descriptor, evidence } = input.authenticated; - if ( - descriptor.installedBinarySha256 !== input.binarySha256 || - descriptor.canonicalPayloadSha256 !== input.payloadSha256 - ) { - throw new Error(`${input.label} delivery descriptor does not bind the extracted artifact`); - } - return { - facts: descriptor, - evidence, - evidenceDigest: verifiedDeliveryEvidenceFacts(evidence).evidenceDigest, - }; - } - const { provenance } = input.authenticated; - return { - facts: { - version: input.manifest.version, - channel: input.manifest.channel, - platformId: input.platformId, - platformTriple: PLATFORM_TRIPLES[input.platformId], - releaseTag: `v${input.manifest.version}`, - releaseName: input.releaseName, - releaseManifestSha256: sha256Bytes(input.manifestBytes), - artifactSha256: input.artifactSha256, - installedBinarySha256: input.binarySha256, - canonicalPayloadSha256: input.payloadSha256, - sourceSha: provenance.sourceCommit, - sourceBranch: provenance.sourceBranch, - sourceCiRunId: provenance.sourceCiRunId, - controlSha: provenance.controlCommit, - }, - evidence: null, - evidenceDigest: createHash('sha256') - .update('genie-legacy-release-proof-v1\0') - .update(input.manifestBytes) - .update(input.identityBytes) - .update(input.bundleBytes) - .digest('hex'), - }; -} - -function parseReleaseManifest( - label: string, - bytes: Uint8Array, - platformId: DeliveryEvidencePlatformId, -): { - schema_version: 1; - channel: DeliveryEvidenceDescriptor['channel']; - version: string; - platforms: string[]; -} { - const value = JSON.parse(Buffer.from(bytes).toString('utf8')) as { - schema_version?: unknown; - channel?: unknown; - version?: unknown; - platforms?: unknown; - }; - const valid = - value.schema_version === 1 && - ['stable', 'dev'].includes(String(value.channel)) && - typeof value.version === 'string' && - Array.isArray(value.platforms) && - value.platforms.includes(platformId); - if (!valid) throw new Error(`${label} release manifest does not bind the requested version/channel/platform`); - return value as { - schema_version: 1; - channel: DeliveryEvidenceDescriptor['channel']; - version: string; - platforms: string[]; - }; -} - -function findReleaseRoot(extractRoot: string): string { - if (existsSync(join(extractRoot, 'genie')) && existsSync(join(extractRoot, 'plugins', 'genie'))) return extractRoot; - const children = readdirSync(extractRoot).filter((name) => lstatSync(join(extractRoot, name)).isDirectory()); - if (children.length === 1) { - const nested = join(extractRoot, children[0] as string); - if (existsSync(join(nested, 'genie')) && existsSync(join(nested, 'plugins', 'genie'))) return nested; - } - throw new Error('release archive does not contain one extracted Genie release root'); -} - -function verifyCapability( - binary: string, - version: string, - binarySha256: string, - executionAdapter: string | undefined, - root: string, -): void { - const args = ['update', '--print-update-capabilities', '--json']; - const command = executionAdapter ?? binary; - const commandArgs = executionAdapter === undefined ? args : [binary, ...args]; - const result = Bun.spawnSync([command, ...commandArgs], { - env: isolatedDogfoodEnv(root), - stdout: 'pipe', - stderr: 'pipe', - timeout: 60_000, - }); - if (result.exitCode !== 0) { - throw new Error(`native capability probe unavailable (${result.exitCode}): ${result.stderr.toString().trim()}`); - } - if (result.stderr.length > 0) - throw new Error(`native capability probe wrote stderr: ${result.stderr.toString().trim()}`); - const report = parseUpdateCapabilityReport(result.stdout.toString().trim()); - if (report === null) throw new Error('native capability probe did not return schema-valid JSON'); - if (report.reportedVersion !== version) throw new Error('native capability probe version mismatch'); - if (report.binarySha256 !== binarySha256) throw new Error('native capability probe binary digest mismatch'); -} - -function verifyLegacyReleaseProvenance(input: { - artifact: string; - bundle: string; - provenance: string; - version: string; - root: string; -}): LegacyProvenanceFacts { - const cosign = Bun.which('cosign'); - const slsaVerifier = Bun.which('slsa-verifier'); - if (cosign === null) throw new Error('cosign is unavailable for previous-release verification'); - if (slsaVerifier === null) throw new Error('slsa-verifier is unavailable for previous-release verification'); - execFileSync( - cosign, - [ - 'verify-blob', - '--bundle', - input.bundle, - '--certificate-identity', - 'https://github.com/automagik-dev/genie/.github/workflows/sign-attest.yml@refs/heads/main', - '--certificate-oidc-issuer', - DELIVERY_EVIDENCE_OIDC_ISSUER, - input.artifact, - ], - { env: isolatedDogfoodEnv(input.root), stdio: 'pipe' }, - ); - const verified = execFileSync( - slsaVerifier, - [ - 'verify-artifact', - input.artifact, - '--provenance-path', - input.provenance, - '--source-uri', - `github.com/${DELIVERY_EVIDENCE_REPOSITORY}`, - '--source-branch', - 'main', - '--print-provenance', - ], - { encoding: 'utf8', env: isolatedDogfoodEnv(input.root), stdio: ['ignore', 'pipe', 'pipe'] }, - ); - const verifyRoot = mkdtempSync(join(input.root, 'legacy-provenance-')); - try { - const verifiedPath = join(verifyRoot, 'verified.json'); - writeFileSync(verifiedPath, verified); - execFileSync( - 'bash', - [join(import.meta.dir, '..', '..', 'scripts', 'release-generic-provenance.sh'), 'verify-reusable', verifiedPath], - { - env: { - ...isolatedDogfoodEnv(input.root), - RELEASE_REPOSITORY: DELIVERY_EVIDENCE_REPOSITORY, - VERSION: input.version, - }, - stdio: 'pipe', - }, - ); - const statement = JSON.parse(verified) as { - predicate?: { - invocation?: { - configSource?: { entryPoint?: unknown; digest?: { sha1?: unknown } }; - parameters?: { event_inputs?: Record }; - environment?: { github_event_payload?: { workflow_run?: Record } }; - }; - }; - }; - const invocation = statement.predicate?.invocation; - const controlCommit = invocation?.configSource?.digest?.sha1; - // Two provenance shapes reach this leg, exactly as - // scripts/release-generic-provenance.sh `verify_reusable` (run just above) - // discriminates them by entryPoint. A stable release.yml dispatch records - // the release identity in `event_inputs`; an automated version.yml run is a - // `workflow_run` and records it in the triggering CI event payload instead. - // - // The previous stable N is routinely the automated shape: a stable release - // promotes an already-published dev prerelease and preserves its immutable - // signed bytes (see release-publish.yml "Promotions must endorse the - // already-published immutable subject bytes" and reconcile-release-note.sh - // stable finalize), so N's generic provenance stays the dev build's. - // Reading only `event_inputs` therefore yields undefined for every real N. - const entryPoint = invocation?.configSource?.entryPoint; - let facts: Record; - if (entryPoint === '.github/workflows/release.yml') { - const parameters = invocation?.parameters?.event_inputs; - facts = { - sourceCommit: parameters?.source_sha, - sourceBranch: parameters?.source_branch, - sourceCiRunId: parameters?.source_ci_run_id, - controlCommit, - }; - } else if (entryPoint === '.github/workflows/version.yml') { - const run = invocation?.environment?.github_event_payload?.workflow_run; - facts = { - sourceCommit: run?.head_sha, - sourceBranch: run?.head_branch, - sourceCiRunId: typeof run?.id === 'number' || typeof run?.id === 'string' ? String(run.id) : undefined, - controlCommit, - }; - } else { - throw new Error('verified previous-release provenance has an unknown release entry point'); - } - if ( - typeof facts.sourceCommit !== 'string' || - !/^[0-9a-f]{40}$/.test(facts.sourceCommit) || - typeof facts.sourceBranch !== 'string' || - !/^(?:main|dev)$/.test(facts.sourceBranch) || - typeof facts.sourceCiRunId !== 'string' || - !/^(?:0|[1-9]\d*)$/.test(facts.sourceCiRunId) || - typeof facts.controlCommit !== 'string' || - !/^[0-9a-f]{40}$/.test(facts.controlCommit) - ) { - throw new Error('verified previous-release provenance omitted a required identity'); - } - return facts as LegacyProvenanceFacts; - } finally { - rmSync(verifyRoot, { recursive: true, force: true }); - } -} - -/** - * Split one candidate invocation into the evidence argv (always the - * candidate's own arguments) and the real spawn vector. Without an execution - * adapter the two coincide; with one, the adapter takes the candidate binary - * as its first positional argument. - */ -export function resolveInvocation(input: { binary: string; args: string[]; executionAdapter?: string }): { - command: string; - argv: string[]; - adapterArgv?: string[]; - spawnArgv: string[]; -} { - if (input.executionAdapter === undefined) { - return { command: input.binary, argv: input.args, spawnArgv: input.args }; - } - const adapterArgv = [input.binary, ...input.args]; - return { command: input.executionAdapter, argv: input.args, adapterArgv, spawnArgv: adapterArgv }; -} - -export function captureCommand(input: { - root: string; - binary: string; - args: string[]; - cwd: string; - env: Record; - executionAdapter?: string; - stdin?: Uint8Array; - timeoutMs?: number; -}): CapturedCommand { - const { command, argv, adapterArgv, spawnArgv } = resolveInvocation(input); - const env = - input.executionAdapter === undefined - ? input.env - : { ...input.env, DOGFOOD_ROOT: input.root, DOGFOOD_ADAPTER_CWD: realpathSync(input.cwd) }; - const result = Bun.spawnSync([command, ...spawnArgv], { - cwd: input.cwd, - env, - stdin: input.stdin, - stdout: 'pipe', - stderr: 'pipe', - timeout: input.timeoutMs ?? 120_000, - }); - if (result.exitedDueToTimeout === true) { - throw new Error(`bounded candidate command timed out: ${[command, ...spawnArgv].join(' ')}`); - } - const cwdStat = statSync(realpathSync(input.cwd)); - return { - executable: realpathSync(command), - executableSha256: sha256File(realpathSync(command)), - candidateBinary: realpathSync(input.binary), - candidateBinarySha256: sha256File(realpathSync(input.binary)), - argv, - ...(adapterArgv === undefined ? {} : { adapterArgv }), - pid: result.pid, - requestedCwd: realpathSync(input.cwd), - cwdIdentity: `${cwdStat.dev}:${cwdStat.ino}`, - exit: result.exitCode, - stdout: result.stdout.toString(), - stderr: result.stderr.toString(), - }; -} - -function capturePtySetup(input: { - root: string; - binary: string; - cwd: string; - env: Record; - executionAdapter?: string; -}): CapturedCommand { - const { command, argv, adapterArgv, spawnArgv } = resolveInvocation({ - binary: input.binary, - args: ['setup', '--codex'], - executionAdapter: input.executionAdapter, - }); - const commandArgs = [command, ...spawnArgv]; - const env = { - ...input.env, - CI: '', - CODEX_THREAD_ID: '', - ...(input.executionAdapter === undefined ? {} : { DOGFOOD_ROOT: input.root }), - }; - let result: ReturnType; - if (process.platform === 'darwin') { - const expect = Bun.which('expect'); - if (expect === null) throw new Error('real-PTY dogfood requires expect(1) on macOS'); - const spawnWords = commandArgs.map((part) => `{${part}}`).join(' '); - const script = [ - 'set timeout 120', - `spawn -noecho ${spawnWords}`, - 'send -- "yes\\r"', - 'expect eof', - 'catch wait result', - 'exit [lindex $result 3]', - ].join('\n'); - result = Bun.spawnSync([expect, '-c', script], { - cwd: input.cwd, - env, - stdout: 'pipe', - stderr: 'pipe', - timeout: 150_000, - }); - } else if (process.platform === 'linux') { - const script = Bun.which('script'); - if (script === null) throw new Error('real-PTY dogfood requires script(1) on Linux'); - const commandLine = commandArgs.map((part) => `'${part.replaceAll("'", `'\\''`)}'`).join(' '); - result = Bun.spawnSync([script, '-qec', commandLine, '/dev/null'], { - cwd: input.cwd, - env, - stdin: Buffer.from('yes\n'), - stdout: 'pipe', - stderr: 'pipe', - timeout: 150_000, - }); - } else { - throw new Error(`real-PTY dogfood is unsupported on ${process.platform}`); - } - if (result.exitedDueToTimeout === true) throw new Error('real-PTY candidate setup timed out'); - const cwdStat = statSync(realpathSync(input.cwd)); - return { - executable: realpathSync(command), - executableSha256: sha256File(realpathSync(command)), - candidateBinary: realpathSync(input.binary), - candidateBinarySha256: sha256File(realpathSync(input.binary)), - argv, - ...(adapterArgv === undefined ? {} : { adapterArgv }), - pid: result.pid, - requestedCwd: realpathSync(input.cwd), - cwdIdentity: `${cwdStat.dev}:${cwdStat.ino}`, - exit: result.exitCode, - stdout: result.stdout.toString(), - stderr: result.stderr.toString(), - }; -} - -function requireExit(observation: CapturedCommand, expected: number, label: string): CapturedCommand { - if (observation.exit !== expected) { - // Both complete streams, with byte counts: a PTY stage merges the child's - // stderr into the transcript and an early spawn-chain death can produce - // almost nothing, so a truncated one-liner (historically just the echoed - // "yes") hides which link failed. exe/argv identify the exact invocation. - throw new Error( - `${label} exited ${observation.exit}, expected ${expected}\n` + - ` exe: ${observation.executable}\n` + - ` argv: ${JSON.stringify(observation.adapterArgv ?? observation.argv)}\n` + - ` stderr[${observation.stderr.length}B]: ${observation.stderr.trim() || ''}\n` + - ` stdout[${observation.stdout.length}B]: ${observation.stdout.trim() || ''}`, - ); - } - return observation; -} - -function parseResultTrailer(output: string): Record { - for (const line of output.split(/\r?\n/).reverse()) { - const trimmed = line.trim(); - const objectStart = trimmed.indexOf('{'); - if (objectStart < 0) continue; - try { - const parsed = JSON.parse(trimmed.slice(objectStart)) as Record; - if ( - parsed.schemaVersion === 1 && - typeof parsed.code === 'string' && - typeof parsed.deliveryComplete === 'boolean' && - typeof parsed.retry === 'boolean' && - typeof parsed.nextAction === 'string' - ) { - return parsed; - } - } catch { - // Keep scanning bounded command output for the canonical trailer line. - } - } - throw new Error(`candidate command did not emit a schema-valid delivery trailer: ${output.trim().slice(-2_000)}`); -} - -interface LifecycleResult { - genieHome: string; - delivery: Record; - doctor: Record; - stages: StageProjection[]; - repositories: { cacheRoot: string; a: RepoObservation; b: Record }; - convergence: { - standalone: { - state: 'standalone'; - command: string; - taskArgs: ['task', 'list', '--json']; - boardArgs: ['board', '--json']; - }; - roles: { expectedCount: number; observedCount: number; current: true; reviewerSha256: string }; - }; -} - -function initRepo(path: string, env: Record): string { - mkdirSync(path, { recursive: true }); - execFileSync('git', ['init', '-q', '-b', 'main'], { cwd: path, env }); - execFileSync('git', ['config', 'user.email', 'dogfood@test.invalid'], { cwd: path, env }); - execFileSync('git', ['config', 'user.name', 'Dogfood'], { cwd: path, env }); - execFileSync('git', ['commit', '--allow-empty', '-q', '-m', 'seed'], { cwd: path, env }); - return realpathSync(path); -} - -function installGeneration(generation: VerifiedGeneration, genieHome: string): string { - const binary = join(genieHome, 'bin', 'genie'); - const payload = join(genieHome, 'plugins', 'genie'); - mkdirSync(dirname(binary), { recursive: true }); - rmSync(payload, { recursive: true, force: true }); - copyFileSync(generation.binaryPath, binary); - chmodSync(binary, 0o755); - cpSync(generation.payloadPath, payload, { recursive: true }); - writeFileSync(join(genieHome, 'VERSION'), `${generation.facts.version}\n`); - // A real release binary resolves its own version from the VERSION file - // ADJACENT to the executable, which in the live layout is - // `$GENIE_HOME/bin/VERSION` — scripts/install-swap.test.ts asserts exactly - // that path. Writing only `$GENIE_HOME/VERSION` leaves the installed - // generation reporting "0.0.0-unknown", so runLifecycle's first assertion - // (that N is the active parent) can never hold for a real published - // tarball. The CI fixture hides this: its stand-in binary is a generated - // shim that hard-codes its version string instead of reading VERSION. - writeFileSync(join(dirname(binary), 'VERSION'), `${generation.facts.version}\n`); - if (sha256File(binary) !== generation.binarySha256) throw new Error('installed generation binary digest drifted'); - const tree = scanPhysicalTree(payload); - if (tree.status !== 'ok' || tree.digest !== generation.payloadSha256) { - throw new Error('installed generation payload digest drifted'); - } - return realpathSync(binary); -} - -function installFakeCodex( - root: string, - previous: VerifiedGeneration, - candidate: VerifiedGeneration, - env: Record, -): { command: string; stateDir: string } { - const stateDir = join(root, 'codex-state'); - const command = join(root, 'bin', 'codex'); - mkdirSync(stateDir, { recursive: true }); - writeFileSync(join(stateDir, 'registered'), `${previous.facts.version}\n`); - writeFileSync( - command, - `#!/bin/bash -set -euo pipefail -cmd="$*" -if [ "\${1:-}" = "--version" ]; then echo "codex 0.0.0-dogfood"; exit 0; fi -if [ "$cmd" = "plugin list --json" ]; then - version=$(tr -d '\\n' < "$FAKE_CODEX_STATE/registered") - enabled=true - grep -q 'enabled = false' "$CODEX_HOME/config.toml" 2>/dev/null && enabled=false - printf '{"installed":[{"pluginId":"genie@automagik","enabled":%s,"version":"%s"}]}\\n' "$enabled" "$version" - exit 0 -fi -if [ "$cmd" = "plugin add genie@automagik --json" ]; then - target="$CODEX_HOME/plugins/cache/automagik/genie/$FAKE_CODEX_TARGET" - rm -rf "$target" - mkdir -p "$target" - cp -R "$GENIE_HOME/plugins/genie/." "$target/" - printf '%s\\n' "$FAKE_CODEX_TARGET" > "$FAKE_CODEX_STATE/registered" - echo '{}' - exit 0 -fi -if [[ "$cmd" == plugin\\ marketplace* ]]; then echo '{}'; exit 0; fi -echo '{}' -`, - ); - chmodSync(command, 0o755); - env.FAKE_CODEX_STATE = stateDir; - env.FAKE_CODEX_TARGET = candidate.facts.version; - return { command: realpathSync(command), stateDir }; -} - -function readRegisteredVersion(stateDir: string): string { - return readFileSync(join(stateDir, 'registered'), 'utf8').trim(); -} - -function seedSentinelWithCli(input: { - root: string; - binary: string; - executionAdapter?: string; - repo: string; - env: Record; - label: string; - initialize: boolean; -}): { expected: TaskIdentity & { token: string }; commands: CapturedCommand[] } { - const commands: CapturedCommand[] = []; - if (input.initialize) { - commands.push( - requireExit( - captureCommand({ - root: input.root, - binary: input.binary, - args: ['init'], - cwd: input.repo, - env: input.env, - executionAdapter: input.executionAdapter, - }), - 0, - `${input.label} init`, - ), - ); - } - const token = randomBytes(20).toString('hex'); - const wish = `dogfood-${input.label}-${token}`; - const title = `task-${input.label}-${token}`; - const worker = `dogfood-${input.label}-${token.slice(0, 12)}`; - const created = requireExit( - captureCommand({ - root: input.root, - binary: input.binary, - args: ['task', 'create', '--title', title, '--wish', wish], - cwd: input.repo, - env: input.env, - executionAdapter: input.executionAdapter, - }), - 0, - `${input.label} task create`, - ); - commands.push(created); - const taskId = created.stdout.match(/Created task (t_[a-zA-Z0-9]+)/)?.[1]; - if (taskId === undefined) throw new Error(`${input.label} task create did not report its task id`); - commands.push( - requireExit( - captureCommand({ - root: input.root, - binary: input.binary, - args: ['task', 'checkout', taskId, '--worker', worker], - cwd: input.repo, - env: input.env, - executionAdapter: input.executionAdapter, - }), - 0, - `${input.label} task checkout`, - ), - ); - const listed = requireExit( - captureCommand({ - root: input.root, - binary: input.binary, - args: ['task', 'list', '--json'], - cwd: input.repo, - env: input.env, - executionAdapter: input.executionAdapter, - }), - 0, - `${input.label} task list`, - ); - commands.push(listed); - const rows = JSON.parse(listed.stdout) as Array>; - const row = rows.find((value) => value.id === taskId); - if (row?.title !== title || row.wish !== wish || row.status !== 'in_progress' || row.claimedBy !== worker) { - throw new Error(`${input.label} seeded task did not round-trip through the exact binary`); - } - return { - expected: { token, wish, taskId, title, status: 'in_progress', claimedBy: worker }, - commands, - }; -} - -function stage( - id: StageProjection['id'], - projection: Omit, - commands: CapturedCommand[], -): StageProjection { - if (commands.length === 0) throw new Error(`${id} has no captured command observation`); - const observation = { schemaVersion: 1 as const, commands }; - return { - id, - command: commands - .map((command) => [command.executable, ...(command.adapterArgv ?? command.argv)].join(' ')) - .join(' && '), - ...projection, - observationPath: `observations/${id}.json`, - observationSha256: sha256Bytes(Buffer.from(serializeStageObservation(observation))), - observation, - }; -} - -function serializeStageObservation(observation: StageProjection['observation']): string { - return `${JSON.stringify(observation, null, 2)}\n`; -} - -function parseStandaloneJson(command: CapturedCommand): Record { - const raw = command.exit === 0 ? command.stdout : command.stderr || command.stdout; - try { - return JSON.parse(raw) as Record; - } catch { - return { error: raw.includes('project-database-unavailable') ? 'project-database-unavailable' : raw.trim() }; - } -} - -function standaloneEvidence(input: StandaloneEvidenceInput): StandaloneEvidence { - const task = captureCommand({ - root: input.root, - binary: input.candidateBinary, - args: ['task', 'list', '--json'], - cwd: input.requestedCwd, - env: input.env, - executionAdapter: input.executionAdapter, - }); - const board = captureCommand({ - root: input.root, - binary: input.candidateBinary, - args: ['board', '--json'], - cwd: input.requestedCwd, - env: input.env, - executionAdapter: input.executionAdapter, - }); - const cwd = realpathSync(input.requestedCwd); - return { - requestedCwd: input.requestedCwd, - effectiveCwd: cwd, - cwdIdentity: board.cwdIdentity, - controlCwd: cwd, - controlCwdIdentity: board.cwdIdentity, - childPid: board.pid, - threadId: `standalone-${input.tag}-${board.pid}`, - isError: board.exit !== 0, - payload: parseStandaloneJson(board), - commands: [task, board], - raw: { - schemaVersion: 1, - kind: 'standalone-genie-task-board', - task: parseStandaloneJson(task), - board: parseStandaloneJson(board), - }, - }; -} - -async function observeStandalone( - input: StandaloneEvidenceInput, - dependencies: DogfoodHarnessDependencies, -): Promise { - if (dependencies.observeStandalone !== undefined) return dependencies.observeStandalone(input); - return standaloneEvidence(input); -} - -function observedRepo( - repo: string, - expected: TaskIdentity & { token: string }, - board: StandaloneEvidence, -): RepoObservation { - if (board.isError) throw new Error(`seeded board failed: ${JSON.stringify(board.payload)}`); - const columns = board.payload.columns; - const tasks = - columns !== null && typeof columns === 'object' && !Array.isArray(columns) - ? Object.values(columns).flatMap((value) => (Array.isArray(value) ? value : [])) - : []; - if (!Array.isArray(tasks) || tasks.length !== 1) throw new Error('seeded board must return exactly one task'); - const task = tasks[0] as Record; - const observed: TaskIdentity = { - wish: String(task.wish), - taskId: String(task.id), - title: String(task.title), - status: task.status as 'in_progress', - claimedBy: String(task.claimedBy), - }; - const expectedIdentity: TaskIdentity = { - wish: expected.wish, - taskId: expected.taskId, - title: expected.title, - status: expected.status, - claimedBy: expected.claimedBy, - }; - if (JSON.stringify(observed) !== JSON.stringify(expectedIdentity)) throw new Error('seeded task identity mismatch'); - if ( - board.effectiveCwd !== board.controlCwd || - board.cwdIdentity !== board.controlCwdIdentity || - board.effectiveCwd !== realpathSync(repo) - ) { - throw new Error('standalone candidate task/board CWD differs from its control process'); - } - return { - root: repo, - requestedCwd: board.requestedCwd, - effectiveCwd: board.effectiveCwd, - cwdIdentity: board.cwdIdentity, - childPid: board.childPid, - sentinel: { token: expected.token, expected: expectedIdentity, observed, boardCount: 1 }, - }; -} - -function standaloneObservationCommands( - evidence: StandaloneEvidence, - candidateBinary: string, - candidateBinarySha256: string, - executionAdapter?: string, -): CapturedCommand[] { - if (evidence.commands !== undefined) return evidence.commands; - const executable = executionAdapter ?? candidateBinary; - return [ - { - executable, - executableSha256: executionAdapter === undefined ? candidateBinarySha256 : sha256File(executionAdapter), - candidateBinary, - candidateBinarySha256, - argv: ['board', '--json'], - pid: evidence.childPid, - requestedCwd: evidence.requestedCwd, - cwdIdentity: evidence.cwdIdentity, - exit: evidence.isError ? 1 : 0, - stdout: JSON.stringify(evidence.raw), - stderr: '', - }, - ]; -} - -function copyRuntimeInput(root: string, label: string, path: string): string { - const directory = join(root, 'runtime-inputs', label); - mkdirSync(directory, { recursive: true }); - const target = join(directory, basename(path)); - copyFileSync(path, target); - if (sha256File(target) !== sha256File(path)) throw new Error(`runtime ${label} input copy digest drifted`); - return realpathSync(target); -} - -function parseDoctor(observation: CapturedCommand): Record { - const doctor = JSON.parse(observation.stdout) as Record; - const summary = doctor.integrationSummary as { codexPlugin?: { state?: unknown; deliveryComplete?: unknown } }; - if ( - doctor.ok !== true || - summary?.codexPlugin?.state !== 'current' || - summary.codexPlugin.deliveryComplete !== true - ) { - throw new Error(`candidate doctor did not report current: ${observation.stdout.slice(0, 500)}`); - } - return doctor; -} - -function observeAssetConvergence(input: { - root: string; - candidate: VerifiedGeneration; - candidateBinary: string; - fakeCodex: string; - repo: string; - codexHome: string; - env: Record; - executionAdapter?: string; -}): { commands: CapturedCommand[]; convergence: LifecycleResult['convergence'] } { - const plugin = requireExit( - captureCommand({ - root: input.root, - binary: input.fakeCodex, - args: ['plugin', 'list', '--json'], - cwd: input.repo, - env: input.env, - }), - 0, - 'candidate Codex registration', - ); - const version = requireExit( - captureCommand({ - root: input.root, - binary: input.candidateBinary, - args: ['--version'], - cwd: input.repo, - env: input.env, - executionAdapter: input.executionAdapter, - }), - 0, - 'candidate binary version', - ); - if (!version.stdout.includes(input.candidate.facts.version)) { - throw new Error('candidate binary did not report the authenticated T generation'); - } - const routePath = join(input.repo, '.codex', 'config.toml'); - if (existsSync(routePath) && /mcp_servers\.genie/.test(readFileSync(routePath, 'utf8'))) { - throw new Error('candidate convergence retained a retired Codex Genie MCP route'); - } - const sourceRoles = join(input.candidate.releaseRoot, 'plugins', 'genie', 'codex-agents'); - const targetRoles = join(input.codexHome, 'agents'); - const roleNames = readdirSync(sourceRoles).filter((name) => name.endsWith('.toml')); - const installedRoleNames = readdirSync(targetRoles).filter((name) => name.endsWith('.toml')); - const reviewerSha256 = sha256File(join(sourceRoles, 'genie-reviewer.toml')); - if ( - roleNames.length === 0 || - installedRoleNames.length !== roleNames.length || - sha256File(join(targetRoles, 'genie-reviewer.toml')) !== reviewerSha256 - ) { - throw new Error('candidate role-agent convergence did not match the authenticated payload'); - } - return { - commands: [plugin, version], - convergence: { - standalone: { - state: 'standalone', - command: input.candidateBinary, - taskArgs: ['task', 'list', '--json'], - boardArgs: ['board', '--json'], - }, - roles: { - expectedCount: roleNames.length, - observedCount: installedRoleNames.length, - current: true, - reviewerSha256, - }, - }, - }; -} - -async function runLifecycle( - root: string, - input: DogfoodEntryInput, - previous: VerifiedGeneration, - candidate: VerifiedGeneration, - dependencies: DogfoodHarnessDependencies, -): Promise { - const genieHome = join(root, 'genie-home'); - const codexHome = join(root, 'codex-home'); - const reposRoot = join(root, 'repos'); - const env = isolatedDogfoodEnv(root, { - GENIE_HOME: genieHome, - CODEX_HOME: codexHome, - GENIE_RELEASE_DOGFOOD: '1', - TERM: 'xterm', - }); - const repoA = initRepo(join(reposRoot, 'a'), env); - const repoB = initRepo(join(reposRoot, 'b'), env); - mkdirSync(codexHome, { recursive: true }); - const { command: fakeCodex, stateDir } = installFakeCodex(root, previous, candidate, env); - const configPath = join(codexHome, 'config.toml'); - const trustedProjects = [...new Set([repoA, realpathSync(repoA), repoB, realpathSync(repoB)])]; - writeFileSync( - configPath, - `[plugins."genie@automagik"]\nenabled = true\n${trustedProjects - .map((path) => `[projects."${path}"]\ntrust_level = "trusted"\n`) - .join('')}`, - ); - - const previousBinary = installGeneration(previous, genieHome); - const previousCache = join(codexHome, 'plugins', 'cache', 'automagik', 'genie', previous.facts.version); - cpSync(previous.payloadPath, previousCache, { recursive: true }); - const seededA = seedSentinelWithCli({ - root, - binary: previousBinary, - executionAdapter: input.executionAdapter, - repo: repoA, - env, - label: 'a', - initialize: true, - }); - const stages: StageProjection[] = [ - stage( - 'seed-repositories', - { - exit: 0, - humanState: 'seeded', - jsonState: 'seeded', - activeVersion: previous.facts.version, - trailer: null, - }, - seededA.commands, - ), - ]; - const nVersion = requireExit( - captureCommand({ - root, - binary: previousBinary, - args: ['--version'], - cwd: repoA, - env, - executionAdapter: input.executionAdapter, - }), - 0, - 'previous binary version', - ); - const nPlugin = requireExit( - captureCommand({ root, binary: fakeCodex, args: ['plugin', 'list', '--json'], cwd: repoA, env }), - 0, - 'previous Codex registration', - ); - if (!nVersion.stdout.includes(previous.facts.version) || !nPlugin.stdout.includes(previous.facts.version)) { - throw new Error('verified previous generation N was not the active parent'); - } - stages.push( - stage( - 'n-parent-active', - { - exit: 0, - humanState: 'current', - jsonState: 'current', - activeVersion: previous.facts.version, - trailer: null, - }, - [nVersion, nPlugin], - ), - ); - - const candidateBinary = installGeneration(candidate, genieHome); - const request = { - schemaVersion: 1, - platformId: input.platformId, - artifact: copyRuntimeInput(root, 'candidate-artifact', candidate.paths.artifact), - manifest: copyRuntimeInput(root, 'candidate-manifest', candidate.paths.manifest), - descriptor: copyRuntimeInput(root, 'candidate-descriptor', candidate.paths.identity), - bundle: copyRuntimeInput(root, 'candidate-bundle', candidate.paths.bundle), - }; - const repaired = requireExit( - captureCommand({ - root, - binary: candidateBinary, - args: ['update', '--publish-local-delivery', JSON.stringify(request)], - cwd: repoA, - env, - executionAdapter: input.executionAdapter, - timeoutMs: 180_000, - }), - 2, - 'candidate local delivery publication', - ); - const repairTrailer = parseResultTrailer(`${repaired.stdout}\n${repaired.stderr}`); - if ( - repairTrailer.code !== 'activation-pending' || - repairTrailer.deliveryComplete !== true || - readRegisteredVersion(stateDir) !== previous.facts.version - ) { - throw new Error('candidate repair did not preserve N until explicit activation consent'); - } - stages.push( - stage( - 't-delivery-repair', - { - exit: 2, - humanState: 'activation-pending', - jsonState: 'activation-pending', - activeVersion: previous.facts.version, - trailer: repairTrailer, - }, - [repaired], - ), - ); - - const activated = requireExit( - capturePtySetup({ - root, - binary: candidateBinary, - cwd: repoA, - env, - executionAdapter: input.executionAdapter, - }), - 0, - 'candidate real-PTY setup', - ); - if ( - !/Activated Codex plugin/.test(`${activated.stdout}\n${activated.stderr}`) || - readRegisteredVersion(stateDir) !== candidate.facts.version - ) { - throw new Error('candidate setup did not activate the exact T generation'); - } - stages.push( - stage( - 'activation-consent', - { - exit: 0, - humanState: 'activated', - jsonState: 'current', - activeVersion: candidate.facts.version, - trailer: null, - }, - [activated], - ), - ); - - const converged = observeAssetConvergence({ - root, - candidate, - candidateBinary, - fakeCodex, - repo: repoA, - codexHome, - env, - executionAdapter: input.executionAdapter, - }); - stages.push( - stage( - 'assets-converged', - { - exit: 0, - humanState: 'current', - jsonState: 'current', - activeVersion: candidate.facts.version, - trailer: null, - }, - converged.commands, - ), - ); - - const standaloneInput = (tag: string, requestedCwd: string): StandaloneEvidenceInput => ({ - tag, - requestedCwd, - candidateBinary, - candidateBinarySha256: candidate.binarySha256, - executionAdapter: input.executionAdapter, - root, - env, - }); - const bBefore = await observeStandalone(standaloneInput('b-before-init', repoB), dependencies); - const beforeColumns = bBefore.payload.columns; - const beforeTasks = - beforeColumns !== null && typeof beforeColumns === 'object' && !Array.isArray(beforeColumns) - ? Object.values(beforeColumns).flatMap((value) => (Array.isArray(value) ? value : [])) - : null; - if (bBefore.isError || beforeTasks === null || beforeTasks.length !== 0) { - throw new Error( - `untouched B standalone board must return an empty task list before seeding: ${JSON.stringify(bBefore.payload)}`, - ); - } - stages.push( - stage( - 'untouched-b-before-init', - { - exit: 0, - humanState: 'empty', - jsonState: 'empty', - activeVersion: candidate.facts.version, - trailer: null, - }, - standaloneObservationCommands(bBefore, candidateBinary, candidate.binarySha256, input.executionAdapter), - ), - ); - - const seededB = seedSentinelWithCli({ - root, - binary: candidateBinary, - executionAdapter: input.executionAdapter, - repo: repoB, - env, - label: 'b', - initialize: true, - }); - const bAfterEvidence = await observeStandalone(standaloneInput('b-after-init', repoB), dependencies); - const bAfter = { ...observedRepo(repoB, seededB.expected, bAfterEvidence), evidenceMode: 'standalone' as const }; - stages.push( - stage( - 'untouched-b-after-init', - { - exit: 0, - humanState: 'current', - jsonState: 'current', - activeVersion: candidate.facts.version, - trailer: null, - }, - [ - ...seededB.commands, - ...standaloneObservationCommands( - bAfterEvidence, - candidateBinary, - candidate.binarySha256, - input.executionAdapter, - ), - ], - ), - ); - - const aEvidence = await observeStandalone(standaloneInput('a-new-thread', repoA), dependencies); - const a = observedRepo(repoA, seededA.expected, aEvidence); - if (a.sentinel.token === bAfter.sentinel.token) throw new Error('two-repo sentinels collided'); - if (JSON.stringify(a.sentinel.observed).includes(bAfter.sentinel.token)) throw new Error('B sentinel leaked into A'); - if (JSON.stringify(bAfter.sentinel.observed).includes(a.sentinel.token)) throw new Error('A sentinel leaked into B'); - stages.push( - stage( - 'new-thread-sentinel', - { - exit: 0, - humanState: 'current', - jsonState: 'current', - activeVersion: candidate.facts.version, - trailer: null, - }, - standaloneObservationCommands(aEvidence, candidateBinary, candidate.binarySha256, input.executionAdapter), - ), - ); - - const doctorObservation = requireExit( - captureCommand({ - root, - binary: candidateBinary, - args: ['doctor', '--json'], - cwd: repoA, - env, - executionAdapter: input.executionAdapter, - }), - 0, - 'candidate doctor', - ); - const doctor = parseDoctor(doctorObservation); - stages.push( - stage( - 'doctor-current', - { - exit: 0, - humanState: 'current', - jsonState: 'current', - activeVersion: candidate.facts.version, - trailer: null, - }, - [doctorObservation], - ), - ); - - const delivery = JSON.parse(readFileSync(join(genieHome, '.codex-plugin-delivery-record.json'), 'utf8')) as Record< - string, - unknown - >; - return { - genieHome, - delivery, - doctor, - stages, - repositories: { - cacheRoot: join(codexHome, 'plugins', 'cache', 'automagik', 'genie'), - a, - b: { - root: repoB, - beforeInit: { - databaseState: 'absent', - result: 'empty', - returnedTasks: 0, - }, - afterInit: bAfter, - }, - }, - convergence: converged.convergence, - }; -} - -function buildManifest( - input: DogfoodEntryInput, - previous: VerifiedGeneration, - candidate: VerifiedGeneration, - lifecycle: LifecycleResult, - repositories: Record, -): Record { - const artifactEvidence = (generation: VerifiedGeneration) => ({ - version: generation.facts.version, - channel: generation.facts.channel, - platformId: generation.facts.platformId, - platformTriple: generation.facts.platformTriple, - releaseTag: generation.facts.releaseTag, - releaseName: generation.facts.releaseName, - manifestSha256: generation.manifestSha256, - artifactSha256: generation.artifactSha256, - binarySha256: generation.binarySha256, - payloadSha256: generation.payloadSha256, - evidenceDigest: generation.evidenceDigest, - provenance: { - kind: generation.evidence === null ? 'release-tarball' : 'delivery-evidence', - repository: DELIVERY_EVIDENCE_REPOSITORY, - predicateType: - generation.evidence === null ? 'https://slsa.dev/provenance/v0.2' : DELIVERY_EVIDENCE_PREDICATE_TYPE, - workflowIdentity: - generation.evidence === null - ? 'https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.1.0' - : DELIVERY_EVIDENCE_WORKFLOW_IDENTITY, - oidcIssuer: DELIVERY_EVIDENCE_OIDC_ISSUER, - sourceCommit: generation.facts.sourceSha, - controlCommit: generation.facts.controlSha, - sourceBranch: generation.facts.sourceBranch, - sourceCiRunId: generation.facts.sourceCiRunId, - identitySha256: generation.identitySha256, - bundleSha256: generation.bundleSha256, - }, - }); - const logicalPaths = (label: 'previous' | 'candidate', paths: GenerationInputPaths) => ({ - artifact: `${label}/${basename(paths.artifact)}`, - manifest: `${label}/${basename(paths.manifest)}`, - identity: `${label}/${basename(paths.identity)}`, - bundle: `${label}/${basename(paths.bundle)}`, - identityKind: paths.identityKind, - }); - return { - kind: 'live-dogfood-evidence', - schemaVersion: LIVE_DOGFOOD_SCHEMA_VERSION, - entry: { - id: `${candidate.facts.version}-${input.platformId}`, - evidenceKind: input.evidenceKind, - availability: 'verified', - platformId: input.platformId, - platformTriple: candidate.facts.platformTriple, - artifactName: candidate.facts.releaseName, - inputs: { - previous: logicalPaths('previous', previous.paths), - candidate: logicalPaths('candidate', candidate.paths), - }, - }, - lifecycle: { - previousVersion: previous.facts.version, - candidateVersion: candidate.facts.version, - channel: candidate.facts.channel, - sourceCommit: candidate.facts.sourceSha, - artifacts: { - previous: artifactEvidence(previous), - candidate: artifactEvidence(candidate), - }, - delivery: { - schemaVersion: lifecycle.delivery.schemaVersion, - deliveryId: lifecycle.delivery.deliveryId, - evidenceDigest: lifecycle.delivery.evidenceDigest, - root: lifecycle.delivery.deliveryRoot, - targetVersion: lifecycle.delivery.targetVersion, - platformId: lifecycle.delivery.platformId, - platformTriple: lifecycle.delivery.platformTriple, - releaseTag: lifecycle.delivery.releaseTag, - releaseName: lifecycle.delivery.releaseName, - releaseManifestSha256: lifecycle.delivery.releaseManifestSha256, - artifactSha256: lifecycle.delivery.artifactSha256, - installedBinarySha256: lifecycle.delivery.installedBinarySha256, - canonicalPayloadSha256: lifecycle.delivery.canonicalPayloadSha256, - }, - convergence: lifecycle.convergence, - stages: lifecycle.stages, - }, - repositories, - }; -} - -function renderEvidence(manifest: Record, doctor: Record): string { - return [ - '# Codex dogfood matrix evidence', - '', - '```json', - JSON.stringify(manifest, null, 2), - '```', - '', - '## Production doctor JSON', - '', - '```json', - JSON.stringify(doctor, null, 2), - '```', - '', - ].join('\n'); -} - -function sha256File(path: string): string { - return createHash('sha256').update(readFileSync(path)).digest('hex'); -} - -function sha256Bytes(bytes: Uint8Array): string { - return createHash('sha256').update(bytes).digest('hex'); -} From 9897597f99ba2c134688689c4a3b9bdb9299ae08 Mon Sep 17 00:00:00 2001 From: Felipe Rosa Date: Tue, 1 Sep 2026 00:29:29 +0000 Subject: [PATCH 3/3] docs(release): correct version.yml's stale version-field count --- .github/workflows/version.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 67f4bb56c..31c49e8c2 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -10,7 +10,10 @@ name: Version # On dev triggers we DERIVE a fresh version (today + build-count), # commit + tag + push back to dev, then call the main-controlled reusable # release workflow with the successful parent CI run. The release guard accepts -# the child only when all six changed fields are the deterministic version bump. +# the child only when every changed version field is the deterministic version +# bump — three files since wish `skills-everywhere-b` (`package.json`, +# `plugins/genie/package.json`, `plugins/genie/orca-plugin.json`), of which the +# guard requires the first two and accepts the third when it moved. # # On main promotion triggers, derive a fresh immutable release identity # without rewriting the source tree. The successful branch CI run and a new