fix(cli): refuse conflicting ledger targets before halt (#797)
A valid owner pin and a v2 ledger that disagreed on target still halted the ledger's binding, so a business-only edit or a foreign ledger could redirect the stop. The ledger target now needs owner corroboration; otherwise the owner-verified pin replaces it, or the check refuses.
This commit is contained in:
1 parent
fce831c053
commit
85e1ec5d15
4 files changed
+71
No files matched your search
@@ -6202,3 +6202,5 @@ After: personally authored three-file skills/ms-ux-improvement coordinator packa
|
||||
2026-11-06 | #797 ui797 support R3 P1 corrections verified | Before: 19e6cb0f1 still emitted WebRTC STUN/TURN UDP despite its flag, and unavailable ledger directories bypassed halt and terminated serve. After: mandatory Bubblewrap network/PID namespaces with an isolated, up loopback; a single namespace TCP relay over a 0600 Unix socket to the existing origin-only proxy, preserving private NSS/TLS validation, control socket permissions and awaited browser-before-proxy cleanup. Real Chromium regressions from both origins create nine ICE agents against six independent IPv4/IPv6/127.0.0.2 STUN/TURN UDP listeners and three TCP listeners; zero datagrams/connections, existing WebSocket/HTTP/SSO/CA negatives retained. Campaign initialization now exclusively pins owner/config/binding/ledger evidence beside the campaign, independently of the ledger volume; missing/renamed/0500 storage still attempts supported CLI halt, explicitly reports nondurable latch/receipt, preserves evidence and never recreates an epoch. Serve survives failures and reasserts each tick/Resume, retaining its stop across storage recovery; invalid/foreign/changed targets refuse. Older intact ledger format preserved. Final sequential whole files: identity 8, browser 10, budget 43 = 61 pass / 0 fail / 0 skip / 0 cancelled. All R1/R2 probes rerun: zero forbidden TCP/UDP, all four storage variants count one halt, original latch preserved, stale-lock/corruption/cap/cutoff/identity negatives retained, SIGTERM/SIGKILL/startup-failure survivors zero. Original R1 general probe exits 1 at its correctly refused split-route setup. Independent final sweep: zero owned processes/listeners at fixed and 43 recorded endpoints; scratch sweep parser corrected before its terminal run. Exactly packages/cli/live plus this single entry and one SESSIONS line; linked-worktree queue refused, no queue/forge writes, push, stash, model call, .53 contact, host network/DNS/trust changes or unrelated-session cleanup. Local implementation ready for independent exact-head review; owner-service successful quiescence remains a later acceptance gate. Evidence/report: /mnt/storage/scratch/mosaic-lead/mvp3/ui797-support-fix-r3-evidence/ and ui797-support-fix-r3-report.md.
|
||||
|
||||
2026-11-06 UTC | #797 ui797 support R4 review corrections verified | Before: unchanged 905d313cd reproduces 13 host pathname D-Bus connections/11 AUTH EXTERNAL requests, four damaged-sidecar cases with zero halt attempts, and a coherent epoch edit accepting 4 of 8 real stored tokens against cap 5. After: empty Bubblewrap mount view with read-only system/browser dependencies, minimal dev/proc, private tmpfs tmp/run/home, browser-owned writable evidence/NSS mounts and explicit environment; actual Chromium D-Bus traffic zero on HTTP/HTTPS, diagnostic pathname/abstract/TCP attempts fail, UDP listener sees zero, synthetic 0600 file/config/runtime paths hidden. Private NSS trust and unchanged production Console OIDC sign-in pass. Version-2 ledger independently pins config bytes; sidecar read/parse/mode/deletion and semantic corruption stop with explicit evidenceErrors and counted supported halts, preserving damage. Serve retains verified target/config/epoch evidence and revalidates marker/root/store/config before targeting and immediately before halt. Normal-path independent campaign hashes/epochs must agree; real coherent-epoch-edit and zero pin hash halt without journal reset. Modern sidecar deletion stops; legacy version-1 requires intact independent config-byte proof and an unpinned legacy ledger refuses, without migration/reinitialization. README requires Bubblewrap/user/network/PID namespaces on every browser host and states identity is HTTPS 17443/oidc behind the Console TLS forwarder, never 17442. Sequential whole files budget 89, identity 8, browser 13, request-lifecycle-live 2 = 112 pass / 0 fail / 0 skip / 0 cancelled. Both unchanged reviewer probes first fail semantically on baseline, then all six controls pass; three further retained probes pass, 31 network records/12 denied CONNECTs and both WebRTC origins retain zero forbidden traffic. External-reader positive control, changed-path 64-hex pin search, ten syntax checks and whitespace pass. Development missing import and explicit PWD assertion corrected; evidence retained. Cleanup finds zero processes from this run, no fixed test listeners and empty/removed TMPDIR; 29 older other-session daemons untouched after overbroad sweep was narrowed to this run. Five packages/cli/live paths plus this entry and one SESSIONS line only; original log bytes preserved. Queue read refused linked worktree; no queue/forge writes, push, stash, model spend, real secrets, .53/live-service/host-trust changes or attribution trailers. Local implementation ready for independent exact-commit review; owner-service quiescence remains outside this author charter. Report/evidence: /mnt/storage/scratch/mosaic-lead/mvp3/ui797-support-fix-r4-report.md and ui797-support-fix-r4-evidence/.
|
||||
|
||||
2026-11-06 UTC | #797 ui797 support R5 conflicting-ledger correction verified | Before: fce831c05 let a valid pin and a v2 ledger disagree on target and still halted the ledger's binding; the reviewer's unchanged target-conflict probe sent 8 halts to the wrong business/config (business-only corruption and a foreign ledger, check and serve startup). After: checkTarget runs corroborate() on every tick. A ledger target directs a halt only when the owner campaign, the owner pin, or the live binding of the owner campaign names the same target; otherwise the owner pin replaces it only when it matches the owner campaign hash, the live config/marker/root/store and the live config digest (evidence 'ledger-target-changed'); anything else refuses with campaign-instance-changed. Retained-target comparison stays as the second guard. Four new regressions (check, serve startup, running service, no corroboration) fail 4/4 on r4 source and pass after. Probe after fix: 0 wrong halts in all six cases. Sequential whole files budget 93, identity 8, browser 13, request-lifecycle-live 2 = 116 pass / 0 fail / 0 skip. No push, stash, .53, live service or model spend. Evidence: /mnt/storage/scratch/mosaic-lead/mvp3/ui797-support-r5-fix-evidence/.
|
||||
@@ -2069,3 +2069,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-11-06 | ui797-support-fix-r2 implementation sub-agent | #797 corrections on feat/ui797-support above bd8340b9b, confined to packages/cli/live plus one appended build entry and this line | All eight review findings addressed with regression tests; sequential whole files identity 8/browser 8/budget 33 (49 pass, 0 fail/skip/cancel), all review probes rerun, owned-process/listener sweeps empty; forced proxy chosen after browser-only containment failed. Local commit authorized; independent review pending. Queue refused linked worktree; no canonical queue/forge writes, push, stash, live/.53 contact, model spend, product-runtime/role/contract/dependency changes or attribution trailers. Report: /mnt/storage/scratch/mosaic-lead/mvp3/ui797-support-fix-r2-report.md.
|
||||
2026-11-06 | ui797-support-fix-r3 implementation sub-agent | #797 two P1 corrections above 19e6cb0f1 on feat/ui797-support | Mandatory browser network namespace/private proxy relay and independently bound unavailable-ledger halt with persistent serve reassertion; final sequential whole-file suites 61 pass, 0 fail/skip/cancel; all R1/R2 probes rerun, zero forbidden TCP/UDP, storage halts counted and latch preserved, zero owned processes/listeners. One BUILD-LOG entry and packages/cli/live changes only; local commit authorized, independent review pending. Queue refused linked worktree; no push/stash/forge/model/.53/host network-DNS-trust or unrelated-session action. Report: /mnt/storage/scratch/mosaic-lead/mvp3/ui797-support-fix-r3-report.md.
|
||||
2026-11-06 UTC | ui797-support-fix-r4 implementation sub-agent | #797 review corrections on feat/ui797-support above 905d313cd | Minimal browser mount/environment boundary; independent config-byte proof and retained binding keep halt on sidecar damage; normal-path campaign hash/epoch agreement; unconditional namespaces and HTTPS 17443/oidc docs. Baseline failures retained; six controls corrected; whole files 112 pass, 0 fail/skip/cancel; five retained probes exit 0, zero forbidden traffic, no owned survivors/listeners, TMPDIR removed. Five support paths, one appended BUILD entry and this line; prefix bytes verified. Queue refused linked worktree; no queue/forge/push/stash/model/secrets/.53/live or unrelated-session action. Local author candidate for independent review. Report: /mnt/storage/scratch/mosaic-lead/mvp3/ui797-support-fix-r4-report.md.
|
||||
2026-11-06 UTC | MVP3 lead #797 (Opus) | #797 support R4 P1 fix on feat/ui797-support above fce831c05 | Conflicting ledger targets now need owner corroboration or are replaced by the owner-verified pin, else refused; 4 regressions fail on r4 and pass after; whole files 116 pass/0 fail; reviewer probe 0 wrong halts. Local commit; independent r5 review next. No push/stash/.53/live action.
|
||||
@@ -137,6 +137,23 @@ function loadLedger(ledger) {
|
||||
(p.version===2 && !/^[0-9a-f]{64}$/.test(p.configHash))) fail('ledger-manifest-invalid');
|
||||
return p;
|
||||
}
|
||||
// A ledger's target may direct a halt only when independent evidence names the
|
||||
// same target: the owner campaign itself, the owner's pin, or the instance the
|
||||
// current owner campaign resolves to. A conflicting ledger never authorizes its
|
||||
// own target. The owner-verified pin replaces it only when the pin agrees with
|
||||
// the owner campaign and the live config/marker/root/store; anything else refuses.
|
||||
function corroborate(p,pin,{campaign,ledger}) {
|
||||
const same=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
|
||||
let owner=null, live=null, liveHash=null;
|
||||
try { owner=campaignAt(campaign); live=binding(owner.c); liveHash=digest(readFileSync(owner.c.config,'utf8')); } catch { /* No owner evidence. */ }
|
||||
if(owner && owner.hash===p.hash && same(owner.c,p.campaign)) return p;
|
||||
if(pin && pin.ledger===ledger && same(pin.binding,p.binding) && pin.configHash===p.configHash) return p;
|
||||
if(live && same(live,p.binding)) return p;
|
||||
if(owner && pin && pin.ledger===ledger && pin.hash===owner.hash && (!pin.campaign || same(pin.campaign,owner.c)) &&
|
||||
same(pin.binding,live) && pin.configHash===liveHash)
|
||||
return {campaign:owner.c,hash:pin.hash,configHash:pin.configHash,binding:pin.binding};
|
||||
fail('campaign-instance-changed');
|
||||
}
|
||||
function checkTarget({campaign,ledger}, retained=null) {
|
||||
let p, pin, reason=null, writable=true;
|
||||
const evidenceErrors=[];
|
||||
@@ -159,6 +176,10 @@ function checkTarget({campaign,ledger}, retained=null) {
|
||||
p={campaign:pin.campaign??c,hash:pin.hash,configHash:pin.configHash,binding:pin.binding};
|
||||
}
|
||||
}
|
||||
if(p.configHash) {
|
||||
const resolved=corroborate(p,pin,{campaign,ledger});
|
||||
if(resolved!==p) { p=resolved; evidenceErrors.push('ledger-target-changed'); }
|
||||
}
|
||||
const c=p.campaign;
|
||||
// Pin the target before any halt. A changed config cannot redirect the stop into live data.
|
||||
checkBinding(p.binding,c); outsideRuntime(ledger,{dataRoot:p.binding.dataRoot},c.config);
|
||||
|
||||
@@ -448,3 +448,50 @@ test('semantic sidecar target damage stops using independent config/marker/root/
|
||||
assert.equal(f.halts.length,1);assert.equal(f.halts[0].business,f.c.business);assert.equal(readFileSync(path,'utf8'),damaged);
|
||||
}
|
||||
});
|
||||
|
||||
// A conflicting ledger never authorizes its own target (support r4 P1).
|
||||
function conflictingLedger(t,kind) {
|
||||
const f=setup(t,100),path=join(f.ledger,'campaign.json');
|
||||
const g=kind==='foreign'?setup(t,100,{business:'unrelated-business'}):null;
|
||||
const alter=()=>{
|
||||
if(g) { writeFileSync(path,readFileSync(join(g.ledger,'campaign.json'))); return; }
|
||||
const p=JSON.parse(readFileSync(path));p.binding.business='unrelated-business';p.campaign.business='unrelated-business';writeFileSync(path,JSON.stringify(p));
|
||||
};
|
||||
const wrong=()=>f.halts.filter(b=>b.business!==f.c.business||b.config!==f.config||b.dataRoot!==f.dataRoot);
|
||||
return {f,g,alter,wrong};
|
||||
}
|
||||
test('a business-only or foreign ledger is replaced by the owner-verified target in check',async t=>{
|
||||
for(const kind of ['business-only','foreign']) {
|
||||
const {f,alter,wrong}=conflictingLedger(t,kind);alter();
|
||||
const result=await check(f.opts,{now:()=>NOW,read:()=>[row()],halt:f.halt});
|
||||
assert.equal(result.stopped,true);assert.ok(result.evidenceErrors.includes('ledger-target-changed'));
|
||||
assert.equal(f.halts.length,1);assert.deepEqual(wrong(),[]);
|
||||
}
|
||||
});
|
||||
test('serve startup never retains a conflicting ledger target',async t=>{
|
||||
for(const kind of ['business-only','foreign']) {
|
||||
const {f,alter,wrong}=conflictingLedger(t,kind),controller=new AbortController(),reports=[];alter();
|
||||
await serveBudget(f.opts,{interval:1,signal:controller.signal,now:()=>NOW,read:()=>[row()],halt:f.halt,report:r=>{reports.push(r);if(reports.length===3)controller.abort();}});
|
||||
assert.equal(reports.length,3);assert.ok(f.halts.length>=1);assert.deepEqual(wrong(),[]);
|
||||
for(const r of reports) assert.ok(r.evidenceErrors.includes('ledger-target-changed'));
|
||||
}
|
||||
});
|
||||
test('a running service halts only its retained owner target when the ledger changes under it',async t=>{
|
||||
for(const kind of ['business-only','foreign']) {
|
||||
const {f,alter,wrong}=conflictingLedger(t,kind),controller=new AbortController(),reports=[];
|
||||
await serveBudget(f.opts,{interval:1,signal:controller.signal,now:()=>NOW,read:()=>[row()],halt:f.halt,report:r=>{
|
||||
reports.push(r);if(reports.length===1)alter();if(reports.length===3)controller.abort();
|
||||
}});
|
||||
assert.equal(reports[0].stopped,false);
|
||||
for(const r of reports.slice(1)) { assert.equal(r.stopped,true);assert.ok(r.evidenceErrors.includes('ledger-target-changed')); }
|
||||
assert.ok(f.halts.length>=1);assert.deepEqual(wrong(),[]);
|
||||
}
|
||||
});
|
||||
test('a conflicting ledger without corroborating owner evidence refuses every halt',async t=>{
|
||||
for(const kind of ['business-only','foreign']) {
|
||||
const {f,alter}=conflictingLedger(t,kind);alter();
|
||||
const pin=f.campaign+'.binding.json',damaged=JSON.parse(readFileSync(pin));damaged.configHash='0'.repeat(64);writeFileSync(pin,JSON.stringify(damaged));
|
||||
await assert.rejects(check(f.opts,{now:()=>NOW,read:()=>[row()],halt:f.halt}),/campaign-instance-changed/);
|
||||
assert.equal(f.halts.length,0);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user