Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05af615fba | ||
|
|
d65b898998 | ||
|
|
824e5de109 | ||
|
|
6b898fab46 |
No files matched your search
@@ -5342,3 +5342,5 @@ Gander: after Duckworth-0100 design approval with C1-C5, prepared the two host-s
|
||||
2026-10-22T17:42:16Z | Sage | Row 253 (#1758, Gander, Duckworth) LANDED 48e450ce8. Before: refactor at aca16404c; case-sudo calls had no supervised session check and the M21 survivor hid behind a fast-exit fixture (mos-1297). After: a fast-forward of 22 commits; manifest 591020a1 rechecked with 0 mismatches; run 5 whole gate green with M1-M40 killed, the M38 PID equals SID assertion added under mos-1298; Duckworth APPROVE rev 3252, note rev 3253, done rev 3254; #1758 comment 30701 and closed; P19 416 comment 719. N1 (fast child reported as exit 85) is DEFERRED as Vikunja 532.
|
||||
|
||||
2026-10-22T18:11:57Z | Sage | Row 275 (#1780, Morgana, Duckworth) LANDED 3410df7c3. Before: refactor at 48e450ce8; viewers had no verb list of their own and board reads were not refused to users. After: a fast-forward of 3 commits; manifest 39847b15 (4 paths) blobs match; gate 17:52:12Z-18:05:00Z green (whole files 7/22/6, bus 834, cli 861, harness 151, business 231, seat 167, 10 scripts with 3 named skips), M6 and M10 killed; Duckworth APPROVE rev 3260 (comment 30717); note and done by sage-2897; #1780 comment 30728 and closed. N1 (board refusal test as a loop over BOARD_VERBS) is non-blocking.
|
||||
|
||||
2026-10-22 18:11:58 UTC | Gander row276 #1781 prepared, gate pending | Before: row253 left5legacy Case1 AS_USER calls unsupervised across sudo pty sessions. After: candidate uses its unchanged supervisor with per-call inner/outer budgets, separate streams, strict display-only failure trailer, fake-only paired process-model cases and15unique mutants. Duckworth0182 B1 corrected the plan before product code;0183 cleared build. Parse-only Bash checks pass; no Node, behavioral test, host, unit or config action. Runtime gate and publication await Sage.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"base": "48e450ce8",
|
||||
"removed_assertions": [
|
||||
" assert.deepEqual([...new Set(cmds)].sort(), [CLI, PRECHECK, CLAUDE, NODE].sort());"
|
||||
],
|
||||
"authority": "mos-1326",
|
||||
"all_other_original_assertion_lines_preserved": true,
|
||||
"command_level_talk_and_precheck_checks_unchanged": true
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# Row276 build, gate pending
|
||||
|
||||
Base48e450ce8, branchgander/row276. Claim rev3255, gander-276-start-1. Brief b0d0b7f76; Mos1324 starts build, Sage2893 approves plan and authorizes local commits. Duckworth0182 B1 corrected the exit-status plan before product code;0183 approves that correction with C1-C4. PLAN.md records decisions. No runtime/Node check has run.
|
||||
|
||||
All5legacy calls use legacy_run: talk320/330 with unchanged conversation300 and concurrent F5 scan; precheck200/210 preserves its existing180s Claude probe; versions5/7; PM list10/12. Both scripts keep the exact old supervisor payload. Case2 code changes only its setuid-member header. Header N2 names inner-cleanup refusal for a uid-changing unkillable member. Root capture/tools N1 counterparts are M10/M11.
|
||||
|
||||
Supervisor0 is the sole pass authority. The dropped-uid builtin-only Node runner exits its original command status. Supervisor87 stays87; only its ffail display number may come from the strict final newline-terminated decimal1..255 trailer. A0trailer under87 gives display87, never exited0. Missing/bad256/999/leading-zero/sign/no-newline records also give87 and the fixed reason. Private display files live in root's mktemp WORK directory, explicitly0700, never in user data. Root reads at most65536stderr bytes using a validated system tail. NODE is invoked only after uid drop; ownership fence means non-user-writable, not non-executable by the user. Test stand-ins retain0755.
|
||||
|
||||
F3/F4 retain the old displayed code and failure strings. F4's stripped stderr follows its stdout when merged; its precheck contract is whole-check success/failure plus last diagnostic, and no existing test asserts interleaving. F6/F7/F10 now require supervisor0 before interpreting stdout, intentionally tightening their old pipeline behavior. Existing content/refusal assertions remain. The one existing wiring assertion now requires five supervised Bash launcher calls plus the two isolated metadata/probe calls; direct talk/precheck argv assertions remain unchanged.
|
||||
|
||||
Tests are written before source, but intentionally unrun because Sage holds all execution until CLEAR. New tests cover each call under exec/hop, exact inner reasons/no-leftovers, stalled outer reason with explicit R3 fixture cleanup, streams, child statuses1/7/81/137, forged trailers before/after actual status, grammar edge refusals, bounded stderr and stdout that looks successful under supervisor failure. Existing case1/case2 whole files are the gate scope because they import the shared fake module. No actual sudo, host or live service is used. Static SUPERVISOR-SHAS and UNIQUENESS verify bytes/targets. Bash-n on source and15variants passes; Node parse/behavior remains pending.
|
||||
|
||||
Gate request: intended RED/GREEN against old script; case1-accept.test.mjs andcase2-unit.test.mjs whole under timeout-k10 900 initially; M1-M15 with passing fresh controls, exact restores and no timeout counted as kill; all10scripts/test-*.sh whole under SHELL-LIMITS.json, based on the longest retained row253/196/278 receipts (ceil3x+120). Busy check before every file. TMPDIR from mktemp-d /mnt/storage/scratch/tmp/gXXX. First unexpected failure/survivor/timeout stops for Sage/Mos. Report named skips,timing,full counts and cwd sweep. No passing claim until that gate.
|
||||
|
||||
Manifest excludes BUILD-LOG for a doc-only append if Sage needs it. DEFERRED's conditional closure lists F5,N1,N2 individually; R3 and532 remain. No new dependency installation, live state, credentials, ports or units touched. Push waits for a Sage gap. No queue writes after claim.
|
||||
|
||||
|
||||
## mos-1326 wiring assertion authorization
|
||||
|
||||
packages/cli/tests/case1-accept.test.mjs:726. The old direct-command shape cannot express the required supervisor. All other old assertion lines are byte-identical, including command-level talk and precheck argv checks. ASSERTION-PRESERVATION.json records the exact static comparison. Structured sudo-argv.jsonl records every argv element before fake path translation. The replacement compares full arrays, never a substring. Its only variable text is the random message marker, separately pinned by the unchanged original talk assertion. Both supervisor bytes and literal bounds are pinned.
|
||||
|
||||
Old:
|
||||
```js
|
||||
assert.deepEqual([...new Set(cmds)].sort(), [CLI, PRECHECK, CLAUDE, NODE].sort());
|
||||
```
|
||||
|
||||
New:
|
||||
```js
|
||||
assert.equal(asMosaic.length,7,'two metadata/probe calls and five legacy calls');
|
||||
assert.deepEqual([...new Set(cmds)].sort(), ['/usr/bin/bash']);
|
||||
// mos-1326: assert every argv element, not flattened/substr command detection.
|
||||
const source=read(SCRIPT);
|
||||
const supervisor=source.split("<<'MOSAIC_TESTER_SUPERVISOR'")[1].split('\n').slice(1).join('\n').split('\nMOSAIC_TESTER_SUPERVISOR')[0]+'\n';
|
||||
assert.equal(createHash('sha256').update(supervisor).digest('hex'),'17b881513c35107e26f78c3722bc4a2927d206b197597be0bdbb2c8a8588bb4c');
|
||||
const runner=/LEGACY_COMMAND_RUNNER='([\s\S]*?)'\nlegacy_display_rc/.exec(source)[1];
|
||||
const f7=/F7_JS='([\s\S]*?)';\ncheck_f7/.exec(source)[1];
|
||||
const raw=read(join(h.hc,'sudo-argv.jsonl')).trim().split('\n').map(line=>JSON.parse(line));
|
||||
const legacy=raw.filter(a=>a[0]==='-u'&&a[1]==='mosaic'&&a[4].startsWith('PATH='));
|
||||
const marker=legacy[0][20].split(':')[0];
|
||||
const argv=(tag,inner,command)=>['-u','mosaic','-H','env',`PATH=/opt/mosaic-stack/node-${NODE_PIN}/bin:/usr/local/bin:/usr/bin:/bin`,
|
||||
'/usr/bin/bash','-c',supervisor,'--',String(inner),'/usr/bin/setsid','/usr/bin/sleep',NODE,'-e',runner,'--',tag,...command];
|
||||
assert.deepEqual(legacy,[
|
||||
argv('talk',320,[CLI,'talk','chief',marker+': this is the Case 1 acceptance; reply with one short line.','--business','mosaic-stack','--tester','--wait','300']),
|
||||
argv('precheck',200,[PRECHECK,'--business','mosaic-stack','--profile','factory']),
|
||||
argv('claude-version',5,[CLAUDE,'--version']),
|
||||
argv('pm-list',10,[NODE,'-e',f7,'--',`${HOME}/.config/mosaic-dev/businesses/mosaic-stack.json`,CLI,'mosaic-stack']),
|
||||
argv('node-version',5,[NODE,'--version']),
|
||||
]);
|
||||
```
|
||||
|
||||
The existing target-uid config query already requires executing this same NODE before any F check, so a root-only executable mode that prevents the dropped uid running it fails preflight. No new root NODE execution is introduced.
|
||||
|
||||
|
||||
## Static review0186 and upstream drift
|
||||
|
||||
Duckworth0186 statically approves164e16647/6887825e4, subject to green gate and log review. N1: M2 is the stand-in for the brief's diagnostic outer-bound mutant. It removes the shared legacy_run outer timer; its named killer runs only the precheck diagnostic in exec/hop with an independently guarded stalled supervisor, exact outer-timeout reason and bounded return. The fixed per-call production budget is also pinned by the inventory assertion. Keep the accepted M1-M15 scope. N2: M5 must die at the streams test because the wiring test reads runner text from source. N3: stderr disk growth retains the old talk.err risk; only root's tail read is bounded, with no new cap claim.
|
||||
|
||||
Sage2898 publication: original branch6887825e4 pushed after range check. Drift48e450ce8..94c33a43c on five manifest paths is only31added DEFERRED lines, inserted into its gaps section; no deletion or source/test drift. UPSTREAM-DRIFT.diff/json retain it. Final integration must keep those entries and row276's appended closure; no integration or manifest rewrite has happened yet. No tests or Node ran.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"docs/plans/DEFERRED.md": "660f0997a686412148bc437de67e2932dd946ca4921fb68d68c29feaf34f390d",
|
||||
"packages/cli/tests/case-sudo-fixture.mjs": "eac5c83f43056bdeb5781101307e17d4bc01f03e97e02f4f5e930436d7e43535",
|
||||
"packages/cli/tests/case1-accept.test.mjs": "fe4bc897497d83ef247b229cd49acf78b1a607af44e803a8a0a866115ff8cf87",
|
||||
"scripts/case1-accept.sh": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"scripts/case2-unit.sh": "d2709a1da6c9a03f4c8b31bf57ad00df4c0ae62a5cbc0e7588cdceb9dc0e2aa1"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"whole_files": [
|
||||
"packages/cli/tests/case1-accept.test.mjs",
|
||||
"packages/cli/tests/case2-unit.test.mjs"
|
||||
],
|
||||
"shells": [
|
||||
"scripts/test-auth.sh",
|
||||
"scripts/test-conductor.sh",
|
||||
"scripts/test-config.sh",
|
||||
"scripts/test-discord.sh",
|
||||
"scripts/test-extension-package.sh",
|
||||
"scripts/test-foundation.sh",
|
||||
"scripts/test-land-work.sh",
|
||||
"scripts/test-queue.sh",
|
||||
"scripts/test-release.sh",
|
||||
"scripts/test-task.sh"
|
||||
],
|
||||
"mutants": [
|
||||
"M1",
|
||||
"M2",
|
||||
"M3",
|
||||
"M4",
|
||||
"M5",
|
||||
"M6",
|
||||
"M7",
|
||||
"M8",
|
||||
"M9",
|
||||
"M10",
|
||||
"M11",
|
||||
"M12",
|
||||
"M13",
|
||||
"M14",
|
||||
"M15"
|
||||
],
|
||||
"red_pattern": "row276 talk legacy inner timeout",
|
||||
"node_seconds": 900,
|
||||
"estimate_minutes": "12-18; no row276 measured runtime yet"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Row276 manifest, unrun candidate
|
||||
|
||||
Source 164e166479378caf0702ab8b1eb3871b54219da3, base48e450ce8, branchgander/row276. Five-path manifest digest b0fdd91183d7e1911ed327e32c03b8de7f0efd4c86bd143fa0ad54390358ee63. BUILD-LOG excluded for append-only doc sweep. SOURCE.json pins committed blobs; no Node or behavior run. Duckworth reviews candidate and15mutants (changes.json), including mos-1326 exact wiring and preserved old assertions. Gate scope and stop rules in BUILD.md; next publication waits for Sage gap.
|
||||
|
||||
|
||||
sage-2901 integration onto94c33a43c: source6b898fab465c200db14313a49c07c06b5dff285d, newbranchgander/row276-94c3; manifest2af7e8bafcbf0bb977a26ab1f57371b8453c7a6659dc7233a68f186ef7b5d5f2. Only DEFERRED changes among prior manifest entries. Four source/test blobs identical to164e16647. integration-94c3/BLOB-PROOF.json and range-diff.txt retain evidence. No execution.
|
||||
@@ -0,0 +1,5 @@
|
||||
660f0997a686412148bc437de67e2932dd946ca4921fb68d68c29feaf34f390d docs/plans/DEFERRED.md
|
||||
eac5c83f43056bdeb5781101307e17d4bc01f03e97e02f4f5e930436d7e43535 packages/cli/tests/case-sudo-fixture.mjs
|
||||
fe4bc897497d83ef247b229cd49acf78b1a607af44e803a8a0a866115ff8cf87 packages/cli/tests/case1-accept.test.mjs
|
||||
46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900 scripts/case1-accept.sh
|
||||
d2709a1da6c9a03f4c8b31bf57ad00df4c0ae62a5cbc0e7588cdceb9dc0e2aa1 scripts/case2-unit.sh
|
||||
@@ -0,0 +1,23 @@
|
||||
# Supervised legacy Case calls implementation plan
|
||||
|
||||
Goal: finish row253 F5 on base48e450ce8, brief docs/plans/2026-10-22_case-sudo-legacy-followup.md (b0d0b7f76), row276/#1781. Reviewer Duckworth. No live host, units, config, ports or credentials. Tests and Node wait for Sage CLEAR; publication waits for a gap.
|
||||
|
||||
Architecture: keep TESTER_SUPERVISOR byte-identical in both scripts. A case1 legacy launcher uses the validated system Bash/setsid/sleep tools after sudo drops uid, with a separate trusted GNU-timeout outer bound and private notice channel. stdout stays streamed to the caller, stderr stays separate. No root import of copy code. All five original calls are covered; no exception remains.
|
||||
|
||||
Bounds (inner/outer seconds): talk320/330, with its unchanged --wait300 conversation and concurrent F5 scan; precheck200/210, preserving its existing timeout180 Claude probe; Claude version5/7; PM launches10/12; Node version5/7. Test extraction substitutes shorter bounds, with tests pinning the production values. Root stderr notice distinguishes outer-timeout from child137. Existing supervisor codes/reasons apply.
|
||||
|
||||
Exit-status compatibility (Duckworth0182 B1): the inline builtin-only Node runner exits with its command's own status. Supervisor0 is the only success authority. On87 (inner-command), root may parse a final stderr trailer only to save a display code for ffail text; legacy_run still returns87 even if that trailer says0. A missing/bad trailer gives display87 plus the fixed inner-command reason, never pass. All other supervisor reasons ignore the trailer. Root saves display-only status in a private WORK file so background talk/command substitutions can report original exit numbers without changing their pass condition. F3/F4 use that number only in existing failure text; no old assertion changes.
|
||||
|
||||
Trailer grammar: one last, newline-terminated line `MOSAIC_LEGACY_STATUS=<decimal>` with canonical decimal0..255 (0 or nonzero-leading up to3 digits). The runner writes it after command stderr and exits with the actual status. A child/escaped descendant may spoof display diagnostics, but can never change supervisor failure into success. Tests cover an earlier forged0 then actual7, an appended forged0 after a real7, malformed/missing records and statuses137/81.
|
||||
|
||||
Root bounds trailer processing to the last65536bytes through a root-validated system tail; oversized diagnostics are explicitly truncated. stdout remains streamed. stderr's retained tail is emitted without only the final valid trailer. On supervisor0 the trailer is ignored as status; on87 it can alter display only. F6/F7/F10 now check supervisor rc before considering stdout, while retaining their original successful output. They capture stderr privately; F4 merges emitted stdout and stripped stderr at its existing capture, so stderr is emitted after buffered capture. Talk keeps its two files. The NODE runner executes only after uid drop. Its existing F9 root-only path check means uid ownership/no group-or-other write, not owner-only execute; activation's executable mode remains required. Tests pin executable-as-user behavior.
|
||||
|
||||
Files: scripts/case1-accept.sh (launcher, five call sites, header); scripts/case2-unit.sh (setuid-member header note only); packages/cli/tests/case-sudo-fixture.mjs (fake-mode cases/seams); packages/cli/tests/case1-accept.test.mjs (per-call tests and extraction); docs/plans/DEFERRED.md (bounded closure when acceptance passes). Row276 packet keeps manifest, mutants and test scope. Existing assertions remain.
|
||||
|
||||
- [ ] Extend fake-only tests for all5 calls under exec and hop: healthy stdout/stderr and exit status, blocking descendants, PID/SID ownership, exact inner reason, no leftover; stalled supervisor outer reason and explicit fixture cleanup residual; status/trailer invalid refusal. Talk retains300s argv and separate diagnostics, background scan.
|
||||
- [ ] Implement legacy launcher and five call sites; remove the remaining AS_USER array. Preserve all row253 supervisor bytes and root-only command fences.
|
||||
- [ ] Add root-side capture/tools counterparts for case1 (row253 N1), header setuid-member inner-cleanup refusal (N2), and exact timeout-vs-child137 tests.
|
||||
- [ ] Prepare mutants: drop talk supervision; drop outer bound on one diagnostic; bypass individual call, wrong/unchecked SID, trailer0 turns failed supervision into success, missing stream/display-code preservation; case1 tools/capture mutants. Unique target and byte-identical restore checks.
|
||||
- [ ] Duckworth reads candidate/manifest/mutants. On Sage CLEAR, intended RED/GREEN, each touched test whole, all mutants with passing controls and no timeout counted as kill, ten whole shell suites under recorded3x bounds. Report counts,timing,skips,sweep; stop on unexpected red/survivor/timeout for ruling.
|
||||
|
||||
Review focus: instant command exit retains fail-closed inner-session residual532; hostile same-uid setsid-away or killed/stalled supervisor remains row253 R3, never claimed cleanup; a member changing uid yields inner-cleanup; no stdout is treated as authority; no outer-timeout parsed from caller stderr.
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"authority": [
|
||||
"mos-1275",
|
||||
"sage-2659"
|
||||
],
|
||||
"scope": "Own Gander row196 receipt set documented for row278, plus the complete row278 run3 shell phase. Row253 old C4 results do not carry. Receipts choose timeouts only.",
|
||||
"consulted_records": [
|
||||
"ec1e7fad8:agents/gander/work/queue-196/evidence/suite-results-run1.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/evidence/suite-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r2/mutants/results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r2/run2/base-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r2/run2/killers/results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r3/gate/base-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r3/gate/killers/results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r3/gate/sso-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r4/gate/base-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r4/gate/killers/results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r4/gate/sso-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r4/gate/whole-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r5/gate/whole-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r5/resume/summary.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r5/resume/whole-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r6/base-results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r6/first-attempt/results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r6/killers/results.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r6/summary.json",
|
||||
"ec1e7fad8:agents/gander/work/queue-196/r6/whole-results.json",
|
||||
"385a7194c:agents/gander/work/queue-278/run3/whole/results.json"
|
||||
],
|
||||
"scripts": {
|
||||
"scripts/test-auth.sh": {
|
||||
"recorded_duration_seconds": 1.9081228229999543,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 126,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "dd4720cb0cbecd2fc523a318f2222d21d257c8c5889996a3d69e522ee971a707",
|
||||
"previous_maximum_seconds": 1.9081228229999543,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r6/whole-results.json",
|
||||
"row278_run3_seconds": 0.816
|
||||
},
|
||||
"scripts/test-conductor.sh": {
|
||||
"recorded_duration_seconds": 8.057173820000026,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 145,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "da84150467494a4c979759f400854411bd93a7efce981139757bf526d688a23b",
|
||||
"previous_maximum_seconds": 8.057173820000026,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r6/whole-results.json",
|
||||
"row278_run3_seconds": 6.078
|
||||
},
|
||||
"scripts/test-config.sh": {
|
||||
"recorded_duration_seconds": 3.1678915339999367,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 130,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "3301c1fabf616bb961d62f6decd0a46dadb0f399b8ba7f3c2ca60023d2b24c78",
|
||||
"previous_maximum_seconds": 3.1678915339999367,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r6/whole-results.json",
|
||||
"row278_run3_seconds": 1.868
|
||||
},
|
||||
"scripts/test-discord.sh": {
|
||||
"recorded_duration_seconds": 8.776727847000002,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 147,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "a5dcf783c51a5d5d184a357f91ebf9fa1c4e054f0cb84a3d1d7668f6156e025e",
|
||||
"previous_maximum_seconds": 8.776727847000002,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r5/resume/whole-results.json",
|
||||
"row278_run3_seconds": 7.431
|
||||
},
|
||||
"scripts/test-extension-package.sh": {
|
||||
"recorded_duration_seconds": 3.01285702399991,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 130,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "5d7b687f16fd1a9414b229501177d530ba3af4eadd3cac319b33f38fdba9bf99",
|
||||
"previous_maximum_seconds": 3.01285702399991,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r5/resume/whole-results.json",
|
||||
"row278_run3_seconds": 1.366
|
||||
},
|
||||
"scripts/test-foundation.sh": {
|
||||
"recorded_duration_seconds": 49.367,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 269,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "2e11038e7f784ec4f539eb768f63bab0fab9c9940334388a1fe2d47ab9e2b008",
|
||||
"previous_maximum_seconds": 39.70872647900006,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r5/resume/whole-results.json",
|
||||
"row278_run3_seconds": 39.901
|
||||
},
|
||||
"scripts/test-land-work.sh": {
|
||||
"recorded_duration_seconds": 16.854,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 171,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "c9d1d5c9b006d5c12e5f796a9828361059f8e0410846fc55c070c0148b15cef4",
|
||||
"previous_maximum_seconds": 12.623263413000037,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r5/resume/whole-results.json",
|
||||
"row278_run3_seconds": 11.338
|
||||
},
|
||||
"scripts/test-queue.sh": {
|
||||
"recorded_duration_seconds": 35.155,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 226,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "c270f181d1fa8b6d42ff4131c339beba786a8fd620bb66f8afdcec05e8b10868",
|
||||
"previous_maximum_seconds": 23.443742031000088,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r5/resume/whole-results.json",
|
||||
"row278_run3_seconds": 21.158
|
||||
},
|
||||
"scripts/test-release.sh": {
|
||||
"recorded_duration_seconds": 12.168674453999964,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 157,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "be706dff832257bd4781abeabd19faa4ac16edf9a4af0c96457e33cf3bb6deb3",
|
||||
"previous_maximum_seconds": 12.168674453999964,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r5/resume/whole-results.json",
|
||||
"row278_run3_seconds": 2.67
|
||||
},
|
||||
"scripts/test-task.sh": {
|
||||
"recorded_duration_seconds": 36.783798266,
|
||||
"receipt": "3b91cfdaf:agents/gander/work/queue-253/run5-gate/SUMMARY.json (with earlier maximum retained)",
|
||||
"limit_seconds": 231,
|
||||
"derivation": "ceil(3 * longest successful whole-file receipt) + 120s headroom",
|
||||
"script_sha256": "10c4abb70bf9b83e617089b06a5436cc7fa6b866a176458b22128c3a15163f94",
|
||||
"previous_maximum_seconds": 36.783798266,
|
||||
"previous_receipt": "ec1e7fad8:agents/gander/work/queue-196/r5/resume/whole-results.json",
|
||||
"row278_run3_seconds": 13.843
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "6b898fab465c200db14313a49c07c06b5dff285d",
|
||||
"base": "94c33a43c0f14e619782e82497c49b0dd6d80de4",
|
||||
"branch": "gander/row276-94c3",
|
||||
"manifest": "2af7e8bafcbf0bb977a26ab1f57371b8453c7a6659dc7233a68f186ef7b5d5f2",
|
||||
"paths": {
|
||||
"docs/plans/DEFERRED.md": "660f0997a686412148bc437de67e2932dd946ca4921fb68d68c29feaf34f390d",
|
||||
"packages/cli/tests/case-sudo-fixture.mjs": "eac5c83f43056bdeb5781101307e17d4bc01f03e97e02f4f5e930436d7e43535",
|
||||
"packages/cli/tests/case1-accept.test.mjs": "fe4bc897497d83ef247b229cd49acf78b1a607af44e803a8a0a866115ff8cf87",
|
||||
"scripts/case1-accept.sh": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"scripts/case2-unit.sh": "d2709a1da6c9a03f4c8b31bf57ad00df4c0ae62a5cbc0e7588cdceb9dc0e2aa1"
|
||||
},
|
||||
"mutants": 15,
|
||||
"tests": "NOT RUN; all runtime and Node checks await Sage CLEAR",
|
||||
"static_checks": [
|
||||
"bash-n both scripts",
|
||||
"bash-n15variants",
|
||||
"git diff --check",
|
||||
"unique-target checks",
|
||||
"unchanged supervisor bytes",
|
||||
"old assertion-line multiset except mos-1326 authorization"
|
||||
],
|
||||
"original_source": "164e166479378caf0702ab8b1eb3871b54219da3"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"base": "48e450ce8",
|
||||
"bytes": 2763,
|
||||
"sha256": "c828087085a1dfdade55c0fb1083f9a2dff8d9b72885708186136497711c9bae",
|
||||
"both_scripts_and_base_byte_identical": true
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
[
|
||||
{
|
||||
"id": "M1",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "f1afa0d8ed98fb96d9d4bbc9e0f4390aeb70f7545375566278d93228b2fe6a9f"
|
||||
},
|
||||
{
|
||||
"id": "M2",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "feba9b8cc38477da84189e1347e370c860313d8c7de9fd9d616e5645694b3119"
|
||||
},
|
||||
{
|
||||
"id": "M3",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "b313e6062f095957086d391dbab0055ef083e8714d1a22e6dd8b99e61a6af162"
|
||||
},
|
||||
{
|
||||
"id": "M4",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "4e9f287d33e7501512d1b58f20587625f67269e1e4302a18b6b36d3d36d53de4"
|
||||
},
|
||||
{
|
||||
"id": "M5",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "b3a7a3ee876316a47b03be8e4e2d8f608d3b1b3f697dd7643089246971c34f68"
|
||||
},
|
||||
{
|
||||
"id": "M6",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "d43e63e0c470695dca61da098ef0260b8cc8d2a76b861fd094459d86b9e9a9f6"
|
||||
},
|
||||
{
|
||||
"id": "M7",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "7cc325a87cd8def5466d6869829a14863af971f7923c90c405a88c21b1c04123"
|
||||
},
|
||||
{
|
||||
"id": "M8",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "b9a33bb8d55296f91dd3b461855c79b53d4b851f24ddd928e6ba6f833baa8521"
|
||||
},
|
||||
{
|
||||
"id": "M9",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "5a95979c618f618ebed2d19f13c6ac1ac1dbc9fcacda7f71a1e5233da51d3c66"
|
||||
},
|
||||
{
|
||||
"id": "M10",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "25c1547df78165f382dcdff3f5a28246ab3cfa480e48aa6892303b59428a1dde"
|
||||
},
|
||||
{
|
||||
"id": "M11",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "2155715c941fb196e9599aa6eb61d3990c650e133c7f6702b64dcc0c35922138"
|
||||
},
|
||||
{
|
||||
"id": "M12",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "1439cf809601ce04af67ff5a6169940ac6a5a562c1c6d563c688002157bf56f3"
|
||||
},
|
||||
{
|
||||
"id": "M13",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "7c9ee12d5dcdd10dda5b50cc379fae31b08a80c3a42c86466b4df1f31a450800"
|
||||
},
|
||||
{
|
||||
"id": "M14",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "ac361aa2f9f01a2f5cc5c4a2392765886540efe9b1c77b178fd81d0432e0bd32"
|
||||
},
|
||||
{
|
||||
"id": "M15",
|
||||
"occurrences": 1,
|
||||
"before": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"after": "39d18b5ea2905bafbe7860db5342768c46b5b9d99b2f187047617593e6b2e007"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
diff --git a/docs/plans/DEFERRED.md b/docs/plans/DEFERRED.md
|
||||
index 0baa52809..7e393b060 100644
|
||||
--- a/docs/plans/DEFERRED.md
|
||||
+++ b/docs/plans/DEFERRED.md
|
||||
@@ -487,6 +487,37 @@ at every gate. Started 2026-09-12 during the control board MVP.
|
||||
touches the webui build: install the pinned esbuild in the gate checkout
|
||||
so the check runs, or record why it can't. (2026-10-22)
|
||||
|
||||
+- **The case supervisor reports a fast child as an inner-session failure.**
|
||||
+ (Row 253 N1, mos-1297 item 5.) A child that exits before the supervisor
|
||||
+ verifies its session is reaped first, so the supervisor exits 85
|
||||
+ (inner-session) instead of the child's own code. Mos measured rc85 in 39
|
||||
+ of 40 fast-child bash runs. It fails closed, so it is not a 10-27 defect;
|
||||
+ row 253 fixed only its own fixture (PYTHON_STUB execs python3). The
|
||||
+ product fix is Vikunja 532, after 10-27. (2026-10-22)
|
||||
+
|
||||
+- **boardSend /api/seen returns 400 for a scan failure after writeMark.**
|
||||
+ (Webby N2 on row 198, ruled sage-2867.) The mark is written, then the
|
||||
+ rescan fails, and the client gets 400 as if its request were bad. Webby
|
||||
+ suggests 500. It does not hold the 198 gate; binds the next row that
|
||||
+ touches serve.mjs. (2026-10-22)
|
||||
+
|
||||
+- **Stale "row 203" text in the access command.** (Quackerjack, row 203
|
||||
+ BUILD.md follow-up.) packages/cli/src/access.mjs lines 26-27, its help
|
||||
+ string at line 36 and line 44, and packages/cli/README.md line 1107 say
|
||||
+ peer homes stay unread "until row 203" adds the federation key. Row 203
|
||||
+ changes only scripts/mosaic-config.mjs and test-config.sh, so once it
|
||||
+ lands those lines point at a row that did not do the work. Binds the next
|
||||
+ row that edits access.mjs. (2026-10-22)
|
||||
+
|
||||
+- **A remote outage can make the first library lookup outlast the tool
|
||||
+ timeout.** (Row 224 residual, sage-2819.) In each 60 s window the first
|
||||
+ lookup still runs an inline fetch, and that fetch can take longer than the
|
||||
+ 60 s tool timeout. Bound the inline fetch in a later row. (2026-10-22)
|
||||
+
|
||||
+- **A sharee's session.list stays refused.** (Row 206, sage-2818, Q7.)
|
||||
+ Whichever of row 193's session wiring or the share wiring lands second
|
||||
+ wires it. (2026-10-22)
|
||||
+
|
||||
## Queue
|
||||
|
||||
Moved to `docs/plans/QUEUE.md` on 2026-09-13. This file holds only gaps.
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"base": "48e450ce8",
|
||||
"upstream": "94c33a43c",
|
||||
"manifest": "b0fdd91183d7e1911ed327e32c03b8de7f0efd4c86bd143fa0ad54390358ee63",
|
||||
"changed": [
|
||||
"docs/plans/DEFERRED.md"
|
||||
],
|
||||
"stat": " docs/plans/DEFERRED.md | 31 +++++++++++++++++++++++++++++++\n 1 file changed, 31 insertions(+)\n",
|
||||
"source_test_path_drift": false,
|
||||
"detail": "31 added lines, no deletions, inserted in upstream gaps section. This is not an exact-tail append. Preserve these upstream entries and append row276 conditional closure at final integration. No rebase performed yet; keep original branch.",
|
||||
"tests": "NOT RUN"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": {
|
||||
"packages/cli/tests/case1-accept.test.mjs": 900,
|
||||
"packages/cli/tests/case2-unit.test.mjs": 900
|
||||
},
|
||||
"basis": "No row276 timing yet; initial900s per whole file; Sage may adjust before CLEAR."
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
[
|
||||
{
|
||||
"id": "M1",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": " # WORK is root's private mktemp directory. The dropped uid cannot write it.",
|
||||
"new": " # WORK is root's private mktemp directory. The dropped uid cannot write it.\n [ \"$tag\" != talk ] || TESTER_SUPERVISOR='shift 3; exec \"$@\"'",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 talk legacy inner timeout"
|
||||
},
|
||||
{
|
||||
"id": "M2",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "LC_ALL=C \"$TS_TIMEOUT\" --verbose --signal=KILL \"${outer}s\" \"$TS_BASH\" -c '\n errors=",
|
||||
"new": "LC_ALL=C \"$TS_BASH\" -c '\n errors=",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 precheck outer bound"
|
||||
},
|
||||
{
|
||||
"id": "M3",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "\"$setsid_bin\" \"$@\" & child=$!",
|
||||
"new": "\"$@\" & child=$!",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 talk legacy inner timeout"
|
||||
},
|
||||
{
|
||||
"id": "M4",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "if [ \"$rc\" = 87 ] && [ \"$valid\" = 1 ] && [ \"$display\" -gt 0 ]; then\n printf '%s\\n' \"$display\" > \"$WORK/legacy-$tag.rc\"\n exit 87",
|
||||
"new": "if [ \"$rc\" = 87 ] && [ \"$valid\" = 1 ] && [ \"$display\" -ge 0 ]; then\n printf '%s\\n' \"$display\" > \"$WORK/legacy-$tag.rc\"\n exit \"$display\"",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 earlier forged trailer"
|
||||
},
|
||||
{
|
||||
"id": "M5",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "process.exitCode=code;",
|
||||
"new": "process.exitCode=0;",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 legacy streams and display status"
|
||||
},
|
||||
{
|
||||
"id": "M6",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "&& [ \"${BASH_REMATCH[1]}\" -le 255 ]; then",
|
||||
"new": "; then",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 trailer grammar"
|
||||
},
|
||||
{
|
||||
"id": "M7",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "(0|[1-9][0-9]{0,2})$'\\n'$",
|
||||
"new": "([0-9]{1,3})$'\\n'$",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 trailer grammar"
|
||||
},
|
||||
{
|
||||
"id": "M8",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "(0|[1-9][0-9]{0,2})$'\\n'$",
|
||||
"new": "(0|[1-9][0-9]{0,2})$'\\n'?$",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 trailer grammar"
|
||||
},
|
||||
{
|
||||
"id": "M9",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": " printf '%s' \"$raw\" >&2",
|
||||
"new": " : # dropped stderr",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 legacy streams and display status"
|
||||
},
|
||||
{
|
||||
"id": "M10",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "[ \"${st% *}\" = 0 ] && [[ \"${st#* }\" =~ ^[0-7]{3,4}$ ]] && (( (8#${st#* } & 8#022) == 0 )) || return 1",
|
||||
"new": "[ \"${st% *}\" = 0 ] && [[ \"${st#* }\" =~ ^[0-7]{3,4}$ ]] || return 1",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 case1 capture and legacy tools"
|
||||
},
|
||||
{
|
||||
"id": "M11",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "[ \"$(wc -c < \"$file\")\" -le \"$maximum\" ] || exit 83",
|
||||
"new": ": # dropped capture byte limit",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row253 C4 acceptance query overflow"
|
||||
},
|
||||
{
|
||||
"id": "M12",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "if [ \"$rc\" = 0 ] && [ \"$v\" = \"$CLAUDE_PIN (Claude Code)\" ];",
|
||||
"new": "if [ \"$v\" = \"$CLAUDE_PIN (Claude Code)\" ];",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 version and PM checks"
|
||||
},
|
||||
{
|
||||
"id": "M13",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "if [ \"$rc\" = 0 ] && [[ \"$out\" =~ ^ok\\ ",
|
||||
"new": "if [[ \"$out\" =~ ^ok\\ ",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 version and PM checks"
|
||||
},
|
||||
{
|
||||
"id": "M14",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "if [ \"$rc\" = 0 ] && [ \"$v\" = \"$NODE_PIN\" ];",
|
||||
"new": "if [ \"$v\" = \"$NODE_PIN\" ];",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 version and PM checks"
|
||||
},
|
||||
{
|
||||
"id": "M15",
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"old": "\"$TS_TAIL\" -c 65536 -- \"$errors\"",
|
||||
"new": "\"$TS_TAIL\" -c 999999999 -- \"$errors\"",
|
||||
"killer": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"pattern": "row276 stderr retention is bounded"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run explicit row276 changes only under Sage's grant. --static runs no tests."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
REPO = HERE.parents[3]
|
||||
|
||||
def digest(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
def busy():
|
||||
p = subprocess.run(['bash', '/mnt/storage/scratch/sage-busy-now.sh'], text=True, capture_output=True)
|
||||
if p.returncode or p.stdout.splitlines()[-1:] != ['CLEAR']:
|
||||
raise RuntimeError('busy refusal: ' + p.stdout + p.stderr)
|
||||
|
||||
def run(command, path, seconds=60):
|
||||
if command[:2] == ['node', '--test']:
|
||||
limits = json.loads((HERE/'WHOLE-GATE-LIMITS.json').read_text())['files']
|
||||
limit = limits.get(command[-1], 900)
|
||||
command = ['timeout','-k','10',str(limit),*command]
|
||||
seconds = limit + 30
|
||||
busy()
|
||||
started = time.monotonic()
|
||||
command_file = path.with_suffix('.command.json')
|
||||
command_file.write_text(json.dumps(command))
|
||||
# Keep command text out of wrapper argv: the busy guard scans every argv.
|
||||
# launch.py execs the structured command. bash owns a pipefail/tee pipeline.
|
||||
p = subprocess.Popen(['bash','-o','pipefail','-c',
|
||||
'python3 "$1" "$2" 2>&1 | tee "$3"', '--',str(HERE/'launch.py'),str(command_file),str(path)],
|
||||
cwd=REPO,start_new_session=True)
|
||||
expired = False
|
||||
try:
|
||||
code = p.wait(timeout=seconds)
|
||||
expired = code in (124, 137)
|
||||
except subprocess.TimeoutExpired:
|
||||
expired = True
|
||||
os.killpg(p.pid, signal.SIGKILL)
|
||||
code = p.wait()
|
||||
finally:
|
||||
try:
|
||||
os.killpg(p.pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
return dict(code=code, timeout=expired, seconds=round(time.monotonic()-started, 3), log=str(path))
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--static', action='store_true')
|
||||
ap.add_argument('--grant')
|
||||
ap.add_argument('--out', type=Path)
|
||||
args = ap.parse_args()
|
||||
definitions = json.loads((HERE/'changes.json').read_text())
|
||||
checked = []
|
||||
for d in definitions:
|
||||
original = (REPO/d['file']).read_bytes()
|
||||
count = original.count(d['old'].encode())
|
||||
if count != 1:
|
||||
raise RuntimeError(f"{d['id']}: target occurs {count} times")
|
||||
changed = original.replace(d['old'].encode(), d['new'].encode(), 1)
|
||||
checked.append(dict(id=d['id'], file=d['file'], occurrences=count,
|
||||
before=digest(original), after=digest(changed)))
|
||||
if args.static:
|
||||
print(json.dumps(checked, indent=2))
|
||||
return
|
||||
if not args.grant or not args.out:
|
||||
ap.error('execution needs --grant and --out')
|
||||
if not os.environ.get('TMPDIR', '').startswith('/mnt/storage/scratch/tmp/g'):
|
||||
raise RuntimeError('TMPDIR must be the granted scratch directory')
|
||||
status = subprocess.check_output(['git','status','--porcelain','--untracked-files=no'],cwd=REPO)
|
||||
args.out.mkdir(parents=True, exist_ok=True)
|
||||
results = dict(grant=args.grant, static=checked, bases={}, changes=[])
|
||||
failures = 0
|
||||
for d in definitions:
|
||||
key = d['killer']+'|'+d['pattern']
|
||||
command = ['node','--test','--test-reporter=tap','--test-name-pattern',d['pattern'],d['killer']]
|
||||
if key not in results['bases']:
|
||||
base = run(command, args.out/(d['id']+'-base.log'))
|
||||
results['bases'][key] = base
|
||||
(args.out/'results.json').write_text(json.dumps(results,indent=2)+'\n')
|
||||
if base['code'] != 0 or base['seconds'] > 30:
|
||||
raise RuntimeError('base failed or exceeded 30 seconds: '+d['id'])
|
||||
target = REPO/d['file']
|
||||
original = target.read_bytes()
|
||||
try:
|
||||
target.write_bytes(original.replace(d['old'].encode(),d['new'].encode(),1))
|
||||
pre = subprocess.run(['bash','-n',str(target)],capture_output=True)
|
||||
if pre.returncode:
|
||||
raise RuntimeError('invalid syntax: '+d['id'])
|
||||
result = run(command,args.out/(d['id']+'.log'))
|
||||
result.update(id=d['id'], base_seconds=results['bases'][key]['seconds'],
|
||||
outcome='timeout' if result['timeout'] else 'killed' if result['code'] != 0 else 'survived')
|
||||
results['changes'].append(result)
|
||||
if result['timeout']:
|
||||
raise RuntimeError('timeout requires Sage/Mos classification: '+d['id'])
|
||||
if result['outcome'] != 'killed':
|
||||
failures += 1
|
||||
finally:
|
||||
target.write_bytes(original)
|
||||
assert target.read_bytes() == original
|
||||
assert subprocess.check_output(['git','status','--porcelain','--untracked-files=no'],cwd=REPO) == status
|
||||
(args.out/'results.json').write_text(json.dumps(results,indent=2)+'\n')
|
||||
if failures >= 1:
|
||||
raise RuntimeError('first unexpected failure: end the row276 window')
|
||||
print(json.dumps(results,indent=2))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"base": "94c33a43c",
|
||||
"source": "6b898fab465c200db14313a49c07c06b5dff285d",
|
||||
"rebased_tip_before_records": "d65b898998f07b3a63024620b10b7566b362dd0a",
|
||||
"old_source": "164e166479378caf0702ab8b1eb3871b54219da3",
|
||||
"four_identical": {
|
||||
"packages/cli/tests/case-sudo-fixture.mjs": {
|
||||
"original_sha256": "eac5c83f43056bdeb5781101307e17d4bc01f03e97e02f4f5e930436d7e43535",
|
||||
"integrated_sha256": "eac5c83f43056bdeb5781101307e17d4bc01f03e97e02f4f5e930436d7e43535"
|
||||
},
|
||||
"packages/cli/tests/case1-accept.test.mjs": {
|
||||
"original_sha256": "fe4bc897497d83ef247b229cd49acf78b1a607af44e803a8a0a866115ff8cf87",
|
||||
"integrated_sha256": "fe4bc897497d83ef247b229cd49acf78b1a607af44e803a8a0a866115ff8cf87"
|
||||
},
|
||||
"scripts/case1-accept.sh": {
|
||||
"original_sha256": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900",
|
||||
"integrated_sha256": "46245e2bc19c8bcab2ac4fbda16cc72f2dfbf656e4bad8db707bb5a254694900"
|
||||
},
|
||||
"scripts/case2-unit.sh": {
|
||||
"original_sha256": "d2709a1da6c9a03f4c8b31bf57ad00df4c0ae62a5cbc0e7588cdceb9dc0e2aa1",
|
||||
"integrated_sha256": "d2709a1da6c9a03f4c8b31bf57ad00df4c0ae62a5cbc0e7588cdceb9dc0e2aa1"
|
||||
}
|
||||
},
|
||||
"deferred_exact_new_base_plus_original_own_tail": true,
|
||||
"build_log_exact_new_base_plus_original_own_tail": true,
|
||||
"manifest": "2af7e8bafcbf0bb977a26ab1f57371b8453c7a6659dc7233a68f186ef7b5d5f2",
|
||||
"changed_manifest_entries": [
|
||||
"docs/plans/DEFERRED.md"
|
||||
],
|
||||
"tests": "NOT RUN"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# sage-2901 integration
|
||||
|
||||
Rebased old29996fb82 onto94c33a43c under newgander/row276-94c3, preserving old branch. DEFERRED auto-merged; only BUILD-LOG conflicted. Both are exact new-base prefixes followed by unchanged row276 tails. Four source/test blobs byte-identical to164e16647. Regenerated five-path manifest 2af7e8bafcbf0bb977a26ab1f57371b8453c7a6659dc7233a68f186ef7b5d5f2. No tests, Node or queue write. BUILD-LOG excluded.
|
||||
@@ -0,0 +1,42 @@
|
||||
diff --git a/docs/plans/DEFERRED.md b/docs/plans/DEFERRED.md
|
||||
index 0baa52809..7e393b060 100644
|
||||
--- a/docs/plans/DEFERRED.md
|
||||
+++ b/docs/plans/DEFERRED.md
|
||||
@@ -487,6 +487,37 @@ at every gate. Started 2026-09-12 during the control board MVP.
|
||||
touches the webui build: install the pinned esbuild in the gate checkout
|
||||
so the check runs, or record why it can't. (2026-10-22)
|
||||
|
||||
+- **The case supervisor reports a fast child as an inner-session failure.**
|
||||
+ (Row 253 N1, mos-1297 item 5.) A child that exits before the supervisor
|
||||
+ verifies its session is reaped first, so the supervisor exits 85
|
||||
+ (inner-session) instead of the child's own code. Mos measured rc85 in 39
|
||||
+ of 40 fast-child bash runs. It fails closed, so it is not a 10-27 defect;
|
||||
+ row 253 fixed only its own fixture (PYTHON_STUB execs python3). The
|
||||
+ product fix is Vikunja 532, after 10-27. (2026-10-22)
|
||||
+
|
||||
+- **boardSend /api/seen returns 400 for a scan failure after writeMark.**
|
||||
+ (Webby N2 on row 198, ruled sage-2867.) The mark is written, then the
|
||||
+ rescan fails, and the client gets 400 as if its request were bad. Webby
|
||||
+ suggests 500. It does not hold the 198 gate; binds the next row that
|
||||
+ touches serve.mjs. (2026-10-22)
|
||||
+
|
||||
+- **Stale "row 203" text in the access command.** (Quackerjack, row 203
|
||||
+ BUILD.md follow-up.) packages/cli/src/access.mjs lines 26-27, its help
|
||||
+ string at line 36 and line 44, and packages/cli/README.md line 1107 say
|
||||
+ peer homes stay unread "until row 203" adds the federation key. Row 203
|
||||
+ changes only scripts/mosaic-config.mjs and test-config.sh, so once it
|
||||
+ lands those lines point at a row that did not do the work. Binds the next
|
||||
+ row that edits access.mjs. (2026-10-22)
|
||||
+
|
||||
+- **A remote outage can make the first library lookup outlast the tool
|
||||
+ timeout.** (Row 224 residual, sage-2819.) In each 60 s window the first
|
||||
+ lookup still runs an inline fetch, and that fetch can take longer than the
|
||||
+ 60 s tool timeout. Bound the inline fetch in a later row. (2026-10-22)
|
||||
+
|
||||
+- **A sharee's session.list stays refused.** (Row 206, sage-2818, Q7.)
|
||||
+ Whichever of row 193's session wiring or the share wiring lands second
|
||||
+ wires it. (2026-10-22)
|
||||
+
|
||||
## Queue
|
||||
|
||||
Moved to `docs/plans/QUEUE.md` on 2026-09-13. This file holds only gaps.
|
||||
@@ -0,0 +1,15 @@
|
||||
1: 164e16647 ! 1: 6b898fab4 fix(case1): supervise legacy user calls across sudo sessions
|
||||
@@ Commit message
|
||||
|
||||
## BUILD-LOG.md ##
|
||||
@@ BUILD-LOG.md: Gander: after Duckworth-0100 design approval with C1-C5, prepared the two host-s
|
||||
- 2026-10-22 14:45 UTC | Gander row253 focused27 on finalaca16404c base | Fresh0604d91fc / manifest573c51cf:27 whole files604/0/0, no timeout/retry, case134/0 in99.281s, case243/0 in66.418s, stack-release includingT46221/0 in583.453s. N9 sandbox bind ran and passed, no focused skips. Seven source blobs unchanged; sweep0 run-created leftovers, pre-existing infrastructure receipts retained. Whole gate and M1-M40 await separate Sage CLEAR after Duckworth rebase check. No live action.
|
||||
+ 2026-10-22T17:42:16Z | Sage | Row 253 (#1758, Gander, Duckworth) LANDED 48e450ce8. Before: refactor at aca16404c; case-sudo calls had no supervised session check and the M21 survivor hid behind a fast-exit fixture (mos-1297). After: a fast-forward of 22 commits; manifest 591020a1 rechecked with 0 mismatches; run 5 whole gate green with M1-M40 killed, the M38 PID equals SID assertion added under mos-1298; Duckworth APPROVE rev 3252, note rev 3253, done rev 3254; #1758 comment 30701 and closed; P19 416 comment 719. N1 (fast child reported as exit 85) is DEFERRED as Vikunja 532.
|
||||
|
||||
- 2026-10-22 14:46 UTC | Gander correction to the row253 focused27 entry immediately above | stack-release whole was218/0/0, not221/0. The27-file sum604/0/0 and duration583.453s are unchanged; raw files/results.json and SUMMARY.json give the exact per-file totals.
|
||||
+ 2026-10-22T18:11:57Z | Sage | Row 275 (#1780, Morgana, Duckworth) LANDED 3410df7c3. Before: refactor at 48e450ce8; viewers had no verb list of their own and board reads were not refused to users. After: a fast-forward of 3 commits; manifest 39847b15 (4 paths) blobs match; gate 17:52:12Z-18:05:00Z green (whole files 7/22/6, bus 834, cli 861, harness 151, business 231, seat 167, 10 scripts with 3 named skips), M6 and M10 killed; Duckworth APPROVE rev 3260 (comment 30717); note and done by sage-2897; #1780 comment 30728 and closed. N1 (board refusal test as a loop over BOARD_VERBS) is non-blocking.
|
||||
+
|
||||
+2026-10-22 18:11:58 UTC | Gander row276 #1781 prepared, gate pending | Before: row253 left5legacy Case1 AS_USER calls unsupervised across sudo pty sessions. After: candidate uses its unchanged supervisor with per-call inner/outer budgets, separate streams, strict display-only failure trailer, fake-only paired process-model cases and15unique mutants. Duckworth0182 B1 corrected the plan before product code;0183 cleared build. Parse-only Bash checks pass; no Node, behavioral test, host, unit or config action. Runtime gate and publication await Sage.
|
||||
|
||||
2: 6887825e4 = 2: 824e5de10 docs(row276): pin supervised-call candidate and fifteen mutants
|
||||
3: 29996fb82 = 3: d65b89899 docs(row276): record static approval and upstream deferred drift
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exec a structured command without putting its text in wrapper argv."""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
with open(sys.argv[1]) as source:
|
||||
command = json.load(source)
|
||||
os.execvp(command[0], command)
|
||||
@@ -605,3 +605,12 @@ Brief: docs/plans/2026-10-22_case-sudo-legacy-followup.md, “Brief: supervised
|
||||
case1-accept.sh's legacy timeout330 around AS_USER talk and its AS_USER precheck, Claude version, PM launches and Node version diagnostics predate the new socket metadata/probe helpers. sudo's pty monitor can move them to a separate session; the old outer timeout does not prove their descendants are killed. The C4 repair in row253 covers only its new config query and peer probe. These legacy paths need the reviewed supervisor or a header explicitly explaining why each remains unsupervised. The follow-up must preserve the 300-second talk/reply contract, concurrent F5 scan, diagnostics and secret-output refusals, with fake-only session-hop tests and Sage's slot. No host or live unit change is authorized.
|
||||
|
||||
Row 276 / #1781 also owns the case1 root-side capture/tools mutant counterparts (duckworth-0112 N1) and documenting the setuid-member cleanup refusal in the headers (N2). A member that changes uid and cannot be killed causes inner-cleanup refusal, never successful observation. N4 is addressed in row253 by allowing SID confirmation until the existing inner watchdog expires, instead of a separate 200 ms scheduling margin.
|
||||
|
||||
|
||||
## Row276 candidate closure of Row253 F5
|
||||
|
||||
Effective when row276 / #1781 lands after Duckworth review and Sage's gate: all five legacy Case1 calls (F3 talk, F4 precheck, F6 Claude version, F7 PM launches, F10 Node version) use row253's byte-identical after-uid supervisor and their own independent outer bound. No named AS_USER exception remains. Talk retains --wait300 and concurrent F5 observation; stdout and stderr remain separate, with root retaining only stderr's last65536bytes. A trailer can choose only a failure display number, never success. Supervisor0 alone permits pass.
|
||||
|
||||
Row253 N1 closure: row276's M10 checks case1's root-owned/non-writable tool fence through both capture and legacy paths; M11 checks case1's root-side capture byte limit. Each must be killed in the accepted gate. Row253 N2 closure: both case-script headers explicitly state that a uid-changing member which cannot be killed causes inner-cleanup refusal, never successful observation.
|
||||
|
||||
The hostile same-uid setsid-away or killed/stalled-supervisor residual remains as row253 R3. This closes none of that boundary. Instant command exit before SID confirmation remains the fail-closed residual tracked as Vikunja532 (mos-1297); row276 does not change the pinned payload.
|
||||
@@ -10,7 +10,8 @@ case_sudo_entry() {
|
||||
here="$BASHPID"
|
||||
read -r sid < <(/bin/ps -o sid= -p "$here")
|
||||
printf "%s %s\n" "$here" "$sid" >> "$record/session-pids"
|
||||
if { [ -f "$record/stall-config" ] && [[ "$*" == *MOSAIC_TESTER_SOCKET_V1* ]]; } ||
|
||||
if { [ -f "$record/stall-legacy-precheck" ] && [[ "$*" == *MOSAIC_LEGACY_STATUS* ]]; } ||
|
||||
{ [ -f "$record/stall-config" ] && [[ "$*" == *MOSAIC_TESTER_SOCKET_V1* ]]; } ||
|
||||
{ [ -f "$record/stall-probe" ] && [[ "$*" == *SO_PEERCRED* ]]; }; then
|
||||
trap '' TERM HUP
|
||||
[ ! -f "$record/stall-output" ] || /bin/cat "$record/stall-output"
|
||||
@@ -140,3 +141,12 @@ ${kind==='member'?`const c=spawn('/usr/bin/python3',['-I','-S','-c',${JSON.strin
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0);
|
||||
}`;
|
||||
}
|
||||
|
||||
// Row276: production budgets include the existing conversation/precheck bounds.
|
||||
export const LEGACY_CASES = Object.freeze([
|
||||
{tag:'talk',inner:320,outer:330,check:'F3'},
|
||||
{tag:'precheck',inner:200,outer:210,check:'F4'},
|
||||
{tag:'claude-version',inner:5,outer:7,check:'F6'},
|
||||
{tag:'pm-list',inner:10,outer:12,check:'F7'},
|
||||
{tag:'node-version',inner:5,outer:7,check:'F10'},
|
||||
]);
|
||||
@@ -5,13 +5,14 @@
|
||||
// sudo runs the stack's user's commands from the tree. Nothing here reads or writes the real /etc,
|
||||
// /proc or /srv, and the stand-in credential is not a credential.
|
||||
import { test } from "node:test";
|
||||
import { createHash } from "node:crypto";
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { tmp } from "./helpers.mjs";
|
||||
import { CASE_SUDO_SESSION, recordedLivePids, killRecordedPids, fixtureGuard, cleanupFixture, pollFixtureGone, blockingQuerySource } from "./case-sudo-fixture.mjs";
|
||||
import { CASE_SUDO_SESSION, recordedLivePids, killRecordedPids, fixtureGuard, cleanupFixture, pollFixtureGone, blockingQuerySource, LEGACY_CASES } from "./case-sudo-fixture.mjs";
|
||||
|
||||
const REPO = fileURLToPath(new URL("../../../", import.meta.url));
|
||||
const SCRIPT = join(REPO, "scripts", "case1-accept.sh");
|
||||
@@ -68,6 +69,7 @@ esac`,
|
||||
// -l -U USER prints the state file; -u jarvis is the bwrap probe; -u mosaic -H env PATH=... CMD runs
|
||||
// CMD from the tree, with the PATH entries and each absolute argument that exists there moved under it.
|
||||
sudo: `${CASE_SUDO_SESSION}
|
||||
/usr/bin/python3 -I -S -c 'import json,os,sys;f=open(os.environ["HC"]+"/sudo-argv.jsonl","a");f.write(json.dumps(sys.argv[1:])+chr(10));f.close()' "$@"
|
||||
case "$1" in
|
||||
-l) [ -f "$HC/sudo-l-$3" ] && cat "$HC/sudo-l-$3"; exit 0 ;;
|
||||
-u) ;;
|
||||
@@ -721,7 +723,26 @@ test("row 195 accept: every in-instance call goes through sudo -u mosaic -H env
|
||||
for(const a of isolated) assert.ok(a.startsWith(`-u mosaic -H env -i HOME=${HOME} PATH=/opt/mosaic-stack/node-${NODE_PIN}/bin:/usr/local/bin:/usr/bin:/bin `),a);
|
||||
for (const a of asMosaic.filter(a=>!isolated.includes(a))) assert.ok(a.startsWith(`${AS_MOSAIC} /`), a);
|
||||
const cmds = asMosaic.filter(a=>!isolated.includes(a)).map((a) => a.slice(AS_MOSAIC.length + 1).split(" ")[0]);
|
||||
assert.deepEqual([...new Set(cmds)].sort(), [CLI, PRECHECK, CLAUDE, NODE].sort());
|
||||
assert.equal(asMosaic.length,7,'two metadata/probe calls and five legacy calls');
|
||||
assert.deepEqual([...new Set(cmds)].sort(), ['/usr/bin/bash']);
|
||||
// mos-1326: assert every argv element, not flattened/substr command detection.
|
||||
const source=read(SCRIPT);
|
||||
const supervisor=source.split("<<'MOSAIC_TESTER_SUPERVISOR'")[1].split('\n').slice(1).join('\n').split('\nMOSAIC_TESTER_SUPERVISOR')[0]+'\n';
|
||||
assert.equal(createHash('sha256').update(supervisor).digest('hex'),'17b881513c35107e26f78c3722bc4a2927d206b197597be0bdbb2c8a8588bb4c');
|
||||
const runner=/LEGACY_COMMAND_RUNNER='([\s\S]*?)'\nlegacy_display_rc/.exec(source)[1];
|
||||
const f7=/F7_JS='([\s\S]*?)';\ncheck_f7/.exec(source)[1];
|
||||
const raw=read(join(h.hc,'sudo-argv.jsonl')).trim().split('\n').map(line=>JSON.parse(line));
|
||||
const legacy=raw.filter(a=>a[0]==='-u'&&a[1]==='mosaic'&&a[4].startsWith('PATH='));
|
||||
const marker=legacy[0][20].split(':')[0];
|
||||
const argv=(tag,inner,command)=>['-u','mosaic','-H','env',`PATH=/opt/mosaic-stack/node-${NODE_PIN}/bin:/usr/local/bin:/usr/bin:/bin`,
|
||||
'/usr/bin/bash','-c',supervisor,'--',String(inner),'/usr/bin/setsid','/usr/bin/sleep',NODE,'-e',runner,'--',tag,...command];
|
||||
assert.deepEqual(legacy,[
|
||||
argv('talk',320,[CLI,'talk','chief',marker+': this is the Case 1 acceptance; reply with one short line.','--business','mosaic-stack','--tester','--wait','300']),
|
||||
argv('precheck',200,[PRECHECK,'--business','mosaic-stack','--profile','factory']),
|
||||
argv('claude-version',5,[CLAUDE,'--version']),
|
||||
argv('pm-list',10,[NODE,'-e',f7,'--',`${HOME}/.config/mosaic-dev/businesses/mosaic-stack.json`,CLI,'mosaic-stack']),
|
||||
argv('node-version',5,[NODE,'--version']),
|
||||
]);
|
||||
assert.deepEqual(sudo.filter((a) => !a.startsWith("-u mosaic")).sort(),
|
||||
["-l -U localadmin", "-l -U mosaic", "-u jarvis /usr/bin/bwrap --version"]);
|
||||
const talk = calls(h, "mosaic").filter((a) => a.startsWith("talk "));
|
||||
@@ -735,3 +756,107 @@ test("row 195 accept: the host check runs with --check and never --enforce", (t)
|
||||
run(h);
|
||||
assert.deepEqual(calls(h, "hostcheck"), ["--check"]);
|
||||
});
|
||||
|
||||
|
||||
// Row276 exercises the real legacy launcher with fixture-only commands, never sudo.
|
||||
function legacyBody() {
|
||||
const source=read(SCRIPT),start=source.indexOf('# row253-supervisor-begin'),end=source.indexOf('# row276-legacy-end');
|
||||
assert.ok(start>=0&&end>start,'legacy supervisor block exists');
|
||||
return source.slice(start,end);
|
||||
}
|
||||
function legacyCall(h,tag,command,{inner='0.5',outer='2',body=legacyBody()}={}) {
|
||||
return spawnSync('/bin/bash',['-c',body+'\nlegacy_run "$@"','--',tag,String(inner),String(outer),...command],{
|
||||
encoding:'utf8',env:{...process.env,PATH:`${h.bin}:${process.env.PATH}`,HC:h.hc,ROOT_DIR:h.root,REAL_NODE:process.execPath,TMPDIR:h.dir,
|
||||
S_USER:'mosaic',S_HOME:'/srv/mosaic',NODE,WORK:h.dir},
|
||||
});
|
||||
}
|
||||
test('row276 legacy call inventory pins every production bound and the conversation contract',()=>{
|
||||
const s=read(SCRIPT);
|
||||
assert.equal(s.includes('${AS_USER[@]}'),false);
|
||||
for(const x of LEGACY_CASES)assert.ok(s.includes(`legacy_run ${x.tag} ${x.inner} ${x.outer} `),x.tag);
|
||||
assert.match(s,/legacy_run talk 320 330 .*--tester --wait 300/);
|
||||
assert.match(s,/legacy_display_rc talk/);assert.match(s,/legacy_display_rc precheck/);
|
||||
});
|
||||
for(const {tag} of LEGACY_CASES)test(`row276 ${tag} legacy inner timeout cleans both sudo models`,async t=>{
|
||||
for(const mode of ['exec','hop']){
|
||||
const h=host(t),records=join(h.hc,'legacy-pids'),files=[join(h.hc,'session-pids'),records];
|
||||
put(join(h.hc,'session-mode'),mode);
|
||||
const file=join(h.dir,'blocked.mjs');put(file,blockingQuerySource(records,'signals')+'\nloadSystem();');
|
||||
const capture=cleanupFixture(t,files),stop=fixtureGuard(t,files,4500);
|
||||
const start=performance.now(),r=legacyCall(h,tag,[NODE,file]);capture();
|
||||
assert.equal(r.status,81,r.stdout+r.stderr);
|
||||
assert.equal(r.stderr,`case1-accept: ${tag} inner-timeout\n`);
|
||||
assert.ok((performance.now()-start)/1000<2,mode);
|
||||
assert.deepEqual(await pollFixtureGone(files),[],mode);await stop();
|
||||
}
|
||||
});
|
||||
test('row276 legacy streams and display status preserve failures without granting success',t=>{
|
||||
for(const mode of ['exec','hop'])for(const code of [0,1,7,81,137]){
|
||||
const h=host(t);put(join(h.hc,'session-mode'),mode);
|
||||
const r=legacyCall(h,'talk',[NODE,'-e',`process.stdout.write('sent retained\\nreply retained\\n');process.stderr.write('original diagnostic\\n');process.exitCode=${code}`],{inner:3,outer:5});
|
||||
assert.equal(r.status,code===0?0:87,r.stderr);
|
||||
assert.equal(r.stdout,'sent retained\nreply retained\n');assert.equal(r.stderr,'original diagnostic\n');
|
||||
assert.equal(read(join(h.dir,'legacy-talk.rc')),String(code===0?0:code)+'\n');
|
||||
}
|
||||
});
|
||||
test('row276 trailer grammar cannot make supervisor failure pass',t=>{
|
||||
for(const trailer of ['0','256','999','01','+1','-1','1-no-newline','missing']){
|
||||
const h=host(t),body=legacyBody().replace('process.stderr.write("\\nMOSAIC_LEGACY_STATUS="+code+"\\n");',
|
||||
trailer==='missing'?'':trailer==='1-no-newline'?'process.stderr.write("\\nMOSAIC_LEGACY_STATUS=1");':`process.stderr.write("\\nMOSAIC_LEGACY_STATUS=${trailer}\\n");`);
|
||||
assert.notEqual(body,legacyBody(),'runner trailer seam matched');
|
||||
const r=legacyCall(h,'talk',[NODE,'-e','process.exitCode=7'],{body,inner:3,outer:5});
|
||||
assert.equal(r.status,87,r.stderr);assert.equal(read(join(h.dir,'legacy-talk.rc')),'87\n');
|
||||
assert.ok(r.stderr.endsWith('case1-accept: talk inner-command\n'),r.stderr);
|
||||
}
|
||||
});
|
||||
test('row276 earlier forged trailer and appended zero remain failures',t=>{
|
||||
for(const late of [false,true]){
|
||||
const h=host(t);let body=legacyBody();
|
||||
if(late)body=body.replace('process.stderr.write("\\nMOSAIC_LEGACY_STATUS="+code+"\\n");','process.stderr.write("\\nMOSAIC_LEGACY_STATUS="+code+"\\nMOSAIC_LEGACY_STATUS=0\\n");');
|
||||
const r=legacyCall(h,'talk',[NODE,'-e','process.stderr.write("\\nMOSAIC_LEGACY_STATUS=0\\n");process.exitCode=7'],{body,inner:3,outer:5});
|
||||
assert.equal(r.status,87,r.stderr);assert.equal(read(join(h.dir,'legacy-talk.rc')),late?'87\n':'7\n');
|
||||
}
|
||||
});
|
||||
test('row276 precheck outer bound discards success status and fixture cleans residual',async t=>{
|
||||
for(const mode of ['exec','hop']){
|
||||
const h=host(t),files=[join(h.hc,'session-pids')];put(join(h.hc,'session-mode'),mode);put(join(h.hc,'stall-legacy-precheck'),'');
|
||||
const capture=cleanupFixture(t,files),stop=fixtureGuard(t,files,4500);
|
||||
const start=performance.now(),r=legacyCall(h,'precheck',[NODE,'-e',''],{inner:0.5,outer:1});capture();const seconds=(performance.now()-start)/1000;
|
||||
assert.equal(r.status,82,r.stderr);assert.equal(r.stderr,'case1-accept: precheck outer-timeout\n');assert.equal(r.stdout,'');
|
||||
assert.ok(seconds>=1&&seconds<3,`${mode}:${seconds}s`);assert.equal(read(join(h.dir,'legacy-precheck.rc')),'82\n');
|
||||
killRecordedPids(files[0]);assert.deepEqual(await pollFixtureGone(files),[],'explicit same-uid stalled-supervisor residual cleanup');await stop();
|
||||
}
|
||||
});
|
||||
test('row276 case1 capture and legacy tools refuse unsafe system tools',t=>{
|
||||
for(const legacy of [false,true]){
|
||||
const h=host(t),statFile=join(h.bin,'stat');edit(statFile,s=>s.replace('case "${@: -1}" in',`case "\${@: -1}" in\n /usr/bin/timeout) echo '0 777';exit 0 ;;`));
|
||||
const body=legacyBody();
|
||||
const r=legacy?legacyCall(h,'node-version',[NODE,'--version'],{body}):spawnSync('/bin/bash',['-c',body+'\ntester_capture 0.5 2 128 mosaic /srv/mosaic /usr/bin /usr/bin/true'],{encoding:'utf8',env:{...process.env,PATH:`${h.bin}:${process.env.PATH}`,HC:h.hc,ROOT_DIR:h.root,REAL_NODE:process.execPath,TMPDIR:h.dir}});
|
||||
assert.equal(r.status,84,r.stdout+r.stderr);if(legacy)assert.equal(r.stderr,'case1-accept: node-version supervisor-tools\n');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('row276 stderr retention is bounded and keeps the final diagnostic',t=>{
|
||||
const h=host(t),r=legacyCall(h,'talk',[NODE,'-e',"process.stderr.write('X'.repeat(70000)+'end diagnostic\\n')"],{inner:3,outer:5});
|
||||
assert.equal(r.status,0,r.stderr);assert.ok(Buffer.byteLength(r.stderr)<=65536);assert.ok(r.stderr.endsWith('end diagnostic\n'));
|
||||
});
|
||||
test('row276 version and PM checks refuse successful-looking stdout on supervisor failure',t=>{
|
||||
const source=read(SCRIPT),h=host(t);
|
||||
for(const [name,next,output] of [['check_f6','// The PM check',`${CLAUDE_PIN} (Claude Code)`],['check_f7','check_f8()', 'ok pm run r-0002'],['check_f10','check_f11()',NODE_PIN]]){
|
||||
const at=source.indexOf(name+'()'),end=source.indexOf(next,at+1);assert.ok(at>=0&&end>at);
|
||||
const code=source.slice(at,end)+'\nfpass(){ echo PASS; }; ffail(){ echo FAIL; }; tester_capture_reason(){ echo inner-command; }; legacy_run(){ printf "%s\n" "$OUTPUT"; return 87; }; '+name;
|
||||
const r=spawnSync('/bin/bash',['-c',code],{encoding:'utf8',env:{...process.env,CLAUDE,CLAUDE_PIN,NODE,NODE_PIN,WORK:h.dir,OUTPUT:output,BUSINESS_FILE:'/fake',COPY:'/fake',BUSINESS:'mosaic-stack'}});
|
||||
assert.equal(r.status,0,r.stderr);assert.equal(r.stdout,'FAIL\n',name);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('row276 full acceptance keeps all five healthy calls in both sudo models',async t=>{
|
||||
for(const mode of ['exec','hop']){
|
||||
const h=host(t),files=[join(h.hc,'session-pids')];put(join(h.hc,'session-mode'),mode);
|
||||
const capture=cleanupFixture(t,files),r=run(h);capture();
|
||||
assert.equal(r.status,0,r.out+r.err);assert.deepEqual(failed(r),[],mode);
|
||||
assert.deepEqual(await pollFixtureGone(files),[],mode);
|
||||
}
|
||||
});
|
||||
+94
-21
@@ -18,8 +18,12 @@
|
||||
# fixed system Python, also as the stack user, for a read-only SO_PEERCRED observation.
|
||||
# The config/probe supervisor is fixed root-validated system Bash plus setsid
|
||||
# and sleep, after the uid drop: query inner/outer 5/7 s, probe 2/4 s. Root
|
||||
# independently bounds its sudo wait. Legacy AS_USER talk and diagnostics below
|
||||
# retain their old behavior and are NOT covered by this supervisor (F5 follow-up).
|
||||
# independently bounds its sudo wait. All legacy calls use the same payload:
|
||||
# talk320/330 s (conversation --wait300), precheck200/210 s (its Claude probe180),
|
||||
# Claude/Node versions5/7 s, PM launches10/12 s. Stderr retains its last65536bytes,
|
||||
# stdout streams to the original caller. Supervisor0 alone permits success.
|
||||
# A member that changes uid and cannot be killed refuses inner-cleanup; hostile
|
||||
# same-uid setsid-away or a killed/stalled supervisor remains the R3 residual.
|
||||
#
|
||||
# One line per check, `<id> <path> <mark> <evidence>`, then `count <mark> N` for each mark and a
|
||||
# result line. F checks are `pass` or `FAIL`. P checks are `closed`, `accepted <ruling>` or
|
||||
@@ -107,8 +111,8 @@ BUS_UNIT="mosaic-bus@$BUSINESS.service" CON_UNIT=mosaic-webui.service
|
||||
HOSTCHECK=/usr/local/lib/mosaic-stack/hostcheck.sh
|
||||
SETTINGS=/etc/claude-code/managed-settings.json
|
||||
LABEL_OUTER='bwrap (enforce)' LABEL_CHILD='bwrap//&unpriv_bwrap (enforce)'
|
||||
AS_USER=(sudo -u "$S_USER" -H env "PATH=$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin")
|
||||
WORK="$(mktemp -d)" || refuse "cannot make a temp dir"
|
||||
chmod 700 "$WORK" || refuse "cannot protect temp dir"
|
||||
trap 'rm -rf -- "$WORK"' EXIT
|
||||
|
||||
# Row253: bounded unprivileged metadata, parsed by root without running copy code.
|
||||
@@ -196,8 +200,8 @@ MOSAIC_TESTER_SUPERVISOR
|
||||
|
||||
tester_tools() {
|
||||
local input target p st magic
|
||||
TS_BASH="" TS_SETSID="" TS_SLEEP="" TS_TIMEOUT="" TS_HEAD=""
|
||||
for input in /bin/bash /usr/bin/setsid /usr/bin/sleep /usr/bin/timeout /usr/bin/head; do
|
||||
TS_BASH="" TS_SETSID="" TS_SLEEP="" TS_TIMEOUT="" TS_HEAD="" TS_TAIL=""
|
||||
for input in /bin/bash /usr/bin/setsid /usr/bin/sleep /usr/bin/timeout /usr/bin/head "$@"; do
|
||||
target="$(realpath -e -- "$input" 2>/dev/null)" || return 1
|
||||
case "$target" in /home/*|/root/*|/run/user/*) return 1 ;; esac
|
||||
[ -f "$target" ] && [ -x "$target" ] && [ ! -L "$target" ] || return 1
|
||||
@@ -213,7 +217,7 @@ tester_tools() {
|
||||
case "$input" in
|
||||
/bin/bash) TS_BASH="$target" ;; /usr/bin/setsid) TS_SETSID="$target" ;;
|
||||
/usr/bin/sleep) TS_SLEEP="$target" ;; /usr/bin/timeout) TS_TIMEOUT="$target" ;;
|
||||
/usr/bin/head) TS_HEAD="$target" ;;
|
||||
/usr/bin/head) TS_HEAD="$target" ;; /usr/bin/tail) TS_TAIL="$target" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
@@ -246,6 +250,68 @@ tester_capture() (
|
||||
cat -- "$file"
|
||||
)
|
||||
|
||||
|
||||
# row276-legacy-begin
|
||||
# The runner preserves a failed command's display code. Its exit status, not
|
||||
# this dropped-uid trailer, controls supervisor success. Root reads only a tail.
|
||||
LEGACY_COMMAND_RUNNER='
|
||||
const {spawnSync}=require("node:child_process");
|
||||
const {constants}=require("node:os");
|
||||
const [tag,command,...args]=process.argv.slice(1);
|
||||
const r=spawnSync(command,args,{stdio:"inherit"});
|
||||
const code=Number.isInteger(r.status)?r.status:r.signal?128+(constants.signals[r.signal]??0):127;
|
||||
if(r.error)process.stderr.write("legacy command "+(r.error.code??"failed")+"\n");
|
||||
process.stderr.write("\nMOSAIC_LEGACY_STATUS="+code+"\n");
|
||||
process.exitCode=code;
|
||||
'
|
||||
legacy_display_rc() {
|
||||
local tag="$1" fallback="$2" value=""
|
||||
[ ! -f "$WORK/legacy-$tag.rc" ] || value="$(<"$WORK/legacy-$tag.rc")"
|
||||
if [[ "$value" =~ ^[1-9][0-9]{0,2}$ ]] && [ "$value" -le 255 ]; then printf '%s' "$value"
|
||||
else printf '%s' "$fallback"; fi
|
||||
}
|
||||
legacy_run() (
|
||||
local tag="$1" inner="$2" outer="$3" notice errors child="" rc=0 raw suffix display=87 valid=0; shift 3
|
||||
# WORK is root's private mktemp directory. The dropped uid cannot write it.
|
||||
printf '%s\n' 84 > "$WORK/legacy-$tag.rc"
|
||||
tester_tools /usr/bin/tail || { printf 'case1-accept: %s supervisor-tools\n' "$tag" >&2; exit 84; }
|
||||
notice="$(mktemp "$WORK/legacy-notice.XXXXXX")" || exit 88
|
||||
errors="$(mktemp "$WORK/legacy-stderr.XXXXXX")" || { rm -f -- "$notice"; exit 88; }
|
||||
trap 'if [ -n "$child" ]; then kill -KILL -- "-$child" 2>/dev/null || :; wait "$child" 2>/dev/null || :; fi; rm -f -- "$notice" "$errors"' EXIT
|
||||
trap 'exit 88' HUP INT TERM
|
||||
LC_ALL=C "$TS_TIMEOUT" --verbose --signal=KILL "${outer}s" "$TS_BASH" -c '
|
||||
errors="$1"; shift
|
||||
exec "$@" 2> "$errors"
|
||||
' -- "$errors" sudo -u "$S_USER" -H env "PATH=$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin" "$TS_BASH" -c "$TESTER_SUPERVISOR" -- "$inner" "$TS_SETSID" "$TS_SLEEP" "$NODE" -e "$LEGACY_COMMAND_RUNNER" -- "$tag" "$@" 2> "$notice" &
|
||||
child=$!
|
||||
wait "$child" 2>/dev/null || rc=$?
|
||||
kill -KILL -- "-$child" 2>/dev/null || :
|
||||
if [ "$rc" = 137 ] && [[ "$(<"$notice")" == *': sending signal KILL to command '* ]]; then rc=82; fi
|
||||
# Sentinel retains the terminal newline through command substitution.
|
||||
raw="$("$TS_TAIL" -c 65536 -- "$errors" && printf .)" || { raw=""; rc=88; }
|
||||
raw="${raw%.}"
|
||||
suffix=$'\nMOSAIC_LEGACY_STATUS='
|
||||
if [[ "$raw" =~ $'\nMOSAIC_LEGACY_STATUS='(0|[1-9][0-9]{0,2})$'\n'$ ]] && [ "${BASH_REMATCH[1]}" -le 255 ]; then
|
||||
display="${BASH_REMATCH[1]}"; valid=1
|
||||
raw="${raw%"$suffix"*}"
|
||||
fi
|
||||
printf '%s' "$raw" >&2
|
||||
if [ "$rc" = 0 ]; then
|
||||
printf '%s\n' 0 > "$WORK/legacy-$tag.rc"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$rc" = 87 ] && [ "$valid" = 1 ] && [ "$display" -gt 0 ]; then
|
||||
printf '%s\n' "$display" > "$WORK/legacy-$tag.rc"
|
||||
exit 87
|
||||
fi
|
||||
case "$rc" in 81|82|84|85|86|88) ;; *) rc=87 ;; esac
|
||||
printf '%s\n' "$rc" > "$WORK/legacy-$tag.rc"
|
||||
if [ -n "$raw" ] && [[ "$raw" != *$'\n' ]]; then printf '\n' >&2; fi
|
||||
printf 'case1-accept: %s %s\n' "$tag" "$(tester_capture_reason "$rc")" >&2
|
||||
exit "$rc"
|
||||
)
|
||||
# row276-legacy-end
|
||||
|
||||
TESTER_QUERY='import {readFileSync} from "node:fs";import {pathToFileURL} from "node:url";const m=await import(pathToFileURL(process.argv[1]).href);const s=m.loadSystem();console.log(JSON.stringify({dataRoot:s.dataRoot,socket:readFileSync(process.argv[2],"utf8").includes("MOSAIC_TESTER_SOCKET_V1")}));'
|
||||
tester_facts() {
|
||||
local LC_ALL=C pattern='^\{"dataRoot":"([A-Za-z0-9/._@+-]+)","socket":(true|false)\}$'
|
||||
@@ -425,10 +491,10 @@ scan_f5() {
|
||||
|
||||
# F3 and F5 together: the message goes out in the background and the scan runs until it is answered.
|
||||
check_f3_f5() {
|
||||
local marker text rc=0 out err n tpid
|
||||
local marker text rc=0 shown out err n tpid
|
||||
marker="case1-accept-$(od -An -N6 -tx1 /dev/urandom | tr -d ' \n')"
|
||||
text="$marker: this is the Case 1 acceptance; reply with one short line."
|
||||
timeout 330 "${AS_USER[@]}" "$COPY/scripts/mosaic" talk chief "$text" --business "$BUSINESS" --tester --wait 300 > "$WORK/talk.out" 2> "$WORK/talk.err" &
|
||||
legacy_run talk 320 330 "$COPY/scripts/mosaic" talk chief "$text" --business "$BUSINESS" --tester --wait 300 > "$WORK/talk.out" 2> "$WORK/talk.err" &
|
||||
tpid=$!
|
||||
while :; do
|
||||
scan_f5 && break
|
||||
@@ -442,24 +508,27 @@ check_f3_f5() {
|
||||
if [ "$rc" = 0 ] && grep -q "^sent [^ ]* to chief" <<< "$out" && [ "$n" -ge 2 ]; then
|
||||
fpass F3 tester-reply "chief replied to $marker"
|
||||
else
|
||||
ffail F3 tester-reply "talk exited $rc with $n lines on stdout; last: $(clean "$(tail -n 1 <<< "${err:-$out}")")"
|
||||
shown="$(legacy_display_rc talk "$rc")"
|
||||
ffail F3 tester-reply "talk exited $shown with $n lines on stdout; last: $(clean "$(tail -n 1 <<< "${err:-$out}")")"
|
||||
fi
|
||||
if [ -n "$F5_OK" ]; then F5_LINE=(fpass "$F5_OK"); else F5_LINE=(ffail "$F5_WHY"); fi
|
||||
}
|
||||
|
||||
check_f4() {
|
||||
local rc=0 out
|
||||
out="$("${AS_USER[@]}" "$COPY/scripts/case2-precheck.sh" --business "$BUSINESS" --profile factory 2>&1)" || rc=$?
|
||||
local rc=0 shown out
|
||||
out="$(legacy_run precheck 200 210 "$COPY/scripts/case2-precheck.sh" --business "$BUSINESS" --profile factory 2>&1)" || rc=$?
|
||||
if [ "$rc" = 0 ]; then fpass F4 precheck "case2-precheck.sh --profile factory exits 0"
|
||||
else ffail F4 precheck "case2-precheck.sh --profile factory exited $rc; last: $(clean "$(tail -n 1 <<< "$out")")"; fi
|
||||
else shown="$(legacy_display_rc precheck "$rc")"; ffail F4 precheck "case2-precheck.sh --profile factory exited $shown; last: $(clean "$(tail -n 1 <<< "$out")")"; fi
|
||||
}
|
||||
|
||||
check_f6() {
|
||||
local want="/opt/mosaic-stack/claude-$CLAUDE_PIN/" v
|
||||
local want="/opt/mosaic-stack/claude-$CLAUDE_PIN/" v rc=0
|
||||
[[ "$CLAUDE" == "$want"* ]] || { ffail F6 claude-pin "--claude $CLAUDE is not under $want"; return; }
|
||||
[[ "$CLAUDE/" != */../* ]] || { ffail F6 claude-pin "--claude $CLAUDE has a .. segment"; return; }
|
||||
v="$("${AS_USER[@]}" "$CLAUDE" --version 2>/dev/null | sed -n 1p)"
|
||||
if [ "$v" = "$CLAUDE_PIN (Claude Code)" ]; then fpass F6 claude-pin "$CLAUDE prints '$v'"
|
||||
v="$(legacy_run claude-version 5 7 "$CLAUDE" --version 2> "$WORK/claude-version.err")" || rc=$?
|
||||
v="$(sed -n 1p <<< "$v")"
|
||||
if [ "$rc" = 0 ] && [ "$v" = "$CLAUDE_PIN (Claude Code)" ]; then fpass F6 claude-pin "$CLAUDE prints '$v'"
|
||||
elif [ "$rc" != 0 ]; then ffail F6 claude-pin "claude-version $(tester_capture_reason "$rc"); last: $(clean "$(tail -c 200 "$WORK/claude-version.err")")"
|
||||
else ffail F6 claude-pin "$CLAUDE --version prints '$(clean "$v")', not '$CLAUDE_PIN (Claude Code)'"; fi
|
||||
}
|
||||
|
||||
@@ -477,9 +546,11 @@ let l;try{l=JSON.parse(r.stdout)}catch{console.log("fail launches list printed n
|
||||
const live=Array.isArray(l)?l.filter((x)=>x&&x.instance===by&&x.live===true):[];
|
||||
console.log(live.length!==1?`fail ${live.length} live launches of ${by}`:live[0].business!==b?`fail the live launch of ${by} is for ${live[0].business}`:`ok ${by} run ${live[0].run}`);'
|
||||
check_f7() {
|
||||
local out
|
||||
out="$("${AS_USER[@]}" "$NODE" -e "$F7_JS" -- "$BUSINESS_FILE" "$COPY/scripts/mosaic" "$BUSINESS" 2>/dev/null | sed -n 1p)"
|
||||
if [[ "$out" =~ ^ok\ [a-z0-9][a-z0-9-]*\ run\ [A-Za-z0-9._-]+$ ]]; then fpass F7 pm-live "one live launch: ${out#ok }"
|
||||
local out rc=0
|
||||
out="$(legacy_run pm-list 10 12 "$NODE" -e "$F7_JS" -- "$BUSINESS_FILE" "$COPY/scripts/mosaic" "$BUSINESS" 2> "$WORK/pm-list.err")" || rc=$?
|
||||
out="$(sed -n 1p <<< "$out")"
|
||||
if [ "$rc" = 0 ] && [[ "$out" =~ ^ok\ [a-z0-9][a-z0-9-]*\ run\ [A-Za-z0-9._-]+$ ]]; then fpass F7 pm-live "one live launch: ${out#ok }"
|
||||
elif [ "$rc" != 0 ]; then ffail F7 pm-live "pm-list $(tester_capture_reason "$rc"); last: $(clean "$(tail -c 200 "$WORK/pm-list.err")")"
|
||||
else ffail F7 pm-live "$(clean "${out#fail }")"; fi
|
||||
}
|
||||
|
||||
@@ -517,10 +588,12 @@ check_f9() {
|
||||
}
|
||||
|
||||
check_f10() {
|
||||
local want="/opt/mosaic-stack/node-$NODE_PIN/bin/node" v
|
||||
local want="/opt/mosaic-stack/node-$NODE_PIN/bin/node" v rc=0
|
||||
[ "$NODE" = "$want" ] || { ffail F10 node-pin "--node $NODE is not $want"; return; }
|
||||
v="$("${AS_USER[@]}" "$NODE" --version 2>/dev/null | sed -n 1p)"
|
||||
if [ "$v" = "$NODE_PIN" ]; then fpass F10 node-pin "$NODE prints $v"
|
||||
v="$(legacy_run node-version 5 7 "$NODE" --version 2> "$WORK/node-version.err")" || rc=$?
|
||||
v="$(sed -n 1p <<< "$v")"
|
||||
if [ "$rc" = 0 ] && [ "$v" = "$NODE_PIN" ]; then fpass F10 node-pin "$NODE prints $v"
|
||||
elif [ "$rc" != 0 ]; then ffail F10 node-pin "node-version $(tester_capture_reason "$rc"); last: $(clean "$(tail -c 200 "$WORK/node-version.err")")"
|
||||
else ffail F10 node-pin "$NODE --version prints '$(clean "$v")', not $NODE_PIN"; fi
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#
|
||||
# The library profile takes exactly these serve flags, each once:
|
||||
# --port N (1..1023) --business ID (the unit's) --public-host NAME
|
||||
# A member that changes uid and cannot be killed refuses inner-cleanup, never success.
|
||||
# --auth-header HEADER --allow-user ID --send
|
||||
# --proof-header NAME --proof-file PATH
|
||||
# The factory profile takes --port N (1..1023) and --business ID, and an
|
||||
|
||||
Reference in new issue
Block a user