Skip to content

FE-1320: Structural private-mission isolation for /compare-specs - #415

Open
lunelson wants to merge 5 commits into
ln/fe-1318-canonical-document-reconciliationfrom
ln/fe-1320-comparison-mission-isolation
Open

FE-1320: Structural private-mission isolation for /compare-specs#415
lunelson wants to merge 5 commits into
ln/fe-1318-canonical-document-reconciliationfrom
ln/fe-1320-comparison-mission-isolation

Conversation

@lunelson

@lunelson lunelson commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Why

Our /compare-specs workflow runs two AI products side by side on the same task to compare their output. The instructions that drive that comparison are private, sensitive context that must never leak into what either product being tested can see or touch — including via something as simple as a relative file path that happens to reach outside the test's own folder.

What

  • Each comparison run now gets its own fresh, temporary folder living completely outside our repository and its mission files, closing off any path from a tested product's workspace back to anything private.
  • Verified this holds even against a deliberately adversarial path designed to escape the sandboxed folder.
  • Confirmed that everything each tested product is supposed to produce is still preserved exactly, and that past historical test evidence is untouched by this change.
  • This closes the structural/file-placement half of the isolation problem. A live end-to-end run with real product accounts, and proving that revising a mission doesn't rewrite past results, is separate operator-scheduled follow-up work.

How to test

  1. npm run test:comparison passes.
  2. The isolation-specific cases (visibility boundary, adversarial path rejection, output/history preservation) are green.

lunelson commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@lunelson lunelson changed the title FE-1320: Isolate comparison mission targets FE-1320: Structural private-mission isolation for /compare-specs Aug 7, 2026
@lunelson
lunelson marked this pull request as ready for review August 7, 2026 16:16
Copilot AI lite review requested due to automatic review settings August 7, 2026 16:16
@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Changes are prompt procedure, documentation, and dev tests—no runtime controller or harness execution code paths. Residual risk is procedural (operators following the updated prompt) rather than production auth or data handling.

Overview
Structural isolation for /compare-specs — each harness now gets a fresh system-temporary target root outside the controller checkout, scratch, and retained run trees, with preflight checks that ancestor paths hold no private-mission.md or other controller-private material. Brunch still launches from the repo root via --workspace; generic harnesses use the external path as cwd. The prompt explicitly closes the CS2 ../../private-mission.md rival and states this is placement hardening, not an OS sandbox.

Evidence retention — teardown now saves harness-visible transcripts from the controller-owned interactive_shell record (not from the external target) and copies only unchanged harness-authored documents before removing the external target root.

Tests and docscompare-specs-prompt.test.ts adds prompt-string oracles plus filesystem fixtures for target visibility, adversarial path rejection, and post-cleanup retention. PLAN/SPEC/TESTING_FINDINGS mark comparison-mission-isolation-hardening complete and defer the live Brunch + Claude witness to saved-mission-comparison-witness.

Reviewed by Cursor Bugbot for commit 1a721c7. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens /compare-specs private-mission isolation by changing the workflow to allocate each harness a fresh, system-temporary external target root outside controller-owned trees, and updates the written procedure + planning docs to match.

Changes:

  • Update the /compare-specs operator prompt to require per-harness external target roots and specify copy/cleanup sequencing.
  • Add/extend tests around the prompt contract and the external-target placement invariants.
  • Update SPEC/PLAN/testing notes to record FE-1320 as the closeout of the prior placement-risk finding.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
TESTING_FINDINGS.md Updates CS2 disposition to reflect FE-1320 closing the placement risk.
src/dev/tests/compare-specs-prompt.test.ts Adds prompt assertions and filesystem-based isolation/retention tests.
memory/SPEC.md Updates D134-L and I67-L to include external target placement boundary language.
memory/PLAN.md Marks the isolation hardening frontier as completed and removes it from “Next”.
docs/archive/PLAN_HISTORY.md Archives the FE-1320 closeout entry and its evidence summary.
.pi/prompts/compare-specs.md Implements the new external-target-root procedure and retention/cleanup ordering in the operator prompt.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +131 to +132
await rm(externalRoot, { recursive: true });
await cp(scratchRun, retainedRun, { recursive: true });
Comment thread .pi/prompts/compare-specs.md Outdated
lunelson and others added 5 commits August 10, 2026 11:08
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 <cursoragent@cursor.com>
Keep the external-target contract coupled to the existing single-actor, immutable-evidence, and prompt-owned workflow boundaries.

Co-authored-by: Cursor <cursoragent@cursor.com>
Return I67-L to its pre-frontier content so any evidence refresh remains owned by ln-sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings August 10, 2026 09:12
@lunelson
lunelson force-pushed the ln/fe-1318-canonical-document-reconciliation branch from 4f5538e to d63c33a Compare August 10, 2026 09:12
@lunelson
lunelson force-pushed the ln/fe-1320-comparison-mission-isolation branch from 14deb5d to 1a721c7 Compare August 10, 2026 09:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/dev/tests/compare-specs-prompt.test.ts:17

  • pathExists treats any access() failure as “does not exist”. That can make the test pass even if the path exists but is unreadable (e.g., EACCES). Restrict the false case to ENOENT and rethrow other errors so the assertion is reliable.
const pathExists = async (path: string): Promise<boolean> => {
  try {
    await access(path);
    return true;
  } catch {

src/dev/tests/compare-specs-prompt.test.ts:100

  • These assertions rely on filesystem enumeration order (readdir / recursive traversal). Directory entry order is not guaranteed across platforms/filesystems, so this can be flaky in CI. Sort the collected names/paths (or use toContain) before asserting exact equality.
      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']);

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1a721c7. Configure here.

- **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 <fresh-target-cwd>`, 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 <fresh-target-cwd>`, 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval omits external target paths

Medium Severity

Step 4 now allocates per-harness system-temporary external target roots, but step 5 still only displays scratch and retained paths. Operators approve the isolation-critical placement without seeing those target roots, so a skipped or in-tree allocation can pass review unnoticed.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1a721c7. Configure here.

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 and verify no comparison-harness shell or process remains. Before dismissing the completed shell record, save its exact harness-visible transcript from the controller-owned `interactive_shell` record into controller-owned scratch evidence. Copy only any unchanged harness-authored document from the external target into that scratch evidence; retain target-cwd/session identity, final process status, and cleanup notes. Then dismiss the shell record, remove that harness's external target root, and verify it is gone before starting another harness.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retention conflicts with absolute targets

Medium Severity

The procedure now requires retaining target-cwd identity for system-temporary external roots while still forbidding workstation-absolute retained paths, and it never directs the established &lt;ephemeral-workspace&gt; placeholder. Agents following both rules either drop target identity or leak absolute temp paths into retained evidence.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1a721c7. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants