Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build-tarballs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
289 changes: 16 additions & 273 deletions .github/workflows/release-publish.yml

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion .github/workflows/version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
75 changes: 0 additions & 75 deletions scripts/build.js

This file was deleted.

85 changes: 42 additions & 43 deletions scripts/release-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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] ?? '';
Expand All @@ -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');
Expand All @@ -243,58 +242,50 @@ 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');
expect(prepare).toContain('--manifest "$SELECTED_MANIFEST"');
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 }}');
Expand All @@ -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'");
});

Expand Down Expand Up @@ -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'",
Expand Down
Loading
Loading