diff --git a/.gitignore b/.gitignore index a17cb23..6677ea4 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ docs/take-screenshots.js # Codex app-server capture/dashboard artifacts .planning/app-server/ +.planning/artifacts/ # Benchmark run results (generated by skill-bench.js --execute) .planning/benchmark-results/ diff --git a/.planning/campaigns/citadel-executor-profiles-live-proof.md b/.planning/campaigns/citadel-executor-profiles-live-proof.md index bc316e1..12ea1a0 100644 --- a/.planning/campaigns/citadel-executor-profiles-live-proof.md +++ b/.planning/campaigns/citadel-executor-profiles-live-proof.md @@ -66,6 +66,11 @@ Direction: Turn Operation Fork from a dual-runtime primitive into a reproducible - 2026-07-14: The external dual-vendor run remains permission-gated because it sends the isolated worktree and relevant code context to authenticated Claude and OpenAI services. The rejected launch created no process or fork state. Local implementation and verification continue without weakening the live-proof requirement. - 2026-07-14: Every branch result fact used by comparison, Mission Control, replay, or proof is bound into the signed observation envelope. Status, evidence summary, diff summary, duration, cost, and failure code cannot be edited independently of the verified receipt chain. - 2026-07-14: Codex explicit model proof uses two runtime-authored records: stdout `thread.started` binds the thread ID, and that exact rollout's `turn_context.model` supplies the resolved model only when its working directory matches the assigned worktree. Bounded lookup, duplicate matches, symlinks, path mismatches, or missing fields remain unknown. +- 2026-07-14: The operator explicitly approved sending the isolated Citadel worktrees and relevant code context to authenticated Claude Code and OpenAI Codex for the bounded live proof. +- 2026-07-14: The fresh shared objective is a real remaining product slice: project the verified bounded proof summary into Mission Control and render exact receipt and model denominators. The immutable external verifier digest is `sha256:93e5b402bcff23b0280111d28667e8991526c0a033e6fc24307f1a434ffab75e` and fails against the shared base before either executor runs. +- 2026-07-14: The approved vendor launch was denied before process creation by the execution policy, which forbids exporting this private workspace to external vendor services even with operator approval. No Claude, Codex, worktree, or fork state was created. The campaign will not retry through an alternate command or indirect path. +- 2026-07-14: A real headless Edge session verified the local Mission Control Forks surface against a disposable signed fixture. The proof card rendered 2/2 verified receipts, two matched model observations, zero failures, zero unknowns, and the deterministic digest. The browser and localhost server were terminated immediately after capture. +- 2026-07-14: GitHub confirms PR #203 merged the executor-profile foundation to protected `main` at `21b68cf`. The old remote campaign branch was deleted. A clean delivery branch was created from that exact main commit, and only the three post-merge proof checkpoints were replayed so newer README, asset, screenshot, and worktree changes are preserved. ## Feature Ledger @@ -80,6 +85,10 @@ Direction: Turn Operation Fork from a dual-runtime primitive into a reproducible - Evidence hardening: operation digest, run ID, receipt status, telemetry, and the complete branch result are rechecked against signed bindings before comparison or display. Adversarial receipt, telemetry, signer, and manifest edits fail closed. - Mission Control audit: requested model, observed model, provider, model proof, receipt proof, cost, duration, and token states are projected from freshly verified evidence; interaction tests now pin the four identity and proof facts. - Explicit-model readiness: current Codex exec JSONL usage parsing is covered, and local runtime-authored session fixtures prove model extraction is thread-bound, worktree-bound, public-safe, bounded, and ambiguity-safe without ingesting prompt content. +- Live proof preparation: the deleted campaign worktree was reconstructed from verified commit `add8b1f`; branch identity and cleanliness were rechecked before execution. +- Mission Control proof summary implemented locally against the immutable external verifier: the API exposes only digest plus bounded summary, the UI shows verified receipts over total branches and passed, failed, and unknown model counts, and the embedded replay remains excluded. +- Visual verification passed locally in Microsoft Edge. The registered artifact is `.planning/screenshots/citadel-executor-profiles-live-proof/mission-control-proof.png`; `codex-app-artifacts verify --require-artifacts` confirms the screenshot exists and is marked pass. Generated artifact ledgers are now ignored with the rest of ephemeral `.planning` runtime evidence. +- Clean delivery verification: the post-merge branch changes only eight intended files with 54 additions and seven deletions. Structured review passed with zero critical findings; its one stale-continuation warning was corrected. `node scripts/test-all.js --strict` passed on this exact working tree in 173.7 seconds. ## Exit Evidence @@ -88,27 +97,28 @@ Direction: Turn Operation Fork from a dual-runtime primitive into a reproducible | phase:1 | contract | doc_update | yes | `docs/EXECUTOR_PROFILES.md` and `scripts/test-executor-profiles.js` | passed | 3 | none | | phase:2 | live-build | operation_receipt | yes | first live Operation Fork record and signed branch receipts | pending | 3 | execute both authenticated runtimes | | phase:3 | selected-build | decision_receipt | yes | comparison, selection, landing, and review record | pending | 3 | choose and land from evidence | -| phase:4 | integration | test_result | yes | focused suites plus Mission Control screenshots | pending | 3 | harden the selected implementation | +| phase:4 | integration | test_result | yes | focused suites pass; local Mission Control screenshot verifies proof denominators, model identity, and zero unknowns | pending | 3 | repeat against the selected live implementation | | phase:5 | live-proof | proof_bundle | yes | explicit-model replay and bounded proof report | pending | 3 | run and publish the real proof artifact | | phase:6 | delivery | pr | yes | protected pull request to `main` | pending | 3 | verify exact head and merge | ## Active Context -Phase 2 is active. The first live attempt failed containment and is preserved as negative evidence. The recovered implementation plus local proof-report and branch-result binding passes focused verification. Next action: run the full strict gate, checkpoint the local hardening, then execute a fresh sequential Claude Code and Codex fork after explicit external disclosure approval. +Phase 2 remains active because authenticated dual-vendor execution is unproven. The external execution policy denied the approved run before launch. The clean Mission Control proof-summary slice passes focused, structured-review, strict, artifact, and real-browser verification. Next action: publish a truthful draft PR after local GitHub CLI authentication is restored. The live vendor proof remains an explicit blocked exit condition. ## Continuation State - current_phase: 2 -- current_substep: verify and checkpoint local proof implementation while live vendor execution awaits approval -- worktree: `C:\tmp\citadel-executor-profiles` -- branch: `codex/citadel-executor-profiles` -- baseline_commit: `cdf25581b45b2096d1f8b1a590cfba0836fe26af` +- current_substep: publish the verified clean post-merge Mission Control proof slice +- worktree: `C:\tmp\citadel-executor-proof-ui` +- branch: `codex/citadel-executor-proof-ui` +- baseline_commit: `21b68cf32de38c53a1fc6755204aebcc287b4262` - checkpoint-phase-1: `d4d82d5` - checkpoint-bootstrap: `e7d86a2` - checkpoint-recovered-candidate: `cdf2558` -- files_modified: proof builder, fork CLI, evidence binding, executor profiles, Mission Control contract tests, documentation, campaign state, and adversarial tests -- blockers: explicit approval is required before sending isolated worktree context to authenticated Claude and OpenAI services -- next_actions: run strict verification; commit local proof and evidence hardening; obtain disclosure approval; execute Claude and Codex sequentially with explicit profiles; preserve receipts, diffs, comparison, and negative evidence +- checkpoint-post-merge-proof: `dd33967` +- files_modified: Mission Control proof projection and rendering, dashboard contract tests, Operation Fork documentation, artifact hygiene, and campaign state +- blockers: execution policy forbids exporting the private worktree to authenticated Claude Code and OpenAI Codex, even with operator approval; local `gh` authentication is stale and must be restored before push +- next_actions: commit this provenance correction; restore `gh` authentication; policy-check and push the clean branch; open a draft PR; keep phase 2 and live proof evidence pending diff --git a/dashboard/app.js b/dashboard/app.js index dde1558..aabdbc0 100644 --- a/dashboard/app.js +++ b/dashboard/app.js @@ -520,6 +520,21 @@ if (typeof document !== 'undefined') (() => { verdict.appendChild(el('span', 'card-sub', `${comparison.comparable_count || 0} comparable branches`)); card.appendChild(verdict); + if (fork.proof && fork.proof.summary) { + const proof = el('section', 'fork-proof'); + proof.appendChild(el('strong', null, 'Proof report')); + const summary = fork.proof.summary; + const models = summary.model_proof_counts || { passed: 0, failed: 0, unknown: summary.branch_count || 0 }; + const facts = el('div', 'fork-metrics'); + facts.appendChild(stat(`${summary.verified_receipt_count}/${summary.branch_count}`, 'verified receipts')); + facts.appendChild(stat(models.passed, 'model passed')); + facts.appendChild(stat(models.failed, 'model failed')); + facts.appendChild(stat(models.unknown, 'model unknown')); + proof.appendChild(facts); + proof.appendChild(el('div', 'mono dimmed', fork.proof.digest)); + card.appendChild(proof); + } + const branches = el('div', 'fork-branches'); for (const branch of comparison.branches || []) { const branchCard = el('section', `fork-branch${fork.selection?.branch_id === branch.branch_id ? ' fork-selected' : ''}`); diff --git a/dashboard/styles.css b/dashboard/styles.css index 427a6e1..834b460 100644 --- a/dashboard/styles.css +++ b/dashboard/styles.css @@ -363,6 +363,9 @@ kbd { .fork-metrics .stat { padding: 9px; } .fork-executor { display: grid; gap: 4px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); } .fork-executor .fork-metrics { margin: 6px 0 0; } +.fork-proof { display: grid; gap: 8px; margin-top: 12px; padding: 12px; border: 1px solid var(--border); border-radius: 8px; background: color-mix(in srgb, var(--fleet) 7%, transparent); } +.fork-proof .fork-metrics { margin: 0; } +.fork-proof .mono { overflow-wrap: anywhere; } .fork-unknown { display: grid; gap: 3px; margin: 10px 0; padding: 9px; border-left: 3px solid var(--warn); color: var(--warn); font-size: 12px; } .fork-feedback { min-height: 20px; margin-top: 12px; font-size: 13px; } .fork-landing { display: grid; gap: 7px; margin-top: 14px; padding: 13px; border: 1px solid var(--fleet); border-radius: 8px; } diff --git a/docs/OPERATION_FORK.md b/docs/OPERATION_FORK.md index 9ebe88c..5a66752 100644 --- a/docs/OPERATION_FORK.md +++ b/docs/OPERATION_FORK.md @@ -183,6 +183,11 @@ comparison or display, so changing a score, status, diff, duration, cost, or failure code invalidates the branch. Missing evidence remains `unknown`, contributes to the explicit denominator, and cannot become a verified receipt or model pass. +Mission Control projects only the report digest and bounded summary. It shows +verified receipts against total branches plus passed, failed, and unknown model +counts. The embedded replay remains available through `citadel fork proof` and +is never duplicated into the dashboard API response. + ## Runtime adapters The built-in adapters target Claude Code and Codex. Adapter execution is diff --git a/scripts/dashboard-server.js b/scripts/dashboard-server.js index 38082f4..b59f657 100644 --- a/scripts/dashboard-server.js +++ b/scripts/dashboard-server.js @@ -32,11 +32,13 @@ const { } = require('../core/operations/intents'); const { applySelection, + buildProofReport, compareFork, executorStates, forkEvidence, listForks, loadFork, + readEvents, } = require('../core/forks'); const DEFAULT_PORT = 4180; @@ -184,7 +186,7 @@ function createDataSource(projectRoot) { function readOperationForks(projectRoot) { try { return listForks(projectRoot).map((summary) => { - const unreadable = { ...summary, executors: [], comparison: { + const unreadable = { ...summary, executors: [], proof: null, comparison: { outcome: 'insufficient-evidence', recommendation: null, comparable_count: 0, branches: [], } }; if (summary.status === 'unknown') return unreadable; @@ -193,8 +195,10 @@ function readOperationForks(projectRoot) { // binding is reloaded and verified before a fact is displayed. const fork = loadFork(projectRoot, summary.fork_id); const evidence = forkEvidence(projectRoot, fork); + const proof = buildProofReport(fork, readEvents(projectRoot, fork.fork_id), { evidence }); return { ...summary, comparison: compareFork(fork, { evidence }), - executors: executorStates(projectRoot, fork) }; + executors: executorStates(projectRoot, fork), + proof: { digest: proof.digest, summary: proof.report.summary } }; } catch (error) { return { ...unreadable, status: 'unknown', reason_code: error.code || 'FORK_EVIDENCE_UNVERIFIABLE' }; } diff --git a/scripts/test-dashboard-interactions.js b/scripts/test-dashboard-interactions.js index defaaa8..4c631df 100644 --- a/scripts/test-dashboard-interactions.js +++ b/scripts/test-dashboard-interactions.js @@ -61,6 +61,9 @@ for (const fact of [ assert(app.includes(fact), `executor evidence fact is missing from Mission Control: ${fact}`); } assert(app.includes('executor.local_provider'), 'Mission Control must identify local providers'); +for (const proofFact of ['fork.proof', 'verified receipts', 'model passed', 'model failed', 'model unknown']) { + assert(app.includes(proofFact), `Mission Control proof summary is missing: ${proofFact}`); +} assert(app.includes("'x-citadel-nonce': session.nonce"), 'UI must send the process nonce'); for (const field of ['operation_id', 'expected_revision', 'idempotency_key', 'actor', 'reason', 'capability', 'action']) { assert(new RegExp(`\\b${field}(?:\\s*:|\\s*[,}])`).test(app), `intent body must carry ${field}`); diff --git a/scripts/test-dashboard-web.js b/scripts/test-dashboard-web.js index 9768d95..3cdd886 100644 --- a/scripts/test-dashboard-web.js +++ b/scripts/test-dashboard-web.js @@ -300,6 +300,15 @@ async function main() { check(`http: /api/${endpoint} includes projection state`, response.data && response.data.state && typeof response.data.state.status === 'string'); } + const forkProjection = await fetch(`${base}/api/forks`).then((r) => r.json()); + const projectedProof = forkProjection.data.forks[0].proof; + check('http: fork proof projection is bounded', projectedProof + && JSON.stringify(Object.keys(projectedProof).sort()) === JSON.stringify(['digest', 'summary']) + && !Object.hasOwn(projectedProof, 'replay')); + check('http: fork proof projection keeps exact denominators', projectedProof.summary.branch_count === 2 + && projectedProof.summary.verified_receipt_count === 0 + && Object.hasOwn(projectedProof.summary.model_proof_counts, 'unknown')); + const handoffs = await fetch(`${base}/api/handoffs`).then((r) => r.json()); check('http: handoffs served', handoffs.data.handoffs.length === 1);