Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fd8f42f64 | ||
|
|
f8b9c2f73e | ||
|
|
4fa7d68348 | ||
|
|
9c6e39b251 | ||
|
|
a54baff217 | ||
|
|
3b17c75508 |
No files matched your search
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"base": "01983cd3f",
|
||||
"test": "packages/cli/tests/stack-release.test.mjs",
|
||||
"base_is_byte_exact_prefix": true,
|
||||
"old_assertions_changed": 0,
|
||||
"old_console_refusal_text_unchanged": true
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# Target config validation for every key implementation plan
|
||||
|
||||
Goal: #1797, mos-1353, brief f0e3dcdf4:docs/plans/2026-10-22_config-checks-any-key-brief.md. Base01983cd3f, branchgander/config-checks-any-key. Reviewer Duckworth. Queue number pending Sage; packet moves to queue-N when assigned. No Node, test or push before Sage gap/CLEAR. Rows270/271/203 land first; rebase under a new branch name before the gate.
|
||||
|
||||
Architecture: config_checks classifies missing/parseable/unreadable config independently of console presence. With a loader, every existing parseable config goes through the target's own validate. Missing config passes. Malformed JSON refuses. With no loader, allow only the oldest four top-level keys: configVersion, environment, dataRoot, execution. Evidence: c3d29e796:scripts/mosaic-config.mjs:114, the first loader commit, rejectUnknownKeys; row200 fixture's LOADER_OLD has the same list. A missing loader plus a later key refuses and names that key, without inventing validation support. The legacy public refusal code config-console-key and existing console-specific message stay byte-identical; new non-console refusals name the loader's first diagnostic and preserve restore-config-backup instruction. The target loader's resolved stdout is discarded. Its first stderr diagnostic is reported and may echo an invalid value.
|
||||
|
||||
Files: scripts/stack-release.sh config_checks/comment only; packages/cli/tests/stack-release.test.mjs appended regression coverage only; docs/TOOLS.md broadens the current console-only release-check description. Existing row200 tests and assertions stay unchanged. No fixture-module edit, flags, pointers, migration, schema, data, live unit or host change.
|
||||
|
||||
Tests to write before implementation, unrun until CLEAR:
|
||||
- A target loader rejecting a non-console key refuses activate and rollback before any unit stop/start, pointer/unit/store mutation; one refused log entry; --plan preserves state and lists refusal. Reuse refused() and planOnly(). Use execution, already known to the current loader, to prove console-free dispatch before row270 integration; add compactionReadBytes/compactionStreams cases on final post270 base if needed.
|
||||
- An accepting target loader is actually called with validate and the selected MOSAIC_CONFIG, succeeds activate/rollback without console, and resolved stdout is not leaked.
|
||||
- Missing-loader target permits oldest-key config but rejects a later key with backup instruction. A stand-in rejecting the existing execution key exercises the generic target-validation branch; missing-loader later-key test may use console until final post270 base supplies non-console new keys.
|
||||
- Missing file passes the isolated real config_checks function; malformed JSON refuses there, since load_config otherwise exits before this function. Tests extract only that function and provide copies/path/check_refuse stand-ins, never live state.
|
||||
- Existing console-key row200 tests and malformed-load test run whole unchanged.
|
||||
|
||||
Mutants, each unique target, syntax preflight and byte-identical restore:
|
||||
M1 restores the console-only skip, killed by the named non-console activate/rollback refusal test.
|
||||
M2 ignores failed target validation, killed by the same refusal assertions.
|
||||
M3 allows a missing-loader target with a later key, killed by the explicit missing-loader regression.
|
||||
M4 treats malformed JSON as absence, killed by isolated parse guard test.
|
||||
M5 skips the loader for an oldest-key config, killed by accepting-loader invocation witness.
|
||||
|
||||
Gate after static review and required rebases: intended RED/GREEN; touched stack-release.test.mjs whole, CLI suite whole and every scripts/test-*.sh whole; M1-M5 with fresh passing controls, no timeout counted as kill, restored bytes/status before next. TMPDIR mktemp-d /mnt/storage/scratch/tmp/gXXX; pipefail/tee and busy check before each start. Node timeout-k10 uses at least3x measured duration plus margin; stack-release prior full receipt around584s implies at least1871s, to be recorded from actual receipts before CLEAR. Shell3x table retained with receipts. No test ran and no behavior passes claimed.
|
||||
|
||||
Review focus: parser races after load_config; target stderr naming only the refused key, stdout never persisted; current versus target loader selection; unknown key with no console; missing loader compatibility set from immutable history; upcoming row270 loader-list fixture drift. Final integration audits every test dependency and hash pin against the named base. Session registration remains Sage's.
|
||||
|
||||
|
||||
Implementation ready, unrun. Four appended tests cover full activate/rollback refusal and plan; actual accepting validate witness/configpath and stdout nonleak; oldest-only missing loader with both compaction keys; missing/invalid JSON isolated realfunction. Stand-in loaders are ESM, matching .mjs. Every old test byte is preserved as a prefix. Existing console refusal text including missing-loader diagnostic is unchanged. Missing-loader compatibility is top-level key compatibility only; current load_config still validates the whole config before normal release operations. Three-path manifest, no fixture-module changes. Static target uniqueness M1-M5 passes. No Node parse, Bash execution or behavior run. Exact full compaction activate/rollback cases must be added against post270 loader, before final gate.
|
||||
|
||||
|
||||
Execution state:
|
||||
- [x] Read brief, config_checks and row200 assertions.
|
||||
- [x] Pin oldest key set from immutable loader history.
|
||||
- [x] Write regression tests, implement bounded scope, prepare unique mutants.
|
||||
- [ ] Duckworth static review.
|
||||
- [ ] Move packet to queue-N on assignment, rebase after270/271/203 and add full compaction-key cases.
|
||||
- [ ] On CLEAR run intendedRED/GREEN, touched whole file, CLI whole, M1-M5, ten shells; commit/push records only when authorized.
|
||||
|
||||
|
||||
## Duckworth0229 static approval, gate conditions
|
||||
|
||||
Source0585b3213, packet e7371025f, manifestd6226d28 approved by static read, note a5a717391:agents/duckworth/work/row1797/ROUND1-0585b3213.md. No runtime proof yet.
|
||||
|
||||
C1: after270/271/203 land, rebase under a new branch name and open a new static round. Add exact compactionReadBytes and compactionStreams full activate/rollback refusals. The existing LOADER_OLD uses the current loader plus concat after270, so it is not a pre270 witness; the new test requires its own pre270 stand-in. Reviewer checks the integrated manifest/test before CLEAR. N3 joins that delta: compactionStreams fixture value1024, matching the real type, instead of ['messages'].
|
||||
|
||||
C2: every existing B/C1/C release now validates its copied real loader, including external/upgrade suites. Any old CLI red is a change-related suspect and goes to Mos/Sage under mos-1272; no automatic placement as an unrelated fixture.
|
||||
|
||||
N1 console plus another refused key retains the old console text by design. N2 missing-loader wording is retained pending final integration review. N4 corrected above: only resolved stdout is suppressed; first stderr may echo a value, so no blanket secrecy claim. No runtime/Node/queue/push authorization is inferred from static approval.
|
||||
@@ -0,0 +1,3 @@
|
||||
eea06caad07c746d194f793603052b210e437b7184009f8b1617c88db49a5517 docs/TOOLS.md
|
||||
a35c49c5703e02f35073450e4a98e1b90cffcda581490f97a70cbdc6d2afee59 packages/cli/tests/stack-release.test.mjs
|
||||
f8fe1348796d40a096362fb0c6d5072c15ca68ff8fe10384a3eaa708eb92c150 scripts/stack-release.sh
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"reviewer": "duckworth",
|
||||
"verdict": "static approve, conditions C1-C2",
|
||||
"message": "duckworth-0229",
|
||||
"source": "0585b3213",
|
||||
"manifest": "d6226d28",
|
||||
"note": "a5a717391:agents/duckworth/work/row1797/ROUND1-0585b3213.md",
|
||||
"next": "wait for row number and post270/271/203 base, new integration static round before gate"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"docs/TOOLS.md": "eea06caad07c746d194f793603052b210e437b7184009f8b1617c88db49a5517",
|
||||
"packages/cli/tests/stack-release.test.mjs": "a35c49c5703e02f35073450e4a98e1b90cffcda581490f97a70cbdc6d2afee59",
|
||||
"scripts/stack-release.sh": "f8fe1348796d40a096362fb0c6d5072c15ca68ff8fe10384a3eaa708eb92c150"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"issue": 1797,
|
||||
"row": "pending Sage assignment",
|
||||
"branch": "gander/config-checks-any-key",
|
||||
"base": "01983cd3f",
|
||||
"source": "0585b321309e4d8a199afb1a1024cf6091f56770",
|
||||
"manifest": "d6226d281c778677a45e90993bbd9c985de89ba0f870d4a3ca12b6923a497e4a",
|
||||
"runtime": "NOT RUN; no Node, tests or push authorized",
|
||||
"after": [
|
||||
270,
|
||||
271,
|
||||
203
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"id": "M1",
|
||||
"occurrences": 1,
|
||||
"before": "f8fe1348796d40a096362fb0c6d5072c15ca68ff8fe10384a3eaa708eb92c150",
|
||||
"after": "804a86442527f2728f633f485eaeca02552df66a34350eac5101127046415188"
|
||||
},
|
||||
{
|
||||
"id": "M2",
|
||||
"occurrences": 1,
|
||||
"before": "f8fe1348796d40a096362fb0c6d5072c15ca68ff8fe10384a3eaa708eb92c150",
|
||||
"after": "1c2acb6397e29ab898d8a89bc5bc691a294be749ca7d7f7897c83ec1b3e9f9ef"
|
||||
},
|
||||
{
|
||||
"id": "M3",
|
||||
"occurrences": 1,
|
||||
"before": "f8fe1348796d40a096362fb0c6d5072c15ca68ff8fe10384a3eaa708eb92c150",
|
||||
"after": "954c9a9735904e1c566d844001b2e85c13cd16eb16e7b4b91b752974bb6dc754"
|
||||
},
|
||||
{
|
||||
"id": "M4",
|
||||
"occurrences": 1,
|
||||
"before": "f8fe1348796d40a096362fb0c6d5072c15ca68ff8fe10384a3eaa708eb92c150",
|
||||
"after": "953d91bf28073b11e4bc5a6da516a0db1b8dfff892083cd3e6fcebe9c47b6bbf"
|
||||
},
|
||||
{
|
||||
"id": "M5",
|
||||
"occurrences": 1,
|
||||
"before": "f8fe1348796d40a096362fb0c6d5072c15ca68ff8fe10384a3eaa708eb92c150",
|
||||
"after": "310c32b16f0fc554d5a7ffeeb0742ea7dd9fc84e1ddafabb25d7ca3f449865ef"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"id": "M1",
|
||||
"file": "scripts/stack-release.sh",
|
||||
"old": " [ \"$key\" != 1 ] || return 0\n if [ \"$key\" != 0 ]; then",
|
||||
"new": " [ \"$key\" != 1 ] || return 0\n [ \"$extra\" = '\"console\"' ] || return 0\n if [ \"$key\" != 0 ]; then",
|
||||
"killer": "config-any-key: a non-console target refusal"
|
||||
},
|
||||
{
|
||||
"id": "M2",
|
||||
"file": "scripts/stack-release.sh",
|
||||
"old": "elif out=\"$(MOSAIC_CONFIG=\"$cfg\" node \"$loader\" validate 2>&1 >/dev/null)\"; then return 0",
|
||||
"new": "elif out=\"$(MOSAIC_CONFIG=\"$cfg\" node \"$loader\" validate 2>&1 >/dev/null)\" || true; then return 0",
|
||||
"killer": "config-any-key: a non-console target refusal"
|
||||
},
|
||||
{
|
||||
"id": "M3",
|
||||
"file": "scripts/stack-release.sh",
|
||||
"old": " [ -n \"$extra\" ] || return 0",
|
||||
"new": " return 0 # drop missing-loader later-key refusal",
|
||||
"killer": "config-any-key: missing-loader copies"
|
||||
},
|
||||
{
|
||||
"id": "M4",
|
||||
"file": "scripts/stack-release.sh",
|
||||
"old": " if [ \"$key\" != 0 ]; then\n check_refuse config-console-key",
|
||||
"new": " if [ \"$key\" = 99 ]; then\n check_refuse config-console-key",
|
||||
"killer": "config-any-key: missing config passes but malformed JSON refuses"
|
||||
},
|
||||
{
|
||||
"id": "M5",
|
||||
"file": "scripts/stack-release.sh",
|
||||
"old": " if [ ! -f \"$loader\" ]; then",
|
||||
"new": " [ -n \"$extra\" ] || return 0\n if [ ! -f \"$loader\" ]; then",
|
||||
"killer": "config-any-key: accepting loaders run validate"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,230 @@
|
||||
# Row 292 (#1797): config_checks runs the target loader on any config
|
||||
|
||||
Builder Gizmoduck (sage-3146, mos-1387), reviewer Duckworth, gate owner Sage. Brief
|
||||
docs/plans/2026-10-22_config-checks-any-key-brief.md. Gander built round 1 on 01983cd3f
|
||||
(gander/config-checks-any-key, packet agents/gander/work/config-checks-any-key/, Duckworth static
|
||||
approve duckworth-0229 with C1-C2 and N1-N4). Gander doesn't build 292; questions about round 1 go
|
||||
to Gander. Nothing in this packet has run. No test, mutant or repo node ran, with one exception
|
||||
recorded under "Disclosure".
|
||||
|
||||
## Rebase (sage-3146 item 1, sage-3148)
|
||||
|
||||
Branch gizmoduck/row292, base b23783f39 (the row 270+271 landing 7328da8ca plus its docs commit;
|
||||
row 203 landed earlier). `git rebase --onto b23783f39 01983cd3f d4c796e44`.
|
||||
|
||||
| commit | rebased | patch-id (stable) before | after |
|
||||
|---|---|---|---|
|
||||
| 0585b3213 fix(release) | 3b17c7550 | a922252a85b1317e09a625193172167b7ea39edb | 15f1a38e3c1e6c72c38b77a460b6cd4ce60c6dd4 |
|
||||
| e7371025f docs (plan, packet) | a54baff21 | 8e9b78ac185a49548e94e76c83f7fea1a982038a | 8e9b78ac185a49548e94e76c83f7fea1a982038a |
|
||||
| d4c796e44 docs (approval) | 9c6e39b25 | fc50b630d432ff7134e3d6c711a054d15ff1f997 | fc50b630d432ff7134e3d6c711a054d15ff1f997 |
|
||||
|
||||
e7371025f and d4c796e44 keep their patch-ids. 0585b3213 changes, by the conflict below.
|
||||
|
||||
### The conflict and its resolution
|
||||
|
||||
Only `scripts/stack-release.sh` conflicted, in `config_checks` (now :1076-1107). Row 203
|
||||
(284d55cf2) made the old probe exit 0 on a `console` or a `federation` key and the refusal read
|
||||
"holds a console or federation key". Gander's commit replaced the probe and split the refusal into a
|
||||
console text and a text for any other key. The test file and docs/TOOLS.md merged without conflict.
|
||||
|
||||
Resolution, per sage-3146 item 1 and sage-3148:
|
||||
|
||||
1. The probe picks `console` first, then `federation`, then the first key outside the oldest set
|
||||
`configVersion, environment, dataRoot, execution`. Without the federation step, a config that
|
||||
holds a later key before `federation` would get Gander's text although the loader refuses
|
||||
federation.
|
||||
2. With no target loader, a console or federation key gets 203's reason "it has no
|
||||
scripts/mosaic-config.mjs", as at b23783f39. Any other later key gets Gander's "to validate
|
||||
configuration key: <key>".
|
||||
3. The refusal for a console or federation key is 203's line, byte for byte as 284d55cf2 and
|
||||
b23783f39 have it (compared by script, not by eye). Gander's text, "is refused by $TO's loader
|
||||
($first); remove the refused key first (restore the config.json backup from the window's step 1),
|
||||
...", applies only when the key is neither console nor federation. In both, `$first` is the
|
||||
loader's first stderr line, which names the refused key (`unsupported configuration key: "<key>"`).
|
||||
4. The comment names 203's protection. docs/TOOLS.md gains one sentence: a console or federation key
|
||||
keeps row 203's message.
|
||||
|
||||
Gander's console-only text "holds a console key" (round 1 at 01983cd3f, before 203) is gone: 203's
|
||||
wording covers console as well. Duckworth's N1 (console plus another refused key keeps the console
|
||||
text) holds, now for federation too.
|
||||
|
||||
Row 203's tests: every line of stack-release.test.mjs up to :2600 at b23783f39 is unchanged. The
|
||||
rebased file only appends. KEY_MSG (:1471), the item 3 console cases (:1575-1580), the row 203
|
||||
federation test (:1585-1595) and :1607 are byte-identical to the base. No 203 test or assertion
|
||||
changed, so nothing goes to Mos under mos-1272 for it.
|
||||
|
||||
### Range-diff
|
||||
|
||||
`git range-diff 01983cd3f..d4c796e44 b23783f39..9c6e39b25`:
|
||||
|
||||
```
|
||||
1: 0585b3213 ! 1: 3b17c7550 fix(release): validate non-console config keys with target loader
|
||||
@@ docs/TOOLS.md: onto the pointer's copy from the target, past any same-copy re-pi
|
||||
+key. Every existing parseable config is checked; a missing file passes. A target
|
||||
+without a loader accepts only the oldest top-level keys: `configVersion`,
|
||||
+`environment`, `dataRoot`, and `execution`; any later key refuses. The refusal
|
||||
-+names the rejected key and says to restore the config.json backup first. So a
|
||||
++names the rejected key and says to restore the config.json backup first. A
|
||||
++`console` or `federation` key keeps row 203's message, which says the config
|
||||
++holds a console or federation key. So a
|
||||
window re-pins to row 192 or later before it writes the key, and a rollback past
|
||||
row 192 removes the key first.
|
||||
|
||||
@@ scripts/stack-release.sh: slot_problem() {
|
||||
-# target's own scripts/mosaic-config.mjs validate; its output, the resolved config, is not kept.
|
||||
+# Row 200 item 3 and #1797: every existing parseable config must be one the
|
||||
+# target loader reads. Older copies without a loader accept only the four keys
|
||||
-+# in c3d29e796's first loader. Resolved config stdout is never kept.
|
||||
++# in c3d29e796's first loader. A console or federation key (row 203) keeps its own refusal text.
|
||||
++# Resolved config stdout is never kept.
|
||||
config_checks() {
|
||||
- local cfg="${MOSAIC_CONFIG:-$HOME/.config/mosaic-dev/config.json}" out loader="$COPIES/$TO/scripts/mosaic-config.mjs" first
|
||||
-- # 0: a console key; 1: no key, or no file; 2: the file doesn't parse, so whether it holds the key
|
||||
+- # 0: a console or federation key (row 203: a pre-203 loader refuses federation too); 1: neither, or no file; 2: the file doesn't parse, so whether it holds the key
|
||||
- # is unknown, and the run refuses rather than pass it (webby-0028 N2). load_config already exits on
|
||||
- # such a file before anything is read or stopped, so this is reached only if the file changes after it.
|
||||
+ local cfg="${MOSAIC_CONFIG:-$HOME/.config/mosaic-dev/config.json}" out loader="$COPIES/$TO/scripts/mosaic-config.mjs" first extra
|
||||
+ # 1: missing file; 2: unreadable JSON. This also fences a change after load_config.
|
||||
++ # extra: "console" or "federation" first (row 203: a pre-203 loader refuses federation too),
|
||||
++ # then the first key outside the oldest set.
|
||||
local key=0
|
||||
- node -e 'const fs=require("fs"),f=process.argv[1];if(!fs.existsSync(f))process.exit(1);let d;try{d=JSON.parse(fs.readFileSync(f,"utf8"))}catch{process.exit(2)}
|
||||
--process.exit(d!==null&&typeof d==="object"&&Object.hasOwn(d,"console")?0:1)' -- "$cfg" 2>/dev/null || key=$?
|
||||
+-process.exit(d!==null&&typeof d==="object"&&(Object.hasOwn(d,"console")||Object.hasOwn(d,"federation"))?0:1)' -- "$cfg" 2>/dev/null || key=$?
|
||||
+ extra="$(node -e 'const fs=require("fs"),f=process.argv[1];if(!fs.existsSync(f))process.exit(1);let d;try{d=JSON.parse(fs.readFileSync(f,"utf8"))}catch{process.exit(2)}
|
||||
+const oldest=["configVersion","environment","dataRoot","execution"];
|
||||
+if(d===null||typeof d!=="object"||Array.isArray(d)){process.stdout.write(JSON.stringify("[configuration shape]"));process.exit(0)}
|
||||
-+const k=Object.hasOwn(d,"console")?"console":Object.keys(d).find(k=>!oldest.includes(k));if(k!==undefined)process.stdout.write(JSON.stringify(k))' -- "$cfg" 2>/dev/null)" || key=$?
|
||||
++const k=Object.hasOwn(d,"console")?"console":Object.hasOwn(d,"federation")?"federation":Object.keys(d).find(k=>!oldest.includes(k));if(k!==undefined)process.stdout.write(JSON.stringify(k))' -- "$cfg" 2>/dev/null)" || key=$?
|
||||
[ "$key" != 1 ] || return 0
|
||||
if [ "$key" != 0 ]; then
|
||||
check_refuse config-console-key "$cfg cannot be read as JSON, so whether $TO's loader reads it is unknown; fix the config first, then run the $OP again"
|
||||
@@ scripts/stack-release.sh: slot_problem() {
|
||||
- if [ ! -f "$loader" ]; then out="it has no scripts/mosaic-config.mjs"
|
||||
+ if [ ! -f "$loader" ]; then
|
||||
+ [ -n "$extra" ] || return 0
|
||||
-+ if [ "$extra" = '"console"' ]; then out="it has no scripts/mosaic-config.mjs"
|
||||
++ if [ "$extra" = '"console"' ] || [ "$extra" = '"federation"' ]; then out="it has no scripts/mosaic-config.mjs"
|
||||
+ else out="it has no scripts/mosaic-config.mjs to validate configuration key: $extra"; fi
|
||||
elif out="$(MOSAIC_CONFIG="$cfg" node "$loader" validate 2>&1 >/dev/null)"; then return 0
|
||||
fi
|
||||
first="${out%%$'\n'*}"
|
||||
-- check_refuse config-console-key "$cfg holds a console key, and $TO's loader refuses it ($first); remove the key first (restore the config.json backup from the window's step 1), then run the $OP again"
|
||||
-+ if [ "$extra" = '"console"' ]; then
|
||||
-+ check_refuse config-console-key "$cfg holds a console key, and $TO's loader refuses it ($first); remove the key first (restore the config.json backup from the window's step 1), then run the $OP again"
|
||||
+- check_refuse config-console-key "$cfg holds a console or federation key, and $TO's loader refuses it ($first); remove the key first (restore the config.json backup from the window's step 1), then run the $OP again"
|
||||
++ if [ "$extra" = '"console"' ] || [ "$extra" = '"federation"' ]; then
|
||||
++ check_refuse config-console-key "$cfg holds a console or federation key, and $TO's loader refuses it ($first); remove the key first (restore the config.json backup from the window's step 1), then run the $OP again"
|
||||
+ else
|
||||
+ check_refuse config-console-key "$cfg is refused by $TO's loader ($first); remove the refused key first (restore the config.json backup from the window's step 1), then run the $OP again"
|
||||
+ fi
|
||||
2: e7371025f = 2: a54baff21 docs(release): record any-key config plan and static review packet
|
||||
3: d4c796e44 = 3: 9c6e39b25 docs(release): record static approval and post-compaction review conditions
|
||||
```
|
||||
|
||||
## C1 and N3 (sage-3146 item 3, duckworth-0229 C1)
|
||||
|
||||
Commit 4fa7d6834, test file only.
|
||||
|
||||
- LOADER_OLD (stack-release-fixture.mjs:301) is today's loader with a shorter first list, so it
|
||||
still reads `compactionReadBytes` and `compactionStreams` through the `.concat`. It is no pre-270
|
||||
witness. The new stand-in `PRE270` is C1's copy of today's loader with the
|
||||
`.concat(["compactionReadBytes", "compactionStreams"])` taken out. That leaves
|
||||
`["configVersion", "environment", "dataRoot", "execution", "console", "federation"]`, the list
|
||||
the loader had at bce491715^ (01b57f48b), before bce491715 added the first key. The stand-in
|
||||
asserts the replace changed something, so a reworded loader fails loudly rather than passing a
|
||||
no-op stand-in. The fixture module isn't edited (not an owned file).
|
||||
- New test "config-any-key: a pre-270 target refuses each row 270 key on a full activate and
|
||||
rollback, naming the key". It seeds on B, activates C1 and installs PRE270 as B's loader. For
|
||||
`compactionReadBytes` 1048576 and `compactionStreams` 1024 in turn, it first runs C1's own loader
|
||||
`validate` on the config (exit 0, so the refusal is the target's). Then it asserts the exact
|
||||
refusal for a full `activate B` and for `rollback`, using `refused()`: exit 3, the exact stderr,
|
||||
no file changed, no start or stop and one refused log line. The text is ANY_CONFIG_MSG with
|
||||
`mosaic-config: unsupported configuration key: "<key>"`. Afterwards it restores the loader and
|
||||
config, and the rollback to B passes.
|
||||
- N3: the missing-loader test sets `compactionStreams` to 1024, not `['messages']`.
|
||||
- The missing-loader test also checks 203's no-loader text word for word for `console` and for
|
||||
`federation`, each placed after `compactionStreams` in the file. This covers resolution items 1
|
||||
and 2, which no 203 test reaches: 203's tests all use a target with a loader.
|
||||
|
||||
## Files (manifest)
|
||||
|
||||
Paths changed from b23783f39 to the candidate, with sha256 at the candidate:
|
||||
|
||||
| path | b23783f39 sha256 | candidate sha256 |
|
||||
|---|---|---|
|
||||
| scripts/stack-release.sh | d3f52f72f2ea345118d30dc34f045f2b5b6abce41697e62f6de07d7a2f25748c | 8f535d53896fefe44525dea39169ac8df29f3d7845f83552a34ec1cd707595bb |
|
||||
| packages/cli/tests/stack-release.test.mjs | 45faa5d236d4cfb5cf3c026d64d039a5f29ce8868efeed4e9428cf85a5f0067f | 5a228aee7c3811f906513809f8a78a1fe4f3a26057089ae6c87918f432a5ba65 |
|
||||
| docs/TOOLS.md | 423babffc7be40a2d578ef00e97cdc49d887762e416fc20726d328d96b2a9533 | 611aeb79d603dd1af1917dcbb3db81b51ac0ae30c5b4991ea72e18bc45ee59de |
|
||||
|
||||
The rest of the diff is packets: Gander's agents/gander/work/config-checks-any-key/ (eight files)
|
||||
and this one (BUILD.md, mutants.py). The manifest with every path and sha256 goes to Duckworth with
|
||||
the round key.
|
||||
|
||||
## Mutants
|
||||
|
||||
agents/gizmoduck/work/queue-292/mutants.py. Each anchor appears exactly once, and each mutated
|
||||
script passes `bash -n` (checked with python3 and bash only, no node). None has run. M1-M5 are
|
||||
Gander's edits (changes.json) on the resolved function. M6-M9 test the resolution. M1 restores the
|
||||
console-only skip, the brief's required mutant.
|
||||
|
||||
### Kill rules (sage-3152)
|
||||
|
||||
Each mutant runs only its killing test: `node --test --test-name-pattern '<pattern>'
|
||||
tests/stack-release.test.mjs`. The patterns are the test names, anchored with ^ and $, and all
|
||||
four are top-level tests. A mutant counts as killed only when all three of these hold:
|
||||
|
||||
1. The run ends with `ℹ fail 1`, and the failing test is the named one. The spec reporter lists
|
||||
it twice, inline and in the failing-tests summary.
|
||||
2. The first error's stack names every listed stack-release.test.mjs line.
|
||||
3. The log holds the listed text. The texts carry no quote characters, which node's inspect may
|
||||
escape.
|
||||
|
||||
Anything else is a STOP that goes to Sage before the next mutant: a fail in another test or at
|
||||
another line, a missing text, zero tests matched, a timeout or a crash before the assertion. A
|
||||
fixture or module-load error is the second-fence trap from 264a M2. A mutant whose targeted run
|
||||
passes gets one run of stack-release.test.mjs whole before it counts as a survivor.
|
||||
|
||||
| id | edit | test (pattern) | stack lines | text | why that assertion |
|
||||
|---|---|---|---|---|---|
|
||||
| M1 | console-only skip restored | non-console target refusal | :517 (refused), :2619 | `actual: 0` | no console key, so the check returns 0 and activate B runs: exit 0, not 3 |
|
||||
| M2 | failed validate ignored (`\|\| true`) | non-console target refusal | :517, :2619 | `actual: 0` | the stand-in's exit 2 is lost, so activate B runs |
|
||||
| M3 | no-loader target passes any later key | missing-loader copies | :2670 | `actual: 0` | compactionReadBytes, the first case, passes with status 0 |
|
||||
| M4 | malformed JSON treated as absent | missing config passes but malformed JSON | :2684 | `actual: 0` | key 2 falls through to the witness loader, which exits 0 |
|
||||
| M5 | oldest-key config skips the loader | accepting loaders run validate | :2654 | `actual: []` | the witness file is never written |
|
||||
| M6 | probe drops the federation preference | missing-loader copies | :2677 | `to validate configuration key: ` | federation after compactionStreams: the probe picks compactionStreams |
|
||||
| M7 | probe drops the console preference | missing-loader copies | :2677 | `to validate configuration key: ` | the same for console, the first case of that loop |
|
||||
| M8 | final refusal treats only console as 203's | missing-loader copies | :2677 | `is refused by old` | federation gets Gander's text, not KEY_MSG |
|
||||
| M9 | no-loader branch treats only console as 203's | missing-loader copies | :2677 | `to validate configuration key: ` | federation's reason gains the later-key wording |
|
||||
|
||||
M1 would also fail the pre-270 test and the row 203 federation test, and M8 the row 203 federation
|
||||
test. The targeted runs don't select those, so they aren't counted.
|
||||
|
||||
## Run plan (on Sage's CLEAR only, after Duckworth approves this round)
|
||||
|
||||
1. stack-release.test.mjs whole, limit 2100 s.
|
||||
2. The cli suite whole, `node --test tests/*.test.mjs` in packages/cli, limit 2100 s.
|
||||
3. The ten `scripts/test-*.sh`, limit 900 s each.
|
||||
4. Only on a green base: M1-M9 under the kill rules above. Targeted runs get 600 s and a
|
||||
whole-file rerun of a survivor gets 2100 s. After each mutant: `git checkout --
|
||||
scripts/stack-release.sh`, sha256 equal to the candidate's (8f535d53...) and a clean tree.
|
||||
|
||||
Every stage gets its own short TMPDIR, `mktemp -d /mnt/storage/scratch/tmp/gXXX`, directly under
|
||||
/mnt/storage/scratch/tmp (sage-3152, 574 run 1's socket-path fault). Every mutant run gets one too.
|
||||
All runs use pipefail, tee and `timeout -k 10`. The busy check passes before each start. A first red
|
||||
in stages 1-3 stops the run.
|
||||
|
||||
The limits come from the run 5 receipts (gizmo-271-out/r4, 01:49Z 10-23). stack-release.test.mjs
|
||||
whole took 682 s (duration_ms 681648), the cli suite 624 s and the longest shell suite 44 s. Each
|
||||
limit is at least 3x. Expected time: about 26 minutes for the base, plus about a minute per targeted
|
||||
mutant (fixture setup and one test, not measured yet), plus 12 minutes per whole-file rerun.
|
||||
|
||||
C2 (duckworth-0229, sage-3146 item 4): every B, C1 and C release now validates its copied real
|
||||
loader. Any red in an old cli test is a suspect from this change. It stops the run and goes to Sage
|
||||
and Mos under mos-1272, unplaced.
|
||||
|
||||
## Disclosure
|
||||
|
||||
During the hold (sage-3146 item 6, no repo node) I ran `node --check
|
||||
packages/cli/tests/stack-release.test.mjs` once, between the rebase finish (02:43:51Z, reflog) and
|
||||
the first C1 commit (02:45:41Z). It is a parse-only syntax check that lasted a few milliseconds. It printed nothing. No other node, test, queue write or https push/fetch has run.
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
# Row 292 mutants. M1-M5 are Gander's (agents/gander/work/config-checks-any-key/changes.json), with
|
||||
# the same edits on the resolved config_checks; M1 restores the console-only skip (the brief's
|
||||
# required mutant). M6-M9 are the resolution's: row 203's console and federation text. `apply <id>`
|
||||
# edits the worktree and prints the touched path; each edit asserts its anchor appears exactly once.
|
||||
# `check` prints each anchor's count and kill. K is read from the tests, not run. A restore is `git checkout -- <path>`, then sha256 against the blob in BUILD.md.
|
||||
import sys
|
||||
WT = "/mnt/storage/scratch/gizmo-292/"
|
||||
S = "scripts/stack-release.sh"
|
||||
T = "packages/cli/tests/stack-release.test.mjs"
|
||||
M = {
|
||||
"M1": (S, ' [ "$key" != 1 ] || return 0\n if [ "$key" != 0 ]; then',
|
||||
' [ "$key" != 1 ] || return 0\n [ "$extra" = \'"console"\' ] || return 0\n if [ "$key" != 0 ]; then'),
|
||||
"M2": (S, 'elif out="$(MOSAIC_CONFIG="$cfg" node "$loader" validate 2>&1 >/dev/null)"; then return 0',
|
||||
'elif out="$(MOSAIC_CONFIG="$cfg" node "$loader" validate 2>&1 >/dev/null)" || true; then return 0'),
|
||||
"M3": (S, ' [ -n "$extra" ] || return 0', ' return 0 # drop missing-loader later-key refusal'),
|
||||
"M4": (S, ' if [ "$key" != 0 ]; then\n check_refuse config-console-key',
|
||||
' if [ "$key" = 99 ]; then\n check_refuse config-console-key'),
|
||||
"M5": (S, ' if [ ! -f "$loader" ]; then', ' [ -n "$extra" ] || return 0\n if [ ! -f "$loader" ]; then'),
|
||||
"M6": (S, 'Object.hasOwn(d,"federation")?"federation":', ''),
|
||||
"M7": (S, 'const k=Object.hasOwn(d,"console")?"console":', 'const k='),
|
||||
"M8": (S, ' if [ "$extra" = \'"console"\' ] || [ "$extra" = \'"federation"\' ]; then\n check_refuse',
|
||||
' if [ "$extra" = \'"console"\' ]; then\n check_refuse'),
|
||||
"M9": (S, '[ "$extra" = \'"federation"\' ]; then out="it has no scripts/mosaic-config.mjs"',
|
||||
'[ "$extra" = \'"federation-off"\' ]; then out="it has no scripts/mosaic-config.mjs"'),
|
||||
}
|
||||
# K: (--test-name-pattern, the test.mjs lines the failing stack must show, the text the failure
|
||||
# must show). Kill rule (sage-3152): the named test fails, its first error's stack names every
|
||||
# listed line, and the log holds the text (no quote characters, which node's inspect may escape). A fail anywhere else, or a crash first, is a STOP to Sage.
|
||||
# A survivor of its targeted run gets one whole-file run before it counts as a survivor.
|
||||
K = {
|
||||
"M1": ('^config-any-key: a non-console target refusal stops activate and rollback before any stop$', [517, 2619], "actual: 0"),
|
||||
"M2": ('^config-any-key: a non-console target refusal stops activate and rollback before any stop$', [517, 2619], "actual: 0"),
|
||||
"M3": ('^config-any-key: missing-loader copies accept only the four oldest keys$', [2670], "actual: 0"),
|
||||
"M4": ('^config-any-key: missing config passes but malformed JSON refuses without calling the loader$', [2684], "actual: 0"),
|
||||
"M5": ('^config-any-key: accepting loaders run validate with the selected config on activate and rollback$', [2654], "actual: []"),
|
||||
"M6": ('^config-any-key: missing-loader copies accept only the four oldest keys$', [2677], "to validate configuration key: "),
|
||||
"M7": ('^config-any-key: missing-loader copies accept only the four oldest keys$', [2677], "to validate configuration key: "),
|
||||
"M8": ('^config-any-key: missing-loader copies accept only the four oldest keys$', [2677], "is refused by old"),
|
||||
"M9": ('^config-any-key: missing-loader copies accept only the four oldest keys$', [2677], "to validate configuration key: "),
|
||||
}
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "list":
|
||||
print(" ".join(M)); sys.exit(0)
|
||||
if cmd == "check":
|
||||
for k, (f, a, b) in M.items():
|
||||
print(k, f, open(WT + f).read().count(a), "kill:", *K[k])
|
||||
sys.exit(0)
|
||||
f, a, b = M[sys.argv[2]]
|
||||
s = open(WT + f).read()
|
||||
assert s.count(a) == 1, (sys.argv[2], s.count(a))
|
||||
open(WT + f, "w").write(s.replace(a, b))
|
||||
print(f)
|
||||
+8
-3
@@ -632,9 +632,14 @@ onto the pointer's copy from the target, past any same-copy re-pin, checked
|
||||
against the set's `SHA256SUMS`. `--send` and `--tester-port` follow the pointer.
|
||||
When that set is missing, fails its sums, holds no readable argv or already ran
|
||||
`--sso`, the run refuses `sso-rollback-flags`. Any activate or rollback also
|
||||
refuses `config-console-key` before anything stops when the config holds a
|
||||
`console` key the target's `scripts/mosaic-config.mjs validate` refuses (a copy
|
||||
before row 192 refuses the key). The message says to remove the key first. So a
|
||||
refuses `config-console-key` before anything stops when the target's
|
||||
`scripts/mosaic-config.mjs validate` refuses the config, including a non-console
|
||||
key. Every existing parseable config is checked; a missing file passes. A target
|
||||
without a loader accepts only the oldest top-level keys: `configVersion`,
|
||||
`environment`, `dataRoot`, and `execution`; any later key refuses. The refusal
|
||||
names the rejected key and says to restore the config.json backup first. A
|
||||
`console` or `federation` key keeps row 203's message, which says the config
|
||||
holds a console or federation key. So a
|
||||
window re-pins to row 192 or later before it writes the key, and a rollback past
|
||||
row 192 removes the key first.
|
||||
|
||||
|
||||
@@ -2598,3 +2598,88 @@ test('row196 rehearsal selects socket only for marked copies and uses scratch da
|
||||
assert.equal(untouched(e),before);
|
||||
});
|
||||
});
|
||||
|
||||
// #1797: validate every config with the target loader, even without console.
|
||||
function anyConfigLoader(file,text){
|
||||
const old=existsSync(file)?readFileSync(file):null;
|
||||
if(old!==null)chmodSync(file,0o644);
|
||||
mkdirSync(dirname(file),{recursive:true});writeFileSync(file,text);
|
||||
return ()=>{if(old===null)rmSync(file);else{writeFileSync(file,old);chmodSync(file,0o444);}};
|
||||
}
|
||||
const ANY_CONFIG_MSG=(e,to,op,why)=>`${e.cfg} is refused by ${to}'s loader (${why}); remove the refused key first (restore the config.json backup from the window's step 1), then run the ${op} again`;
|
||||
test('config-any-key: a non-console target refusal stops activate and rollback before any stop',async t=>{
|
||||
const e=await mk(t);await seed(e);
|
||||
let r=await run(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
|
||||
const why='mosaic-config: unsupported configuration key: "execution"';
|
||||
const undo=anyConfigLoader(join(COPIES,B,'scripts/mosaic-config.mjs'),`console.error(${JSON.stringify(why)});process.exitCode=2;\n`);
|
||||
try{
|
||||
assert.equal(Object.hasOwn(JSON.parse(readFileSync(e.cfg,'utf8')),'console'),false);
|
||||
for(const op of ['activate','rollback']){
|
||||
const args=op==='activate'?['activate',B,'acme']:['rollback','acme'];
|
||||
await refused(e,args,'config-console-key',ANY_CONFIG_MSG(e,B,op,why));
|
||||
}
|
||||
const out=await planOnly(e,3,'rollback','acme');
|
||||
assert.deepEqual(out.filter(l=>l.startsWith('refuses ')),[`refuses config-console-key: ${ANY_CONFIG_MSG(e,B,'rollback',why)}`]);
|
||||
}finally{undo();}
|
||||
});
|
||||
// Duckworth C1 (duckworth-0229): B's fixture loader (LOADER_OLD) is today's loader with a shorter
|
||||
// first list, so it still reads the row 270 keys. This stand-in is C1's loader without the row 270
|
||||
// concat, the list the loader had before bce491715 added the first of the two keys.
|
||||
const PRE270=(now)=>{const old=now.replace('.concat(["compactionReadBytes", "compactionStreams"])','');assert.notEqual(old,now);return old};
|
||||
test('config-any-key: a pre-270 target refuses each row 270 key on a full activate and rollback, naming the key',async t=>{
|
||||
const e=await mk(t);await seed(e);
|
||||
let r=await run(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
|
||||
const original=readFileSync(e.cfg,'utf8'),now=join(COPIES,C1,'scripts/mosaic-config.mjs');
|
||||
assert.equal(Object.hasOwn(JSON.parse(original),'console'),false);
|
||||
const undo=anyConfigLoader(join(COPIES,B,'scripts/mosaic-config.mjs'),PRE270(readFileSync(now,'utf8')));
|
||||
try{
|
||||
for(const [key,value] of [['compactionReadBytes',1048576],['compactionStreams',1024]]){
|
||||
writeFileSync(e.cfg,JSON.stringify({...JSON.parse(original),[key]:value}));
|
||||
// C1's own loader reads the key, so the refusal is the target's.
|
||||
const ok=spawnSync(NODE,[now,'validate'],{encoding:'utf8',env:{...e.env,MOSAIC_CONFIG:e.cfg}});assert.equal(ok.status,0,ok.stderr);
|
||||
const why=`mosaic-config: unsupported configuration key: "${key}"`;
|
||||
await refused(e,['activate',B,'acme'],'config-console-key',ANY_CONFIG_MSG(e,B,'activate',why));
|
||||
await refused(e,['rollback','acme'],'config-console-key',ANY_CONFIG_MSG(e,B,'rollback',why));
|
||||
}
|
||||
}finally{undo();writeFileSync(e.cfg,original);}
|
||||
r=await run(e,'rollback','acme');assert.equal(r.code,0,r.out+r.err);assert.equal(pointer(e).copy,B);
|
||||
});
|
||||
test('config-any-key: accepting loaders run validate with the selected config on activate and rollback',async t=>{
|
||||
const e=await mk(t);await seed(e);const witness=join(e.root,'loader-calls.jsonl'),canary='resolved-config-must-not-leak';
|
||||
const undos=[B,C1].map(id=>anyConfigLoader(join(COPIES,id,'scripts/mosaic-config.mjs'),`import fs from 'node:fs';fs.appendFileSync(${JSON.stringify(witness)},JSON.stringify({copy:${JSON.stringify(id)},args:process.argv.slice(2),config:process.env.MOSAIC_CONFIG})+'\\n');console.log(${JSON.stringify(canary)});\n`));
|
||||
try{
|
||||
for(const args of [['activate',C1,'acme'],['rollback','acme']]){
|
||||
const r=await run(e,...args);assert.equal(r.code,0,r.out+r.err);assert.equal((r.out+r.err).includes(canary),false);
|
||||
}
|
||||
assert.deepEqual(lines(witness).map(JSON.parse),[
|
||||
{copy:C1,args:['validate'],config:e.cfg},{copy:B,args:['validate'],config:e.cfg},
|
||||
]);
|
||||
}finally{for(const undo of undos)undo();}
|
||||
});
|
||||
function isolatedConfigChecks(e){
|
||||
const source=readFileSync(SR,'utf8'),body=source.split('config_checks() {')[1]?.split('\n}\n')[0];assert.ok(body,'config_checks exists');
|
||||
return spawnSync('bash',['-c','config_checks() {'+body+'\n}\ncheck_refuse(){ printf "%s: %s\\n" "$1" "$2" >&2; exit 3; }; config_checks'],{
|
||||
encoding:'utf8',env:{...e.env,COPIES:join(e.root,'target-copies'),TO:'old',OP:'activate'},
|
||||
});
|
||||
}
|
||||
test('config-any-key: missing-loader copies accept only the four oldest keys',async t=>{
|
||||
const e=await mk(t);let r=isolatedConfigChecks(e);assert.equal(r.status,0,r.stderr);
|
||||
const original=JSON.parse(readFileSync(e.cfg,'utf8'));
|
||||
for(const [key,value] of [['compactionReadBytes',1048576],['compactionStreams',1024]]){
|
||||
writeFileSync(e.cfg,JSON.stringify({...original,[key]:value}));r=isolatedConfigChecks(e);
|
||||
assert.equal(r.status,3,r.stderr);assert.match(r.stderr,new RegExp(`configuration key: "${key}"`));
|
||||
assert.ok(r.stderr.includes('restore the config.json backup from the window\'s step 1'));
|
||||
}
|
||||
// Row 203's text for a console or federation key stays word for word with no loader too, even
|
||||
// when a later key comes first in the file.
|
||||
for(const [key,value] of [['console',{}],['federation',{self:'hub',peers:[]}]]){
|
||||
writeFileSync(e.cfg,JSON.stringify({...original,compactionStreams:1024,[key]:value}));r=isolatedConfigChecks(e);
|
||||
assert.equal(r.status,3,r.stderr);assert.equal(r.stderr,`config-console-key: ${KEY_MSG(e,'old','activate','it has no scripts/mosaic-config.mjs')}\n`);
|
||||
}
|
||||
});
|
||||
test('config-any-key: missing config passes but malformed JSON refuses without calling the loader',async t=>{
|
||||
const e=await mk(t),file=join(e.root,'target-copies/old/scripts/mosaic-config.mjs'),witness=join(e.root,'validate-called');
|
||||
mkdirSync(dirname(file),{recursive:true});writeFileSync(file,`import fs from 'node:fs';fs.writeFileSync(${JSON.stringify(witness)},'called');\n`);
|
||||
rmSync(e.cfg);let r=isolatedConfigChecks(e);assert.equal(r.status,0,r.stderr);assert.equal(existsSync(witness),false);
|
||||
writeFileSync(e.cfg,'{');r=isolatedConfigChecks(e);assert.equal(r.status,3,r.stderr);assert.match(r.stderr,/cannot be read as JSON/);assert.equal(existsSync(witness),false);
|
||||
});
|
||||
+21
-11
@@ -1073,27 +1073,37 @@ slot_problem() {
|
||||
[ "$size" != 0 ] || echo "is empty"
|
||||
}
|
||||
|
||||
# Row 200 item 3 and the design's phase 2 rollback order: a config.json with a `console` key must be
|
||||
# one the target's loader reads, or its bus, Console and seats fail on their next read. Runs the
|
||||
# target's own scripts/mosaic-config.mjs validate; its output, the resolved config, is not kept.
|
||||
# Row 200 item 3 and #1797: every existing parseable config must be one the
|
||||
# target loader reads. Older copies without a loader accept only the four keys
|
||||
# in c3d29e796's first loader. A console or federation key (row 203) keeps its own refusal text.
|
||||
# Resolved config stdout is never kept.
|
||||
config_checks() {
|
||||
local cfg="${MOSAIC_CONFIG:-$HOME/.config/mosaic-dev/config.json}" out loader="$COPIES/$TO/scripts/mosaic-config.mjs" first
|
||||
# 0: a console or federation key (row 203: a pre-203 loader refuses federation too); 1: neither, or no file; 2: the file doesn't parse, so whether it holds the key
|
||||
# is unknown, and the run refuses rather than pass it (webby-0028 N2). load_config already exits on
|
||||
# such a file before anything is read or stopped, so this is reached only if the file changes after it.
|
||||
local cfg="${MOSAIC_CONFIG:-$HOME/.config/mosaic-dev/config.json}" out loader="$COPIES/$TO/scripts/mosaic-config.mjs" first extra
|
||||
# 1: missing file; 2: unreadable JSON. This also fences a change after load_config.
|
||||
# extra: "console" or "federation" first (row 203: a pre-203 loader refuses federation too),
|
||||
# then the first key outside the oldest set.
|
||||
local key=0
|
||||
node -e 'const fs=require("fs"),f=process.argv[1];if(!fs.existsSync(f))process.exit(1);let d;try{d=JSON.parse(fs.readFileSync(f,"utf8"))}catch{process.exit(2)}
|
||||
process.exit(d!==null&&typeof d==="object"&&(Object.hasOwn(d,"console")||Object.hasOwn(d,"federation"))?0:1)' -- "$cfg" 2>/dev/null || key=$?
|
||||
extra="$(node -e 'const fs=require("fs"),f=process.argv[1];if(!fs.existsSync(f))process.exit(1);let d;try{d=JSON.parse(fs.readFileSync(f,"utf8"))}catch{process.exit(2)}
|
||||
const oldest=["configVersion","environment","dataRoot","execution"];
|
||||
if(d===null||typeof d!=="object"||Array.isArray(d)){process.stdout.write(JSON.stringify("[configuration shape]"));process.exit(0)}
|
||||
const k=Object.hasOwn(d,"console")?"console":Object.hasOwn(d,"federation")?"federation":Object.keys(d).find(k=>!oldest.includes(k));if(k!==undefined)process.stdout.write(JSON.stringify(k))' -- "$cfg" 2>/dev/null)" || key=$?
|
||||
[ "$key" != 1 ] || return 0
|
||||
if [ "$key" != 0 ]; then
|
||||
check_refuse config-console-key "$cfg cannot be read as JSON, so whether $TO's loader reads it is unknown; fix the config first, then run the $OP again"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$loader" ]; then out="it has no scripts/mosaic-config.mjs"
|
||||
if [ ! -f "$loader" ]; then
|
||||
[ -n "$extra" ] || return 0
|
||||
if [ "$extra" = '"console"' ] || [ "$extra" = '"federation"' ]; then out="it has no scripts/mosaic-config.mjs"
|
||||
else out="it has no scripts/mosaic-config.mjs to validate configuration key: $extra"; fi
|
||||
elif out="$(MOSAIC_CONFIG="$cfg" node "$loader" validate 2>&1 >/dev/null)"; then return 0
|
||||
fi
|
||||
first="${out%%$'\n'*}"
|
||||
check_refuse config-console-key "$cfg holds a console or federation key, and $TO's loader refuses it ($first); remove the key first (restore the config.json backup from the window's step 1), then run the $OP again"
|
||||
if [ "$extra" = '"console"' ] || [ "$extra" = '"federation"' ]; then
|
||||
check_refuse config-console-key "$cfg holds a console or federation key, and $TO's loader refuses it ($first); remove the key first (restore the config.json backup from the window's step 1), then run the $OP again"
|
||||
else
|
||||
check_refuse config-console-key "$cfg is refused by $TO's loader ($first); remove the refused key first (restore the config.json backup from the window's step 1), then run the $OP again"
|
||||
fi
|
||||
}
|
||||
|
||||
# Row 189 (mos-0972): --no-send drops --send from RUN_CONSOLE, so the unit and the pointer both lose
|
||||
|
||||
Reference in new issue
Block a user