review: preserve row 91 static findings pending round 2 (#1585)

This commit is contained in:
2026-10-12 19:03:54 -05:00
parent 58a3766d48
commit 4f85812d38
4 changed files with 88 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# Row 91 round 1 review, in progress
Candidate 39d6eb7d43917d22ac9606c645cca1fd39fbcfe4a3c5b6079c635b54c73cc900, eight paths, detached base c974cafa. Reconstructed under ~/filbert-scratch/r91-review/wt with Darkwing's build.patch applied by git apply --index and node_modules symlinked. Manifest 8/8; initial candidate-path drift check empty (static-receipt.json). No tests or verdict yet.
Decision 128 lifecycle question, static findings: Registry.running consults the host's in-memory sessions, not broker claims or role-release events. A voluntary role.release or human revoke therefore does not remove a live session from the launch guard. The normal exit handler calls endRun and removes the registry entry only after the child's close event. Start failure kills the child and awaits that same close promise before ending a bound run. Launcher.close waits for every child close and then for the exit handlers to finish; its kill timeout does not itself release the role. Recovery is the known exception: a fresh Registry starts empty and recover ends leftover launches after the five-second wait without confirming death. That is #244 under decision 128. No separate early-release path has been identified in these static call sites yet; runtime/integration verification remains pending.
Main allowlist risk still being investigated: unsafe() walks the top-level .git and validates only its config; update() then invokes status with --ignore-submodules=none. A session can add a tracked gitlink and a nested working-tree repository with its own config. Hypothesis: status enters that nested repository and runs a clean filter there even though the top-level config passed the allowlist. The SAFE hook/fsmonitor/protocol overrides do not disable filter drivers. An isolated regression probe, submodule-filter.test.mjs, is prepared and NOT RUN. It creates only private temporary repositories and a marker outside the fixture workspace but inside the probe's own temp directory, fingerprints the approved root, and expects no marker from ensureCheckouts. It must be run only after the reserved test slot clears before this is called a finding.
Primary references for the hypothesis: Git status documents modified/untracked submodule inspection with ignore-submodules=none (https://git-scm.com/docs/git-status), and attributes documents configured clean filter commands (https://git-scm.com/docs/gitattributes). Those references motivate the probe; they do not prove this candidate's behavior.
Darkwing is running row 97's browser suite/mutants in the reserved slot and explicitly asked to hold all tests, including row 91. Do not execute the probe or full gate until coordinated scratch-clear. Subsequent acceptance requires seat, cli, bus and harness suites plus every scripts/test-*.sh on this rebased candidate. Candidate edits, policy changes, protected git operations, live fleet operations and Console/bus changes remain out of scope.
2026-10-13 coordination update: Sage requires the row 91 round 2 packet, rebased onto 6b0215aa including row 90 e918a915, before any probe, suite or verdict. Preserve these round 1 static findings; the bundle files acquire row 90's two lines each and new digests. Darkwing owns the r2 gate slot, then Filbert owns the row 91 run including the submodule probe. After finishing, send slot clear directly to Researcher for row 97 r2 and copy Sage.
Decision 127 static boundary check on landed row 90: runner.mjs imports bus/client.mjs, which imports BusError from bus/broker.mjs, which imports tasks/src/requirements.mjs. RUNNER_FILES binds the requirements module read-only, while requirements.json is not mounted. The module performs no file read at import time; the host Broker constructor loads its data. Row 91 imports checkout.mjs only from the host cli/launcher.mjs; the bundle receives checkout metadata and prompt text, without importing the checkout implementation. checkout.mjs belongs outside RUNNER_FILES and runs before session spawning. Confirm these facts against the r2 candidate and its sandbox import-walk test during the gate.
@@ -0,0 +1,19 @@
{
"candidate": "39d6eb7d43917d22ac9606c645cca1fd39fbcfe4a3c5b6079c635b54c73cc900",
"base": "c974cafa",
"head_at_check": "e918a915a8b0fab0befe171f069b3f3204d758ed",
"manifest_matches": {
"packages/cli/README.md": true,
"packages/cli/src/launcher.mjs": true,
"packages/cli/tests/launcher.test.mjs": true,
"packages/harness/src/bundle.mjs": true,
"packages/harness/tests/bundle.test.mjs": true,
"packages/seat/README.md": true,
"packages/seat/src/checkout.mjs": true,
"packages/seat/tests/checkout.test.mjs": true
},
"drift_paths": [
"packages/harness/src/bundle.mjs",
"packages/harness/tests/bundle.test.mjs"
]
}
@@ -0,0 +1,53 @@
// Independent host-git probe. Only private temporary repositories and a marker in this
// probe's own temp directory are used. Do not run while another seat holds the test slot.
import test from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, readdirSync, lstatSync, readlinkSync, existsSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { createHash } from 'node:crypto';
const candidate=process.env.MOSAIC_REVIEW_ROOT ?? resolve(process.env.HOME,'filbert-scratch/r91-review/wt');
const {ensureCheckouts,gitEnv}=await import(pathToFileURL(join(candidate,'packages/seat/src/checkout.mjs')));
const quote=s=>"'"+s.replaceAll("'","'\"'\"'")+"'";
function git(cwd,...args){
const r=spawnSync('git',['-c','user.name=probe','-c','[email protected]',...args],{cwd,env:gitEnv(),encoding:'utf8'});
assert.equal(r.status,0,`git ${args.join(' ')}: ${r.stderr}`);return r.stdout.trim();
}
function fingerprint(dir){
const h=createHash('sha256');
function walk(d){for(const name of readdirSync(d).sort()){
const p=join(d,name),s=lstatSync(p);h.update(`${p}\0${s.mode}\0${s.nlink}\0`);
if(s.isDirectory())walk(p);else if(s.isSymbolicLink())h.update(readlinkSync(p));else if(s.isFile())h.update(readFileSync(p));
}}walk(dir);return h.digest('hex');
}
test('host checkout must not execute a nested submodule clean filter',async t=>{
const dir=mkdtempSync(join(tmpdir(),'filbert-r91-submodule-'));
t.after(()=>rmSync(dir,{recursive:true,force:true}));
const root=join(dir,'root'),workspace=join(dir,'workspace');mkdirSync(root);mkdirSync(workspace);
git(root,'init','-q','-b','main');writeFileSync(join(root,'README'),'root\n');git(root,'add','README');git(root,'commit','-qm','root');
const projects={stack:{root}};
assert.equal((await ensureCheckouts({workspace,projects}))[0].outcome,'cloned');
const clone=join(workspace,'stack'),sub=join(clone,'sub');mkdirSync(sub);
git(sub,'init','-q','-b','main');writeFileSync(join(sub,'payload'),'one\n');writeFileSync(join(sub,'.gitattributes'),'payload filter=escape\n');
git(sub,'add','payload','.gitattributes');git(sub,'commit','-qm','submodule');
writeFileSync(join(clone,'.gitmodules'),`[submodule "sub"]\n\tpath = sub\n\turl = ${root}\n`);
git(clone,'add','.gitmodules','sub');git(clone,'commit','-qm','add submodule locally');
// Top-level config stays exactly the accepted fresh-clone config. The payload resides
// only in the nested repository's config and working tree, outside the top-level walk.
const marker=join(dir,'host-command-ran');
const script=join(sub,'.git','filter.sh');
writeFileSync(script,`#!/bin/sh\nprintf ran > ${quote(marker)}\ncat\n`,{mode:0o755});
git(sub,'config','filter.escape.clean',`/bin/sh ${quote(script)}`);
writeFileSync(join(sub,'payload'),'two\n');
const top=readFileSync(join(clone,'.git','config'),'utf8');
assert.ok(!top.includes('filter.') && !top.includes('include') && !top.includes('submodule.'));
assert.equal(existsSync(marker),false,'setup did not execute the payload');
const before=fingerprint(root);
const result=await ensureCheckouts({workspace,projects});
const evidence={result,marker_created:existsSync(marker),root_unchanged:fingerprint(root)===before};
console.log(JSON.stringify(evidence));
assert.equal(evidence.root_unchanged,true,'approved root unchanged');
assert.equal(evidence.marker_created,false,'host git ran an untrusted nested filter outside the workspace');
});