gander: design tester socket migration for host scripts
This commit is contained in:
1 parent
c3d88840c5
commit
d8d7c34a7d
4 files changed
+968
No files matched your search
@@ -5189,3 +5189,5 @@ Before: managed project/thread verbs had no Console conversation area. After: fi
|
||||
2026-10-20 19:34:28 UTC | Gander row 196 build paused at design mismatch | Before: approved design promised Unix tester cleanup only for the owned inode. After: scratch test shows Node/libuv close unlinks a replacement inode before JS close handlers. Evidence agents/gander/work/queue-196/evidence/tester-cleanup-blocker.txt. Sent Sage/Duckworth gander-0016/0017, recommending explicit same-uid residual with private 0700 parent retained. Interpreter, concurrent peer, principal, human, peer-transport, transport, tester and process single files passed. Partial code uncommitted, tester transport not wired. No suites, mutant batches or live actions.
|
||||
|
||||
2026-10-21 18:45 UTC | Gander row 196 preservation before rebase | Sage-2051, Mos order mos-1193, explicitly authorizes commit and fast-forward push of the current build before deferred full verification. Product and evidence preserved together; prior focused checks and stopped gates are recorded in the row196 packet, with no claim of full acceptance. Next: rebase onto a36523713 on new gander/row196-r2, report range-diff/patch-id/conflicts, then await Sage's clear for M1-M29 and all requested suites. No live action, force push, branch deletion or tests during hold.
|
||||
|
||||
2026-10-21 21:24:47 UTC | Gander row253 #1758 design r1 | Investigation began21:12Z under sage-2162; recorded now. Before: case2 installer still renders tester TCP and case1 acceptance requires it. After: proposed design covers socket marker migration, actual configured dataRoot within unchanged hardening, target-uid metadata queries, kernel-peer acceptance observation and explicit same-uid residual. Initial inventory saved; no passing final static-check claim, product edit, test, host or live action. Duckworth design review precedes code.
|
||||
@@ -0,0 +1,711 @@
|
||||
{
|
||||
"base": "c3d88840c",
|
||||
"stage": "pre-implementation; not the passing final static check",
|
||||
"occurrences": [
|
||||
{
|
||||
"file": "scripts/case2-unit.sh",
|
||||
"line": 25,
|
||||
"text": "# optional --tester-port N (1024..65535, never --port). Only factory takes"
|
||||
},
|
||||
{
|
||||
"file": "scripts/case2-unit.sh",
|
||||
"line": 163,
|
||||
"text": " --tester-port) [ \"$PROFILE\" = factory ] || die \"the library profile refuses --tester-port: the tester listener takes writes from loopback with no sign-in\" ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/case2-unit.sh",
|
||||
"line": 193,
|
||||
"text": " --tester-port) tester_port=\"$value\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/case2-unit.sh",
|
||||
"line": 194,
|
||||
"text": " [[ \"$value\" =~ ^[1-9][0-9]{3,4}$ ]] && [ \"$value\" -ge 1024 ] && [ \"$value\" -le 65535 ] || die \"--tester-port must be 1024..65535: $value\" ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/case2-unit.sh",
|
||||
"line": 204,
|
||||
"text": " [ -z \"$tester_port\" ] || [ \"$tester_port\" -ne \"$port\" ] || die \"--tester-port must differ from --port\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/instance.sh",
|
||||
"line": 114,
|
||||
"text": "# this instance's, whose ExecStart takes PORT as --port or --tester-port."
|
||||
},
|
||||
{
|
||||
"file": "scripts/instance.sh",
|
||||
"line": 132,
|
||||
"text": " --tester-port) [[ \"$tok\" =~ ^[0-9]{1,5}$ ]] && ports+=(\"$((10#$tok))\") ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"line": 65,
|
||||
"text": " case \"$a\" in --port) PORT=\"$v\"; i=$((i + 1)) ;; --tester-port) TESTER_PORT=\"$v\"; i=$((i + 1)) ;; esac"
|
||||
},
|
||||
{
|
||||
"file": "scripts/case1-accept.sh",
|
||||
"line": 88,
|
||||
"text": " refuse \"$ARGS_FILE lacks a numeric --port or --tester-port after --\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 43,
|
||||
"text": "# Console), --tester-socket (private Unix tester), or legacy --tester-port N"
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 74,
|
||||
"text": "# --tester-port that another installed Console unit in the unit directory"
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 96,
|
||||
"text": " FLAGS: [--port N] [--business ID] [--board URL] [--sso [--send] | --public-host NAME --auth-header HEADER --allow-user ID [--send]] [--tester-socket | --tester-port N (legacy copies)] [--proof-header NAME --proof-file PATH]"
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 97,
|
||||
"text": " --tester-port: legacy copies only; 1024..65535, different from --port and never 7332; loopback tester listener."
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 137,
|
||||
"text": " --tester-port) [ $# -ge 2 ] || usage; TESTER_PORT=\"$2\"; TESTER_SET=1; SERVE_SET=1; shift 2 ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 182,
|
||||
"text": " echo \"webui-service: --tester-port must be 1024..65535, different from --port and never 7332: $TESTER_PORT\" >&2"
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 266,
|
||||
"text": " [ \"$TESTER_SET\" = 0 ] || flags=\"$flags --tester-port $TESTER_PORT\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 462,
|
||||
"text": " if grep -Eq '^ExecStart=.*( --allow-user [^ ][^ ]*( --proof-header [^ ]+ --proof-file /[^ ]+)?| --sso) --send( --tester-port [0-9]+| --tester-socket)?$' \"$target\"; then"
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 467,
|
||||
"text": " if grep -Eq '^ExecStart=.* --sso( --send)?( --tester-port [0-9]+)?$' \"$target\"; then"
|
||||
},
|
||||
{
|
||||
"file": "scripts/webui-service.sh",
|
||||
"line": 471,
|
||||
"text": " tester_port=\"$(sed -n 's/^ExecStart=.* --tester-port \\([0-9][0-9]*\\)$/\\1/p' \"$target\")\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 47,
|
||||
"text": " stack-release.sh activate ID BUSINESS [--pm | --pm-after-harness] [--migrate] [--tester-socket | --tester-port N (legacy copies)]"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 63,
|
||||
"text": "activate and rollback --plan take --expect-from OLD. --pm, --pm-after-harness, --tester-port, the"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 126,
|
||||
"text": " --tester-port) [ \"$OP\" = activate ] && [ $# -ge 2 ] && [ -n \"$2\" ] || usage; TESTER_PORT=\"$2\"; shift 2 ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 797,
|
||||
"text": " elif grep -q -- '--tester-port' \"$cli\"; then supported=1; fi"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 800,
|
||||
"text": " --tester-port) i=$((i + 1)); inherited=\"${BASE_CONSOLE[i]:-}\" ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 814,
|
||||
"text": " [ -z \"$inherited\" ] || DROP_TESTER=\"--tester-port $inherited (replaced by private Unix socket)\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 830,
|
||||
"text": " check_refuse tester-port-invalid \"--tester-port $TESTER_PORT is not a whole port from 1024 to 65535 other than 7332$other\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 832,
|
||||
"text": " [ \"$supported\" = 1 ] || check_refuse tester-port-target \"$TO's Console does not support --tester-port\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 833,
|
||||
"text": " RUN_CONSOLE=(\"${without_tester[@]}\" --tester-port \"$TESTER_PORT\")"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 837,
|
||||
"text": " RUN_CONSOLE=(\"${without_tester[@]}\") DROP_TESTER=\"--tester-port $inherited\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 878,
|
||||
"text": " # --send and --tester-port follow the pointer, so a --no-send or a tester port set after that"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 885,
|
||||
"text": " --tester-port) i=$((i + 1)) ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 893,
|
||||
"text": " --tester-port) i=$((i + 1)); BASE_CONSOLE+=(--tester-port \"${P_CONSOLE[i]:-}\") ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 897,
|
||||
"text": " SSO_PLAN=\"sso: dropped; $TO's Console does not support --sso, so the rollback takes the Console flags from $set/$f, with --send and --tester-port as the pointer has them\""
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 903,
|
||||
"text": "# --tester-port. Sets SSO_DROP (the plan's dropped preview flags), PROOF_PLAN and PROOF_STEP."
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 919,
|
||||
"text": " case \"${kept[i]}\" in --send | --tester-port | --tester-socket) break ;; esac"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 1076,
|
||||
"text": " case \"${without_proof[i]}\" in --send | --tester-port | --tester-socket) break ;; esac"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 1134,
|
||||
"text": "# Row 190: the target Console's --port (7332 when absent) and --tester-port against every other"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 1142,
|
||||
"text": " --tester-port) ports+=(\"${RUN_CONSOLE[i + 1]:-}\") ;;"
|
||||
},
|
||||
{
|
||||
"file": "scripts/stack-release.sh",
|
||||
"line": 2106,
|
||||
"text": " for ((i = 0; i < ${#P_CONSOLE[@]}; i++)); do [ \"${P_CONSOLE[i]}\" != --tester-port ] || reserved+=(\"${P_CONSOLE[i + 1]:-}\"); done"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case2-unit.test.mjs",
|
||||
"line": 80,
|
||||
"text": "const factoryFlags = () => [\"--port\", \"443\", \"--business\", \"acme\", \"--tester-port\", \"7333\"];"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case2-unit.test.mjs",
|
||||
"line": 328,
|
||||
"text": " [\"--tester-port\", \"7333\", /the library profile refuses --tester-port/],"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case2-unit.test.mjs",
|
||||
"line": 364,
|
||||
"text": " refuses(unit(h, { profile: \"factory\", flags: [\"--port\", \"443\", \"--business\", \"acme\", \"--tester-port\", \"443\"] }), /--tester-port must be 1024\\.\\.65535|--tester-port must differ from --port/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case2-unit.test.mjs",
|
||||
"line": 365,
|
||||
"text": " refuses(unit(h, { profile: \"factory\", flags: [\"--port\", \"443\", \"--business\", \"acme\", \"--tester-port\", \"1443\", \"--port\", \"1443\"] }), /given twice/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case2-unit.test.mjs",
|
||||
"line": 378,
|
||||
"text": " refuses(unit(h, { profile: \"factory\", flags: [\"--port\", \"443\", \"--business\", \"acme\", \"--tester-port\", \"443\"] }), /--tester-port must be 1024\\.\\.65535/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case2-unit.test.mjs",
|
||||
"line": 380,
|
||||
"text": " assert.match(read(UNIT), /--tester-port must differ from --port/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release-fixture.mjs",
|
||||
"line": 306,
|
||||
"text": " for(const [f,text,mode] of [['scripts/mosaic',script(mosaicStub),0o755],['packages/webui/src/cli.mjs',[B,C1,C].includes(id)?`// fixture Console, never run\\n// serve accepts '--tester-port'\\n// serve accepts '--proof-header' (row 178)\\n${[C1,C].includes(id)?\"// serve accepts '--sso' (row 192)\\n\":''}`:'// fixture Console before tester listener, never run\\n',0o644],['packages/bus/src/store.mjs',store,0o644],['packages/bus/src/migrate-schema.mjs',`(${migrateStub})();\\n`,0o644],['packages/plugins/src/kinds.mjs',[C1,C].includes(id)?KINDS_NEW:KINDS_OLD,0o644],...ssoFiles(id).map(([f,text])=>[f,text,0o644])]){"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 681,
|
||||
"text": "const testerFlags=(e,port='7333')=>['--port',e.port,'--business','acme','--tester-port',port];"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 687,
|
||||
"text": " let r=await run(e,'activate',C1,'acme','--tester-port','7333');assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 688,
|
||||
"text": " assert.deepEqual(pointer(e).console,testerFlags(e));assert.match(consoleText(e),/--tester-port 7333(?:\\n|$)/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 690,
|
||||
"text": " r=await run(e,'activate',B,'acme','--tester-port','7334');assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 692,
|
||||
"text": " assert.match(consoleText(e),/--tester-port 7334(?:\\n|$)/);assert.doesNotMatch(consoleText(e),/--tester-port 7333/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 693,
|
||||
"text": " assert.equal((consoleText(e).match(/--tester-port/g)||[]).length,1);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 694,
|
||||
"text": " r=await run(e,'activate',C1,'acme','--plan');assert.equal(r.code,0,r.out+r.err);assert.match(r.out,/Console: .*--tester-port 7334/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 697,
|
||||
"text": " assert.deepEqual(pointer(e).console,testerFlags(e,'7334'));assert.match(consoleText(e),/--tester-port 7334(?:\\n|$)/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 702,
|
||||
"text": " let r=await run(e,'activate',B,'acme','--tester-port','7333');assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 705,
|
||||
"text": " assert.match(r.out,/drop[^\\n]*--tester-port 7333/i);assert.doesNotMatch(r.out.split('\\n').find((l)=>l.startsWith('Console: ')),/--tester-port/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 708,
|
||||
"text": " assert.deepEqual(pointer(e).console,['--port',e.port,'--business','acme']);assert.doesNotMatch(consoleText(e),/--tester-port/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 709,
|
||||
"text": " assert.match(stepText(e),/drop[^\\n]*--tester-port 7333/i);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 714,
|
||||
"text": " const r=await run(e,'activate',A,'acme','--tester-port','7333');assert.equal(r.code,3,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 718,
|
||||
"text": " assert.match(readFileSync(join(repo,'packages/webui/src/cli.mjs'),'utf8'),/--tester-port/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 724,
|
||||
"text": " const r=await run(e,'activate',C1,'acme','--tester-port',value);assert.equal(r.code,4,`${value}: ${r.out}${r.err}`);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 731,
|
||||
"text": " let r=await run(e,'activate',C1,'acme','--tester-port','7333');assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 732,
|
||||
"text": " r=await run(e,'activate',A,'acme','--plan');assert.equal(r.code,0,r.out+r.err);assert.match(r.out,/drop[^\\n]*--tester-port 7333/i);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 736,
|
||||
"text": " edit(join(e.units,CON),'--tester-port 7333','--tester-port 7334');const n=calls(e).length;"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 738,
|
||||
"text": " edit(join(e.units,CON),'--tester-port 7334','--tester-port 7333');"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 740,
|
||||
"text": " assert.deepEqual(pointer(e).console,['--port',e.port,'--business','acme']);assert.doesNotMatch(consoleText(e),/--tester-port/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 741,
|
||||
"text": " assert.match(stepText(e),/drop[^\\n]*--tester-port 7333/i);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 746,
|
||||
"text": " let r=await run(e,'activate',C1,'acme','--pm-after-harness','--tester-port','7333');assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 749,
|
||||
"text": " assert.deepEqual(pointer(e).console,testerFlags(e));assert.match(consoleText(e),/--tester-port 7333(?:\\n|$)/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 821,
|
||||
"text": " const rule=(port,main)=>`--tester-port ${port} is not a whole port from 1024 to 65535 other than 7332${main?` and the Console's --port ${main}`:''}`;"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 827,
|
||||
"text": " const r=await run(e,'activate',C1,'acme','--pm-after-harness','--plan','--tester-port',port);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 830,
|
||||
"text": " const u=await run(e,'activate',C1,'acme','--pm-after-harness','--tester-port',port);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 839,
|
||||
"text": " const n=calls(e).length,u=await run(e,'activate',C1,'acme','--tester-port','80');"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 843,
|
||||
"text": " await t.test('a target Console without --tester-port',async(t)=>{"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 846,
|
||||
"text": " const r=await run(e,'activate',A,'acme','--tester-port','7333','--plan');"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 848,
|
||||
"text": " assert.deepEqual(r.out.split('\\n').filter((l)=>l.startsWith('refuses ')),[`refuses tester-port-target: ${A}'s Console does not support --tester-port`,`refuses unknown-kind-for-target: the business file names plug-in kind(s) harness, which ${A} does not know`]);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 850,
|
||||
"text": " await refused(e,['activate',A,'acme','--tester-port','7333'],'tester-port-target',`${A}'s Console does not support --tester-port`);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1056,
|
||||
"text": " const r=await run(e,'activate',C1,'acme','--tester-port','7333');assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1057,
|
||||
"text": " assert.deepEqual(pointer(e).console,[...expected,'--tester-port','7333']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1058,
|
||||
"text": " assert.ok(consoleText(e).includes([...expected,'--tester-port','7333'].join(' ')));"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1129,
|
||||
"text": " await t.test('tests 1 and 9: the pair goes after the preview flags, before --send and the kept --tester-port, as the unit renders it; a new pair replaces it',async(t)=>{"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1132,
|
||||
"text": " let r=await prun(e,'activate',C1,'acme','--tester-port','7333');assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1135,
|
||||
"text": " assert.ok(out.find((l)=>l.startsWith('Console: ')).endsWith(` ${PREVIEW.join(' ')} ${pair(f).join(' ')} --send --tester-port 7333`));"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1137,
|
||||
"text": " assert.deepEqual(pointer(e).console,[...pre,...pair(f),'--send','--tester-port','7333']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1138,
|
||||
"text": " assert.ok(conExec(e).endsWith(` --checkout ${repo} ${[...pre,...pair(f),'--send','--tester-port','7333'].join(' ')}`),conExec(e));"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1141,
|
||||
"text": " assert.deepEqual(pointer(e).console,[...pre,...pair(g,'X-Proof-Two'),'--send','--tester-port','7333']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1162,
|
||||
"text": " await refused(e,['activate',B,'acme','--tester-port','7333'],'already-active',`${B} is the pointer's copy`);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1351,
|
||||
"text": " const e=await mk(t),base=await seedPreview(e,undefined,['--send','--tester-port','7333']),want=noSend(base);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1352,
|
||||
"text": " assert.deepEqual(want.slice(-2),['--tester-port','7333']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1367,
|
||||
"text": " await refused(e,['activate',B,'acme','--tester-port','7333'],'already-active',`${B} is the pointer's copy`);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1468,
|
||||
"text": "const ROLLBACK_PLAN=(set)=>`sso: dropped; ${B}'s Console does not support --sso, so the rollback takes the Console flags from ${set}/${SHOW}, with --send and --tester-port as the pointer has them`;"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1480,
|
||||
"text": " let r=await prun(e,'activate',B,'acme','--tester-port','7333',...pair(f));assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1481,
|
||||
"text": " assert.deepEqual(pointer(e).console,[...pre,...pair(f),'--send','--tester-port','7333']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1483,
|
||||
"text": " const want=['--port',e.port,'--business','acme','--sso','--send','--tester-port','7333'];"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1486,
|
||||
"text": " `sso: on; drops ${DROPS}; carries --port ${e.port} --business acme --send --tester-port 7333`]);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1563,
|
||||
"text": " const out=await planOnly(e,3,'activate',C,'acme','--sso','--tester-port','80');"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1564,
|
||||
"text": " assert.deepEqual(out.filter((l)=>l.startsWith('refuses ')),[`refuses tester-port-invalid: --tester-port 80 is not a whole port from 1024 to 65535 other than 7332 and the Console's --port ${e.port}`,`refuses sso-slot: ${bad('is inside the repository or the copies')}`]);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1584,
|
||||
"text": " const e=await mk(t),base=await seedPreview(e,undefined,['--send','--tester-port','7333']);await ssoConfig(e);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1594,
|
||||
"text": " const want=[...noSend(base.slice(0,-2)),'--tester-port','7333'];"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1682,
|
||||
"text": " assert.deepEqual(ssoLines(await planOnly(a,0,'rollback','acme')),[`sso: dropped; ${B}'s Console does not support --sso, so the rollback takes the Console flags from ${set}/${show}, with --send and --tester-port as the pointer has them`]);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 1811,
|
||||
"text": " writeFileSync(e.pointer,JSON.stringify({copy,business:'acme',pm:true,claude:e.claude,console:['--port',e.port,'--business','acme','--tester-port',TESTER],schema:store,activation:'manual',at:'2026-10-20T00:00:00Z'})+'\\n');"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 2198,
|
||||
"text": " ['beta\\'s tester port on alpha\\'s port',b,['--port',free,'--tester-port',a.port],1,`webui-service: refusing port-claimed: ${held}; nothing was changed\\n`],"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 2202,
|
||||
"text": " ['beta\\'s tester port on 7333',b,['--port',free,'--tester-port','7333'],4,'webui-service: port-reserved: 7333 is reserved for the default instance (7330 7332 7333)\\n'],"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 2217,
|
||||
"text": " await refused('--tester-port','--tester-port',a.port);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 2280,
|
||||
"text": " let r=await run(e,'activate',B,'acme','--tester-port','7333');assert.equal(r.code,0,r.out+r.err);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 2290,
|
||||
"text": " r=await run(e,'activate',C1,'acme','--tester-socket','--plan');assert.equal(r.code,0,r.out+r.err);assert.match(r.out,/--tester-socket/);assert.match(r.out,/drop.*--tester-port 7333/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 2292,
|
||||
"text": " assert.deepEqual(pointer(e).console,[...base,...pair(proof),'--tester-socket']);assert.match(consoleText(e),/--tester-socket/);assert.doesNotMatch(consoleText(e),/--tester-port/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 2319,
|
||||
"text": " assert.doesNotMatch(conExec(e),/ --send(?: |$)|--tester-port|--public-host|--proof-header/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release.test.mjs",
|
||||
"line": 2388,
|
||||
"text": " assert.equal(context.argv.includes('--tester-socket'),marked);assert.equal(context.argv.includes('--tester-port'),false);"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"line": 28,
|
||||
"text": " \"--node\", NODE, \"--claude\", CLAUDE, \"--pm\", \"--\", \"--port\", \"732\", \"--business\", \"mosaic-stack\", \"--tester-port\", \"7333\"];"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case1-accept.test.mjs",
|
||||
"line": 477,
|
||||
"text": " \"no tester port\": [(h) => put(h.args, ARGS.slice(0, -2).join(\"\\n\") + \"\\n\", 0o644), /lacks a numeric --port or --tester-port after --$/],"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case1-hostcheck.test.mjs",
|
||||
"line": 68,
|
||||
"text": " \"[Service]\\nExecStart=/opt/mosaic-stack/node/bin/node /srv/mosaic/copies/x/packages/webui/src/cli.mjs serve --port 732 --business mosaic-stack --tester-port 7333\\n\");"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/case1-hostcheck.test.mjs",
|
||||
"line": 215,
|
||||
"text": " put(join(h.root, \"etc\", \"systemd\", \"system\", \"mosaic-webui.service\"), \"[Service]\\nExecStart=/x serve --business mosaic-stack --tester-port 7333\\n\");"
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/stack-release-external.test.mjs",
|
||||
"line": 75,
|
||||
"text": " for(const a of [['activate',C1,'acme','--pm'],['activate',C1,'acme','--tester-port','7444'],['activate',C1,'acme','--no-proof'],['activate',C1,'acme','--no-send'],['activate',C1,'acme','--sso'],['activate',C1,'acme','--proof-header','X','--proof-file','/p'],['rollback','acme'],['gate',B,'acme'],['activate',C1,'acme','--expect-from',B],['confirm',C1,'acme','--rollback',B],['confirm',C1,'acme','--cause','A5']]){"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/README.md",
|
||||
"line": 128,
|
||||
"text": "need `--port`, and 7330, 7332 or 7333 as `--port` or `--tester-port` exits 4"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/README.md",
|
||||
"line": 130,
|
||||
"text": "`--tester-port` that another installed Console unit in the unit directory"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/README.md",
|
||||
"line": 144,
|
||||
"text": "`serve --sso [--send] [--tester-port N]` makes the Console an OpenID Connect"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/README.md",
|
||||
"line": 176,
|
||||
"text": "- With `--tester-port`, the tester listener refuses any request carrying an"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/README.md",
|
||||
"line": 1001,
|
||||
"text": "There is no tester TCP listener. `--tester-port` is retired for this copy."
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/tester-cli.test.mjs",
|
||||
"line": 25,
|
||||
"text": " for (const flags of [['--tester-port', '7332'], ['--port', '7444', '--tester-port', '7444'], ['--tester-port', '7444', '--port', '7444'], ...['1023', '65536', '1.5', '-1', 'nope'].map(p => ['--tester-port', p])]) {"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 125,
|
||||
"text": " const r = await run(dir, ['render', '--tester-port', port, ...PREVIEW, '--send']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 127,
|
||||
"text": " assert.match(execStart(r.stdout), new RegExp(` --tester-port ${port}( |$)`));"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 129,
|
||||
"text": " assert.ok(execStart(r.stdout).endsWith(` ${PREVIEW_FLAGS} --send --tester-port ${port}`));"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 136,
|
||||
"text": " const r = await run(dir, ['render', '--port', main, '--tester-port', '7332']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 150,
|
||||
"text": " ['--tester-port', ''], ['--tester-port', '1023'], ['--tester-port', '65536'],"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 151,
|
||||
"text": " ['--tester-port', '-1'], ['--tester-port', '3.5'], ['--tester-port', '1e4'],"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 152,
|
||||
"text": " ['--tester-port', 'abc'], ['--tester-port', '7333 '], ['--tester-port', '7333\\n'],"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 153,
|
||||
"text": " ['--port', '7444', '--tester-port', '7444'],"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 154,
|
||||
"text": " ['--tester-port', '7444', '--port', '7444'],"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 164,
|
||||
"text": " assert.equal((await run(dir, ['render', '--tester-port'])).status, 4);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 172,
|
||||
"text": " const six = await run(dir, ['render', '--tester-port', '007333']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 175,
|
||||
"text": " assert.equal(six.stderr, 'webui-service: --tester-port must be 1024..65535, different from --port and never 7332: 007333\\n');"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 176,
|
||||
"text": " const five = await run(dir, ['render', '--tester-port', '07333']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 178,
|
||||
"text": " assert.ok(execStart(five.stdout).endsWith(' --tester-port 07333'), execStart(five.stdout));"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 186,
|
||||
"text": " const installed = await run(dir, ['install', ...INSTALL, '--dir', unitDir, '--no-reload', '--port', String(port), '--tester-port', '1024', ...PREVIEW, '--send'], bin);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 188,
|
||||
"text": " assert.match(installed.stdout, /^serve: .* --tester-port 1024( |$)/m);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 189,
|
||||
"text": " assert.match(execStart(readFileSync(join(unitDir, 'mosaic-webui.service'), 'utf8')), / --tester-port 1024( |$)/);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 199,
|
||||
"text": " const refused = await run(dir, [verb, '--dir', unitDir, '--tester-port', '7333'], bin);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 799,
|
||||
"text": " const r = await run(dir, ['install', ...INSTALL, '--dir', unitDir, '--no-reload', '--port', String(port), ...PREVIEW, '--send', '--tester-port', '1024', '--proof-header', 'x-console-proof', '--proof-file', file], bin);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/tests/service.test.mjs",
|
||||
"line": 813,
|
||||
"text": " const both=await run(dir,['render','--tester-socket','--tester-port','7333']);"
|
||||
},
|
||||
{
|
||||
"file": "packages/webui/src/cli.mjs",
|
||||
"line": 30,
|
||||
"text": " if (flag === '--tester-port') throw new Error('--tester-port retired; activate explicitly with --tester-socket');"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Row253 build record
|
||||
|
||||
2026-10-21 design stage. Read brief Static check and Gate before starting. Busy check was CLEAR; claimant gander-253-start recorded rev2995. Branch gander/row253 starts from origin/refactor c3d88840c, after196 landed7247d80c7. Plain linked node_modules retained.
|
||||
|
||||
Only DESIGN.md and this record/inventory are written. No product code, tests, host config, units or slot files changed. BASE-PORT-INVENTORY.json records the initial source hits; it is not a passing final static check. The final check must classify every remaining hit after implementation.
|
||||
|
||||
Design r1 proposes configured dataRoot resolution as the target user, socket metadata plus an isolated system-Python SO_PEERCRED observation, unchanged hardening, P8 migration and honest unnamed P10 same-uid evidence. Duckworth approval is required before implementation. No test slot requested until the final static check passes; every single/pattern run also needs the slot under sage-2142.
|
||||
@@ -0,0 +1,248 @@
|
||||
# Row 253: private tester socket in the host scripts
|
||||
|
||||
Gander, 2026-10-21. F27, #1758. Base c3d88840c, after row196 landing
|
||||
7247d80c7. Brief: docs/plans/2026-10-21_tester-socket-scripts.md at
|
||||
0cdead66c. Reviewer Duckworth; gate owner Sage. Status: proposed, no product
|
||||
code or tests changed. Claim gander-253-start is queue rev2995.
|
||||
|
||||
## Outcome and scope
|
||||
|
||||
The factory installer renders a bare --tester-socket flag instead of a TCP
|
||||
port. Case1 acceptance consumes that saved form and examines the private
|
||||
listener without claiming that a private pathname defeats hostile same-uid
|
||||
processes. The library profile still has no tester. No .53 or .54 host may
|
||||
take a copy carrying196 before253 lands, per mos-1198.
|
||||
|
||||
Change scripts/case2-unit.sh, scripts/case1-accept.sh and their two existing
|
||||
CLI test files. Their comments/usage and fixture docs move with the code.
|
||||
Do not edit the deliberate legacy paths named in the brief. No runtime bus,
|
||||
Console, role, grant, control-board, dependency or systemd-template change.
|
||||
No live installer or acceptance run, provider, home/config, unit or host access.
|
||||
|
||||
## Choices
|
||||
|
||||
1. Recommended: resolve the configured dataRoot as the stack user, preserve
|
||||
hardening, check the final pathname's metadata and use SO_PEERCRED on a
|
||||
connection to identify its listener. This supports custom roots within the
|
||||
unit's existing writable home and matches196's kernel-peer approach.
|
||||
2. Assume <home>/.mosaic-dev and inspect a pathname in /proc/net/unix. This
|
||||
misses a custom config and is not a reliable final-path ownership check:
|
||||
196 binds a temporary name and renames it. A socket's filesystem inode also
|
||||
must not be confused with its kernel socket inode. Do not use this option.
|
||||
3. Add a TCP relay, relax hardening or give acceptance a configurable probe
|
||||
executable. All widen the surface or trust inputs and are outside this row.
|
||||
|
||||
There is no second bus principal verifier. The acceptance probe observes an
|
||||
OS endpoint; it grants no capability and sends no HTTP or bus request.
|
||||
|
||||
## Installer arguments and preflight
|
||||
|
||||
Factory keeps required --port (1..1023) and --business; tester remains optional
|
||||
for the installer. --tester-socket is a single marker, takes no value and keeps
|
||||
its position in the rendered flags. The existing duplicate-flag check applies.
|
||||
A following stray value is an unknown flag. --tester-port always refuses in
|
||||
serve arguments, for both profiles, before unit writes or systemctl actions:
|
||||
|
||||
case2-unit: case2-unit.sh retires tester TCP; use --tester-socket
|
||||
|
||||
Library rejects --tester-socket with an explicit library-profile refusal. Its
|
||||
other flags, proof-file checks and install behavior are unchanged. No legacy
|
||||
conversion or fallback to TCP is introduced in this installer.
|
||||
|
||||
When the factory socket marker is present, a small inline metadata query runs
|
||||
the selected copy's config loader under the target uid with sudo -u USER -H,
|
||||
env -i, explicit HOME and the same pinned-node-first PATH used by the unit.
|
||||
Root never imports user-writable copy code as root. It invokes the previously
|
||||
validated --node path after dropping uid, with the copy module path as a
|
||||
separate argument. This resolves the default instance just as the rendered
|
||||
unit does; no MOSAIC_CONFIG or named-instance override is inherited.
|
||||
|
||||
The query returns only dataRoot and whether that copy's Console carries the
|
||||
MOSAIC_TESTER_SOCKET_V1 marker. It prints no config values, credentials or child
|
||||
diagnostics. Bound it to five seconds and 4096 output bytes; reject errors,
|
||||
extra output/keys, malformed results, missing config and unsupported targets.
|
||||
No root process opens a config or credential under the user's home. Existing
|
||||
root metadata checks remain root metadata checks. Both script headers explain
|
||||
the new, explicitly unprivileged config query.
|
||||
|
||||
Before any unit writes, validate the socket location:
|
||||
|
||||
- dataRoot is an absolute normalized plain path, below the declared home,
|
||||
with no symlink in its existing path. Never expand the unit's writable scope.
|
||||
- Neither home nor dataRoot is under /home, /root, /run/user, /tmp or /var/tmp.
|
||||
These roots conflict with the existing ProtectHome/PrivateTmp contract.
|
||||
- <dataRoot>/console/tester.sock is at most 107 ASCII bytes, matching196.
|
||||
- A missing console parent is allowed; runtime creates it. An existing parent
|
||||
must be a real directory, not a symlink, owned by the target uid with no
|
||||
group/other permissions. A foreign owner, non-directory or unsafe mode refuses.
|
||||
- Do not create/chmod the parent or inspect/delete an existing tester entry in
|
||||
the installer. A running Console may own it during a re-pin. Runtime196 owns
|
||||
binding and cleanup. Reinstall still does not restart running units.
|
||||
|
||||
The dataRoot query and all refusal families get fake-only tests. A failed
|
||||
preflight leaves both units and the systemctl log unchanged.
|
||||
|
||||
## How the rendered unit reaches the socket
|
||||
|
||||
The unit still has User/Group for the stack account, ProtectSystem=strict,
|
||||
ReadWritePaths=<home>, ProtectHome=yes and PrivateTmp=yes, in the same order.
|
||||
Its ExecStart contains --tester-socket, with no pathname argument. The Console
|
||||
loads dataRoot and derives console/tester.sock itself. Case1/2's /srv/mosaic
|
||||
home is outside ProtectHome's protected hierarchies; the allowed dataRoot is
|
||||
inside the existing writable home, and the socket is outside private /tmp.
|
||||
No extra capability or writable directory is added. Systemd's documented
|
||||
namespace rules motivate the location refusals, rather than a hardening change.
|
||||
[systemd.exec](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html)
|
||||
|
||||
A mutable same-uid config can change between preflight and start. The unit's
|
||||
writable boundary is still home; runtime's own config/socket checks still
|
||||
refuse unsafe state. This row does not promise an immutable same-uid config.
|
||||
The host window must verify the rendered unit actually starts under its
|
||||
namespace; scratch tests inspect its directives, not a real systemd namespace.
|
||||
|
||||
## Acceptance arguments and endpoint observation
|
||||
|
||||
The saved args must contain one --tester-socket and the numeric --port.
|
||||
Reject a missing/duplicate marker, a marker with an old numeric value, and
|
||||
--tester-port with the same migration wording naming case1-accept.sh. Preserve
|
||||
all existing pre-- argument validation and the default-instance contract.
|
||||
Malformed saved inputs still refuse with exit2 before functional checks.
|
||||
|
||||
Acceptance resolves the actual configured tester dataRoot with the same
|
||||
unprivileged, bounded query. It does not silently use the default path. An
|
||||
unreadable/invalid config or a root outside the unit's allowed home refuses
|
||||
before the functional talk. The old DATA_ROOT used by F11 remains unchanged;
|
||||
generalizing that separate Discord check is outside this row.
|
||||
|
||||
P10 independently requires the Console's systemd MainPID to be positive and
|
||||
stable, and its four /proc uid fields to be the stack uid. Check the private
|
||||
parent's type, owner and mode, and the final entry's non-symlink socket type,
|
||||
owner and exact 0600 mode. Snapshot parent/socket dev+ino and metadata before
|
||||
and after the peer probe; a replacement or MainPID change fails observation.
|
||||
The port floor must still be numeric and above the privileged main Console
|
||||
port, retaining that existing part of the observation.
|
||||
|
||||
The bounded probe runs as the stack user with an empty inherited environment,
|
||||
the fixed system /usr/bin/python3 resolved to a root-owned ELF, safe root-owned
|
||||
ancestors, and -I -S. This is the system Python dependency already required by
|
||||
196; never use PATH python, a home shim or a configurable helper. Unsafe/missing
|
||||
Python is a failed P10 observation, not a fallback. Resolve/check the system
|
||||
entry and target using metadata and the ELF header; do not run copy Python code
|
||||
as root. Test seams emulate these root facts and the system executable.
|
||||
|
||||
The probe opens AF_UNIX/SOCK_STREAM, uses a one-second connect timeout, reads
|
||||
SO_PEERCRED and closes. An outer three-second process-group bound applies. It
|
||||
returns only pid/uid/gid, with exact structure, bounded output and numeric
|
||||
validation. pid must equal MainPID and uid the stack uid; gid is validated but
|
||||
not an identity grant. No application bytes, cookies, credentials or proof
|
||||
envelope are sent. Linux supplies the peer credentials for the connected
|
||||
stream, so the check does not depend on the temporary bind pathname.
|
||||
[unix(7), SO_PEERCRED](https://man7.org/linux/man-pages/man7/unix.7.html)
|
||||
|
||||
This is a snapshot. Hostile same-uid processes can alter same-uid state or
|
||||
interfere with the unprivileged probe. Root, ptrace and later pathname changes
|
||||
are not defeated here. The probe does not prove another uid is denied; that
|
||||
remains a host-window observation, not a fake test claim.
|
||||
|
||||
## P8 and P10 outcome lines
|
||||
|
||||
P8 must change with removal of TESTER_PORT. It checks the main Console port is
|
||||
loopback-only and held only by MainPID, and that nothing listens on TCP7331 or
|
||||
the retired TCP7333. Socket ownership belongs to P10. Its healthy line is:
|
||||
|
||||
P8 lan-listeners closed <port> is on 127.0.0.1 only by MainPID <pid>; no TCP listener on 7331 or 7333; tester uses <socket>
|
||||
|
||||
An unexpected listener, missing main listener or wrong owner produces P8
|
||||
lan-listeners unnamed with the observed listener summaries, as today.
|
||||
|
||||
Do not reuse accepted mos-1101 for a new Unix-socket claim: that ruling named a
|
||||
TCP-port condition. Proposed safe default pending any new Mos classification:
|
||||
|
||||
P10 tester-socket-squat unnamed boundary=observed path=<socket> parent=0700 owner=<uid> socket=0600 peer_pid=<pid> peer_uid=<uid> floor=<floor> port=<port>; hostile same-uid substitution remains
|
||||
|
||||
Any missing/unsafe metadata, wrong peer, unsafe interpreter, malformed/timeout
|
||||
probe, floor miss or changed snapshot produces one P10 line instead:
|
||||
|
||||
P10 tester-socket-squat unnamed boundary=failed reason=<bounded-code> path=<socket> MainPID=<pid>; hostile same-uid substitution remains
|
||||
|
||||
Healthy observation does not mark the same-uid squat path closed or invent an
|
||||
accepted ruling. P checks still never change exit status. Tests pin the reason
|
||||
and observed/failed distinction, so validation cannot disappear behind an
|
||||
always-unnamed mark. Sage/Mos may supply a new classification at design review;
|
||||
it must be explicit before changing this proposed mark. No Jason question is
|
||||
needed merely to retain unnamed evidence.
|
||||
|
||||
## Evidence and planned checks
|
||||
|
||||
Keep the existing scratch fixtures, fake account database/systemctl/curl/ss/
|
||||
sudo/stat and fake interpreter. Add copy config/marker fixtures and probe
|
||||
metadata under those same roots. No test connects to a live socket or runs the
|
||||
real host scripts without their existing seams. Fixture fakes verify the
|
||||
unprivileged command argv; no test claims a real uid drop or namespace.
|
||||
The fake system interpreter executes the actual probe payload against a fake
|
||||
Python socket API, checking AF_UNIX/SOCK_STREAM, timeout, connect, getsockopt
|
||||
arguments and returned peer bytes. This catches errors in the inline probe
|
||||
without opening an OS listener. The metadata query is tested with nonzero
|
||||
status plus valid-looking output as well as malformed output, so removing its
|
||||
status refusal cannot hide behind a later parse error.
|
||||
|
||||
| Checks | Required observations |
|
||||
|---|---|
|
||||
| T1 factory render | Bare marker exactly once, argument order retained, no TCP flag, hardening unchanged; duplicate/stray value refuses |
|
||||
| T2 profile/migration | Both profiles refuse retired TCP; library refuses socket; all refuse before writes/start |
|
||||
| T3 config/target | Invalid/missing/noisy/timed-out metadata and unmarked target refuse; query uses target uid and clean environment |
|
||||
| T4 location | Default and custom in-home roots work; outside-home, protected/private hierarchies, symlinks and >107-byte pathname refuse |
|
||||
| T5 parent | Missing parent allowed; file/symlink/foreign owner/group-other mode refuse without mutation or systemctl calls |
|
||||
| T6 saved args | Required socket marker; missing/duplicate/stray value and retired TCP refuse exit2 before talk |
|
||||
| T7 P8 | Main-only loopback succeeds; LAN/wrong owner/7331 or7333 listener gives unnamed |
|
||||
| T8 P10 | Exact metadata, stable MainPID and matching peer produce boundary=observed; custom configured root used |
|
||||
| T9 failed P10 | Parent/socket faults, wrong PID/uid, malformed/timeout probe, unsafe Python, bad floor and changed snapshot each give failed reason |
|
||||
| T10 preserved behavior | Functional checks, their exit semantics, library behavior and all other P paths retained |
|
||||
|
||||
Planned checks to kill, one target change at a time in isolated snapshots:
|
||||
|
||||
| IDs | Removed requirement |
|
||||
|---|---|
|
||||
| M1-M3 | Installer TCP refusal, library socket refusal, duplicate socket refusal |
|
||||
| M4-M5 | Config-query failure refusal, unsupported-target refusal |
|
||||
| M6-M8 | Home containment, protected/private path refusal,107-byte limit |
|
||||
| M9-M11 | Existing parent type/symlink, uid, mode checks |
|
||||
| M12-M15 | Acceptance TCP refusal, required marker, duplicate/stray marker rejection, config-query refusal |
|
||||
| M16 | P8 retired TCP7333 check |
|
||||
| M17-M18 | P10 parent and final socket metadata checks |
|
||||
| M19-M21 | Peer PID, peer uid, malformed/timeout probe refusal |
|
||||
| M22-M23 | Safe fixed system interpreter, stable metadata/MainPID snapshot |
|
||||
| M24-M25 | Target-uid invocation in installer and acceptance metadata/probe paths |
|
||||
| M26 | Privileged main-port floor observation |
|
||||
| M27 | Probe returns its own PID rather than the connected peer's credentials |
|
||||
|
||||
Before execution, give every definition one unique source target and a named
|
||||
killer test. Preserve raw logs, base times, syntax preflight and byte-identical
|
||||
SHA/Git restoration. A timeout counts only under Sage's explicit rules and
|
||||
must kill the process group. Refusal variants in T1-T9 must not be merely
|
||||
assertions of a generic nonzero exit.
|
||||
|
||||
## Static inventory and gate
|
||||
|
||||
After implementation, enumerate every --tester-port hit in scripts/packages
|
||||
in BUILD.md and a machine-readable file. The only permitted hits are the brief's
|
||||
deliberate legacy producers/parsers/tests and this row's explicit refusals and
|
||||
negative fixtures. Inspect arguments and generated units, not just filenames.
|
||||
Readmes must describe retirement rather than produce old argv. No modification
|
||||
to webui-service, stack-release, instance, their tests/fixture, case1-hostcheck
|
||||
or tester-cli negative calls is planned. Stop and ask Sage if one is needed.
|
||||
|
||||
Then request Sage's slot. Under that grant, run CLI whole, WebUI whole, every
|
||||
shell test suite whole and all named checks above. Use an isolated candidate,
|
||||
plain linked node_modules, a short gXXX TMPDIR under /mnt/storage/scratch/tmp,
|
||||
busy &&, pipefail and tee. Single/pattern runs need the slot too, per sage-2142.
|
||||
No harness/helper/log argument may match the busy detector pattern, per
|
||||
mos-1205. Two failures end the window; report any stop to Sage/Mos before a
|
||||
retry. Return the slot with script-summed counts/timings and a leftover sweep.
|
||||
|
||||
Design approval precedes code. The manifest, immutable candidate hashes,
|
||||
static inventory and gate evidence go to Duckworth before Sage's landing.
|
||||
Queue moves use the helper, with a coordinated queue-write gap. Sage posts the
|
||||
jarvis review request after the in-review move. BUILD-LOG/session history stays
|
||||
append-only; Sage continues session registration. No host deployment is part
|
||||
of row253 acceptance.
|
||||
Reference in new issue
Block a user