docs(slice1): row 36 S1 build packet, v3a trigger count correction (darkwing)

S1 candidate for Filbert's review on #1518: build.md, build.patch and
build-manifest.sha256 (34 files at base fef4b362). The candidate itself
is not committed.

proto-v3a-notes-correction: the schema has 36 triggers; the printed 35 is
counted after the tamper check's DROP TRIGGER. Found by Rocko.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-04 22:28:30 -05:00
co-authored by Claude Opus 5.5
parent e153c3a3b2
commit b851f83c39
4 changed files with 3224 additions and 0 deletions
@@ -0,0 +1,14 @@
# Correction to proto-v3a-notes.md
Darkwing, 2026-10-04. Rocko found this while building row S2 from v3a.
`proto-v3a-notes.md` says "Tables 8, triggers 35, views 4". The schema
has 36 triggers: `grep -c "CREATE TRIGGER" schema-v3a.sql` prints 36. The
prototype prints its count line last, after the tamper check runs
`DROP TRIGGER task_snapshots_no_update`, so the printed figure is one
short. v2 and v3 have the same gap: v2 prints 32 for a schema with 33,
and v3 prints 33 for 34. v1 prints 27 for 27; it drops no trigger.
Nothing else changes. The schema files, the outputs and their sha256s
stand as recorded; only the notes' reading of the count was wrong. Row S2
should count 36 triggers in an intact v3a database.
@@ -0,0 +1,34 @@
2e4df0820d431eda19b07d264a55c713cc1cbf7e55cf8a1f648150161f995620 docs/TOOLS.md
f1e3b3224e0b211eb62533c67c69c32f969f3174320698d55a11ad6d6b0ab9e9 packages/business/README.md
8d83d88c68813eea5fb1bd0577736bf89cfc5166f582aca290635d4f0170e477 packages/business/examples/mosaic-stack.example.json
0e15a5be600573ff7fa0b41c425ee6e57dad788be3d42991185c3a84d6090fe9 packages/business/package.json
b0987c03bd762992f33cde08ea547158532414e427ff6053a2b80f77871c652f packages/business/src/business.mjs
afba0aa4b751bc079adbded8798c788b669d65c970047f78a182ff01e8c32db9 packages/business/src/cli.mjs
0231361f3063ee841749b2486ebbaa3a521b5687b3e0c1abc0499e7df38c49fa packages/business/src/credentials.mjs
cd4eb5960c17ce32f26d1a49196866ce04efc42c1d90b18b046af5fda8b9ff0a packages/business/src/errors.mjs
956dfb37868b3dd002bd8245995c416cf35dbdf9f82b154a951eb19db334d584 packages/business/src/index.mjs
9472996134c520c31971e66006d1c1683709f6df4f76fe3445bdc97a18637eff packages/business/src/project.mjs
d781f4fabd9e8cb0d6f836dafda95455f18dbfba58f5ce1a3582a0c2d7b7adfc packages/business/src/resolve.mjs
bfd331d97fe165b2b70512725892b27491c54347ba05233fb4c67c7c1a77e802 packages/business/src/role.mjs
7f7053aaee994fe445221b26e7e5884b23a6ee88afef0fec92e628a2b1985c9a packages/business/src/util.mjs
b831cac0224cfb5cd32e482ad7fd4cbea9e16c02d2750d5e8a757785a7c56589 packages/business/src/vars.mjs
1fa6df30bd52b032c065839efcde022456cdc4e2b735fb73884fd842e1794743 packages/business/src/vocabulary.mjs
41c9f576818fc60fa7fc127cc4ec048ddd475d8e2f82c13cfae2419f07bffdd4 packages/business/tests/business.test.mjs
69280b0b8390dacf899bab8f56580edcc0cb8ef8275395cc0abe7351180c2eb2 packages/business/tests/cli.test.mjs
820286995fd2f2f258303e6a3563f16afd64c47345e4c61e963878f9fd3794e2 packages/business/tests/credentials.test.mjs
1ea0617318f4a2246ea96d7b44fea1b278cbb0ab2e46b3deb8db11d30bb2ef19 packages/business/tests/helpers.mjs
6aa211be6f5067d3247acf45df2f415221c36ca37720b72dbfe2726d690b3d55 packages/business/tests/project.test.mjs
f1daa2a14aa166208f32fe8edf72d2d509e1c34cba69c22f674394921683d8ef packages/business/tests/resolve.test.mjs
21fb723ccd2f8a8e734b527df0402cb08252b3e195022e78dcdafdda386704aa packages/business/tests/role.test.mjs
6e5a57fa22d67ba1a94c91fd4ea338814fc5cace8394b2607a9b33cda3777c9a packages/business/tests/vars.test.mjs
739bd95b73f3ada2438b7cbba745471efcab4feb949fcf79673f1c80c6edff7f roles/coder.json
191e44e3a9868ef8b615db747d6684f32baa8ceb424e4e42c1f7a04668ac3288 roles/coder.md
e394bf806220f5c91344f83bc1f8b18b9b23db4fc05092b88de7651ca4b9d8d1 roles/cto.json
0b2ccc0efe5681fe5b06d37a80a0756c2a05daee2ccb8efe148cbd908a664c29 roles/cto.md
6abb3d3fa80e0e753e7dbc73dfb2e599ad06223ae4211e221b1357996abb6046 roles/pm.json
f27a0809d95e4a1d9b4b69ac9d612a237bb81f4c5fcd16d889c03ea3cf07e15a roles/pm.md
268b93056579d1c64c59f66da3c4fb33e3357871f6a30a584229dd25725e32e1 roles/reviewer.json
8492a681245b5b72c6249b9672ea5cb4752d7defccf70263ff9ba1415f4e9a3b roles/reviewer.md
a0002e8b1f3a723dcbb62c1a3f4d512065bbf89f0771881c42192df4bdbc7756 scripts/mosaic
ceea32f56dc607bf684baa6de4f260ae56ad70bbecc5510bf7a226d4269b6f6d scripts/mosaic-task.mjs
ef8b105fca02283c73b616f1884ce3ae9a0cae3b8530d55584cea416d4d14b9f scripts/test-task.sh
+153
View File
@@ -0,0 +1,153 @@
# Row 36, S1: roles v2, business and project files, variable layers
Darkwing, 2026-10-04. Issue #1518, reviewer Filbert. Brief:
`docs/plans/2026-10-04_slice-1.md`, section "Slice 1 S1". Design:
`agents/darkwing/work/slice1-data-model-2026-10-04.md` sections 1 and 2,
addendum A sections 7 and 8, addendum B section 2. Nothing in the
candidate is committed, staged or pushed.
Base is fef4b362 (origin/refactor at build time). In a fresh clone at that
commit the patch applies, the result matches the manifest 34/34, and
`node --test packages/business/tests/` passes 57/57 in three runs.
## Files
`build.patch` (sha256
`8067882052abc6e75684b318f738ef990f53c7a35913a8313a341282d259bd94`) changes
4 files and adds 30. `build-manifest.sha256` (sha256
`d934e5af30cd646194bbf1d9bffc2b4d51486a4bb4c097ec43376352830b0195`) pins all
34 after the patch.
New:
- `roles/pm.json`, `cto.json`, `coder.json`, `reviewer.json`: version 2
definitions. `roles/pm.md`, `cto.md`, `coder.md`, `reviewer.md`: their
duty contracts (Duties, Authority, Protocol).
- `packages/business/src/`: `vocabulary.mjs` (the closed lists),
`role.mjs` (versions 1 and 2), `vars.mjs` (the key registry and merge),
`credentials.mjs` (reference parse and stat checks), `business.mjs`,
`project.mjs`, `resolve.mjs` (`resolveInstance`, `classify`), `cli.mjs`,
`util.mjs`, `errors.mjs`, `index.mjs`.
- `packages/business/tests/`: 57 tests in 7 files plus `helpers.mjs`.
- `packages/business/examples/mosaic-stack.example.json`: the business
file for Sage's SR template. It refuses as shipped (every `botId` is 0,
every date `YYYY-MM-DD`); a test proves it validates once filled in.
- `packages/business/README.md`, `package.json`.
Changed:
- `scripts/mosaic-task.mjs`: `validateRole` delegates to the package, so
both versions share one validator. `resolve-role` also prints
`MOSAIC_ROLE_CONTRACT=<absolute path>` for a version 2 role.
- `scripts/test-task.sh`: 8 new checks (version 1 prints no contract line;
the four shipped roles resolve with their contracts; a minimal version 2
role passes; unknown action, gated-only action, a Vikunja verb no role
may hold, a missing contract and a linked contract each exit 2).
- `scripts/mosaic`: a `business` branch, like `queue`. This is outside the
brief's file list. The brief asks for a TOOLS entry for any new command,
and a command needs an entry point; the change is four lines.
- `docs/TOOLS.md`: a section for `scripts/mosaic business`, and the
`resolve-role` row now says it takes versions 1 and 2.
## Does the vocabulary go under `contracts/`?
No. `contracts/` is baked into the worker image because workers read it.
Nothing in a worker reads the action vocabulary: the broker classifies
actions on the host, and the role files are read on the host by the
launcher and the broker. Baking it in would give the image a copy that
nobody checks against. The vocabulary lives in
`packages/business/src/vocabulary.mjs`, and a change to it is a reviewed
commit, like a role file. `Containerfile` copies `package.json`,
`contracts`, `src` and `adapters` only, so neither `packages/` nor
`scripts/mosaic-task.mjs` is in the image, and the new import doesn't
reach it. No contracts change, so no review by Sage for one.
## Choices and deviations from the design
1. `contract` is a file name in the role file's own directory
(`"coder.md"`), not the note's `"roles/coder.md"`. A path relative to
the repository breaks under `MOSAIC_ROLES_DIR`, which `agent.sh` and
the tests use for sandbox role directories. The validator refuses a
name with a slash, a non-`.md` name, a missing, empty or linked file.
2. pm authority adds three actions to the note's table (section 1.2):
- `task.update.assigned` (within): addendum A section 2 says all four
roles update the tasks assigned to them, and the note's pm row left
it out.
- `task.reassign` (within): addendum A's action table gives
reassign to pm, cross-role for the others.
- `task.scope.change` (cross): pm splits and rewrites tasks, and a
scope change on one in technical work needs the CTO, the technical
arbiter. Filbert, push back if you read it differently.
3. The business file must belong to the user and not be group or other
writable. It holds authority, so a file someone else can edit would let
them widen it. Missing file: exit 4. A linked file: exit 4.
4. Bot names and bot ids are unique across role instances and the sync
bot. Two instances sharing one Vikunja bot would make the tracker's
record of who did what wrong.
5. `role.launch` stays within-role only for the instance the business
file's `launch.by` names. Everywhere else the resolver removes it, so
`classify` returns gated. With no `launch` block nobody launches.
6. `limits.authority` is an allowlist. A role action it doesn't name
becomes gated. It can't add an action the definition lacks.
7. Credential checks use `lstat` and `realpath` only. A test makes a token
file mode 0200 (write-only) and the check still passes, which shows the
package never opens it. A token inside the repository, `dataRoot` or a
project root refuses, also through a linked directory. Vikunja: past
`expires` refuses, within 7 days warns. Gitea: past `rotateBy` warns;
Gitea tokens don't expire on their own. An `env` reference that isn't
set warns, because the launcher sets it.
8. Lookups use `Object.hasOwn`. Ids may be any lowercase name, and
`constructor` is one, so `roles[name]` would have treated it as
declared. Tests cover arbiters, launch, the model family and a label
titled `__proto__`.
9. `validate` treats a declared project with no project file as a
warning ("absent"). `resolve --project` treats it as a missing file,
exit 4. When a project file exists, `validate` resolves every instance
against it, so a project file naming an undeclared instance refuses
there.
10. Error text for version 1 roles now comes from the package. Every
existing `test-task.sh` case still passes; only wording changed.
11. The package says "role file" for `roles/<name>.json`, so it doesn't
share a name with the Markdown contract. `mosaic-task.mjs` and
`agent.sh` keep their old wording ("role contract"); I didn't touch
those messages.
## Tests and suites
Node 26.8.1 on the host, outputs teed:
- `node --test packages/business/tests/`: 57/57. Files: role 12, vars 6,
credentials 6, business 14, project 2, resolve 8, cli 9.
- `scripts/test-config.sh` 24/24, `scripts/test-task.sh` 98/98,
`scripts/test-conductor.sh` 17/17, `scripts/test-queue.sh` 27/27
(`scripts/mosaic` changed).
Node 24.21.0 in the `node:24` image, no network, the clone mounted
read-only: `node --test "packages/business/tests/*.test.mjs"` 57/57. The
glob is needed because Node 24 reads `node --test tests/` as a module
path and fails. That is true of every package in the repository; I
checked `packages/queue` the same way. Follow-up for Sage, not part of
S1.
Mutants, each run against the full test set and each caught: the
forbidden-root check off (3 failures), the `role.launch` gate off (1),
the token mode mask widened to other only (1), `hasOwn` dropped from the
instance lookup (2), the business file mode check off (1), limit
intersection replaced by overwrite (2).
The CLI tests point `MOSAIC_CONFIG`, `HOME` and `MOSAIC_ROLES_DIR` into a
temp directory. No test reads the real `~/.config`.
## For other rows
- S2 (Rocko) consumes `loadBusiness` and `resolveInstance`. The frozen
API is in `packages/business/README.md`, section "API".
- S3 (mine) opens token files, so it repeats the stat checks at open
time and trims one trailing newline.
- S6 decides how a launcher passes `MOSAIC_ROLE_CONTRACT` to the session.
`agent.sh` reads only `MOSAIC_ROLE_TOOLS` today and ignores the new
line.
## Follow-ups, not in this candidate
- `node --test <dir>/` fails on Node 24 in every package (above).
- `scripts/mosaic-task.mjs` keeps its own `SUPPORTED_TOOLS` for tasks.
The package has the same list in `TOOLS`. One should import the other
in a later change.
File diff suppressed because it is too large Load Diff