fix(case1): match factory project key to repository id (#1821)

This commit is contained in:
jason.woltje committed 2026-10-23 20:14:55 -05:00
1 parent 99930362e4
commit c5f96836d5
13 files changed
+1859 -2

No files matched your search

+4
View File
@@ -5453,3 +5453,7 @@ Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault r
2026-10-24 | Goldie | Row 323 / #1819 second red STOP (mos-1560, sage-3864) | Isolated revoke-window whole-file run1 on unchanged571376c30 failed0/1 after20.126s, same no RESULT at ask167/test194. Start00:24:22Z, load1.46/1.68/2.70, busyhelperCLEAR, fresh scratch goldieBObDQ9. No run2/3/CLI retry/mutant/restored run; no source/old-test edit. Sage goldie-0018 has log/load/error; slot returned, awaits base-dd6cb5872 root cause. Remote remains unverified571.
2026-10-24 | Goldie | Row 323 / #1819 accepted evidence (mos-1561, sage-3869) | Sage traced revoke-window reds to37-byte TMPDIR making109-byte tools.sock >107 limit (session.mjs278), not Codex. Fresh31-byte /scratch/tmp/XXXXXX confirmation passes1/1 in0.84s. On unchanged571376c30 serialized timeout-k10: business261/261, new7/7, tester-switch mutant killed/restored SHA85e6b9ac, restored new7/7. Every log starts UTC/load/helperCLEAR; all positive runs0 fail/skip/cancel. CLI whole acceptance comes from Sage gate under mos-1561; no local CLI rerun. Evidence-only notes/log commit follows, no old test/source changes.
2026-10-24 | Goldie | Row325 / #1821 before (mos-1563/1564) | Real .mosaic/project.json idstack disagrees with Case1 project's mosaic-stack key; row323 scratch fixture omitted project file and only warned. New goldie/row325 at99930362e changes only that JSON key and expressly authorized old assertion key; new real-byte project-copy validation tests require valid and reject old key. Mutant prepared; no tests before Sage CLEAR, no queue writes/host actions.
2026-10-24 | Goldie | Row325 / #1821 after (sage-3882 CLEAR) | Only Case1 projects key becomesstack; only old test70 expected key updated under mos-1564. New3-case test copies real .mosaic/project.json bytes, requires CLI project valid, rejects old key. Serialized shortTMP timeout-k10: new3/3, business264/264, old-key mutant killed/restored SHA9e43bfef, restored new3/3; all positive0 fail/skip/cancel. Manifest4/4 verifies including unchanged project document. Logs/load lines and mutant diff in BUILD.md; no queue writes/host action, Gladstone review/Sage gate pending.
+48
View File
@@ -0,0 +1,48 @@
# Row 325 / #1821 — Case 1 project key
Goldie, 2026-10-24. Branch goldie/row325, base99930362e4b8a64d5425b10ca087b2d1f93ae538. Separate worktree /home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325. Brief pinned at60d78e44e:docs/plans/2026-10-24_case1-project-key-brief.md, section Brief: re-key the Case1 project to stack. Mos-1563 fixes the business document and preserves project id. Reviewer Gladstone; Sage owns review posting/landing. No queue writes authorized or performed.
## Defect and change
The real .mosaic/project.json declares id stack; the Case1 business document incorrectly keyed its project mosaic-stack. Real business validation checks equality at packages/business/src/cli.mjs:80, so the install refused once the host held the project file. Row323's scratch execution root omitted that file and exercised only the warning/absent branch, missing the deployment mismatch.
Only the projects object's key changes to stack in docs/guides/case1-business.mosaic-stack.json. Business id stays mosaic-stack, root stays /srv/mosaic/src/mosaic-stack, and every other field is unchanged. No .mosaic/project.json edit or product/schema/role change.
The new packages/business/tests/case1-project-key.test.mjs reads the repo's real .mosaic/project.json bytes, asserts exactly one matching Case1 key, copies those bytes unchanged into a scratch project root, and calls the actual business CLI validate path with a temporary system/business config. Positive output must report the project valid, not absent. Negative coverage re-keys the same fixture to mosaic-stack and requires exit2 plus the real project-id mismatch message; missing-file warnings cannot stand in for validation.
Mos-1564 (mos-1272) authorizes exactly one pre-existing test line: case1-business-file.test.mjs:70's expected key changes to stack; all other bytes/assertions remain unchanged. No other old test is touched. This is explicitly the obsolete expectation caused by the product correction, not a relaxed assertion.
## Verification plan / state
No tests run before Sage CLEAR. Static node --check, JSON parse and diff checks only. On CLEAR, one run at a time: new test file whole, business suite whole (includes the one updated old test), one old-key mutant, restored whole new file to verify restoration. Every run gets fresh `export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)`, timeout-k10, and the established UTC/load/busy-helper log header. Stop/report on any other old-test red; no further old-test edits. First unexpected new-test red is recorded/reported under the continuing mos-1272 rule.
mutant.py changes only the projects key back to mosaic-stack, runs the new exact-key assertion, requires expected exit1/fail1, then restores/checks bytes in finally. Prepared, not run. Sage's landing gate supplies CLI suite counts.
The dependency setup is an ignored root node_modules symlink to /mnt/storage/src/mosaic-stack/node_modules; no dependency installation/pin change. MANIFEST.sha256 covers business JSON, the one old test, new test, and real project document as an unchanged reference. Final review report goes to both Sage and Gladstone with tip/base/manifest. No host contact, credential access, queue write or refactor push.
## Verification — sage-3882 CLEAR
All four runs were serial on the same final source bytes, using timeout-k10, fresh31-byte TMPDIRs from /mnt/storage/scratch/tmp/XXXXXX, and date-u/uptime/busy-helper headers. Node v26.8.1. No first-run new-test red and no old-test red. No CLI suite run; Sage's landing gate supplies that scope.
| Run / log | UTC start | Load averages | Busy | Result / TMPDIR |
| --- | --- | --- | --- | --- |
| test-new.log | 2026-10-24 01:13:40 | 1.34,1.60,2.50 | CLEAR | Whole new file3/3 pass, 172.161691ms; hchZu2 |
| test-business.log | 2026-10-24 01:13:50 | 1.36,1.59,2.49 | CLEAR | Whole business suite264/264 pass, 6478.924028ms; yWVbwY |
| mutant-driver.log + mutant.log | 2026-10-24 01:14:04 | 1.36,1.58,2.47 | CLEAR | Old-key mutant killed, targeted new test0/1, expected exit1; driver exit0; YsOE6N |
| test-restored.log | 2026-10-24 01:14:14 | 1.15,1.53,2.44 | CLEAR | Restored new file3/3 pass, 169.948442ms; Y43Jju |
All TMPDIRs are under /mnt/storage/scratch/tmp/. New/restored/mutant driver limits180s, business600s, each timeout-k10. Positive runs have0 failures/skips/cancellations. The business suite includes the entire old case1-business-file.test.mjs, with only the mos-1564-approved key assertion changed.
Mutant diff in business JSON:
```diff
"projects": {
- "stack": {
+ "mosaic-stack": {
```
The exact-key new test fails deepStrictEqual (actual mosaic-stack, expected stack); raw evidence is mutant.log. mutant.py restores in finally and checks exact bytes. Restored JSON SHA256 `9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f`; all4 MANIFEST.sha256 entries verify, including unchanged .mosaic/project.json. Whole new file passed again after restoration. git diff --check passed.
Final source accounting: business JSON1+/1-, only projects key; old test1+/1-, only expected key at70; new test file with3 cases; no other old-test line touched. Business id/root remain unchanged. Root-cause gap is now covered by byte-identical real project fixture plus required valid result and old-key refusal. No host action, credentials, queue writes, refactor push or unapproved scope change.
Logs have trailing whitespace normalized for git diff --check, with substantive output/results unchanged. The slot is returned in the report accompanying the pushed candidate; Gladstone first review and Sage landing gate remain pending.
@@ -0,0 +1,4 @@
9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f docs/guides/case1-business.mosaic-stack.json
9c3bf7f349d0ee8626743e1388b9a596c7f3c274beaedad5106db5f2ff8f65e7 packages/business/tests/case1-business-file.test.mjs
b543ac6afbfb336c5566f359ff357904b36f413d5f75fcd54a5a8b72aef1dbd6 packages/business/tests/case1-project-key.test.mjs
044714a37c267b70279977ea8ca429df7a28463271b884afd79a01bc27afa259 .mosaic/project.json
@@ -0,0 +1,6 @@
Sat Oct 24 01:14:04 UTC 2026
20:14:04 up 10 days, 11:33, 2 users, load average: 1.36, 1.58, 2.47
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/YsOE6N bytes=31
old-key mutant: exit 1
restored sha256 9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f
+40
View File
@@ -0,0 +1,40 @@
TAP version 13
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
not ok 1 - row 325: the Case 1 project's sole key matches the repository project id
---
duration_ms: 2.058961
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325/packages/business/tests/case1-project-key.test.mjs:38:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
+ 'mosaic-stack'
- 'stack'
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 'stack'
actual:
0: 'mosaic-stack'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325/packages/business/tests/case1-project-key.test.mjs:42:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 44.416722
+26
View File
@@ -0,0 +1,26 @@
"""Row 325 old-project-key mutant; run only in Sage's test slot."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
source = root / 'docs/guides/case1-business.mosaic-stack.json'
fixed = source.read_bytes()
needle = b'"projects": {\n "stack": {'
assert fixed.count(needle) == 1
try:
source.write_bytes(fixed.replace(needle, b'"projects": {\n "mosaic-stack": {'))
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
'--test-name-pattern=row 325: the Case 1 project',
'packages/business/tests/case1-project-key.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Path(__file__).with_suffix('.log').write_text(result.stdout)
print(f'old-key mutant: exit {result.returncode}', flush=True)
assert result.returncode == 1, result.stdout
assert '# fail 1\n' in result.stdout and 'mosaic-stack' in result.stdout, result.stdout
finally:
source.write_bytes(fixed)
assert source.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
File diff suppressed because it is too large. Load diff
+32
View File
@@ -0,0 +1,32 @@
Sat Oct 24 01:13:40 UTC 2026
20:13:40 up 10 days, 11:33, 2 users, load average: 1.34, 1.60, 2.50
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/hchZu2 bytes=31
TAP version 13
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
ok 1 - row 325: the Case 1 project's sole key matches the repository project id
---
duration_ms: 4.262251
type: 'test'
...
# Subtest: row 325: real business validate reports the copied repository project valid
ok 2 - row 325: real business validate reports the copied repository project valid
---
duration_ms: 67.962164
type: 'test'
...
# Subtest: row 325: the old mosaic-stack project key refuses against those same project bytes
ok 3 - row 325: the old mosaic-stack project key refuses against those same project bytes
---
duration_ms: 57.926665
type: 'test'
...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 172.161691
@@ -0,0 +1,32 @@
Sat Oct 24 01:14:14 UTC 2026
20:14:14 up 10 days, 11:34, 2 users, load average: 1.15, 1.53, 2.44
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/Y43Jju bytes=31
TAP version 13
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
ok 1 - row 325: the Case 1 project's sole key matches the repository project id
---
duration_ms: 4.401838
type: 'test'
...
# Subtest: row 325: real business validate reports the copied repository project valid
ok 2 - row 325: real business validate reports the copied repository project valid
---
duration_ms: 60.555647
type: 'test'
...
# Subtest: row 325: the old mosaic-stack project key refuses against those same project bytes
ok 3 - row 325: the old mosaic-stack project key refuses against those same project bytes
---
duration_ms: 63.16989
type: 'test'
...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 169.948442
+2
View File
@@ -1618,3 +1618,5 @@ are never rewritten or removed; corrections are new entries.
2026-10-24T00:25Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 mos-1560 stop | First isolated revoke-window whole-file rerun failed0/1 at20.126s under load1.46/1.68/2.70/helperCLEAR. Second-red rule: stopped with no further tests/edits, manifest unchanged, remote571 unverified. Log and load lines in BUILD.md; Sage notified goldie-0018, slot returned.
2026-10-24T00:38Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 accepted verification | mos-1561/sage-3869: short31-byte TMPDIR fixes socket-length refusal; revoke-window1/1, business261/261, new7/7, mutant killed/bytes restored, new7/7 again. Every run logs time/load/helper; no local CLI rerun, Sage gate supplies it. Notes/logs evidence only; Duckworth review/in-review move next.
2026-10-24T00:41Z | Duckworth (Opus 5.5) | row 323 round 1 static review on 6a0f0f4f8 (sage-3865, sage-3870) | APPROVE (static) C1 Sage full gate cli, manifest 903ef022 matches, fields sourced, tests read shipped bytes, tester mutant real, N1-N2 doc (duckworth-0300) |
2026-10-24T01:13Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row325 / #1821 Case1 project key | New goldie/row325 worktree at99930362e; pinned brief60d78e44e. Draft JSON key correction, new real-project-byte tests, sole old line70 key edit under mos-1564. Static checks only; requesting Sage CLEAR, no queue writes.
2026-10-24T01:14Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row325 / #1821 verified | sage-3882: new3/3, business264/264, one key mutant killed/restored, new3/3 again, per-run fresh shortTMP/load/helperCLEAR. Four manifest entries verify; sole old assertion key change, no queue writes. Candidate push/report to Sage and Gladstone next.
+1 -1
View File
@@ -7,7 +7,7 @@
"technical": "chief"
},
"projects": {
"mosaic-stack": {
"stack": {
"root": "/srv/mosaic/src/mosaic-stack"
}
},
@@ -67,7 +67,7 @@ test("row 323: committed Case 1 document loads unchanged through the business lo
assert.equal(b.tracker, null);
assert.equal(Object.hasOwn(document(), "tracker"), false);
assert.equal(Object.hasOwn(document(), "library"), false);
assert.deepEqual(b.projects, { "mosaic-stack": { root: "/srv/mosaic/src/mosaic-stack" } });
assert.deepEqual(b.projects, { "stack": { root: "/srv/mosaic/src/mosaic-stack" } });
assert.deepEqual(Object.keys(b.roles), ["chief", "assistant"]);
assert.deepEqual(b.plugins.harness, ["claude-code"]);
assert.equal(b.launch.by, "chief");
@@ -0,0 +1,65 @@
// Row 325 (#1821): factory validation must load the repository's actual project document.
import { test } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { REPO, REPO_ROLES, systemConfig, tmp, writeJson } from "./helpers.mjs";
const CASE1 = join(REPO, "docs/guides/case1-business.mosaic-stack.json");
const PROJECT = join(REPO, ".mosaic/project.json");
const document = () => JSON.parse(readFileSync(CASE1, "utf8"));
function fixture(t, { oldKey = false } = {}) {
const root = tmp();
t.after(() => rmSync(root, { recursive: true, force: true }));
const doc = document();
assert.equal(Object.keys(doc.projects).length, 1);
const [key] = Object.keys(doc.projects);
const projectRoot = join(root, "checkout");
const projectFile = join(projectRoot, ".mosaic/project.json");
const bytes = readFileSync(PROJECT);
mkdirSync(join(projectRoot, ".mosaic"), { recursive: true });
writeFileSync(projectFile, bytes);
assert.deepEqual(readFileSync(projectFile), bytes, "fixture must use the real project's unmodified bytes");
const entry = { ...doc.projects[key], root: projectRoot };
doc.projects = { [oldKey ? "mosaic-stack" : key]: entry };
const config = systemConfig(root);
writeJson(join(root, "config/businesses/mosaic-stack.json"), doc);
return {
project: JSON.parse(bytes), projectFile,
validate: () => spawnSync(process.execPath, [join(REPO, "packages/business/src/cli.mjs"), "validate", "mosaic-stack"], {
env: { ...process.env, HOME: root, MOSAIC_CONFIG: config, MOSAIC_ROLES_DIR: REPO_ROLES },
encoding: "utf8", timeout: 30000,
}),
};
}
test("row 325: the Case 1 project's sole key matches the repository project id", () => {
const doc = document();
const project = JSON.parse(readFileSync(PROJECT, "utf8"));
assert.equal(project.id, "stack");
assert.deepEqual(Object.keys(doc.projects), [project.id]);
assert.equal(doc.id, "mosaic-stack");
assert.equal(doc.projects[project.id].root, "/srv/mosaic/src/mosaic-stack");
});
test("row 325: real business validate reports the copied repository project valid", (t) => {
const x = fixture(t);
const r = x.validate();
assert.equal(r.status, 0, r.stdout + r.stderr);
const result = JSON.parse(r.stdout);
assert.equal(result.business, "mosaic-stack");
assert.deepEqual(result.projects, { [x.project.id]: "valid" });
assert.doesNotMatch(r.stderr, /no project file|project variables/);
});
test("row 325: the old mosaic-stack project key refuses against those same project bytes", (t) => {
const x = fixture(t, { oldKey: true });
const r = x.validate();
assert.equal(r.status, 2, r.stdout + r.stderr);
assert.equal(r.stdout, "");
assert.match(r.stderr, /has id stack, but business mosaic-stack declares it as mosaic-stack/);
assert.ok(r.stderr.includes(x.projectFile));
assert.doesNotMatch(r.stderr, /no project file/);
});