fix(upgrade): refuse legacy library --send before units stop (#385)
This commit is contained in:
1 parent
ab2bc23c7f
commit
d0ddfa8de2
7 files changed
+84
-3
No files matched your search
@@ -5819,3 +5819,4 @@ Before: agents can read the library; supporting-path and push-credential depende
|
||||
|
||||
2026-10-29T03:27:31Z | Codex implementation sub-agent | #385 OIDC-only writer preparation on exact base 09a847edaa25b609bac7728ff83f224708a29399 | Added fail-closed CLI, programmatic and service-render refusals for `--send` without `--sso`; retained read-only header preview/proof; migrated inventoried writer tests to real in-process OIDC login/callback sessions and updated docs. Focused regression 5/5 and selected service cases 4/4 pass; syntax and diff checks pass. The broader writer suite is blocked before execution because the clean checkout has no `node_modules` and exact pinned `[email protected]` is unavailable; no install, commit, push, PR, queue move or green/completion claim.
|
||||
2026-10-29T05:49:53Z | Codex Daybreak Blue xhigh implementation sub-agent | CORRECTION and Mos2255 #385 review repair R2 | The 03:27:31Z dependency/no-commit statements are historical: exact pinned `[email protected]` is now materialized through the ignored `node_modules` fixture with no install, and original preparation is committed at `9936a1be1a37fdb30ba53cf2f14b7897225c3e4a`. On that unchanged HEAD, uncommitted R2 source/tests/docs make Case 2 library preview read-only until #412, drop inherited non-SSO `--send` during release planning, and refuse duplicate service `--send`. Focused RED was 0/2, 0/1, 0/1; focused GREEN and selected T53/T55/T57/T58 fixture blocks pass. No root/native/live/full-suite/CI action, commit, push, PR/API, merge or frozen/live-pin change.
|
||||
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on ab2bc23c7 | R2-F1: `scripts/stack-upgrade.sh` `read_args` now scans the serve argv after `--` and refuses `args-unreadable` before anything stops when the profile is library (no `--profile`, or the last one says library, as `case2-unit.sh` reads it) and `--send` is present; the message names the remediation, and the args file is never rewritten. `do_upgrade` and `do_rollback` both call `read_args` first, so rollback is covered; factory and tester paths are unchanged. RED on ab2bc23c7: new stack-upgrade case 0/1 (exit 2 rollback-failed, A4 and render-old failed, both units stopped), controls 1/1; GREEN 2/2. R2-I1: one component case runs the real `sso_for_target` then `send_for_target` on a legacy preview `--send` pointer, carried after `--sso` and before `--tester-socket`. R2-I2: TOOLS.md says a same-copy plain activate on a legacy pointer refuses already-active and needs `--no-send`. R2-I3: no code change; `uninstall`/`status` with `--send --send` exit 1, not 4, fail-closed either way. host-run-sheet section 7 gains the migration step. Whole files, one at a time: stack-upgrade 23/23 (69 s), stack-release 225/225 (608 s; a first run was cut by my 600 s background limit at 221 passing, its orphan fixture holder killed), case2-unit 48/48 (73 s). No root, systemd, container, network, push, PR or live action.
|
||||
@@ -1881,3 +1881,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-26 | Sage | Mos1909/1915/1917 real postlanding reviews | Boyd/Dewey/Filbert/Gladstone real authored candidate52ba verdictfiles independently byte/hash-verified, allAPPROVE scoped; Dewey656broaderPARTIAL retained. Formal330R1/331R4 requests failedpre-send/no comments or approvals; no borrowed/copied/mintedkeys. LegacyhelperGETname jason.woltje is NOT ownJarvis, ownslotabsent; rows tracking-open/#436, files cited via normalqueue. NewFilbertNEW-330-01 filedtask665 exactreadback, notlandingreopen/gate. Repair646-648/652-656 remainOPEN untilactualcomments;1917 conditionalbacklog routing retained. #662scope1338posted;660PR405REVISE/notready. Product1968inputs unchanged; no source/helper/test/gate edits/runs; peerjournal prefixes preserved.
|
||||
2026-10-29T03:27:31Z | Codex Daybreak Blue xhigh implementation sub-agent | #385 OIDC-only Console writes, branch work/vikunja-19-385-q284-complete at exact base 09a847edaa25b609bac7728ff83f224708a29399 | Source/test/docs preparation complete for review; focused refusal 5/5 and selected service 4/4 pass, broader real-OIDC writer tests blocked by absent pinned oauth4webapi@3.8.8; no install/commit/push/PR/queue move or overall-green claim.
|
||||
2026-10-29T05:49:53Z | Codex Daybreak Blue xhigh implementation sub-agent | Mos2255 #385 review repair R2 on work/vikunja-19-385-q284-complete at unchanged HEAD 9936a1be1a37fdb30ba53cf2f14b7897225c3e4a/base 09a847edaa25b609bac7728ff83f224708a29399 | Corrects the 03:27:31Z historical blocker: pinned oauth4webapi3.8.8 fixture is now present without install and original prep is committed. F1/F2/F3 repaired in uncommitted source/tests/docs; F4/F7 disclosed only, F6 append conflicts preserved. Three focused test files only: intentional RED captured, GREEN controls plus T53 12/12, T55 8/8, T57 13/13 and T58 11/11 pass. No root/native/live/full-suite/CI, commit/push/PR/API/merge, frozen packet or live-pin action.
|
||||
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on work/vikunja-19-385-q284-complete at ab2bc23c7 | R2-F1 fixed: stack-upgrade refuses a library args file holding `--send` after `--` before any unit stops, for upgrade and rollback; R2-I1 component case and R2-I2 TOOLS.md precision added, R2-I3 noted. RED 0/1 then GREEN 2/2; stack-upgrade 23/23, stack-release 225/225, case2-unit 48/48. One local commit; no push, PR, Vikunja, Gitea or live action.
|
||||
+3
-1
@@ -494,7 +494,9 @@ every table in COUNTED.
|
||||
pointer's, and takes the Console flags from the rollback set of the activation that turned it on.
|
||||
--no-sso drops the pointer's --send, and so does a rollback to a Console without --sso.
|
||||
An inherited legacy --send beside header-preview flags is dropped during planning because preview is
|
||||
read-only; it never reaches the service installer.
|
||||
read-only; it never reaches the service installer. The drop alone is not a change: an activate of the
|
||||
pointer's own copy refuses already-active unless another flag changes the Console (check_target's
|
||||
list), so on a legacy pointer pass --no-send there.
|
||||
```
|
||||
|
||||
This is an operator plan, not an action agents run. Both reviews and the
|
||||
|
||||
@@ -385,7 +385,21 @@ but omits `--send`. That Console is read-only until Case 2 gains real OIDC in #4
|
||||
explicitly refuses `--send`. Proxy preview and proof-protected reads remain available. The `factory`
|
||||
profile and its private tester socket are unchanged.
|
||||
|
||||
Precheck line: none. `stack-upgrade.sh` refuses `args-unreadable` on any of these rules.
|
||||
A library host installed before #385 has a `--send` line after `--`, because the earlier
|
||||
`case2-unit.sh` required it. `stack-upgrade.sh upgrade` and `rollback` read the args file before
|
||||
anything stops, and when the profile is `library` (no `--profile` line, or the last one says
|
||||
`library`) and `--send` appears after `--`, they refuse `args-unreadable` with both units still
|
||||
running. The script never rewrites the file. To migrate, as root:
|
||||
|
||||
1. Open `/etc/mosaic-stack/dev.args` and delete the one line that reads `--send`. Change nothing else.
|
||||
2. Check it: `stat -c '%U %a' /etc/mosaic-stack/dev.args` still gives `root` and a mode with no group or
|
||||
other write, and `grep -cx -- --send /etc/mosaic-stack/dev.args` prints `0`.
|
||||
3. Rerun the same `stack-upgrade.sh` command.
|
||||
|
||||
The Console stays read-only after the upgrade, until #412 (#385).
|
||||
|
||||
Precheck line: none. `stack-upgrade.sh` refuses `args-unreadable` on any of these rules, before it stops
|
||||
a unit.
|
||||
|
||||
## 8. Enrollment, once per host
|
||||
|
||||
|
||||
@@ -1363,6 +1363,7 @@ test('Mos2255 source component: legacy preview send drops while explicit and SSO
|
||||
exact('check_refuse() {','\n\n# Item 1 and item 4'),
|
||||
exact('has_flag() {','\n\n# Row 200'),
|
||||
exact('send_for_target() {','\n\n# Row 187:'),
|
||||
exact('sso_for_target() {','\n\n# Row 200 item 2'),
|
||||
].join('\n');
|
||||
const runCase=(setup)=>{
|
||||
const r=spawnSync('/bin/bash',['-c',`${component}
|
||||
@@ -1394,6 +1395,11 @@ printf 'console=%s\\nplan=%s\\nstep=%s\\nrefuses=%s\\n' "\${RUN_CONSOLE[*]}" "$S
|
||||
assert.deepEqual(runCase(`P_CONSOLE=(${preview} --sso --send); RUN_CONSOLE=(${preview} --send)`),{
|
||||
console:preview,plan:'send: dropped; the run drops --sso, and --send needs it (F39)',step:'drop --send; the run drops --sso',refuses:'',
|
||||
});
|
||||
// R2-I1 (#385): activate --sso on a legacy preview --send pointer. The real sso_for_target builds the
|
||||
// run's flags from the pointer's, and send_for_target carries --send after --sso, before the tester flag.
|
||||
assert.deepEqual(runCase(`SSO=1; P_CONSOLE=(${preview} --send --tester-socket); RUN_CONSOLE=("\${P_CONSOLE[@]}"); sso_for_target`),{
|
||||
console:'--port 7332 --business acme --sso --send --tester-socket',plan:'send: carried',step:'',refuses:'',
|
||||
});
|
||||
});
|
||||
|
||||
test('T55 row 189: activate --no-send drops --send, and rollback and pm-on carry it off',async(t)=>{
|
||||
|
||||
@@ -433,6 +433,52 @@ test('row 233: the args file must be the running user\'s, with no group or other
|
||||
assert.ok(!existsSync(join(e.sd,'root-systemctl.log')));assert.deepEqual(live(e),['active','active',B,B]);
|
||||
});
|
||||
|
||||
// R2-F1 (#385, PR 1836): base case2-unit.sh required --send for the library profile, and the current one
|
||||
// refuses it, so a legacy args file would stop both units at A2 and fail the A4 render and render-old.
|
||||
// read_args refuses it first, for an upgrade and a rollback alike, and never rewrites the file.
|
||||
const legacySend=(f)=>`${f} has --send after --, which case2-unit.sh refuses for the library profile: remove the --send line, then rerun; the library Console is read-only until #412 (#385) (nothing was stopped)`;
|
||||
test('#385 R2-F1: a library args file holding --send after -- refuses args-unreadable before anything stops, for upgrade and rollback',async(t)=>{
|
||||
const e=await useed(t);
|
||||
const legacy=e.argsText+'--send\n';
|
||||
const cases=[
|
||||
['the default profile',legacy,()=>upgrade(e,C1)],
|
||||
['an explicit --profile library',legacy.replace('--\n','--profile\nlibrary\n--\n'),()=>upgrade(e,C1)],
|
||||
['the last --profile is library',legacy.replace('--\n','--profile\nfactory\n--profile\nlibrary\n--\n'),()=>upgrade(e,C1)],
|
||||
['a rollback',legacy,()=>drive(e,B,'rollback','--instance','dev','--to',B,...TO)],
|
||||
];
|
||||
for(const [why,text,run] of cases){
|
||||
writeFileSync(e.args,text);chmodSync(e.args,0o640);
|
||||
const n=existsSync(e.slog)?slog(e).length:0;
|
||||
const r=await run();assert.equal(r.code,3,`${why}: ${r.all}`);
|
||||
assert.equal(r.err.split('\n').at(-2),`stack-upgrade: args-unreadable: ${legacySend(e.args)}`,why);
|
||||
assert.deepEqual(slog(e).slice(n).map((x)=>x.split(' ')[0]),['start','result'],why);
|
||||
assert.equal(slog(e).at(-1),`result exit=3 args-unreadable ${legacySend(e.args)}`,why);
|
||||
// The file is left as the operator wrote it.
|
||||
assert.equal(readFileSync(e.args,'utf8'),text,why);assert.deepEqual(readdirSync(e.etc),['dev.args'],why);
|
||||
}
|
||||
assert.ok(!existsSync(join(e.sd,'sudo.log')));assert.ok(!existsSync(join(e.sd,'render.log')));
|
||||
assert.ok(!existsSync(join(e.sd,'root-systemctl.log')));assert.deepEqual(live(e),['active','active',B,B]);
|
||||
});
|
||||
|
||||
test('#385 R2-F1 controls: library args without --send and a factory args file pass read_args as before',async(t)=>{
|
||||
const e=await useed(t);
|
||||
// A bundle that does not exist stops the run at its first step after read_args, with nothing stopped.
|
||||
const gone=join(e.root,'no.bundle');
|
||||
for(const [why,text] of [
|
||||
['library, no --send',e.argsText.replace('--\n','--profile\nlibrary\n--\n')],
|
||||
['default profile, no --send',e.argsText],
|
||||
['factory with --send, which case2-unit.sh refuses at the render as before',e.argsText.replace('--\n','--profile\nfactory\n--\n')+'--send\n'],
|
||||
['factory, no --send',e.argsText.replace('--\n','--profile\nfactory\n--\n')],
|
||||
]){
|
||||
writeFileSync(e.args,text);chmodSync(e.args,0o640);
|
||||
const n=existsSync(e.slog)?slog(e).length:0;
|
||||
const r=await drive(e,C1,'upgrade','--instance','dev','--bundle',gone,'--sha',C1,...TO);assert.equal(r.code,3,`${why}: ${r.all}`);
|
||||
assert.deepEqual(slog(e).slice(n).map((x)=>x.split(' ')[0]),['start','fetch','result'],why);
|
||||
assert.match(slog(e).at(-1),/^result exit=3 fetch-failed /,why);
|
||||
}
|
||||
assert.ok(!existsSync(join(e.sd,'root-systemctl.log')));assert.deepEqual(live(e),['active','active',B,B]);
|
||||
});
|
||||
|
||||
test('row 233: hostcheck (row 240): a nonzero --check or a missing file refuses profile-drift before the plan',async(t)=>{
|
||||
const e=await useed(t);
|
||||
fault(e,'hostcheck-rc','1');
|
||||
|
||||
@@ -121,8 +121,12 @@ logl "start rc=0 argv:$(printf ' %q' "$0" "${ARGV[@]}")"
|
||||
|
||||
# I2's file: one case2-unit.sh argument per line, owned by root with no group or other write. Sets
|
||||
# ARGS (the lines), S_USER, S_HOME, BUSINESS, NODE and ARGS_COPY, and the paths derived from them.
|
||||
# #385 R2-F1: base case2-unit.sh required --send for the library profile (its default, and the last
|
||||
# --profile wins, as there), and the current one refuses it. A2 would stop both units, and A4 and
|
||||
# render-old would both fail on the same args, so a library file holding --send after -- refuses
|
||||
# here, before anything stops. The file is never rewritten; the operator removes the line.
|
||||
read_args() {
|
||||
local f="$ETC/$INSTANCE.args" st i copies=0 v
|
||||
local f="$ETC/$INSTANCE.args" st i copies=0 v profile=library
|
||||
[ -f "$f" ] && [ ! -L "$f" ] || refuse args-unreadable "no regular file $f"
|
||||
st="$(stat -c '%u %a' "$f")" || refuse args-unreadable "cannot stat $f"
|
||||
[ "${st% *}" = "$(id -u)" ] && (( (8#${st#* } & 8#022) == 0 )) || refuse args-unreadable "$f must be $(id -un)'s with no group or other write (it is uid ${st% *}, mode ${st#* })"
|
||||
@@ -137,12 +141,19 @@ read_args() {
|
||||
case "${ARGS[i]}" in
|
||||
--user) S_USER="$v" ;; --home) S_HOME="$v" ;; --business) BUSINESS="$v" ;; --node) NODE="$v" ;;
|
||||
--copy) ARGS_COPY="$v"; copies=$((copies + 1)) ;;
|
||||
--profile) profile="$v" ;;
|
||||
esac
|
||||
i=$((i + 1)) ;;
|
||||
*) refuse args-unreadable "$f has ${ARGS[i]} before --, which case2-unit.sh does not take" ;;
|
||||
esac
|
||||
done
|
||||
[ "$copies" = 1 ] || refuse args-unreadable "$f needs exactly one --copy before --, not $copies"
|
||||
if [ "$profile" = library ]; then
|
||||
for ((i = i + 1; i < ${#ARGS[@]}; i++)); do
|
||||
[ "${ARGS[i]}" != --send ] ||
|
||||
refuse args-unreadable "$f has --send after --, which case2-unit.sh refuses for the library profile: remove the --send line, then rerun; the library Console is read-only until #412 (#385)"
|
||||
done
|
||||
fi
|
||||
[[ "$S_USER" =~ ^[a-z_][a-z0-9_-]*$ ]] && [[ "$S_HOME" =~ ^/[A-Za-z0-9/._+-]+$ ]] && [[ "$BUSINESS" =~ ^[a-z0-9][a-z0-9-]*$ ]] &&
|
||||
[[ "$NODE" =~ ^/[A-Za-z0-9/._+-]+$ ]] && [[ "$ARGS_COPY" =~ ^[0-9a-f]{40}$ ]] ||
|
||||
refuse args-unreadable "$f lacks a plain --user, --home, --business, --node or --copy"
|
||||
|
||||
Reference in new issue
Block a user