From 89394585bf619687a7d8b7a46b1061be249887c8 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Wed, 5 Aug 2026 15:57:08 +0200 Subject: [PATCH 1/4] FE-1320: Isolate comparison mission targets Place each harness in a fresh external temporary root so controller-private mission material is absent from ordinary target-relative traversal while retained evidence stays exact. Co-authored-by: Cursor --- .pi/prompts/compare-specs.md | 8 +- docs/archive/PLAN_HISTORY.md | 6 + memory/PLAN.md | 22 +--- memory/SPEC.md | 2 +- .../__tests__/compare-specs-prompt.test.ts | 120 +++++++++++++++++- 5 files changed, 133 insertions(+), 25 deletions(-) diff --git a/.pi/prompts/compare-specs.md b/.pi/prompts/compare-specs.md index 4f3c6dbb4..353fdfb69 100644 --- a/.pi/prompts/compare-specs.md +++ b/.pi/prompts/compare-specs.md @@ -19,6 +19,8 @@ Use these repository-relative homes and no substitutes: A mission is ordinary-language Markdown for the top-level session playing the simulated user. It is not controller YAML, a Brunch seed, comparison-harness selection, or harness configuration. Never use `.fixtures/seeds/` as mission input. Never put the mission text, file, or path in a harness context or cwd. A harness receives only its approved minimal framing and the natural opening and later answers that the top-level session chooses from mission truth. +Every harness gets a fresh system-temporary external target root outside the controller checkout, scratch run tree, and retained run tree. Before launch, resolve the target and controller paths and verify that they are separate and that its ancestor directories contain no controller-private run material. Brunch still launches from the Brunch repository root and receives the external target through `--workspace`; generic harnesses launch with the external target as their cwd. This placement closes ordinary target-relative ancestor traversal, including the known `../../private-mission.md` path. It is not an OS sandbox and does not claim isolation from unrestricted absolute-path or whole-host discovery. + A saved mission is editable. An approved run is historical evidence: never overwrite an existing run directory, private mission snapshot, `harness-setup.md` snapshot, transcript, target output, or report. Later mission revisions affect future runs only. Historical setup snapshots keep their existing names and bytes; never rename them. Keep retained paths repository-relative; do not retain workstation-absolute paths. All operator choices and approvals must work through ordinary typed text. If a structured question tool happens to exist, it may improve presentation, but correctness and progress must never depend on it. @@ -57,7 +59,7 @@ Run setup is intentionally small, run-specific, and never written into the reusa - **Brunch:** use built-in Specify mode, plus only the output instruction needed to identify the requested review-ready document and path. - **Generic harnesses:** use a small instruction to conduct a question-led specification conversation and author the requested review-ready Markdown document. Do not preload mission facts or prescribe conclusions. 3. Preflight only each selected harness's filesystem and adapter prerequisites, including the pinned `pi-interactive-shell` package and cleanup capability where applicable. Do not launch a synthetic conversation to test provider/model access. Report provider/model failure honestly if the real harness launch fails. Check Pi's adapter only when Pi is selected. -4. Allocate a fresh isolated target cwd/session for each selected harness. This is the harness's comparison target, not necessarily the controller process cwd: Brunch's controller must launch from the Brunch repository root and address the fresh target through `--workspace `, while a generic harness's structured spawn uses the fresh target cwd as its process cwd. The top-level session remains the shared simulated-user actor, so disclose harness order and shared actor context; do not claim fresh-per-harness actor isolation or equivalence to a rigorous campaign. +4. Allocate a separate fresh system-temporary external target root for each selected harness using the host's standard temporary-directory facility. Resolve its canonical path and fail setup unless it is outside the controller checkout, scratch run tree, and retained run tree and its ancestor directories contain no `private-mission.md`, `harness-setup.md`, or other controller-private run material. This is the harness's comparison target, not necessarily the controller process cwd: Brunch's controller must launch from the Brunch repository root and address the external target through `--workspace `, while a generic harness's structured spawn uses the external target as its process cwd. The top-level session remains the shared simulated-user actor, so disclose harness order and shared actor context; do not claim fresh-per-harness actor isolation or equivalence to a rigorous campaign. 5. Display together: the complete private mission; selected harnesses and order; exact per-harness target-visible framing; mission id; collision-safe run id; requested target document path; scratch and retained paths; and each adapter. Clearly label the mission **top-level-session-only** and the framing **harness-visible**. 6. Ask through ordinary text for explicit **approve**, **revise**, or **reject**. Revise and redisplay the complete setup, or reject and stop; do not launch partially. @@ -91,8 +93,8 @@ Run selected harnesses sequentially. For each harness: Generic harnesses author the requested Markdown file themselves in their cwd. An unavailable or mismatched adapter makes the harness failed; never substitute, silently drop it, or reconstruct output. 6. Retain the exact harness-visible initial framing and transcript, including every user answer and decision. This is the disclosure record: any mission fact visible to the harness must have arrived through a natural opening or subsequent answer. -7. Acquire the harness-authored document if it exists. Never author, reconstruct, complete, rewrite, or improve it. Missing or partial output remains missing or partial. -8. On every outcome, retain state, harness-visible interaction, target-cwd/session identity, final process status, document that exists, and cleanup notes. Kill remaining processes, dismiss the completed shell record, and verify no comparison-harness shell or process remains before starting another. +7. Locate the harness-authored document if it exists without altering it. Never author, reconstruct, complete, rewrite, or improve it. Missing or partial output remains missing or partial. +8. On every outcome, kill remaining processes, dismiss the completed shell record, and verify no comparison-harness shell or process remains. Only after that process cleanup, copy the exact harness-visible transcript and any unchanged harness-authored document from the external target into controller-owned scratch evidence; retain target-cwd/session identity, final process status, and cleanup notes. Then remove that harness's external target root and verify it is gone before starting another harness. Do not notify completion while any harness is ready, running, or waiting. After every selected harness is finished or failed, give one aggregate notification. Review scratch assembly, then deliberately copy it to the unused immutable run identity without changing `provenance.json`, snapshots, transcripts, or harness-authored documents. diff --git a/docs/archive/PLAN_HISTORY.md b/docs/archive/PLAN_HISTORY.md index b6ae028c3..754bf9ba4 100644 --- a/docs/archive/PLAN_HISTORY.md +++ b/docs/archive/PLAN_HISTORY.md @@ -3,6 +3,12 @@ This file is the active POC-line plan archive for `memory/PLAN.md`. Legacy pre-`next` history was moved out of the live docs tree with the old archived implementation. +## 2026-08-05 FE-1320 comparison mission isolation closeout + +`comparison-mission-isolation-hardening` ([FE-1320](https://linear.app/hash/issue/FE-1320/structural-private-mission-isolation-for-compare-specs)) closed the CS2 placement risk without introducing a controller runtime or generic sandbox. The prompt-owned `/compare-specs` procedure now allocates each harness a fresh system-temporary external target root outside controller checkout, scratch, and retained trees; Brunch still launches from the repository root and receives the target through `--workspace`. + +The actual-entry-point contract proves target-root `read`/`ls`/`find`/`grep` visibility excludes controller-private mission material, the known `../../private-mission.md` rival fails, controller snapshots and exact target-authored output survive process and target cleanup, and historical FE-1215 retained bytes remain untouched. This boundary covers ordinary target-relative ancestor traversal only; it does not claim unrestricted absolute-path or whole-host isolation. The real Brunch + Claude run and revision-immutability evidence remain owned by `saved-mission-comparison-witness`. + ## 2026-08-05 FE-1318 canonical document reconciliation closeout `canonical-document-reconciliation` closed FE-1316 rows R06, R07, and R12 without creating another planning or comparison status store. The planning-substrate assessment now records its PLAN-replacement prescription as unadopted; PLAN, SPEC, Linear/Graphite, and co-located topology retain their established authority split. diff --git a/memory/PLAN.md b/memory/PLAN.md index d9615334e..9416de873 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -15,7 +15,7 @@ - **Ship gate:** FE-1187 `walkthrough-remediation-2` is still the sole remaining ship-gate frontier. `remediation-4` landed the mixed-settlement review contract, populated-only Impact Ledger rendering, terminal-adaptive shell, persistent Specify elicitation style, deterministic menus, Ask mechanics, Continue-language cleanup, KA handoff, and the R6 terminal-status rail; extractor/oracle hardening and the fresh R8–R10 0/3 campaign remain paused at user direction. Execute evidence O7–O9 is still KA-owned. - **Shared host follow-through:** FE-1200 proved the standalone web foundation, but the TUI still owns a separate live `AgentSession`, `SessionEventRelay`, and `/rpc/driver` sidecar path. The open architecture arc is `shared-session-host-convergence`. -- **Comparison lanes:** the repo keeps two distinct evaluation doors: seed-based intra-product testing and mission-driven cross-product comparison. FE-1241 closed the brownfield comparison cases; FE-1215 landed the approachable operator workflow; the remaining operator residue is the structural mission-isolation hardening plus the later real Brunch+Claude saved-mission witness. +- **Comparison lanes:** the repo keeps two distinct evaluation doors: seed-based intra-product testing and mission-driven cross-product comparison. FE-1241 closed the brownfield comparison cases; FE-1215 landed the approachable operator workflow; FE-1320 structurally separated controller-private mission material from fresh external harness targets. The remaining operator residue is the later real Brunch+Claude saved-mission witness. - **Executor / Execute evidence queue:** several KA fronts are implementation-merged but still carry explicit outer evidence: `host-landing`, `executor-plan-synthesis`, `execution-comparison-tracer`, `executor-plan-coherence`, `executor-slice-verification-repair`, and `greenfield-secure-drop-demo`. - **Current seams and discipline:** Brunch ships on `1.0.0-alpha.x`. D125-L's live ask registry is the structured-exchange headless surface; the transcript-backed pending projection remains compatibility-only. Sweep classification remains fail-closed on exchange-schema terminal names (D117-L); the larger capture-conditional watermark question remains A40-L. Co-located `src/**/TOPOLOGY.md` files own current topology; scratch evidence is not durable until promoted to `.fixtures/runs/`. @@ -52,16 +52,15 @@ ### Recently Completed +- 2026-08-05 `comparison-mission-isolation-hardening` (FE-1320) — **✓ complete:** `/compare-specs` now places every harness in a fresh system-temporary external target root outside controller checkout, scratch, and retained trees; actual-entry-point tests cover the CS2 `../../private-mission.md` rival, target-root visibility, retained exact output, and target cleanup without claiming whole-host isolation. - 2026-08-05 `canonical-document-reconciliation` (FE-1318) — **✓ complete:** the unadopted PLAN-replacement proposal is historical, seven superseded design surfaces now point to current authority, comparison prose distinguishes four execution cases from three configured E2E contracts and one retained witness, and active document links resolve. - 2026-08-05 `host-landing-oracle-identity` (FE-1317) — **✓ complete:** the immutable host-landing oracle pack now includes all five behavior-bearing PTY inputs, with regression coverage for one-file-at-a-time hash sensitivity and identical-input stability. -- 2026-08-06 `system-reorientation-audit` (FE-1316) — **✓ complete:** thirteen post-cleanup concerns received evidence-backed dispositions; one stale exchange-guidance defect was fixed, two bounded frontiers were admitted, and existing owners absorbed the remaining promoted work without creating a parallel queue. Older completion history and archived completed frontier definitions live in [`docs/archive/PLAN_HISTORY.md`](../docs/archive/PLAN_HISTORY.md). ### Next - `saved-mission-comparison-witness` — later operator-owned frontier: FE-1215 is landed, so the remaining work is scheduling the real Brunch + Claude `/compare-specs` witness, then revising/rerunning the saved mission to prove historical snapshots remain immutable. Definition below. -- `comparison-mission-isolation-hardening` — admitted from FE-1215 smoke (`TESTING_FINDINGS.md` CS2): move the private mission outside the target-reachable tree (or equivalently jail the target's file tools) so the D134-L/I67-L mission boundary is structural, not conduct-dependent. Definition below. - `shared-session-host-tracer` — prove the one-host TUI attachment seam that FE-1200 deliberately left open. Definition below. - `shared-session-host-cutover` — wait-gated on the tracer; close the enumerated TUI/web host surface, then delete the raw sidecar relay and `/rpc/driver`. Definition below. @@ -136,19 +135,6 @@ Legacy link target; see Horizon. - **Dependencies:** hard on operator scheduling; FE-1215's D134-L remediation landing is already satisfied. - **Traceability:** req 24/A5-L; D70-L; D134-L/I67-L; FE-1210 evidence discipline; SPEC Verification Design `saved-mission-comparison-witness` gate. -### comparison-mission-isolation-hardening - -- **Name:** Structural private-mission isolation for `/compare-specs` -- **Linear:** unassigned — create at pickup in Frontend / brunch. -- **Branch:** tbd at pickup; stack after the landed FE-1215 workflow when this follow-on starts. -- **Kind:** bounded hardening over the retained D134-L mission boundary. -- **Certainty:** proving. -- **Status:** admitted from `TESTING_FINDINGS.md` CS2; not started. -- **Objective:** make the private mission structurally unreachable from the comparison target by storing it outside the target-reachable tree or equivalently constraining target file tools to the harness cwd. -- **Acceptance:** the target cannot discover or read the private mission by normal `read`/`ls`/`find`/`grep` access; the run still retains the private mission for controller/operator evidence; no FE-1215 historical run is rewritten; the fix does not widen into a generic comparison-runner redesign. -- **Verification:** focused artifact-path and target-visibility tests plus one targeted smoke over the retained `/compare-specs` flow. -- **Traceability:** D134-L/I67-L; `TESTING_FINDINGS.md` CS2. - ### shared-session-host-tracer - **Name:** Shared session host tracer — one runtime authority for TUI and React @@ -200,14 +186,10 @@ active: blocked_reentry: extractor/oracle reconciliation -> fresh R8–R10 0/3 campaign -> A48-L -> consolidated outer checkpoint cross_stream: O7/O8/O9 + carved Execute beats stay KA-owned -[stack]-> cli-mode-entry - next: saved-mission-comparison-witness gated_by: operator availability depends_on: landed FE-1215 workflow - comparison-mission-isolation-hardening - source: TESTING_FINDINGS.md CS2 from FE-1215 witness - hardens: D134-L/I67-L private-mission boundary shared-session-host-tracer retires: A47-L proves: one host-owned writable runtime + real TUI + React attachment diff --git a/memory/SPEC.md b/memory/SPEC.md index ff9bdded9..a2a910821 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -394,7 +394,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I64-L | Every hosted-session lifecycle operation, driver request, open-ask read/answer, and live event is addressed by durable `(specId, sessionId)`; one JSONL session has at most one writable hosted runtime and one active driver, while different targets may run concurrently and any number of observers may attach. Transport connection, route, or host-local object identity never substitutes for the durable target. | covered by FE-1200 host/RPC negative-space contracts plus `standalone-web-session-host.concurrency.test.ts`: two production-wired targets, duplicate-open/second-driver/cross-target rivals, target-local contiguous frames, overlapping asks, failure recovery, reconnect, and separate JSONL readback | D132-L; D33-L; req 31 | | I65-L | For every required product-visible Brunch transcript family, validated semantic details have one shared presentation projection and audience-specific adapters; after streaming settles or reconnects, the web session view equals a fresh JSONL-derived semantic projection modulo declared ephemeral progress. | covered by `session-presentation.test.ts` no-loss/malformed-detail cases for every required persisted terminal shape (including questionnaire read-back), `session-route.test.tsx` React render/answer cases for free text and listed single/multi choices, headless schema-envelope questionnaire coverage, distinct candidate/review-set/digest production settle/reconnect witnesses in `standalone-web-session-host.real-entry.test.ts`, the two-target concurrency differential, and receipt-bearing review settlement | D133-L; D17-L, D19-L; req 12, req 32 | | I66-L | Specify elicitation style is exactly one active-branch, last-entry-wins value in `interrogate | disambiguate | propose`; it remains prompt-visible across kicks and resume/session switch/tree navigation until the user changes it. One-shot process moves and dismissal cannot overwrite it. Style-less new sessions and operational-mode switches may open the menu automatically; resume/session switch/tree/abort may not. | planned FE-1187 active-branch rivals, prompt-composition tests, menu-juncture negative space, and live resume/tree walkthrough; establishing frontier: `walkthrough-remediation-2` | D98-L, D109-L | -| I67-L | During an approachable comparison run, the invoking Pi session is the sole simulated-user actor and no more than one comparison-harness interactive shell is live. No actor subprocess may own another interactive shell; the private mission remains outside every harness context/cwd; and every choice or approval remains operable through ordinary text when no custom question tool exists. | FE-1215 prompt/README review plus the focused stock-Pi, normal-width Brunch smoke are covered; the full Brunch + Claude run, report judgment, and revision-immutability proof belong to `saved-mission-comparison-witness` | D134-L; D70-L | +| I67-L | During an approachable comparison run, the invoking Pi session is the sole simulated-user actor and no more than one comparison-harness interactive shell is live. No actor subprocess may own another interactive shell; the private mission remains outside every harness context/cwd; and every choice or approval remains operable through ordinary text when no custom question tool exists. | FE-1215 prompt/README review plus the focused stock-Pi, normal-width Brunch smoke are covered; FE-1320's actual-entry-point prompt contract, target-visibility rival, and retained-flow smoke cover ordinary target-relative ancestor isolation through a fresh external target root without claiming an OS sandbox; the full Brunch + Claude run, report judgment, and revision-immutability proof belong to `saved-mission-comparison-witness` | D134-L; D70-L | | I68-L | Every Brunch agent turn has one current, role-owned control block and one capability-honest resource surface: changed runtime state replaces stale Brunch prompt content across every supported provider carrier; every advertised resource is either readable under the agent's actual grant or fully bundled into its task; and no prompt instructs an agent to use a missing tool/resource. Context provenance preserves the distinct ownership and lifetime of spec posture, elicitation style, asking agenda, origination continuity, and on-demand reads. | covered by the landed provider-carrier replacement matrix, production recorder → persisted trajectory → report projection for advertised/read/provider-visible resource states, background grant/bundle/no-tools tests, behavioral control-ownership tests, and topology/import contract checks | D135-L; D58-L, D90-L, D91-L, D98-L, D101-L, D102-L, D118-L | | I69-L | Every scoped execution worker receives the exact approved content of each requirement its slice derives from, plus any target-visible public-contract material that governs that slice; graph reference codes or planner-authored paraphrase alone are insufficient. This content remains bounded to slice-linked target-visible truth and never includes controller-only oracle material. | structurally covered by exact title/body persistence, transitive dependency requirement resolution, unknown/duplicate/malformed-reference rejection, run-pinned hash-verified three-file packet staging, mutation/redaction/symlink rivals, sealed-worker brief rendering, synthesis/request integration, and serial/parallel artifact parity; the unchanged frozen Petri rerun remains outer evidence, not a merge gate | D126-L, D135-L; I58-L | diff --git a/src/dev/__tests__/compare-specs-prompt.test.ts b/src/dev/__tests__/compare-specs-prompt.test.ts index a431f689b..a848b7b5a 100644 --- a/src/dev/__tests__/compare-specs-prompt.test.ts +++ b/src/dev/__tests__/compare-specs-prompt.test.ts @@ -1,10 +1,32 @@ -import { readFile } from 'node:fs/promises'; +import { access, cp, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join, relative, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; const promptPath = fileURLToPath(new URL('../../../.pi/prompts/compare-specs.md', import.meta.url)); +const pathExists = async (path: string): Promise => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + +const findEntries = async (root: string): Promise => { + const entries = await readdir(root, { withFileTypes: true }); + const descendants = await Promise.all( + entries.map(async (entry) => { + const path = join(root, entry.name); + return entry.isDirectory() ? [path, ...(await findEntries(path))] : [path]; + }), + ); + return descendants.flat(); +}; + describe('/compare-specs operator prompt', () => { it('continues without another selection turn when exactly one mission is eligible', async () => { const prompt = await readFile(promptPath, 'utf8'); @@ -13,4 +35,100 @@ describe('/compare-specs operator prompt', () => { expect(prompt).toContain('Only ask the operator to select a mission when more than one is eligible'); expect(prompt).toContain('if none are eligible, report that and stop'); }); + + it('places every harness target in a fresh system-temporary root outside controller trees', async () => { + const prompt = await readFile(promptPath, 'utf8'); + + expect(prompt).toContain('fresh system-temporary external target root'); + expect(prompt).toContain('outside the controller checkout, scratch run tree, and retained run tree'); + expect(prompt).toContain('its ancestor directories contain no controller-private run material'); + expect(prompt).toContain( + 'Brunch still launches from the Brunch repository root and receives the external target through `--workspace`', + ); + expect(prompt).toContain( + 'This placement closes ordinary target-relative ancestor traversal, including the known `../../private-mission.md` path', + ); + expect(prompt).toContain( + 'It is not an OS sandbox and does not claim isolation from unrestricted absolute-path or whole-host discovery', + ); + }); + + it('keeps controller-private mission material outside target-root file visibility', async () => { + const fixtureRoot = await mkdtemp(join(tmpdir(), 'compare-specs-prompt-')); + const controllerCheckout = join(fixtureRoot, 'controller-checkout'); + const scratchRun = join(controllerCheckout, '.fixtures', 'scratch', 'comparisons', 'fixture-run'); + const externalRoot = await mkdtemp(join(tmpdir(), 'compare-specs-target-')); + const target = join(externalRoot, 'targets', 'brunch'); + + try { + await mkdir(target, { recursive: true }); + await mkdir(scratchRun, { recursive: true }); + await writeFile(join(scratchRun, 'private-mission.md'), 'controller-only phrase\n'); + await writeFile(join(scratchRun, 'harness-setup.md'), 'controller-only setup\n'); + await writeFile(join(target, 'visible.md'), 'approved harness framing\n'); + + expect(relative(controllerCheckout, target).startsWith('..')).toBe(true); + expect(relative(scratchRun, target).startsWith('..')).toBe(true); + + const lsVisible = await readdir(target); + expect(lsVisible).toEqual(['visible.md']); + + const findVisible = await findEntries(target); + expect(findVisible.map((entry) => relative(target, entry))).toEqual(['visible.md']); + + const grepVisible = (await Promise.all(findVisible.map((entry) => readFile(entry, 'utf8')))).filter( + (text) => text.includes('controller-only phrase'), + ); + expect(grepVisible).toEqual([]); + expect(findVisible.some((entry) => entry.includes('private-mission.md'))).toBe(false); + + const cs2Rival = resolve(target, '../../private-mission.md'); + expect(dirname(cs2Rival)).not.toBe(scratchRun); + await expect(readFile(cs2Rival, 'utf8')).rejects.toMatchObject({ code: 'ENOENT' }); + } finally { + await rm(fixtureRoot, { recursive: true, force: true }); + await rm(externalRoot, { recursive: true, force: true }); + } + }); + + it('retains controller snapshots and exact target output after external target cleanup', async () => { + const fixtureRoot = await mkdtemp(join(tmpdir(), 'compare-specs-retained-flow-')); + const scratchRun = join(fixtureRoot, 'controller-checkout', 'scratch', 'fixture-run'); + const retainedRun = join(fixtureRoot, 'controller-checkout', 'retained', 'fixture-run'); + const externalRoot = await mkdtemp(join(tmpdir(), 'compare-specs-target-')); + const target = join(externalRoot, 'claude-code'); + const targetOutput = join(target, 'review-ready-spec.md'); + const targetOutputBytes = '# Target-authored output\n\nExact bytes stay unchanged.\n'; + const reportBytes = '# Operator-only report\n'; + + try { + await mkdir(scratchRun, { recursive: true }); + await mkdir(target, { recursive: true }); + await writeFile(join(scratchRun, 'private-mission.md'), 'approved private mission\n'); + await writeFile(join(scratchRun, 'harness-setup.md'), 'approved setup\n'); + await writeFile(targetOutput, targetOutputBytes); + + expect(await findEntries(target)).not.toContain(join(scratchRun, 'private-mission.md')); + + await mkdir(join(scratchRun, 'lanes', 'claude-code'), { recursive: true }); + await cp(targetOutput, join(scratchRun, 'lanes', 'claude-code', 'review-ready-spec.md')); + await writeFile(join(scratchRun, 'report.md'), reportBytes); + await rm(externalRoot, { recursive: true }); + await cp(scratchRun, retainedRun, { recursive: true }); + + expect(await pathExists(target)).toBe(false); + expect(await readFile(join(retainedRun, 'private-mission.md'), 'utf8')).toBe( + 'approved private mission\n', + ); + expect(await readFile(join(retainedRun, 'harness-setup.md'), 'utf8')).toBe('approved setup\n'); + expect(await readFile(join(retainedRun, 'lanes', 'claude-code', 'review-ready-spec.md'), 'utf8')).toBe( + targetOutputBytes, + ); + expect(await readFile(join(retainedRun, 'report.md'), 'utf8')).toBe(reportBytes); + expect((await findEntries(retainedRun)).some((entry) => entry.startsWith(target))).toBe(false); + } finally { + await rm(fixtureRoot, { recursive: true, force: true }); + await rm(externalRoot, { recursive: true, force: true }); + } + }); }); From ae81cb5468a16a9c7cc216444b47407e0f35013d Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Wed, 5 Aug 2026 15:57:57 +0200 Subject: [PATCH 2/4] FE-1320: Pin comparison prompt invariants Keep the external-target contract coupled to the existing single-actor, immutable-evidence, and prompt-owned workflow boundaries. Co-authored-by: Cursor --- src/dev/__tests__/compare-specs-prompt.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/dev/__tests__/compare-specs-prompt.test.ts b/src/dev/__tests__/compare-specs-prompt.test.ts index a848b7b5a..5ba49edfa 100644 --- a/src/dev/__tests__/compare-specs-prompt.test.ts +++ b/src/dev/__tests__/compare-specs-prompt.test.ts @@ -39,6 +39,12 @@ describe('/compare-specs operator prompt', () => { it('places every harness target in a fresh system-temporary root outside controller trees', async () => { const prompt = await readFile(promptPath, 'utf8'); + expect(prompt).toContain('invoking top-level project Pi session is the sole simulated-user actor'); + expect(prompt).toContain('Keep at most one comparison-harness shell live at a time'); + expect(prompt).toContain( + 'Never launch a separate simulated-user process or let one interactive shell own another', + ); + expect(prompt).toContain('Never put the mission text, file, or path in a harness context or cwd'); expect(prompt).toContain('fresh system-temporary external target root'); expect(prompt).toContain('outside the controller checkout, scratch run tree, and retained run tree'); expect(prompt).toContain('its ancestor directories contain no controller-private run material'); @@ -51,6 +57,15 @@ describe('/compare-specs operator prompt', () => { expect(prompt).toContain( 'It is not an OS sandbox and does not claim isolation from unrestricted absolute-path or whole-host discovery', ); + expect(prompt).toContain( + 'Only after that process cleanup, copy the exact harness-visible transcript and any unchanged harness-authored document', + ); + expect(prompt).toContain( + 'never overwrite an existing run directory, private mission snapshot, `harness-setup.md` snapshot, transcript, target output, or report', + ); + expect(prompt).toContain( + 'do not replace it with a parser, controller schema, helper state machine, generic runner, campaign framework, or automatic judge', + ); }); it('keeps controller-private mission material outside target-root file visibility', async () => { From 7b3c5ffe4c9da21b3c4a61b2e4153adda1e713ed Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Wed, 5 Aug 2026 15:59:37 +0200 Subject: [PATCH 3/4] FE-1320: Restore SPEC ownership boundary Return I67-L to its pre-frontier content so any evidence refresh remains owned by ln-sync. Co-authored-by: Cursor --- memory/SPEC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/memory/SPEC.md b/memory/SPEC.md index a2a910821..ff9bdded9 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -394,7 +394,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I64-L | Every hosted-session lifecycle operation, driver request, open-ask read/answer, and live event is addressed by durable `(specId, sessionId)`; one JSONL session has at most one writable hosted runtime and one active driver, while different targets may run concurrently and any number of observers may attach. Transport connection, route, or host-local object identity never substitutes for the durable target. | covered by FE-1200 host/RPC negative-space contracts plus `standalone-web-session-host.concurrency.test.ts`: two production-wired targets, duplicate-open/second-driver/cross-target rivals, target-local contiguous frames, overlapping asks, failure recovery, reconnect, and separate JSONL readback | D132-L; D33-L; req 31 | | I65-L | For every required product-visible Brunch transcript family, validated semantic details have one shared presentation projection and audience-specific adapters; after streaming settles or reconnects, the web session view equals a fresh JSONL-derived semantic projection modulo declared ephemeral progress. | covered by `session-presentation.test.ts` no-loss/malformed-detail cases for every required persisted terminal shape (including questionnaire read-back), `session-route.test.tsx` React render/answer cases for free text and listed single/multi choices, headless schema-envelope questionnaire coverage, distinct candidate/review-set/digest production settle/reconnect witnesses in `standalone-web-session-host.real-entry.test.ts`, the two-target concurrency differential, and receipt-bearing review settlement | D133-L; D17-L, D19-L; req 12, req 32 | | I66-L | Specify elicitation style is exactly one active-branch, last-entry-wins value in `interrogate | disambiguate | propose`; it remains prompt-visible across kicks and resume/session switch/tree navigation until the user changes it. One-shot process moves and dismissal cannot overwrite it. Style-less new sessions and operational-mode switches may open the menu automatically; resume/session switch/tree/abort may not. | planned FE-1187 active-branch rivals, prompt-composition tests, menu-juncture negative space, and live resume/tree walkthrough; establishing frontier: `walkthrough-remediation-2` | D98-L, D109-L | -| I67-L | During an approachable comparison run, the invoking Pi session is the sole simulated-user actor and no more than one comparison-harness interactive shell is live. No actor subprocess may own another interactive shell; the private mission remains outside every harness context/cwd; and every choice or approval remains operable through ordinary text when no custom question tool exists. | FE-1215 prompt/README review plus the focused stock-Pi, normal-width Brunch smoke are covered; FE-1320's actual-entry-point prompt contract, target-visibility rival, and retained-flow smoke cover ordinary target-relative ancestor isolation through a fresh external target root without claiming an OS sandbox; the full Brunch + Claude run, report judgment, and revision-immutability proof belong to `saved-mission-comparison-witness` | D134-L; D70-L | +| I67-L | During an approachable comparison run, the invoking Pi session is the sole simulated-user actor and no more than one comparison-harness interactive shell is live. No actor subprocess may own another interactive shell; the private mission remains outside every harness context/cwd; and every choice or approval remains operable through ordinary text when no custom question tool exists. | FE-1215 prompt/README review plus the focused stock-Pi, normal-width Brunch smoke are covered; the full Brunch + Claude run, report judgment, and revision-immutability proof belong to `saved-mission-comparison-witness` | D134-L; D70-L | | I68-L | Every Brunch agent turn has one current, role-owned control block and one capability-honest resource surface: changed runtime state replaces stale Brunch prompt content across every supported provider carrier; every advertised resource is either readable under the agent's actual grant or fully bundled into its task; and no prompt instructs an agent to use a missing tool/resource. Context provenance preserves the distinct ownership and lifetime of spec posture, elicitation style, asking agenda, origination continuity, and on-demand reads. | covered by the landed provider-carrier replacement matrix, production recorder → persisted trajectory → report projection for advertised/read/provider-visible resource states, background grant/bundle/no-tools tests, behavioral control-ownership tests, and topology/import contract checks | D135-L; D58-L, D90-L, D91-L, D98-L, D101-L, D102-L, D118-L | | I69-L | Every scoped execution worker receives the exact approved content of each requirement its slice derives from, plus any target-visible public-contract material that governs that slice; graph reference codes or planner-authored paraphrase alone are insufficient. This content remains bounded to slice-linked target-visible truth and never includes controller-only oracle material. | structurally covered by exact title/body persistence, transitive dependency requirement resolution, unknown/duplicate/malformed-reference rejection, run-pinned hash-verified three-file packet staging, mutation/redaction/symlink rivals, sealed-worker brief rendering, synthesis/request integration, and serial/parallel artifact parity; the unchanged frozen Petri rerun remains outer evidence, not a merge gate | D126-L, D135-L; I58-L | From 14deb5d2d49b52047c64bf2f40735c6fdc214514 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Wed, 5 Aug 2026 17:01:27 +0200 Subject: [PATCH 4/4] FE-1320: Sync comparison isolation authority --- TESTING_FINDINGS.md | 4 ++-- memory/PLAN.md | 4 ++-- memory/SPEC.md | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/TESTING_FINDINGS.md b/TESTING_FINDINGS.md index a1f4dbcb1..766a8fd7e 100644 --- a/TESTING_FINDINGS.md +++ b/TESTING_FINDINGS.md @@ -477,14 +477,14 @@ Observation: the interview advanced well past the splash into a populated graph Expected: one direct normal-width shell exposes a usable Brunch Specify exchange while preserving mission isolation and cleanup. Disposition: pass — meets FE-1215's focused stock-Pi/direct-Brunch smoke leaf (scope card consumed on completion; status in `memory/PLAN.md` §operator-comparison-workflow). -#### CS2 · private-mission isolation · high · pass with residual risk +#### CS2 · private-mission isolation · high · pass; placement risk closed Concern: whether the private mission leaked into target-visible input or was ingested from disk. Evidence: transcript scan of the session/`debug/trajectory.ndjson` — zero occurrences of mission-only phrasings (`private-mission`, `top-level-session-only`, `The PM wants`, `Decision latitude`, `Conversational and disclosure posture`) and zero references to the `private-mission.md`/`harness-setup.md` paths; the one matching phrase is the mission's sanctioned natural opening. Full tool-call audit: 20 calls (12 `ask`, 4 `mutate_graph`, 2 `present_digest`, 1 `present_candidates`, 1 `read`); the sole `read` targeted `src/agents/skills/propose/SKILL.md`; no `ls`/`find`/`grep` were used. Observation: no mission text, path, or wholesale payload entered the Brunch cwd/session; the spec content came from the elicitation exchanges, not file ingestion. Expected: the private-mission boundary holds without a separate actor process. Residual risk: `private-mission.md` is stored at the run root, two levels **above** the Brunch target cwd (`targets/brunch`), and the Brunch agent's active tools include generic `read`/`ls`/`find`/`grep` (per `debug/system-prompt.md`). Isolation held **behaviorally** (the agent did not traverse up), not by a filesystem jail; a differently-behaved contender or retry could `read ../../private-mission.md`. -Disposition: pass for this run; residual isolation-hardening routed to `ln-plan` as `comparison-mission-isolation-hardening` (`memory/PLAN.md`). Owner: that follow-up frontier. Re-entry trigger: any future comparison run, before treating placement isolation as guaranteed. +Disposition: closed by FE-1320. `/compare-specs` now gives each harness a fresh system-temporary external target root outside controller checkout, scratch, and retained trees; actual-entry-point tests reject the `../../private-mission.md` rival and preserve exact controller evidence through target cleanup. This closes ordinary target-relative ancestor traversal, not unrestricted absolute-path or whole-host discovery. The real Brunch + Claude conduct witness remains owned by `saved-mission-comparison-witness`. #### CS3 · harness cleanup · medium · unverified diff --git a/memory/PLAN.md b/memory/PLAN.md index 9416de873..4762030cd 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -132,7 +132,7 @@ Legacy link target; see Horizon. - **Objective:** prove that the corrected approachable saved-mission workflow works through its actual entry point and that mission revision affects future runs without rewriting historical evidence. - **Acceptance:** a stock Pi session runs the real `/compare-specs` prompt through ordinary-text approvals; the top-level session is the sole simulated-user actor and drives one direct Brunch shell then one direct Claude shell; the first run proves mission-consistent conduct, no mission leakage, honest outcomes/cleanup, unchanged target-authored documents, one aggregate notification, and a readable report; the operator then revises the mission and approves a second run whose edits do not rewrite the first run's retained snapshots. - **Verification:** outer only — operator-led first run plus revision/rerun through the real stock-Pi prompt, with artifact inspection and snapshot fingerprints. -- **Dependencies:** hard on operator scheduling; FE-1215's D134-L remediation landing is already satisfied. +- **Dependencies:** hard on operator scheduling; FE-1215 direct-control remediation and FE-1320 target-placement hardening are satisfied. - **Traceability:** req 24/A5-L; D70-L; D134-L/I67-L; FE-1210 evidence discipline; SPEC Verification Design `saved-mission-comparison-witness` gate. ### shared-session-host-tracer @@ -189,7 +189,7 @@ active: next: saved-mission-comparison-witness gated_by: operator availability - depends_on: landed FE-1215 workflow + dependencies_satisfied: FE-1215 direct control | FE-1320 external target placement shared-session-host-tracer retires: A47-L proves: one host-owned writable runtime + real TUI + React attachment diff --git a/memory/SPEC.md b/memory/SPEC.md index ff9bdded9..d7095017a 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -305,7 +305,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | D125-L | Headless ask discovery is process-local live state, not transcript scanning or a second event plane. Every no-UI ask mode registers its full D116-L payload in the live ask registry; handle-gated `session.openAsks` discovers it and `session.answerExchange` resolves the unchanged string broker contract, with per-mode decoding in the ask collector. `session.submitExchangeResponse` remains the distinct transcript-mutation path. The transcript-backed pending projection survives only for file/observer compatibility until legacy `present_question` reads retire. | [`src/session/TOPOLOGY.md`](../src/session/TOPOLOGY.md), [`src/rpc/TOPOLOGY.md`](../src/rpc/TOPOLOGY.md) | active — materialized 2026-07-13 | | D132-L | Standalone interactive web uses one cwd-scoped combined Brunch host with a target-addressed inventory of sealed, in-process Pi `AgentSession`s (2026-07-14). The same process serves React assets/WebSocket Brunch RPC and owns coordinator/graph authority; it does not construct `InteractiveMode`, expose raw Pi RPC, spawn one Pi child per session, host multiple projects, or promise in-flight survival across host restart. One durable session target has one driver/many observers and cannot be opened as duplicate writable runtimes; write leases wait for real same-session contention. Hosted-session mutations return the complete `LiveSessionHostResult` discriminated `{status}` union as JSON-RPC success payloads, including domain refusals; only malformed boundary input and thrown host failures use JSON-RPC errors. FE-1200 materialized the one-target path and validated simultaneous target isolation (A43-L). Depends on: D5-L, D10-L, D33-L, D39-L, D84-L; req 4, req 31. Supersedes: D10-L/D72-L read-only-sidecar posture and D84-L singleton/TUI-owned target topology. | [`src/app/TOPOLOGY.md`](../src/app/TOPOLOGY.md), [`src/session/TOPOLOGY.md`](../src/session/TOPOLOGY.md), [`src/rpc/TOPOLOGY.md`](../src/rpc/TOPOLOGY.md), [`src/web/TOPOLOGY.md`](../src/web/TOPOLOGY.md) | active — target-addressed host and concurrent-session isolation materialized 2026-07-14 | | D133-L | Web and TUI share transport-neutral presentation semantics, not platform components or separately-authored domain decoders (2026-07-14). Validated Brunch `toolResult.details` project to a shared semantic presentation model; LLM-context, TUI, and React adapters render that meaning for their audiences. Web hydrates from a named JSONL-derived product projection and overlays target-addressed live events, then refetches canonical truth at settlement/reconnect. The host emits neither ANSI/TUI strings nor ready-made HTML, and no chat mirror/event store is introduced. FE-1200 materialized the full required persisted family inventory: ordinary text; free-text, choice, choices, and bounded-questionnaire terminal read-back; candidate, review-set, and digest offers/continuations; and receipt-bearing review settlement. Live React controls answer free text and listed single/multi choices; bounded questionnaires remain answerable headlessly through D38-L's schema-tagged string/JSON envelope, without a dedicated React questionnaire form. Depends on: D17-L, D19-L, D104-L; req 12, req 17, req 32. | [`src/projections/TOPOLOGY.md`](../src/projections/TOPOLOGY.md), [`src/web/TOPOLOGY.md`](../src/web/TOPOLOGY.md) | active — full required-family coverage materialized 2026-07-15 | -| D134-L | The approachable `/compare-specs` control topology is one top-level project Pi session acting as the simulated user and driving exactly one comparison-harness interactive subshell at a time. It does not spawn a Pi actor that then opens a nested interactive shell. The top-level agent alone receives the private mission; each harness receives only its approved minimal framing and the user's natural messages. Shared top-level context and lane order are acceptable and disclosed for this exploratory workflow; isolation-sensitive studies continue to use FE-1210's separate rigorous campaign recipe. Ordinary text interaction is the stock-Pi baseline for choices and approvals; a custom structured-question tool may enhance presentation but is never required. Setup checks are bounded to actual selected-harness prerequisites—no throwaway Pi/Claude provider turns or synthetic actor launches on every run. Supersedes: FE-1215's unmaterialized fresh-nested-actor design note. | [`.pi/prompts/compare-specs.md`](../.pi/prompts/compare-specs.md), [`testing/comparisons/missions/README.md`](../testing/comparisons/missions/README.md); PLAN `operator-comparison-workflow` | active — remediation materialized and focused Brunch smoke witnessed 2026-07-17; full comparison witness deferred | +| D134-L | The approachable `/compare-specs` control topology is one top-level project Pi session acting as the simulated user and driving exactly one comparison-harness interactive subshell at a time. It does not spawn a Pi actor that then opens a nested interactive shell. The top-level agent alone receives the private mission; each harness receives only its approved minimal framing and the user's natural messages. Each harness works in a fresh system-temporary external target root outside the controller checkout, scratch, and retained trees; Brunch launches from its repository root and addresses that target through `--workspace`. This placement blocks ordinary target-relative ancestor traversal into controller-private run material, including the witnessed `../../private-mission.md` rival, but is not an OS sandbox and makes no whole-host isolation claim. Shared top-level context and lane order are acceptable and disclosed for this exploratory workflow; isolation-sensitive studies continue to use FE-1210's separate rigorous campaign recipe. Ordinary text interaction is the stock-Pi baseline for choices and approvals; a custom structured-question tool may enhance presentation but is never required. Setup checks are bounded to actual selected-harness prerequisites—no throwaway Pi/Claude provider turns or synthetic actor launches on every run. Supersedes: FE-1215's unmaterialized fresh-nested-actor design note. | [`.pi/prompts/compare-specs.md`](../.pi/prompts/compare-specs.md), [`testing/comparisons/missions/README.md`](../testing/comparisons/missions/README.md); PLAN `saved-mission-comparison-witness` | active — direct-shell remediation and FE-1320 target-placement hardening materialized; full comparison witness deferred | | D136-L | Petrinaut's common mechanical gate uses the full pinned HASH checkout. Before a candidate lane, the controller materializes the parent tree and exact handoff, runs only `corepack yarn install --immutable --mode=skip-build`, and requires tracked source to remain clean. After the lane terminates at `promotion_prepared`, closed focused builds launch the standalone `/optimization` route; a deterministic loopback optimizer grades scenario-first configuration, fixed/optimized bindings, objective direction, request construction, progress/completion/error/cancellation, upstream abort, same-origin secrecy, and source-backed accessibility semantics. Synthetic fixtures prove sensitivity but do not author stronger semantics than the calibrated source behavior. The broad `/processes/draft` host/iframe shell remains non-gating outer evidence. Depends on: D134-L, A49-L retirement. | [`src/dev/TOPOLOGY.md`](../src/dev/TOPOLOGY.md) | active — learning-first gate materialized 2026-07-22 | | D137-L | Historical replay preparation is experimental hygiene for fast product learning, not an adversarial security boundary (revised 2026-07-23). The controller materializes the pinned source tree into a fresh repository with no remote, adds the exact content-addressed packet, runs the one case-owned dependency recipe when required, checks source identity, packet bytes, and tracked cleanliness, then returns a lane-ready Brunch or Claude descriptor. Both lanes disable web/MCP surfaces where supported and bound file tools to the target, but the study reports this limitation rather than probing external services or proving host isolation. Depends on: A49-L retirement, D136-L. | [`src/dev/TOPOLOGY.md`](../src/dev/TOPOLOGY.md) | active — simplified for learning throughput | | D60-L | Agent context splits into pull / projection / render / surface, distinguishes graph-truth from active-context reads, and keeps `workspace.state` separate. Agent context (what the agent reasons over) spans `cwd` (filesystem kickoff heuristic — `.brunch?`, session count/length, README/markdown sizes, file counts), `graph` (overview/list/query), and `node` (variable-hop neighborhood). ... | [`src/graph/TOPOLOGY.md`](../src/graph/TOPOLOGY.md), [`src/projections/TOPOLOGY.md`](../src/projections/TOPOLOGY.md), [`src/agents/contexts/TOPOLOGY.md`](../src/agents/contexts/TOPOLOGY.md), [`src/app/TOPOLOGY.md`](../src/app/TOPOLOGY.md) | active | @@ -394,7 +394,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I64-L | Every hosted-session lifecycle operation, driver request, open-ask read/answer, and live event is addressed by durable `(specId, sessionId)`; one JSONL session has at most one writable hosted runtime and one active driver, while different targets may run concurrently and any number of observers may attach. Transport connection, route, or host-local object identity never substitutes for the durable target. | covered by FE-1200 host/RPC negative-space contracts plus `standalone-web-session-host.concurrency.test.ts`: two production-wired targets, duplicate-open/second-driver/cross-target rivals, target-local contiguous frames, overlapping asks, failure recovery, reconnect, and separate JSONL readback | D132-L; D33-L; req 31 | | I65-L | For every required product-visible Brunch transcript family, validated semantic details have one shared presentation projection and audience-specific adapters; after streaming settles or reconnects, the web session view equals a fresh JSONL-derived semantic projection modulo declared ephemeral progress. | covered by `session-presentation.test.ts` no-loss/malformed-detail cases for every required persisted terminal shape (including questionnaire read-back), `session-route.test.tsx` React render/answer cases for free text and listed single/multi choices, headless schema-envelope questionnaire coverage, distinct candidate/review-set/digest production settle/reconnect witnesses in `standalone-web-session-host.real-entry.test.ts`, the two-target concurrency differential, and receipt-bearing review settlement | D133-L; D17-L, D19-L; req 12, req 32 | | I66-L | Specify elicitation style is exactly one active-branch, last-entry-wins value in `interrogate | disambiguate | propose`; it remains prompt-visible across kicks and resume/session switch/tree navigation until the user changes it. One-shot process moves and dismissal cannot overwrite it. Style-less new sessions and operational-mode switches may open the menu automatically; resume/session switch/tree/abort may not. | planned FE-1187 active-branch rivals, prompt-composition tests, menu-juncture negative space, and live resume/tree walkthrough; establishing frontier: `walkthrough-remediation-2` | D98-L, D109-L | -| I67-L | During an approachable comparison run, the invoking Pi session is the sole simulated-user actor and no more than one comparison-harness interactive shell is live. No actor subprocess may own another interactive shell; the private mission remains outside every harness context/cwd; and every choice or approval remains operable through ordinary text when no custom question tool exists. | FE-1215 prompt/README review plus the focused stock-Pi, normal-width Brunch smoke are covered; the full Brunch + Claude run, report judgment, and revision-immutability proof belong to `saved-mission-comparison-witness` | D134-L; D70-L | +| I67-L | During an approachable comparison run, the invoking Pi session is the sole simulated-user actor and no more than one comparison-harness interactive shell is live. No actor subprocess may own another interactive shell; the private mission remains outside every harness context and outside the fresh system-temporary external target root; ordinary target-relative ancestor traversal cannot reach controller-private run material; and every choice or approval remains operable through ordinary text when no custom question tool exists. This is a placement boundary, not an OS sandbox or whole-host isolation claim. | FE-1215 covers direct stock-Pi control; FE-1320 actual-entry-point tests cover external target placement, the `../../private-mission.md` rival, target-root visibility, exact-output retention, and cleanup. The full Brunch + Claude run, report judgment, and revision-immutability proof belong to `saved-mission-comparison-witness`. | D134-L; D70-L | | I68-L | Every Brunch agent turn has one current, role-owned control block and one capability-honest resource surface: changed runtime state replaces stale Brunch prompt content across every supported provider carrier; every advertised resource is either readable under the agent's actual grant or fully bundled into its task; and no prompt instructs an agent to use a missing tool/resource. Context provenance preserves the distinct ownership and lifetime of spec posture, elicitation style, asking agenda, origination continuity, and on-demand reads. | covered by the landed provider-carrier replacement matrix, production recorder → persisted trajectory → report projection for advertised/read/provider-visible resource states, background grant/bundle/no-tools tests, behavioral control-ownership tests, and topology/import contract checks | D135-L; D58-L, D90-L, D91-L, D98-L, D101-L, D102-L, D118-L | | I69-L | Every scoped execution worker receives the exact approved content of each requirement its slice derives from, plus any target-visible public-contract material that governs that slice; graph reference codes or planner-authored paraphrase alone are insufficient. This content remains bounded to slice-linked target-visible truth and never includes controller-only oracle material. | structurally covered by exact title/body persistence, transitive dependency requirement resolution, unknown/duplicate/malformed-reference rejection, run-pinned hash-verified three-file packet staging, mutation/redaction/symlink rivals, sealed-worker brief rendering, synthesis/request integration, and serial/parallel artifact parity; the unchanged frozen Petri rerun remains outer evidence, not a merge gate | D126-L, D135-L; I58-L | @@ -778,7 +778,7 @@ Dev-loop artifacts route to gitignored `.fixtures/scratch///`, res | Middle | **Capture-quality evaluator + live conduct tracer** | `src/probes/__tests__/capture-quality-loop.test.ts` pins false-commit and gradient-routing scoring over fixed extraction fixtures; it does not prove current elicitor conduct. The live control/treatment proof belongs to `capture-ledger-tracer`, while sweep-window mechanics remain covered separately by session/projection tests (I30-L, A22-L, A40-L). | | Middle | **Subagent-reconciliation oracle battery (`subagent-reconciliation`)** | Four deterministic faux-substrate oracles for the foreground/background agent reconciliation. … | | Outer | Manual walkthrough with checklist | UX/presentation life: TUI chrome, spec/session picker, web shell feel, coherence visibility, elicitation usefulness. Adds: ambient-affordance rendering from establishment-offer structured-exchange facets; … | -| Outer | **Saved-mission comparison witness** (`saved-mission-comparison-witness`) | After FE-1215's D134-L remediation, a real stock-Pi `/compare-specs` Brunch + Claude run proves one top-level simulated-user actor can drive one direct, normal-width harness shell at a time; plain-text choices/approvals work without `ask_user_question`; private-mission isolation, target-visible evidence, cleanup, aggregate notification, and report usefulness remain visible. Revising and rerunning the saved mission then proves prior run snapshots remain byte-stable. No surrogate or simulated gate substitutes for these entry-point witnesses. | +| Outer | **Saved-mission comparison witness** (`saved-mission-comparison-witness`) | After FE-1215's direct-control remediation and FE-1320's structural target-placement hardening, a real stock-Pi `/compare-specs` Brunch + Claude run proves one top-level simulated-user actor can drive one direct, normal-width harness shell at a time; plain-text choices/approvals work without `ask_user_question`; the external target placement, target-visible evidence, cleanup, aggregate notification, and report usefulness remain visible under real harness conduct. Revising and rerunning the saved mission then proves prior run snapshots remain byte-stable. No surrogate or simulated gate substitutes for these entry-point witnesses. | | Inner | **FE-1230 case-contract validation** | Public specification, accessibility/serve contract, lane manifest, budgets, validity rules, and controller-only oracle manifest are schema-valid, content-addressed before lane launch, and path-separated so hidden material cannot enter a target cwd. | | Middle | **FE-1230 independent black-box browser + accessibility journeys** | After one target test/build and one static-server start, each manifest-declared journey opens a fresh browser context, establishes prerequisite state only through public controls/import, and retains a claim-linked `passed`, `setup_failed`, or `assertion_failed` verdict without blocking later journeys. The suite covers mount/accessibility, creation/full pointer drag release, weighted firing/reset/reload, invalid operations/cascade deletion, and JSON round-trip/new-clear behavior with per-journey console/module/network evidence. | | Middle | **FE-1230 reference Petri model differential + metamorphic checks** | A tiny controller-owned P/T reference model independently computes enablement and weighted firing; the rendered marking/enablement agrees before and after firing, reset is idempotent, reload returns to the initial marking, export/import preserves structure, and disabled firing plus invalid operations leave state unchanged. | @@ -867,7 +867,7 @@ The first required probe is M0: after manual TUI interaction, a checker proves ` ### Design Notes -- **Operator-led cross-product comparisons (FE-1215; D134-L remediation before later `saved-mission-comparison-witness` evidence).** The PM-facing comparison door is deliberately distinct from rigorous frozen-packet evaluation. One project Pi prompt conversationally creates or revises a rich private **agent-as-user mission** for a simulated user: their objective, context, priorities, preferences, constraints, knowledge, uncertainty, decision latitude, and conversational posture. The invoking top-level Pi agent receives that mission and directly performs the user's side of each interaction while driving exactly one comparison-harness subshell at a time; it must not spawn a Pi actor that opens another interactive shell. Each comparison harness receives only minimal visible framing plus the opening user message and subsequent mission-grounded answers. Harness selection and framing are run setup, not mission content. Ordinary conversational text is the portable baseline for operator choices and approvals; environment-specific structured-question tools are optional presentation only. Setup checks cover actual selected-harness prerequisites without synthetic actor/provider turns on every run. Editable missions live outside `.fixtures/` under `testing/comparisons/missions/`; each run snapshots the private mission, separately identified target-visible setup/interactions, and outputs under `.fixtures/runs/agent-as-user-comparison/` while temporary lane work stays in scratch. The readable operator report may expose the full private mission so elicitation can be compared against what each harness actually learned, but it keeps that baseline separate from target-visible evidence and declares no automatic winner or prescribed rubric. Because the top-level actor context spans sequential harnesses, this approachable workflow discloses order and does not claim the per-lane actor-process isolation required by rigorous frozen-packet studies; frozen reveal policies, matched budgets, blinding, fresh-per-lane actor sessions, structured adjudication, multi-run statistics, and scripted judges remain separate tools for focused improvement/regression claims. +- **Operator-led cross-product comparisons (FE-1215 + FE-1320; outer evidence remains `saved-mission-comparison-witness`).** The PM-facing comparison door is deliberately distinct from rigorous frozen-packet evaluation. One project Pi prompt conversationally creates or revises a rich private **agent-as-user mission** for a simulated user: their objective, context, priorities, preferences, constraints, knowledge, uncertainty, decision latitude, and conversational posture. The invoking top-level Pi agent receives that mission and directly performs the user's side of each interaction while driving exactly one comparison-harness subshell at a time; it must not spawn a Pi actor that opens another interactive shell. Each comparison harness receives only minimal visible framing plus the opening user message and subsequent mission-grounded answers. Harness selection and framing are run setup, not mission content. Each harness receives a fresh system-temporary external target root outside controller checkout, scratch, and retained trees; Brunch remains rooted in its repository and addresses the target through `--workspace`. This structurally closes ordinary target-relative ancestor traversal into controller-private run material without claiming an OS sandbox or unrestricted absolute-path isolation. Ordinary conversational text is the portable baseline for operator choices and approvals; environment-specific structured-question tools are optional presentation only. Setup checks cover actual selected-harness prerequisites without synthetic actor/provider turns on every run. Editable missions live outside `.fixtures/` under `testing/comparisons/missions/`; each run snapshots the private mission, separately identified target-visible setup/interactions, and outputs under `.fixtures/runs/agent-as-user-comparison/` while temporary lane work stays in scratch. The readable operator report may expose the full private mission so elicitation can be compared against what each harness actually learned, but it keeps that baseline separate from target-visible evidence and declares no automatic winner or prescribed rubric. Because the top-level actor context spans sequential harnesses, this approachable workflow discloses order and does not claim the per-lane actor-process isolation required by rigorous frozen-packet studies; frozen reveal policies, matched budgets, blinding, fresh-per-lane actor sessions, structured adjudication, multi-run statistics, and scripted judges remain separate tools for focused improvement/regression claims. - **FE-1230 execution-comparison oracle boundary.** Execution cases are distinct from private elicitation missions: the human-approved specification and a minimal public runtime/accessibility contract are visible to every lane, while exact browser journeys, reference-model states, expected results, claim mapping, and adversarial fixtures remain controller-only and outside every lane cwd. The public contract requires a static production build at `dist/`, `npm run build`, `npm test`, and stable accessible roles/names for the canvas and named controls; it does not prescribe framework, source topology, implementation decomposition, test library, or internal state model. The versioned `petri-editor-browser-v2` suite tests/builds once, then runs every declared journey from a fresh browser context with public-only setup, per-journey runtime evidence, and non-blocking claim-linked verdicts that distinguish harness/setup failure from product assertion failure. Brunch stops at `promotion_prepared` and never lands. The retained first pair remains immutable; replay/promotion waits for its exact artifact paths. Mutants, masked/process judging, repetitions, and generalized campaign machinery are deferred until one valid end-to-end path exists. The tracer pins `anthropic/claude-opus-4-8` in both products, but same model does not imply equivalent hidden prompting or thinking controls. - **I69-L worker-contract-carry oracle boundary.** Requirement identity is graph-native, so external heading renumbering is not itself a defect; the worker must receive the current graph code together with the exact approved requirement content. Planner definitions remain useful task summaries but are not an authority substitute. Public packet identity may be carried by a bounded content-addressed location rather than duplicating every packet byte in every prompt. Controller-only oracle journeys, expected states, and fixture paths remain excluded. Structural delivery and negative space gate merges; one unchanged Petri rerun checks provider reality without turning an n=1 model outcome into a reliability claim. - **Brownfield historical-replay oracle boundary (`brownfield-comparison-cases`).** Historical implementations bootstrap independent claims and focused rivals; matching code, decomposition, diff shape, or architecture is never a gate. Brunch replays only FE-1201's backend host-landing behavior through public `/brunch:land`, a controller-supplied settled session under `PI_OFFLINE=1`, and an independent temporary-repository Git model. Petrinaut replays only PR #9051's optimization UI in the full pinned HASH checkout; pre-existing optimizer backend/API capability is disclosed baseline. D136-L makes standalone `/optimization` the deterministic fake-provider gate after the one immutable install and closed focused builds; broad `/processes/draft` integration remains non-gating. D137-L creates the same pinned remote-free source plus exact packet for both executors and records its lightweight contamination ceiling. A merged-reference calibration is rerun only when the source, public contract, or oracle changes, not before every provider attempt. Real optimizer, fully provisioned host/iframe, repetitions, and masked reviews remain optional follow-up evidence.