Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57862d6144 | ||
|
|
7da3822443 | ||
|
|
65e919e5ce | ||
|
|
0626464874 | ||
|
|
66299eeef7 | ||
|
|
569bb6b177 | ||
|
|
fb404c909d | ||
|
|
4c0a2c4fd4 | ||
|
|
2f06aad3f0 | ||
|
|
6863d4c852 | ||
|
|
9ef5798acb | ||
|
|
4897520c31 | ||
|
|
4ca2703079 | ||
|
|
25c09f19fe | ||
|
|
a6da8718c4 | ||
|
|
ee26cfbada | ||
|
|
a4da4e6df4 | ||
|
|
7309260aeb | ||
|
|
7c543fd4a1 | ||
|
|
284d55cf2c | ||
|
|
93a491d732 | ||
|
|
c7b754d7b1 | ||
|
|
a94c95522f |
No files matched your search
+6
-8
@@ -5345,6 +5345,10 @@ Gander: after Duckworth-0100 design approval with C1-C5, prepared the two host-s
|
||||
|
||||
2026-10-22 18:11:58 UTC | Gander row276 #1781 prepared, gate pending | Before: row253 left5legacy Case1 AS_USER calls unsupervised across sudo pty sessions. After: candidate uses its unchanged supervisor with per-call inner/outer budgets, separate streams, strict display-only failure trailer, fake-only paired process-model cases and15unique mutants. Duckworth0182 B1 corrected the plan before product code;0183 cleared build. Parse-only Bash checks pass; no Node, behavioral test, host, unit or config action. Runtime gate and publication await Sage.
|
||||
|
||||
2026-10-22T22:51:32Z | Megavolt | Row 207 (193c, #1705): rebased onto 01983cd3f as 712f79c0a; gate green under sage-3041 (access.test 31/31, M1-M8 killed on named tests, cli 894/894, ten scripts under mos-1275 limits). Correction: the first run's kill check grepped TAP "not ok" against node's spec output and was stopped and rerun. Runs note in 9bb406c6c.
|
||||
|
||||
2026-10-22T22:51:32Z | Sage | Row 207 (#1705, Megavolt, Duckworth) LANDED c4f77373f. Before: refactor at f0e3dcdf4; mosaic access had no user verbs. After: fast-forward of 4 commits, a cherry-pick of the gated 01983cd3f..9bb406c6c (range-diff 4/4 equal, patch-ids equal, the 4 manifest blobs equal; the base moved only by the #1797 brief, which no test reads; no open branch touches the manifest). Duckworth APPROVE c30849 rev 3356; done rev after the note. N1 (any-business refusal untested) DEFERRED. No live change. Correction: my first 207 review request ran under timeout 120, which killed it while it wrote the outcome; the comment (c30841) had posted, so I removed the dead lock and resolved it at rev 3350.
|
||||
|
||||
2026-10-22T23:03:39Z | Gyro Q195 O2 rewrite, #1752 / Vikunja #448, mos-1356 | Before: landed O3 design 95e3f7166/blob 4cb59999. After: Q231/Q233 full WordCraft O2 design on gyro/editor-o2 from refactor c4f77373f; canonical git DOCX, original Markdown/wiki preservation, human cadence default 0 with instant accepted-save commits, full rich-feature first slice. Ten briefs include independent source/dependency security clearance before any build queue entry, exact editor-only CSP approval gate, restricted source/build/bridge, separate reviewed DOCX citation gate and flags-only agent access. Existing Case 2 semantics unchanged; any integration requirement change goes through Mos to Jason. Static checks: ten briefs/seven fields, independent reviewers, unique anchors, current source/base/blob pins, docs scope and append-only logs pass. No builds, browser/worker runs, code, queue or live changes. Fenton review pending; CSP and implementation gates remain closed.
|
||||
|
||||
2026-10-22T23:03:53Z | Gyro Q195 O2 push guard | CI queue guard identity gyro / purpose push returned exit 0, documented degraded-pass: CANNOT_ASSERT credential-unresolvable. Audit: /mnt/storage/scratch/gyro-q195-ci-queue-audit.jsonl. No credential fallback or CI-green claim.
|
||||
@@ -5359,12 +5363,6 @@ Gander: after Duckworth-0100 design approval with C1-C5, prepared the two host-s
|
||||
|
||||
2026-10-22T23:13:48Z | Gyro mos-1359 / r3 push guard | D13 records save-group timing as Mos's accepted reading of Q226 F41, not Jason's ruling; both defaults remain human-only settings. Push guard identity gyro/purpose push: exit 0 documented degraded-pass, CANNOT_ASSERT credential-unresolvable; audit gyro-q195-ci-queue-audit.jsonl, no credential fallback or CI-green claim.
|
||||
|
||||
2026-10-23T00:52:58Z | Gyro Q235-Q237 policy rewrite started, Vikunja 571, mos-1366/1367 | Before: approved O2 e36bce352 keeps Markdown originals at their paths and Q36 notices open; landed W4 2af460ab7 has the same boundary. Imported that byte-identical W4 baseline as 1ac76a417, from refactor 062646487, to make the policy-only revision diff explicit. Prepared P1-P6/B1-B4: human one-way per-story import, human-set source-repo archive, DOCX current reader, scoped format map/archived non-canon reads, W7 conversion publication after W4b/approved PRD/10-27 and Jason first-real-book go. Q234 exact CSP, mos-1363 and S0 M1-M14 retained. W4 locator/extractor, shares and restore-fence blocks compare byte-identical to the approved baseline. Scope/source review ongoing; Mos impact map must be folded before Fenton handoff. No build/browser/worker run, code, queue, private-book or live action.
|
||||
2026-10-22T23:23:42Z | Rocko, W4 #1799 | Before, D24-D26 had no separate exact DOCX citation/access contract. After, 15e91538f proposed a versioned schema, parser/story/view/refusal rules, live authorization/sharing and generic/git/session bypass closure, compatibility gates and 22 acceptance rows. Two JSON examples parse, diff check clean; no code or implementation acceptance. Megavolt review pending.
|
||||
|
||||
2026-10-23T01:37:07Z | Gyro Vikunja571 final impact-map / mos-1372/1375 fold | Prepared parent/W4 rewrite against brain-redux d183246 impact map at stack 062646487. P1-P6/B1-B4 and engineering Q1-Q16 are mapped to eleven briefs. D9 replaces the earlier private O2-only context/per-document/share model: normal Case 2 access in any authorized thread/room, supporting writes intact, changed policy awaits PRD 1.17 approval. New DOCX story creation has the same opening gate as human per-story import. Host-only push to human-set off-host remote replaces the no-remote ban; remoteURL defaults unset and same-host bare is disallowed. W7 gates on Hermes #423 step5, W4b, PRD approval, post-10-27 and first-real-book go; owns conversion events COUNTED/--events/pre-conversion restore reconciliation, manifest order/next_chapter, creative role cite wording, archive/old-path edits and format/link/citation routing. Store+off-host acknowledgment precedes archive commit/push, then catalogue publication; post-push failures remain resumable pending with pinned source. Current checks pass: complete brief fields, policy/map coverage, exact Q234 CSP, locator/projection mechanics byte-equivalent except explicit authority wording, A01-A35 IDs, immutable map pin, four-doc scope and append-only logs. W4 authority/remote/recovery sections reopened for Fenton; older APPROVE not reused. No product/build/browser/worker/private-book/queue/live action. Earlier entry describing preserved shares/fence applies only to the pre-D9 intermediate draft; D9 now withdraws those unimplemented structures.
|
||||
|
||||
2026-10-23T01:37:55Z | Gyro 571 push guard | identity gyro/purpose push: exit 0 documented degraded-pass CANNOT_ASSERT credential-unresolvable, scratch audit gyro-q195-ci-queue-audit.jsonl; no credential fallback or CI-green claim.
|
||||
|
||||
2026-10-23T01:41:59Z | Gyro sage-3121 A1-A3 PRD alignment | Before: 8d209016b adds a current Connections graph, excludes new Markdown stories in converted books and defaults archiveFolder. After: parent/W4 align with PRD1.17 d6 sha256 886b93c571f87bfbb2f27ee3113b6800a32d8f89196d95ace33566031bee380a: clickable editor links only; graph/tests later pending J-b; new different-path Markdown in a converted book remains canon until import, only recreated converted old paths are unmapped; archive folder is human-set in library mapping with no default, missing folder refuses conversion. Static A1-A3/11-brief checks pass; W4 locator/extractor/access/remote/recovery blocks unchanged. Local commit only, push held for Sage's named gap while Gizmoduck holds 270+271 run5. No product/build/browser/worker/queue/live action. Fenton gets local SHA through shared object store.
|
||||
|
||||
2026-10-23T01:49:08Z | Gyro fenton-0012 B1/B2 and N1-N6, mos-1379 | B1: off-host remote must deny the real shared jason.woltje seat-helper credential as well as seat identities until #431; private visibility/dummy-token denial is insufficient; W3b/A16/A26 require that future fixture proof. B2 follows mos-1379: human-only editor.agentReadComments and editor.agentReadTrackedDeletions both default false, comment/original/markup reads/resolves/index/context refuse when off; final access remains D9 and no role allowlist/per-document grant. N1 staging-ref allowlist and retained private refused/published audit refs; N2 URLs literal with fidelity-report loss and confirmation; N3 future paths marked new or predecessor row; N4 rename/sharing wording fixed; N5 historical thread text not refreshed; N6 protected VM1012 backup cited in P5. Static checks pass, locator JSON/package/projection mechanics retained. Local commit only; HTTPS push still held for Sage's named gap. No credentials tested/read/printed, no product/build/browser/worker/queue/live action. Fenton gets exact local SHA/blobs.
|
||||
2026-10-22T23:55:11Z | Sage, W4 #1799 docs landing | Megavolt reviewed four rounds (CHANGES 15e91538f, 019a0ea5b, 54246cb32; APPROVE 994c3f7fb, #1799 c30868/c30870/c30876/c30884). Landed here as cherry-picks: docs/plans/2026-10-22_q195-docx-citations.md blob 2af460ab7 and docs/reviews/2026-10-22_q195-docx-citations.md blob bedde1716, both new files. 32 acceptance rows go to W4b; R4-N1 (re-migration after a pre-W4b restore keeps the rotated fence and needs owner confirmation) goes into W4b's brief and A32. W4 is design-approved only: its gate still needs Sage's W4b implementation brief, which waits for S0 (#1798) PASS. Q36 open, adoption closed. Docs only, no test reads these files.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Vikunja574: deterministic fixture Git before/after comparisons
|
||||
|
||||
Owner Gander, gate Sage. mos-1369 A4 and mos-1370; sage-3107 and sage-3110. Take574 before573, as separate candidates. Base062646487451993302c768c6812b827c834e952b (origin/refactor at start), branchgander/v574. No queue move before Sage places the row. Build/push authorized now; every test/proof run waits Sage CLEAR after270+271 run5. Row292 remains independently held for final base and C1 review.
|
||||
|
||||
## Exact authorized delta
|
||||
|
||||
Only prepend `-c maintenance.auto=false -c gc.auto=0` to each fixture helper's Git argv. No production change, environment change, assertion change or fingerprint/snapshot change. Full-root comparisons still include .git. Production checkout.mjs's SAFE argv already contains both flags; gitEnv() alone does not. The helper called gitEnv() but skipped SAFE.
|
||||
|
||||
- packages/seat/tests/checkout.test.mjs:git34-35 commits fixture roots; once():88-91 fingerprints root including.git. Whole clone comparison246-249 also covers it.
|
||||
- packages/seat/tests/working.test.mjs:git27-28 commits fixture repositories; fingerprint():40 and refusal checks83-86/120-122 cover the whole root.
|
||||
- packages/cli/tests/git-socket.test.mjs:gitIn32-33 commits projects; fingerprint():43 and once79-101 cover project/.git.
|
||||
- packages/cli/tests/lifecycle.test.mjs:gitIn47-49, commit71; snapshot459 walks every regular file, T11 compares pb.root831/839 including project/.git. Outside original list, explicitly authorized sage-3110 / mos-1370 before editing.
|
||||
|
||||
Static preservation check removes only the four inserted argv elements from each candidate file and requires exact equality with its base blob. Thus every original assertion, fingerprint and snapshot byte stays intact. No new tests mirror the flags; runtime evidence is the requested trace over real fixture commits and whole files.
|
||||
|
||||
## Named but unchanged
|
||||
|
||||
- bus/grant-enroll.test.mjs imports fingerprint from host-grant.mjs for SSH host keys. No fixture Git helper or Git command; unchanged.
|
||||
- bus/grant-executor.test.mjs uses SSH-key fingerprints; its commit references are grant transaction steps, not Git fixture commits. No Git helper; unchanged.
|
||||
- bus/host-tools.test.mjs:28 hashes decoded SSH public-key bytes. No Git helper; unchanged.
|
||||
- conversation/reader.test.mjs:52/71/73 fingerprints session fixture files, but creates no Git repository and makes no Git call. Unchanged.
|
||||
|
||||
## Other fixture audit, screened and unchanged
|
||||
|
||||
- cli/lifecycle.test.mjs: whole-root .git snapshot is exposed; now included by explicit scope ruling above.
|
||||
- cli/launcher.test.mjs: fixture commits, but the before/after byte check961-971 names the sessions JSON file, not the repository tree or Git object directory. No maintenance-lock fingerprint exposure in that comparison.
|
||||
- cli/thread-launch.test.mjs and session-launch.test.mjs: fixture repositories, but compared before/transcripts/events/session-store values are application histories or evidence directories, not a whole .git tree. Unchanged.
|
||||
- cli/publish-socket.test.mjs and seat/publish.test.mjs: fixture commits, with ref values and fake-Gitea request/credential assertions. No whole-root fingerprint/snapshot; unchanged.
|
||||
- seat/landing.test.mjs: fixture commits support gate/root-path checks. The tree() helper builds a fixture and comparisons concern gate steps/results and output, not a whole-root byte inventory. Unchanged.
|
||||
- harness/git-tools.test.mjs: fixture commits support advertised object/ref and request assertions; no source-root byte fingerprint. Unchanged.
|
||||
- project/project.test.mjs, human-file.test.mjs and read.test.mjs: before/after checks use HEAD/status, spec content, or a record path; no full .git inventory including maintenance.lock. tester-init.test.mjs checks created records and identity. Unchanged.
|
||||
- bus/project-files.test.mjs via spec-fixtures.mjs: state() compares rev-parse HEAD and status --porcelain; detached maintenance changes neither asserted logical value. Unchanged.
|
||||
- library/mirror.test.mjs and sync.test.mjs via helpers.mjs: before/after byte checks name the sync-state file; comparison does not inventory Git objects/maintenance.lock. Unchanged.
|
||||
- cli/stack-copy.test.mjs: tree() compares published copies, which exclude source .git; source fixture already configures gc.auto0. No whole-source-repo .git fingerprint. Unchanged.
|
||||
- cli/stack-release-fixture.mjs and release tests: fp() compares unit, wrapper, pointer and store bytes, and tree() comparisons cover data/copies; source fixture.git is outside those roots and already sets gc.auto0. Unchanged.
|
||||
- webui/pinned-copy.test.mjs: fixture commits and gc.auto0; before/after assertion names config bytes and copied output, not source.git inventory. Unchanged.
|
||||
- queue/store.test.mjs via helpers.mjs: snap337 compares queue/view/.git/mosaic-queue.head only, not object/maintenance-lock paths. review tests compare request/log records. Unchanged.
|
||||
- ledger/ledger.test.mjs: Git fixture history is read as logical commits/timestamps, no whole-root .git fingerprint comparison found. Unchanged.
|
||||
|
||||
This is an audit of fixture before/after comparisons, not a claim every unflagged fixture Git helper is fixed. No outside named scope was changed without approval. Mos's provided probe /mnt/storage/scratch/mos-1369/maint-lock-probe.py was read only, never executed.
|
||||
|
||||
## Trace and gate plan, no execution yet
|
||||
|
||||
Real Git: /usr/bin/git, `git version 2.55.0` (read-only version query). gitEnv() retains PATH and scrubs outer tracing env, so an outer GIT_TRACE2 alone would be vacuous. On CLEAR create an executable git shim in own scratch, outside the candidate. It only exports GIT_TRACE2 to a per-run absolute trace path and execs /usr/bin/git with the unchanged argv. The candidate contains no shim.
|
||||
|
||||
Positive control first: a fresh fixture-style repo/commit WITHOUT either flag, traced through that shim, must show a maintenance child_start. Do not accept a missing/empty trace as success. Allow the detached positive-control child to finish and verify its scratch repo is idle before the flagged proof.
|
||||
|
||||
Flagged control and four touched files whole then use the shim. Require observed fixture commit commands in each applicable trace and zero maintenance child_start. Classify a maintenance child by the trace's child_start and `maintenance run --auto` command, not by a substring in this plan. Save the raw trace, tested SHA, exact command/timeouts, command/child counts, and restored-tree check. No assertion weakening or .git exclusion is allowed. Any old red, timeout, maintenance child, or missing positive control stops for Sage/Mos before a retry.
|
||||
|
||||
Recommended slot scope: positive/flagged fixture control plus the four touched files whole with GIT_TRACE2, and any broader seat/CLI suite Sage requests. Record per-file timeout-k10 at least3x successful historical whole-file receipt with headroom before launch; use900s where timing is unknown. TMPDIR from mktemp-d /mnt/storage/scratch/tmp/gXXXXXX, never /tmp. Pipefail/tee and busy check before each start. Trace runs are test work and are not authorized before CLEAR. No dependencies installed and no live ports/units/config accessed. Session registration remains Sage's.
|
||||
|
||||
## Filbert static approval and proof refinements
|
||||
|
||||
filbert-v574-7da38224-gander statically approves source65e919e5c / packet7da382244 / manifestcdda41f5. No runtime proof yet. Their independent audit found no missed fixture within the whole-root .git race class.
|
||||
|
||||
P1: checkout and working tests resolve command-v-git when loading their own command wrappers. The trace shim therefore lives in a stable absolute scratch bin directory for the entire run, including fixture hooks. It chains only to /usr/bin/git2.55.0 and is not deleted until all calls and detached children finish.
|
||||
|
||||
P2: add GIT_TRACE2_EVENT alongside GIT_TRACE2 in the scratch shim, pointing at a per-run directory. Git writes one JSON event file per process, avoiding interleaved event lines. Count child_start argv tokens for maintenance/run/--auto exactly and report actual fixture commit start counts alongside zero maintenance. The shim only sets tracing variables and execs the real git; no Git argv/config or candidate file is changed by tracing.
|
||||
|
||||
P3: both positive and flagged controls call the real gitEnv() from checkout.mjs, with PATH resolving that same shim. A shell-only unflagged commit is not sufficient. Positive requires observed fixture commit and maintenance child_start; flagged requires observed fixture commit and zero such child. No missing/empty trace counts as a pass.
|
||||
|
||||
P4: after each traced whole file, wait for no git process whose /proc cwd is under that run TMPDIR, including deleted cwd, before counting. Detached maintenance could write events after Node exits. Save the cwd sweep and count trace only after the writer set is empty. A nonempty/unstable writer set is a stop, never silently ignored.
|
||||
|
||||
P5: green fingerprints alone are not the proof. The trace commit count and maintenance-child count are required for every changed suite.
|
||||
|
||||
Additional screened files from Filbert, unchanged: bus/publish.test.mjs lists JSON files only; discord/git.test.mjs checks .vault-locks only; stack-release-external checks named files and tree(e.state); queue/write.test.mjs lists gitDir entries filtered to .tmp. Shared helpers plugins/tests/helpers, runs/tests/helpers, conversation/tests/harness and case-sudo-fixture make no Git calls.
|
||||
|
||||
Follow-up note, outside574: product Git commits in queue/src/store.mjs, discord/src/git.mjs and harness/src/tools.mjs do not carry both maintenance flags. No whole-root .git fingerprint currently covers those roots. A future fingerprint test there must account for detached maintenance. This note authorizes no product change or assertion change here. working.mjs fetches at183/237/245 and harness git-tools fetch249 use --no-auto-maintenance; existing production checkout SAFE has both flags.
|
||||
@@ -0,0 +1,4 @@
|
||||
721af7a3745dae10d0dad9226e7ba5ce32754b5da7367aa13f970d7f03677a3c packages/cli/tests/git-socket.test.mjs
|
||||
a0cae5797886b729600dcbcad8e38ec94f97143c77fd91648050417c2d5630f7 packages/cli/tests/lifecycle.test.mjs
|
||||
cb3941fed464fd4c88aac7c05bb32db1b1776858650979523f64dc6fc3baf7d3 packages/seat/tests/checkout.test.mjs
|
||||
22cbdd43fc11c6f234d0f2c1512d5039839d1242fd421e62f2aa3d3db1c9de05 packages/seat/tests/working.test.mjs
|
||||
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"file": "packages/cli/tests/git-socket.test.mjs",
|
||||
"base_sha256": "ec4bbbc98adab6561205e5ec8e2f2495f1f55224da53e567c7a72e36b5410709",
|
||||
"candidate_sha256": "721af7a3745dae10d0dad9226e7ba5ce32754b5da7367aa13f970d7f03677a3c",
|
||||
"only_inserted_argv_flags": true,
|
||||
"assertions_and_fingerprint_unchanged": true
|
||||
},
|
||||
{
|
||||
"file": "packages/cli/tests/lifecycle.test.mjs",
|
||||
"base_sha256": "5ce79205c4ca6e5f6e77671968ce9d8b8ebfdbdde07b31676f2d05b284acb938",
|
||||
"candidate_sha256": "a0cae5797886b729600dcbcad8e38ec94f97143c77fd91648050417c2d5630f7",
|
||||
"only_inserted_argv_flags": true,
|
||||
"assertions_and_fingerprint_unchanged": true
|
||||
},
|
||||
{
|
||||
"file": "packages/seat/tests/checkout.test.mjs",
|
||||
"base_sha256": "663040d7c737aba6403e6397bacf1325b558cc14df3e5677f0a28f9499c45127",
|
||||
"candidate_sha256": "cb3941fed464fd4c88aac7c05bb32db1b1776858650979523f64dc6fc3baf7d3",
|
||||
"only_inserted_argv_flags": true,
|
||||
"assertions_and_fingerprint_unchanged": true
|
||||
},
|
||||
{
|
||||
"file": "packages/seat/tests/working.test.mjs",
|
||||
"base_sha256": "15234267a0e28c53b13a4219ea45e71608722fb137d1f266adfd3f80040eb7c6",
|
||||
"candidate_sha256": "22cbdd43fc11c6f234d0f2c1512d5039839d1242fd421e62f2aa3d3db1c9de05",
|
||||
"only_inserted_argv_flags": true,
|
||||
"assertions_and_fingerprint_unchanged": true
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"vikunja": 574,
|
||||
"queue": "pending Sage placement",
|
||||
"gate": "sage",
|
||||
"base": "062646487451993302c768c6812b827c834e952b",
|
||||
"source": "65e919e5cef30911b9902f410549da431423cec4",
|
||||
"branch": "gander/v574",
|
||||
"manifest": "cdda41f5baa6b04e6e5bb9b82beff8e00ceef440c6702565cc86944f249bb5d3",
|
||||
"tests": "NOT RUN; no trace/Node/test until Sage CLEAR",
|
||||
"scope_authority": [
|
||||
"mos-1369",
|
||||
"sage-3110",
|
||||
"mos-1370"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
# Row 203, FED R2: the federation key in the system config (#1701)
|
||||
|
||||
Owner Quackerjack, reviewer Webby, gate owner Sage. Brief: docs/plans/2026-10-20_federation-r1-r2-r5.md,
|
||||
"Brief: FED R2". Design: DESIGN.md section 3.1 (D1), section 4 (the key shape), section 7 row R2, at
|
||||
gizmoduck/federation-design b93f098f0.
|
||||
|
||||
Base: aca16404c, rebased onto f2735bb6a (origin/refactor, shared object store). Rebased onto 253's tip
|
||||
only if 253 lands first, under a new branch name.
|
||||
|
||||
## Commits
|
||||
|
||||
- 0eb4b6f1f, rebased to e704a18ae: the loader, the first FED R2 tests, and these notes.
|
||||
- Second commit on top of e704a18ae (sage-2998): B1 and B2 below, the fixture fix, and the notes
|
||||
rewrite. Its sha goes in the status line once committed.
|
||||
|
||||
## Shape
|
||||
|
||||
```json
|
||||
"federation": {
|
||||
"self": "hub",
|
||||
"peers": [{ "id": "factory", "origin": "https://mosaic.woltje.com", "label": "Software factory" }]
|
||||
}
|
||||
```
|
||||
|
||||
- Optional. Absent: the config loads and prints what it printed before (no `federation` in the output).
|
||||
- `self` and each `peers[].id` match `^[a-z][a-z0-9-]{0,31}$`. A peer id may not equal `self` or another peer's id.
|
||||
- `peers[].origin` must be an https origin exactly as `new URL(origin).origin` returns it: no path, no query,
|
||||
no userinfo, no trailing slash.
|
||||
- `peers[].label` is a non-empty string of at most 80 characters.
|
||||
- Labels and URLs only. Nothing here grants trust to a peer (section 3.1 D1, section 3.2 W1).
|
||||
|
||||
## Readers of the key
|
||||
|
||||
Two readers read the raw key. Nothing else reads it (`git grep "federation"` at e704a18ae):
|
||||
|
||||
1. The loader, `scripts/mosaic-config.mjs` (`validate`, the `federation` branch). This is the only reader of
|
||||
the validated value. Its output is read by the CLI and business code, which read named fields only.
|
||||
The output is checked in `scripts/test-config.sh`.
|
||||
2. The stack-release probe, `config_checks` in `scripts/stack-release.sh` (B1, below). It reads the config
|
||||
file itself, before a copy is activated or rolled back to, and asks whether the target copy's loader
|
||||
refuses a key the config holds.
|
||||
|
||||
Stale wording, NOT changed in this row (Sage rulings sage-2808 and sage-2809):
|
||||
`packages/cli/src/access.mjs` is out of row 203. Megavolt's row 207 changes that file. Follow-up, to be
|
||||
carried to DEFERRED by Sage:
|
||||
- access.mjs lines 26-27 (`ACCESS_HELP` comment): "Peer homes are empty until the federation key lands
|
||||
(row 203)". Still partly true after this row, since the access context does not read the key.
|
||||
- access.mjs line 36 (inside the `ACCESS_HELP` string, user-visible help): "Peer homes are not read yet
|
||||
(row 203 adds the federation key)". The same claim, in help text. No test in `packages/cli/tests` pins it
|
||||
(`git grep` for "row 203" and "Peer homes" finds nothing in tests).
|
||||
- access.mjs line 44 comment: "peers is empty until row 203".
|
||||
- `packages/cli/README.md` line 1107: "until row 203 adds the". Outside the manifest; flagged in the
|
||||
same follow-up.
|
||||
|
||||
Behavior after this row: `accessContextFor` still returns `peers: []` (access.mjs line 58). Row 203 adds the
|
||||
config key only, so grants naming a peer home still refuse. Wiring the key into the access context is not in
|
||||
the FED R2 brief.
|
||||
|
||||
## B1: the stack-release probe (sage-2998, in scope)
|
||||
|
||||
Webby's early look (webby-0189): a config holding `federation` and no `console` passed `config_checks`, because
|
||||
the probe looked only for `console`. A rollback or activate to a pre-203 copy would then pass its checks, and
|
||||
that copy's loader refuses `unsupported configuration key: "federation"` on the next read.
|
||||
|
||||
Fix (scripts/stack-release.sh, `config_checks`):
|
||||
- The probe's node check now reads `console` or `federation` (`Object.hasOwn(d,"console")||Object.hasOwn(d,"federation")`).
|
||||
- The refusal keeps the check name `config-console-key`, so existing callers and tests do not change. The
|
||||
message now reads "holds a console or federation key". This is a wording change to an existing refusal,
|
||||
and `KEY_MSG` in `packages/cli/tests/stack-release.test.mjs` moves with it.
|
||||
- The new test is `row 203: a config whose federation key the target loader refuses ...` in
|
||||
`packages/cli/tests/stack-release.test.mjs`, after the row 200 console test. Target B's loader has no
|
||||
federation key in its list, so it refuses the key. Activate to B and rollback to B are both refused, and
|
||||
a rollback succeeds once the key is removed.
|
||||
|
||||
Fixture (packages/cli/tests/stack-release-fixture.mjs, line 300): `LOADER_OLD` builds B's loader by a string
|
||||
replace on the `rejectUnknownKeys` list. Adding `federation` to that list in this row left the replace with no
|
||||
match, and the fixture's `assert.notEqual(LOADER_OLD, LOADER)` would fail at load. The replace now targets
|
||||
`"execution", "console", "federation"]`, so B's loader refuses both keys. This regression came from my
|
||||
e704a18ae, and the fix is in this commit.
|
||||
|
||||
## B2: a case and a mutant for each refusal (sage-2998: "one mutant per refusal")
|
||||
|
||||
Webby's list is covered below. Each refusal has a case in `scripts/test-config.sh` (FED R2 block) and a
|
||||
mutant. M5 is killed by the self case; M13 covers the duplicate half of the id check that M5 did not reach.
|
||||
|
||||
## Refusals and their tests
|
||||
|
||||
`scripts/test-config.sh`, FED R2 block:
|
||||
|
||||
| Case | Refusal (exit 2) | Mutant (killed by) |
|
||||
|---|---|---|
|
||||
| valid key loads and is in the output | none | base |
|
||||
| unknown top-level sub-key | `unsupported federation key: "extra"` | M1 |
|
||||
| unknown peer sub-key | `unsupported federation.peers[0] key: "trust"` | M2 |
|
||||
| origin over http | `federation.peers[0].origin must be an https origin ...` | M3 |
|
||||
| origin with a path | same | M4 |
|
||||
| origin with userinfo | same | M4 |
|
||||
| origin with a trailing slash | same | M4 |
|
||||
| peer id equal to self | `federation.peers[0].id ... is already named` | M5 |
|
||||
| duplicate peer id | `federation.peers[1].id "factory" is already named` | M13 |
|
||||
| peer label empty | `federation.peers[0].label must be a non-empty string ...` | M6 |
|
||||
| peer label over 80 characters | `federation.peers[0].label must be ...` | M12 |
|
||||
| federation not an object | `"federation" must be a JSON object` | M7 |
|
||||
| self does not match the pattern | `federation.self must match ...` | M8 |
|
||||
| peers not an array | `federation.peers must be an array` | M9 |
|
||||
| peer not an object | `federation.peers[0] must be a JSON object` | M10 |
|
||||
| peer id does not match the pattern | `federation.peers[0].id must match ...` | M11 |
|
||||
| self missing | `federation.self must match ...` | M15 |
|
||||
| peer id missing | `federation.peers[0].id must match ...` | M16 |
|
||||
| peer label missing | `federation.peers[0].label must be ...` | M17 |
|
||||
| no federation key | loads, no federation output | base |
|
||||
| B1: target loader refuses federation | `config-console-key` refusal (stack-release.test) | M14 |
|
||||
|
||||
Mutant plan (run on Sage's gate only, one edit each, a named test must fail). Before the gate each edit string
|
||||
must occur exactly once in its file. I checked all of M1 to M17 with grep -cF: each is 1. The
|
||||
gate's mutant runner checks the count again before it applies each edit:
|
||||
|
||||
- M1: `rejectUnknownKeys(value, ["self", "peers"], "federation");` removed. Killed by "federation unknown sub-key".
|
||||
- M2: `rejectUnknownKeys(peer, ["id", "origin", "label"], where);` removed. Killed by "federation peer unknown sub-key".
|
||||
- M3: `url.protocol !== "https:" ||` removed. Killed by "origin over http".
|
||||
- M4: `|| url.origin !== value` removed. Killed by "origin with a path", "origin with userinfo" and
|
||||
"origin with a trailing slash".
|
||||
- M5: `if (ids.has(peer.id)) {` changed to `if (false) {`. Killed by "peer id equal to self".
|
||||
- M6: `peer.label.trim() === "" ||` removed. Killed by "peer label empty".
|
||||
- M7: `if (!isPlainObject(value)) {` changed to `if (false) {`. Killed by "federation not an object".
|
||||
- M8: `|| !FEDERATION_ID_PATTERN.test(value.self)` removed. Killed by "self does not match the pattern".
|
||||
- M9: `if (!Array.isArray(value.peers)) {` changed to `if (false) {`. Killed by "peers not an array".
|
||||
- M10: `if (!isPlainObject(peer)) {` changed to `if (false) {`. Killed by "peer not an object".
|
||||
- M11: `|| !FEDERATION_ID_PATTERN.test(peer.id)` removed. Killed by "peer id does not match the pattern".
|
||||
- M12: ` || peer.label.length > FEDERATION_LABEL_MAX` removed. Killed by "peer label over 80 characters".
|
||||
- M13: ` ids.add(peer.id);` removed. Killed by "duplicate peer id".
|
||||
- M14 (stack-release.sh): `||Object.hasOwn(d,"federation")` removed. Killed by the row 203 federation test in
|
||||
`stack-release.test.mjs`.
|
||||
- M15: `typeof value.self !== "string" || ` removed. Killed by "federation self missing". Without the typeof
|
||||
half, an absent self passes the pattern (`/^[a-z]…$/.test(undefined)` is true).
|
||||
- M16: `typeof peer.id !== "string" || ` removed. Killed by "federation peer id missing". Same reason.
|
||||
- M17: `typeof peer.label !== "string" || ` removed. Killed by "federation peer label missing". Without it, a
|
||||
missing label throws a TypeError at `.trim()` (exit 1), not the exit 2 refusal.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- No config is written. `~/.config/mosaic-dev/config.json` is not touched. Tests use `$SANDBOX` only.
|
||||
- No change to `packages/cli/src`, `scripts/bootstrap.sh`, or any reader beyond the stack-release probe.
|
||||
|
||||
## Manifest (sha256 of committed blobs; the list is the review scope)
|
||||
|
||||
- scripts/mosaic-config.mjs
|
||||
- scripts/test-config.sh
|
||||
- scripts/stack-release.sh (B1)
|
||||
- packages/cli/tests/stack-release.test.mjs (B1 test, KEY_MSG)
|
||||
- packages/cli/tests/stack-release-fixture.mjs (B's loader fixture; without this, the suite fails to load)
|
||||
- agents/quackerjack/work/queue-203/BUILD.md
|
||||
|
||||
## Slot estimate (for Sage's gap)
|
||||
|
||||
- test-config.sh: about 5 min, padded.
|
||||
- The cli suite: about 5 min, padded.
|
||||
- stack-release.test.mjs: up to 1800 s, per Sage. It copies the loader, so it runs whole at the gate (Webby N3).
|
||||
- Mutants M1–M13 and M15–M17 each re-run the FED block of test-config.sh. M14 re-runs stack-release.test.mjs.
|
||||
The per-mutant cost is not measured: this run has not been timed.
|
||||
|
||||
## Status
|
||||
|
||||
- e704a18ae: the loader, first tests and notes, rebased onto f2735bb6a.
|
||||
- 8d34a9e94: B1, B2, the fixture fix and the notes rewrite (Mos approved the two old-test edits, mos-1346).
|
||||
- Third commit on top: B3 cases M15–M17 and these notes (sha in the message to Sage and Webby).
|
||||
- NOT YET RUN: no node during Gander's slot. The test-config.sh, cli and stack-release gates run when Sage
|
||||
announces a CLEAR. `bash -n` passes on both shell scripts.
|
||||
- Not pushed, not claimed. Per invariant 8 the gate runs before any commit is called green.
|
||||
|
||||
## Review notes (Webby, webby-0189, answered here)
|
||||
|
||||
- B1: fixed as above (scope per sage-2998). The probe's check name `config-console-key` is kept on purpose (Webby
|
||||
N5, Sage): renaming it would change callers and tests for no gain in this row. The message says "console or
|
||||
federation key"; the name is the only old word left.
|
||||
- B3 (webby-0191): missing self, missing peer id, and missing peer label each have a case and a mutant (M15–M17).
|
||||
The typeof halves are the only thing refusing an absent value, since the pattern test coerces undefined.
|
||||
- B2: a case and a mutant for each uncovered refusal, plus the trailing-slash case under M4 (N4) and the
|
||||
duplicate-peer case (M13).
|
||||
- N1: this file is rewritten with the current base and status.
|
||||
- N2: consumers checked by `git grep "mosaic-config"`. The loader's output is read by `packages/cli/src/config.mjs`,
|
||||
`packages/business/src/*.mjs`, `packages/webui/src/sso-config.mjs`, `packages/seat/src/seat.mjs`,
|
||||
`packages/discord/src/binding.mjs`, `packages/conversation/src/guard.mjs`, and the shell scripts that call
|
||||
`validate` (bootstrap.sh, common.sh, instance.sh, land-work.sh, stack-copy.sh, stack-release.sh,
|
||||
conductor-apply.sh). Checked by `git grep "federation"` over packages and scripts: no reader outside the
|
||||
loader and the stack-release probe mentions `federation`. I did NOT read each call site for which fields it
|
||||
uses; that is still open for Webby's static read. Not run.
|
||||
- N3: the cli stack-release tests copy the loader; they run whole at the gate. Listed in the manifest and
|
||||
the slot estimate.
|
||||
- N4: trailing-slash case added (see table; M4).
|
||||
|
||||
## Gate on the rebased tip (Sage sage-3055, sage-3057; claim rev 3361)
|
||||
|
||||
Brief pin: docs/plans/2026-10-20_federation-r1-r2-r5.md re-pinned to 8adfb261f (sage-203-repin). "Brief: FED R2"
|
||||
is byte-identical between b624ddd49 and 8adfb261f (Sage's diff).
|
||||
|
||||
Rebase: onto origin/refactor c7b754d7b (row 207 landed at c4f77373f on that tip). The three commits are now
|
||||
93a491d73 (loader), 284d55cf2 (stack-release probe and fixture), 7c543fd4a (B3 cases). Blob check against the
|
||||
pre-rebase candidate d5428c1a8 (`git rev-parse <commit>:<path>`), all six identical:
|
||||
|
||||
| blob | path |
|
||||
|---|---|
|
||||
| 4c26453a0 | scripts/mosaic-config.mjs |
|
||||
| 06b9d8ea2 | scripts/test-config.sh |
|
||||
| 92bd6f7d0 | scripts/stack-release.sh |
|
||||
| beb8da252 | packages/cli/tests/stack-release.test.mjs |
|
||||
| f080bf2f5 | packages/cli/tests/stack-release-fixture.mjs |
|
||||
| 6f29dbbde | agents/quackerjack/work/queue-203/BUILD.md (pre-gate-commit blob) |
|
||||
|
||||
Timeouts (Mos mos-1275: `timeout -k 10 <S>` per script, at least 3x its longest run; written here before the
|
||||
first run). Basis: row 202's gate on this machine (scratch g202gatelBKGE0): cli suite 620.3 s (duration_ms
|
||||
620297.68, EXIT=0); the ten scripts together finished in about 2.5 min, so no single script was measured on its own.
|
||||
Figures are set above 3x of those, and the stack-release.test run is set from Sage's up-to-1800 s estimate.
|
||||
They are estimates, not measured. A timeout hit is a red and goes to Mos first. Mos rules on these figures.
|
||||
|
||||
| target | timeout S (seconds) | basis |
|
||||
|---|---|---|
|
||||
| scripts/test-auth.sh | 900 | 3x of a row 202 script, padded |
|
||||
| scripts/test-conductor.sh | 900 | same |
|
||||
| scripts/test-config.sh | 900 | same; also each mutant run of the FED block |
|
||||
| scripts/test-discord.sh | 900 | same |
|
||||
| scripts/test-extension-package.sh | 900 | same |
|
||||
| scripts/test-foundation.sh | 900 | same |
|
||||
| scripts/test-land-work.sh | 900 | same |
|
||||
| scripts/test-queue.sh | 900 | same |
|
||||
| scripts/test-release.sh | 900 | same |
|
||||
| scripts/test-task.sh | 900 | same |
|
||||
| packages/cli suite | 2400 | 3x of 620 s is 1861 s |
|
||||
| stack-release.test.mjs (whole) | 5400 | 3x of Sage's 1800 s estimate |
|
||||
|
||||
Run command shape: `timeout -k 10 <S> <cmd>`. Queue verbs are never wrapped in a timeout.
|
||||
|
||||
Mutant kill rule: each of M1-M17 must fail on an assertion, not on a crash. The mutant run records the failing
|
||||
test name. Skips are named in the gate record.
|
||||
|
||||
## Gate results on 7309260ae (23:00-23:16Z)
|
||||
|
||||
- stack-release.test.mjs, whole: 219 pass, 0 fail, 0 skipped, 583.5 s (timeout 5400 s).
|
||||
- test-config.sh: selftest 51 pass, 0 fail, EXIT 0, 2.4 s (timeout 900 s).
|
||||
- Mutants, one run each, file restored from git after each. Each run of M1-M13 and M15-M17 is 2.4 s
|
||||
(the first run is the timing: 2.4 s, so the 900 s timeout is far above it).
|
||||
|
||||
| mutant | result | killed by (FAIL line in test-config.sh, or stack-release.test) |
|
||||
|---|---|---|
|
||||
| M1 | killed | federation unknown sub-key |
|
||||
| M2 | killed | federation peer unknown sub-key |
|
||||
| M3 | killed | federation peer origin over http |
|
||||
| M4 | killed | federation peer origin with a path (also userinfo, trailing slash) |
|
||||
| M5 | killed | federation peer id equal to self (also duplicate peer id) |
|
||||
| M6 | killed | federation peer label empty |
|
||||
| M7 | killed | federation not an object |
|
||||
| M8 | killed | federation self pattern |
|
||||
| M9 | killed | federation peers not an array |
|
||||
| M10 | killed | federation peer not an object |
|
||||
| M11 | killed | federation peer id pattern |
|
||||
| M12 | killed | federation peer label over 80 characters |
|
||||
| M13 | killed | duplicate peer id |
|
||||
| M14 | killed | row 203 subtest of T57 in stack-release.test.mjs (parent T57 also fails) |
|
||||
| M15 | killed | federation self missing |
|
||||
| M16 | killed | federation peer id missing |
|
||||
| M17 | killed | federation peer label missing |
|
||||
|
||||
Correction (new entry; the kill-text column in the mutant plan above is kept as written): the plan named
|
||||
the kill labels for M8 and M11 as "self does not match the pattern" and "peer id does not match the pattern".
|
||||
Those labels are not in the test file. The assertions that fail are "federation self pattern" and
|
||||
"federation peer id pattern". Both mutants are killed; only the label text in the plan was wrong.
|
||||
|
||||
M14 note: `node --test --test-name-pattern="row 203"` runs zero inner tests, because the row 203 case is a
|
||||
t.test subtest of T57 and the filter skips the parent. The first automated M14 run reported SURVIVED for
|
||||
that reason (rc 0 in 0.9 s, no FAIL line). It was re-run with `--test-name-pattern="T57|row 203"`: the row
|
||||
203 subtest fails (rc 1), so M14 is killed. The automated result is the wrong one and is not the gate record.
|
||||
|
||||
## cli suite (23:20-23:37Z)
|
||||
|
||||
- First run (timeout 2400 s, 627.6 s): 895 tests, 816 pass, 77 fail, 2 cancelled, EXIT 1. Cause: this worktree
|
||||
had no node_modules, so scripts/case2-persona-import.mjs (and the other suites that import it) could not
|
||||
resolve package 'yaml' (declared at 2.9.0 in package.json). Not a federation failure. Not in the gate record.
|
||||
- Fix in the environment only: node_modules symlinked to /mnt/storage/src/mosaic-stack/node_modules (the row 202
|
||||
recipe). The symlink is excluded through .git/info/exclude and is not committed.
|
||||
- Rerun (timeout 2400 s, 621.9 s): 895 tests, 895 pass, 0 fail, 0 cancelled, 0 skipped, EXIT 0.
|
||||
|
||||
## Gate summary (23:00-23:40Z, rebased tip c7b754d7b, code tree 7309260ae)
|
||||
|
||||
| gate item | result | timeout S | measured |
|
||||
|---|---|---|---|
|
||||
| stack-release.test.mjs, whole | 219 pass, 0 fail, 0 skipped | 5400 | 583.5 s |
|
||||
| test-config.sh | 51 pass, 0 fail | 900 | 2.4 s |
|
||||
| mutants M1-M17 (named assertion) | 17 of 17 killed | 900 (per run) | 2.4 s per config run; M14 by row 203 subtest |
|
||||
| cli suite (npm test) | 895 pass, 0 fail, 0 skipped | 2400 | 621.9 s |
|
||||
| test-auth.sh | 15 pass, 0 fail | 900 | 1 s |
|
||||
| test-conductor.sh | 17 pass, 0 fail | 900 | 6 s |
|
||||
| test-config.sh | 51 pass, 0 fail | 900 | 2 s |
|
||||
| test-discord.sh | 81 pass, 0 fail | 900 | 8 s |
|
||||
| test-extension-package.sh | 18 pass, 0 fail | 900 | 1 s |
|
||||
| test-foundation.sh | 44 pass, 0 fail | 900 | 41 s |
|
||||
| test-land-work.sh | 86 pass, 0 fail | 900 | 12 s |
|
||||
| test-queue.sh | 28 pass, 0 fail, 1 skip | 900 | 21 s |
|
||||
| test-release.sh | 14 pass, 0 fail | 900 | 2 s |
|
||||
| test-task.sh | 91 pass, 0 fail, 2 skip | 900 | 14 s |
|
||||
|
||||
Named skips:
|
||||
- test-queue.sh: `queue verify and render --check` skipped. This checkout is not the queue's canonical root
|
||||
(/mnt/storage/src/mosaic-stack). The check runs on the canonical checkout only.
|
||||
- test-task.sh: two live-provider cases skipped by design (row 244: no provider call in the gate).
|
||||
|
||||
Timeouts: the ten per-script figures above were written before the first run and were not changed. Every script
|
||||
finished far inside its timeout. No timeout was hit, and none was placed around a queue verb.
|
||||
|
||||
Environment: the first cli run failed for a missing node_modules in the worktree; see the cli section above.
|
||||
The rerun is the gate record.
|
||||
+6
-4
@@ -1549,10 +1549,12 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-22 | Della | Row258 E01 3094e3bfa focused manifest review | APPROVED 11-path manifest/static F1/F2/N1 corrections; file and full-blob digest recipes verified. M1-M11 fixed; all execution/landing evidence still required in CLEAR. No shared-log edit, Node, tests, HTTPS Git or live action.
|
||||
2026-10-22T17:55:40Z | Sage (Opus 5.5, 1ef1e4f8) | Row 253 landing; post-253 doc sweep | 253 LANDED 48e450ce8. Sweep on sage/sweep-253: Gosalyn Case 2 design and rows, 229 design, Gyro 0acbdb0d8, Della 179391055, 268 docs, seat dirs for Della, Scrooge, Huey, Louie and Ludwig, human-users-build:70 under Q173, DEFERRED 253 N1, 198 N2, row 203 text, 224 residual and 206 sharee list, and seat log lines.
|
||||
2026-10-22 14:48 UTC | Researcher (Sonnet 5.5, 9bac0794) | Rows 220 and 216 resumed (sage-2708, sage-2716, sage-2719); local commits only | 220 e4a204836 on researcher/row220-r2 (on ea6dc9eff): C1 run tests, C2 default-reader M7 test, configDirectory fixture scope, BUILD.md round 2b. 216 4f3fbc25a on researcher/row216-r2 (on aca16404c): index cache, find/get/search/manuscript/universeInfo, PRD 1.14 levels. Nothing pushed, claimed or run; node --check not run. Waits for Sage's CLEAR and go; rebase onto 253 on a new branch before any gate.
|
||||
2026-10-22 | Megavolt | row 207 (193c, #1705) mosaic access user add/remove | gate green on 712f79c0a (31/31, M1-M8 killed, cli 894/894, ten scripts); pushed megavolt/row207-r3 9bb406c6c; to Duckworth.
|
||||
2026-10-22T22:51:32Z | Sage (Opus 5.5, 1ef1e4f8) | Post-207 gap: brief push, row 292 add, stuck-request repair, 207 landing | #1797 brief f0e3dcdf4 pushed; row 292 (#1797, Gander) briefed after 270/271/203. Dead queue locks removed twice; requests for 207/270 abandoned and reposted as jarvis (c30841, c30845), 291 requested (c30846); verdicts posted for Duckworth (c30847, c30849) and Webby (c30848). 207 LANDED c4f77373f, #1705 closed c30855, P19 269 c808. DEFERRED: 207 N1, 270 N6.
|
||||
2026-10-22T23:03:39Z | Gyro (Codex gpt-6.1-sol, mos-1356) | Q195/Q231/Q233 O2 design rewrite, gyro/editor-o2 from c4f77373f | Canonical queue next gyro: nothing. Ten docs-only briefs; full-feature WordCraft, independent 15-crate/199-package security review, editor-only CSP candidate, instant git DOCX saves and retained Markdown/wiki sources, committed-only DOCX citation gate, no agent story prose. Static structure/source-anchor/scope checks only; no code, build/run, queue or live change. Fenton review requested after push.
|
||||
2026-10-22T23:09:38Z | Gyro (Codex gpt-6.1-sol, fenton-0006) | Q195 O2 design corrections on gyro/editor-o2 | F1 requirement boundary sent to Mos, adoption gated; F2-F6 source-checked docs fixes prepared. Ten briefs and two open owner items checked statically. No implementation, build/browser/worker run, queue or live action; Fenton re-check after push.
|
||||
2026-10-22T23:13:27Z | Gyro (Codex gpt-6.1-sol, fenton-0007) | Q195 O2 r3 F7/F8 | Save-group keys/defaults/validation and W3b refusal/W4b lift ownership explicit. Static docs checks only. Q36 remains open; no implementation or gate lift. Direct Fenton/Mos handoff after push.
|
||||
2026-10-23T00:52:58Z | Gyro (Codex gpt-6.1-sol, mos-1366/1367) | Vikunja571 Q235-Q237 O2 parent/W4 format-policy rewrite on gyro/editor-o2 | Canonical queue next gyro: nothing. Prepared P1-P6 and B1-B4 docs, baseline W4 import 1ac76a417; no product/build/browser/worker/private-file/queue/live action. Mos impact map fold precedes review/push handoff.
|
||||
2026-10-23T01:37:07Z | Gyro (Codex gpt-6.1-sol, mos-1368/1372/1375) | Vikunja571 O2 parent/W4 revision complete for review | P1-P6/B1-B4 and A17-A41/Q1-Q16 folded; same-thread Case 2 lookups/support writes, human-set host-pushed off-host remote, Hermes/restore/counting/order/role gates. Static docs checks pass; old locator/projection mechanics retained, authority/access review reopened. No code, build/browser/worker run, private conversion, queue or live action. Direct Fenton/Mos/Sage handoff after push.
|
||||
2026-10-23T01:41:59Z | Gyro (Codex gpt-6.1-sol, sage-3121) | 571 PRD d6 A1-A3 alignment | Both designs corrected and statically checked; local commit for Fenton in shared repo object store. HTTPS push held for Sage gap, independent review pending. No product, test-slot, queue or live change.
|
||||
2026-10-23T01:49:08Z | Gyro (Codex gpt-6.1-sol, fenton-0012/mos-1379) | 571 B1/B2 and N1-N6 docs corrections | Real-helper denial gate and two off-by-default human review settings; static acceptance/brief/immutable-mechanics checks pass. Local candidate for Fenton via shared Git objects; no HTTPS push before Sage gap, no runtime or credential experiment.
|
||||
2026-10-22 23:23:42Z | rocko | Q195 O2 W4 #1799 committed DOCX citation/access design | pushed rocko/q195-w4-design-20261022 at 15e91538f; blob 662abdb7; direct handoff to Megavolt; static docs checks only, review pending; Q36 open/adoption closed.
|
||||
2026-10-22 | Megavolt (Claude Opus 5.5) | Q195 O2 W4 DOCX citation/access design review (#1799, sage-3061/3067) | four rounds: CHANGES 15e91538f, 019a0ea5b, 54246cb32; APPROVE 994c3f7fb (verdict 67f82a12d, unpushed pending CLEAR); M1 closed with no ruling; R4-N1 to W4b
|
||||
2026-10-22 23:48Z | rocko | Q195 O2 W4 #1799 design approved | W4 #1799 design approved at 994c3f7fb (Megavolt 67f82a12d), 32 acceptance rows assigned to W4b; static docs/public-fixture inspection only, no implementation PASS. Q36 remains OPEN and adoption CLOSED. Sage's accepted implementation brief remains required. Row219 remains separately held pending your next direction.
|
||||
2026-10-22T23:55:11Z | Sage (Opus 5.5, 1ef1e4f8) | Row 203 landing, O2 design landing, W4 docs landing | 203 LANDED a4da4e6df (Webby c30877, rev 3366), #1701 closed c30882, P19 269 c835. O2 design (mos-1360/1361) pushed 25c09f19f, blob 322ec50f matches fenton-0008. W4 #1799 APPROVE round 4 posted c30884; design 2af460ab7 and review bedde1716 land here. Queue revs 3360-3368 in 4ca270307. 270+271 r4 rebased by Gizmoduck onto 25c09f19f for new Duckworth rounds.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,107 +1,78 @@
|
||||
# Q195 O2 W4: committed DOCX citations and Case 2 access
|
||||
# Q195 O2 W4: committed DOCX citations and access
|
||||
|
||||
Policy rewrite for Vikunja 571, mos-1366/1367/1368/1372/1375, Q234–Q237.
|
||||
Normative parent: `docs/plans/2026-10-21_q195-writing-editor.md` in the same revision.
|
||||
Original W4: Rocko/Megavolt, #1799, refactor 062646487, blob
|
||||
`2af460ab772f8d204598dc8cb79b3c161f311623`. Gyro writes this assigned revision; Fenton
|
||||
independently reviews it. The reviewer-owned verdict file is untouched.
|
||||
Impact map: brain-redux d183246, `projects/mosaic-mvp/case2-docx-impact-map.md`, stack pin
|
||||
062646487. Sections 1/2/6/7 and A19/A20/A28 are reopened, plus part/view authorization
|
||||
wording. Exact locator, package-validation and projection mechanics in sections 3–5 remain
|
||||
from the reviewed baseline except their replaced per-document-scope wording.
|
||||
Design for Gitea #1799, assignment sage-3060/mos-1360. Owner Rocko;
|
||||
independent reviewer Megavolt. Normative parent: `e36bce352`,
|
||||
`docs/plans/2026-10-21_q195-writing-editor.md`, blob
|
||||
`322ec50f2669cffc0f8256d6beffdaccc4d8dba8`, D12, D24-D26, W4 and W4b.
|
||||
This is a proposed implementation contract, not access enablement or a parser
|
||||
security PASS. Reviewer owns the separate verdict file; the author does not
|
||||
write an approval there.
|
||||
|
||||
## 1. Confirmed format policy and approval gates
|
||||
## 1. Gates and compatibility
|
||||
|
||||
Q235–Q237 P1–P6 and mos-1372 D1/D2 settle the design basis: canon and playground story
|
||||
files become DOCX, retaining universe/book and source scope. Type-folder notes, agent files,
|
||||
instructions, PRDs, character sheets, summaries and supporting documentation stay Markdown.
|
||||
An unmarked-folder story is eligible; the human selects one story file at a time. New
|
||||
stories start directly as DOCX in the human-selected universe/book or playground, without a
|
||||
Markdown precursor, under the same production-opening gate as import.
|
||||
W4b implements this contract only after independent W4 APPROVE and Sage's
|
||||
accepted implementation brief, S0 and its other parent gates. A release must
|
||||
ship extractor, resolver, authorization and sharing together; no partial
|
||||
agent read rollout. Missing server readiness refuses every O2 read, resolve,
|
||||
share and flag. A human setting, pin or request field cannot assert readiness.
|
||||
W5 owns flags, not chapter writes. No agent execute, edit, replace, save,
|
||||
tracked-change or comment-writing operation is introduced.
|
||||
|
||||
The October 27 release stays Markdown. Production import/new-story opening waits for W4b,
|
||||
W7, the post-10-27 release, the approved Case 2 PRD/brief amendment, Hermes retirement
|
||||
(#423 step 5) and configured off-host durability. The first real use on Melanie's books
|
||||
also waits for Jason's scoped go and mos-1374's protected manual VM1012 backup before the
|
||||
first real import, handled by its authorized human operator, not this design seat. All starts are human-only; `library.convert` has no role
|
||||
grant or agent request path and stays a hard wait unless an approved PRD changes it.
|
||||
Nothing in this document authorizes conversion, new private story creation or deployment.
|
||||
Q234's exact frame CSP, mos-1363 and S0 M1–M14 remain unchanged implementation gates.
|
||||
Q36 / Vikunja 562 is OPEN. Production Markdown adoption remains closed under
|
||||
mos-1358, independently of whether this design is approved. If Jason approves
|
||||
superseded notices, a separate C2 reader/reply row must implement and pass
|
||||
them before adoption; otherwise Mos records Jason's disposition before any
|
||||
adoption brief proceeds. This document neither implements nor decides notices.
|
||||
|
||||
mos-1372 D9 replaces the prior W4 O2-only-conversation, per-document-grant and per-send-share
|
||||
requirements for Case 2 lookups. Agents read converted stories through the normal Case 2
|
||||
lookups in any thread or room under the same live project/universe/source-scope checks as
|
||||
Markdown canon/playground. Final accepted text follows that access. Under mos-1379,
|
||||
editor.agentReadComments and editor.agentReadTrackedDeletions are human-only booleans,
|
||||
both false by default. The former gates comment stories; the latter gates original/markup
|
||||
views. This is two settings, not a role-level allowlist or per-document grant. They can then use existing supporting-Markdown write tools under
|
||||
Q223. The agent never writes a story/DOCX/archive file. D9 is marked as a choice for Jason
|
||||
in PRD 1.17 approval; it is design text, not an already approved role/policy expansion.
|
||||
No code may rely on that changed requirement before the approved PRD and independent review.
|
||||
Existing C2 Markdown, its line-cite schema, library grants and supporting-doc
|
||||
writer remain unchanged. An authorized old Markdown original is historical
|
||||
source, never the current DOCX or a permission to its later edits. No DOCX
|
||||
paragraph is called a line. Following an adopted source from a C2 read,
|
||||
indexing it as Markdown, changing C2 replies or a synchronized Markdown
|
||||
projection requires a separately approved Case 2 amendment through Mos/Jason.
|
||||
W4b must test old C2 fixtures unchanged alongside the new discriminated DOCX
|
||||
contract. It may migrate only new O2 records, with a named schema edge Sage
|
||||
allocates; it must not reinterpret existing line citations in place.
|
||||
|
||||
Human import commits the verified DOCX to a private staging ref first and obtains its
|
||||
off-host push acknowledgment. It then creates/pushes the source archive move+mark through
|
||||
new human-only bus plumbing on the C2-02/C2-03b writer, not two single-path saves. Finally
|
||||
one host publication protocol advances writing-published and the catalogue under the
|
||||
publication/read lock. No staged ID is reader-visible before catalogue CAS. Before that
|
||||
switch, agents read the source at its pinned commit; sync cannot advance it early. After
|
||||
it, only the current DOCX is a chapter. Pre-push failure leaves source Markdown unmoved;
|
||||
after archive push, failure is a visible resumable pending operation, never an assertion
|
||||
that nothing moved, a rollback push or Markdown revival. Old bytes/history remain.
|
||||
## 2. Trust and document identity
|
||||
|
||||
The archive is in the source-repository folder set by a human in the library mapping.
|
||||
There is no default; missing/invalid folder keeps conversion closed. It is recorded with
|
||||
converted date, source commit/blob/path and replacement ID in the operation record. It is
|
||||
read-only in the Console. Explicit `archived` scope returns only requested authorized
|
||||
records, always marked and non-canon; default/current queries never opt into it. An old
|
||||
source lookup returns a typed conversion record and routes an authorized DOCX read, never
|
||||
stale Markdown or fake DOCX lines. Historical line cites still verify at commit/blob and
|
||||
show converted/historical state; archive access alone is not current story authorization.
|
||||
Pre-conversion text already in thread history is not rewritten/refreshed; its cites verify
|
||||
against their original commit/blob and show historical/converted state, not the current DOCX.
|
||||
A Markdown file recreated at a converted old path is unmapped, neither canon nor playground,
|
||||
excluded from lookups and flagged to the human. A new Markdown story at a different path in
|
||||
a converted book stays canon until import, flagged and offered for human import; no automatic
|
||||
conversion. Existing playground scope rules remain. Clickable DOCX editor links stay; no
|
||||
DOCX graph exists yet. A Connections graph and its tests wait for Jason's J-b answer.
|
||||
The host reader alone opens `<dataRoot>/writing/<business>/<project>/`.
|
||||
Business, project and document are opaque IDs resolved through the host's
|
||||
owned manifest, never request paths. Each document has a stable ID and one
|
||||
canonical DOCX path; a rename does not change its identity. A copy/new import
|
||||
gets a new document ID and no inherited grant, share, pin or cache authority.
|
||||
Origin metadata is evidence only. Copying a source does not launder access.
|
||||
|
||||
Every conversion runs the pinned extractor before archiving and refuses unsupported output.
|
||||
An independently reviewed importer preserves every wiki token and front-matter key.
|
||||
The v1 extractor does not accept OOXML hyperlinks: emit Markdown label/URL as literal text
|
||||
with a fidelity-report entry for lost clickable behavior, never w:hyperlink/r:id. Bare URLs
|
||||
stay literal; wiki links stay exact and host-clickable. Unsafe/unrepresentable URLs refuse.
|
||||
This handled literal mode is explicitly confirmed before archive push; metadata
|
||||
moves to the canonical manifest (type/name/appears_in/relationships feed the index). Links
|
||||
resolve across Markdown/DOCX through owned aliases, not host paths. A lost link fails the
|
||||
fidelity report. Plain links/front-matter values resolve or raise R4; unresolved chapter
|
||||
embeds must not take the old silent attachment-drop path. Images/.canvas/.base remain attachments.
|
||||
A read snapshots one reachable committed manifest and its DOCX blob from the
|
||||
same commit. It never combines HEAD's manifest with another commit's DOCX.
|
||||
The reader accepts only server-resolved commits reachable from that document's
|
||||
published history; arbitrary git objects and another document's same blob
|
||||
are not selectable. Historical access also requires the document's current
|
||||
live authorization. Removed history, missing blobs or missing extractor
|
||||
revisions refuse; there is no worktree, export or engine-memory fallback.
|
||||
The commit/blob field is a locator, not an authority token.
|
||||
|
||||
## 2. Document identity, order and freshness
|
||||
The project's sole publication ref is `refs/heads/writing-published`; only
|
||||
the human writer advances it, atomically and without force updates. Each
|
||||
commit contains the book manifest. For a document, its publication commits
|
||||
are the first-parent commits on that ref that add or change its DOCX blob or
|
||||
its canonical manifest entry (including deletion/reinstatement). The first
|
||||
commit is a publication; a sibling-only save is not. `currentCommit` is the
|
||||
latest such commit for this document at the captured ref tip. Version latest
|
||||
selects that commit; a locator commit must be one of those publication commits,
|
||||
not an arbitrary intervening project commit. Deleted document entries refuse
|
||||
current reads, including history, until explicitly reinstated and granted.
|
||||
Published history is exactly that document's reachable publication sequence.
|
||||
A sibling save does not change its currentCommit, stale flag or cursor.
|
||||
Staleness is still exact commit inequality within that document, even if a
|
||||
human deliberately saves an earlier byte-identical blob as a new publication.
|
||||
|
||||
Host code alone opens `<dataRoot>/writing/<business>/<project>/`. Opaque document IDs resolve
|
||||
through the canonical manifest, never caller paths. Conversion preserves book/chapter identity
|
||||
and scope, binding the original path/name as an alias to the DOCX ID. New stories get a fresh
|
||||
ID and the human-selected universe/book or playground. No format-based canon promotion.
|
||||
The map tells authorized agents format, source scope, owned location and current version;
|
||||
archived entries are available only in explicit archived requests. No host paths/credentials.
|
||||
|
||||
A lookup snapshots the reachable committed manifest and DOCX blob from one publication.
|
||||
No worktree, draft, archive or engine-memory fallback. `refs/heads/writing-published` is the
|
||||
canonical served ref. A document's publications are the first-parent commits adding or
|
||||
changing its blob/manifest entry; sibling-only saves do not change its currentCommit.
|
||||
History locators name only such reachable publications, with current project/universe checks.
|
||||
Deleted entries refuse until explicitly reinstated by an authorized human. New local saves
|
||||
publish before acknowledgment. A lookup starting after an acknowledged save returns that
|
||||
save or a later one and names it. A lookup already in flight stays on its captured version;
|
||||
never label an observed older snapshot latest. Recheck live access before emitting text.
|
||||
|
||||
D10's ordered chapter-ID manifest drives manuscript paging and next_chapter after conversion,
|
||||
replacing row216's initial path-order rule (RULINGS.md:32–34). Before the first conversion,
|
||||
seed the order from that existing rule; replace each source entry with its DOCX identity in
|
||||
place. A human reorder is a manifest commit. next_chapter never names an archive. Pending
|
||||
unconverted files retain their legacy identity/scope until their individual publication
|
||||
switch; no file has two working formats and new stories start as DOCX. Book-null notes
|
||||
stay out of manuscript; hybrid exclusions and source-universe boundaries still apply.
|
||||
The writer's successful commit publishes atomically before Saved. A latest
|
||||
lookup begun after that receipt resolves that published commit, not an old
|
||||
cache entry. A lookup already in progress remains internally on its captured
|
||||
commit. Return the captured version explicitly; never label it latest after
|
||||
observing a newer publish. Recheck live grants immediately before returning
|
||||
content, not just before asynchronous extraction.
|
||||
|
||||
## 3. Exact wire schema, version 1
|
||||
|
||||
@@ -110,7 +81,7 @@ commits/blobs are lowercase 40-hex Git object IDs in the repository's current
|
||||
SHA-1 object format. A future object format requires a new schema revision.
|
||||
Digest values are lowercase 64-hex SHA-256. Integers are safe nonnegative
|
||||
JSON integers. No floats, coercion, duplicate JSON keys or implicit defaults
|
||||
except the explicitly listed request defaults below. These are proposed DOCX records for the Case 2 lookup/citation union.
|
||||
except the explicitly listed request defaults below. These are proposed O2-only records.
|
||||
|
||||
A locator is exactly:
|
||||
|
||||
@@ -243,10 +214,10 @@ extractor supports them; never guess equivalent namespaces.
|
||||
|
||||
| Class | Exact part names | Story selector | Agent exposure |
|
||||
| --- | --- | --- | --- |
|
||||
| body | word/document.xml | main | final by default; existing project/universe access |
|
||||
| footnote | word/footnotes.xml | w:footnote/@w:id | own story; same project/universe access as its document |
|
||||
| endnote | word/endnotes.xml | w:endnote/@w:id | own story; same project/universe access as its document |
|
||||
| comment | word/comments.xml | w:comment/@w:id | explicit part request plus human comments setting; same project/universe access |
|
||||
| body | word/document.xml | main | final by default, explicit grant |
|
||||
| footnote | word/footnotes.xml | w:footnote/@w:id | own story plus owning document grant |
|
||||
| endnote | word/endnotes.xml | w:endnote/@w:id | own story plus owning document grant |
|
||||
| comment | word/comments.xml | w:comment/@w:id | only explicit comment scope |
|
||||
| layout | word/headerN.xml, word/footerN.xml (N positive decimal) | none | denied in v1 |
|
||||
| supporting | separately declared non-story document | separate identity | C2 tools/scopes only; no DOCX scope inheritance |
|
||||
| opaque | media, styles, numbering, theme, fonts, custom XML, embedded objects | none | no raw bytes through O2 reader |
|
||||
@@ -317,12 +288,8 @@ kind paragraph-mark-insert or paragraph-mark-delete with its revision ID;
|
||||
it never becomes a whole-paragraph deletion span. A mark-only insertion's
|
||||
text stays in original; a mark-only deletion's text stays in final. Inline
|
||||
run revisions remain independent. No view edits or accepts/rejects changes.
|
||||
Original/markup require editor.agentReadTrackedDeletions=true; comment stories require
|
||||
editor.agentReadComments=true. Both are human-only and false by default (mos-1379); a comment
|
||||
in original/markup needs both. Check them in addition to ordinary project/universe access,
|
||||
refusing with editor-access-refused and no text when off. No role-level allowlist or
|
||||
per-document grant is added. A final-body read stays available and never silently includes
|
||||
deleted revisions/comments. Human editor review/save remains full-featured and unaffected. Revision author/date metadata
|
||||
Original/markup and comments require separate scopes, so final body access
|
||||
never reveals hidden deletion/comment text. Revision author/date metadata
|
||||
is omitted from agent responses in v1.
|
||||
|
||||
Closed story/run leaf table (all names in w namespace unless stated):
|
||||
@@ -405,8 +372,7 @@ plain text, and tokens cannot cross paragraph/view boundaries.
|
||||
## 6. Reading APIs, verification, paging and staleness
|
||||
|
||||
These are proposed typed tool schemas over the existing runner socket, not
|
||||
native WordCraft MCP. The same reader backs Case 2 lookups in any authorized thread or
|
||||
room under mos-1372 D9. Caller/session/business come from the bound broker.
|
||||
native WordCraft MCP. Caller/session/business come from the bound broker.
|
||||
Agents cannot override them using object fields.
|
||||
|
||||
- `editor_read_committed({project,document,version,textView,part,cursor,limit})`:
|
||||
@@ -446,14 +412,14 @@ without progress.
|
||||
|
||||
Cursor is a host-authenticated opaque token binding principal, session,
|
||||
project/document, commit/blob, extractor, view, part, next ordinal, the complete
|
||||
project/universe authorization and format-publication revision vector defined in section 7 and expiration (5 minutes). It carries no plaintext content.
|
||||
authority revision vector defined in section 7 and expiration (5 minutes). It carries no plaintext content.
|
||||
Tampering, reuse by another session, wrong request parameters or a revoked
|
||||
scope refuses. A latest cursor continues its captured snapshot with stale
|
||||
metadata if a save intervenes; starting again with null cursor returns the
|
||||
new publication. Cursor is not a capability and grants are rechecked every
|
||||
page. Repeated ordinal ranges are stable within one committed projection.
|
||||
|
||||
Resolver steps: authorize the current principal, live project/universe/source scope and publication membership; load
|
||||
Resolver steps: authorize the current principal tuple, live scopes and exact/historical share (there is no separate undefined historical-view scope); load
|
||||
that commit's manifest/blob; validate package; run the exact pinned extractor;
|
||||
select exact part/story/ordinal and mandatory XML-ID value; select code-point range;
|
||||
require literal quote equality and SHA-256(UTF-8 quote) equality. Require both
|
||||
@@ -465,13 +431,14 @@ an explicit old-version label. If current grant no longer allows it, even
|
||||
old quote resolution refuses and the UI hides cached content.
|
||||
|
||||
Errors: `editor-context-unavailable` for missing readiness; `editor-access-refused`
|
||||
for absent live project/universe authorization (same response for nonexistent or foreign IDs);
|
||||
for absent ownership/grant/share (same response for nonexistent or foreign IDs);
|
||||
`editor-document-unsupported` for parser limits or unsupported features after
|
||||
authorization; `editor-version-unavailable` for authorized missing history or
|
||||
extractor; `editor-citation-invalid` for locator/quote mismatch;
|
||||
`editor-cursor-invalid` for invalid paging; `editor-range-too-large` for size.
|
||||
The exact refusal envelope is `{ok:false,error:{code,message}}`; code is one
|
||||
of those enum strings or editor-policy-conflict (409, stale catalogue/settings revision), and message is fixed server text for that code (no user
|
||||
of those enum strings or editor-policy-conflict/editor-sharing-unsupported
|
||||
(both 409, defined in section 7), and message is fixed server text for that code (no user
|
||||
input). Both missing/foreign and ungranted IDs use the same code/message/status
|
||||
403. Syntax errors before identity lookup use editor-citation-invalid, 400;
|
||||
context unavailable is 503, cursor-invalid 400, version unavailable 409,
|
||||
@@ -479,142 +446,417 @@ unsupported/range-too-large 422. Successful tools return `{ok:true,result}`
|
||||
where result is the defined response; errors never include result/partial
|
||||
rows. Return no source text, XML, path or untrusted parser error in refusals.
|
||||
|
||||
## 7. Authority data, dedicated sessions and sharing
|
||||
|
||||
### 6.1 Case 2 query and citation adapters
|
||||
### 7.1 Scope and principal records
|
||||
|
||||
Keep `library_find`, `library_get`, `library_search`, `library_manuscript` and the universe
|
||||
lookup. Host adapters join Markdown metadata/read results and the committed DOCX extractor
|
||||
into one typed, scope-filtered index; only current identities enter normal lookups. A request
|
||||
on a converted alias returns its conversion record and the authorized DOCX result. Every
|
||||
answer names format, store/owned ID and captured commit. No loss of chapter search/manuscript
|
||||
or supporting-doc workflow is accepted. Same project/universe/source-scope rules apply in
|
||||
all threads/rooms; archive/draft scope never becomes canon because its format is DOCX.
|
||||
O2 context is permitted only in newly created **O2-dedicated conversations
|
||||
and execution sessions**. Existing C2 threads/sessions cannot be converted,
|
||||
attached to O2 context or resumed/forked into O2 mode; new IDs and a fresh
|
||||
empty context are mandatory. There is no mixed-thread turn. The UI offers
|
||||
"Open dedicated writing conversation", never silently changes an existing
|
||||
chat's permissions. Existing C2-02 supporting-doc writes and C2-06 replies
|
||||
retain their semantics. The narrowing below belongs only to this explicitly
|
||||
opted-in O2 mode, closing review M1 without a Case 2 amendment. It changes
|
||||
neither the Case 2 schema nor the meaning of its replies/line citations.
|
||||
|
||||
DOCX pages use paragraph locators and opaque cursors, not invented line equivalents.
|
||||
Retain section 6's 64-KiB/1..100 paragraph bounds (default20); human reply limits still apply.
|
||||
A manuscript cursor binds ordered-manifest commit, the selected document publication vector,
|
||||
principal/request/scope revisions and expiry. Resume only that snapshot with current access;
|
||||
changed/foreign cursor refuses. next_chapter uses the next visible current manifest ID,
|
||||
never an archive or a guessed sort of DOCX names. Index/cache keys include local publication
|
||||
and extractor revisions; no minute-old Markdown refresh window is copied to DOCX.
|
||||
Authorization principal is a tuple, not a seat's display name:
|
||||
`{business,human,agentInstance,conversation,executionSession}`. human is the
|
||||
stable authenticated identity that owns the O2 conversation; agentInstance
|
||||
and executionSession come from its bound broker run. The request cannot
|
||||
supply/replace them. All human readers and all agent lookups require live
|
||||
R5 project/universe access plus the O2 grant below; role files grant the
|
||||
operation only. The business owner is not an implicit reader of someone
|
||||
else's private conversation. A viewer can read only their authorized human
|
||||
view, never write a share/settings change or invoke an agent by sharing.
|
||||
An owner/collaborator with current project write access may share within
|
||||
their own readable O2 scope. Agent scope and sharing human scope intersect;
|
||||
a human cannot share a part or view they lack.
|
||||
|
||||
C2-06's checked cite union preserves legacy `{path,commit,lines,quote,as?}` for Markdown and
|
||||
adds `{kind:"docx",locator,as?}` where locator is section3's exact range/selection. Never infer
|
||||
a cite kind from a .docx suffix or parse DOCX as lines. The host checks project/universe,
|
||||
source canon/playground/archive status, fetched provenance, quote and commit before message
|
||||
storage. requireCitation and as:canon apply to both; historical converted/archived sources
|
||||
cannot assert current canon. The host reads Mosaic-Agent trailers from the correct source
|
||||
repository or writing-store publication, never trusts an agent author field. Console and
|
||||
the shared reply renderer show format, locator/lines, version, staleness and converted state.
|
||||
C2-18 Discord remains a separate ruled delivery task, not enabled by a shared renderer.
|
||||
O2 scopes live in the new append-only bus table writing_access_events, not
|
||||
access.json or role-file wildcard changes. Proposed human-only verb
|
||||
`editor.access.set({project,document,subject,scope,expectedRevision})` takes
|
||||
subject exactly `{kind:"human"|"agent",id}`; agent id is the business's
|
||||
bus agent **instance ID**, equal to principal.agentInstance, never a role
|
||||
definition ID or a seat display name. scope is exactly
|
||||
`{enabled,parts,views,history,mayShare}`. parts is a sorted unique nonempty
|
||||
subset of body/footnote/endnote/comment (all stories within the selected
|
||||
class); views is a sorted unique nonempty subset final/original/markup;
|
||||
history and mayShare are booleans. mayShare must be false for agent subjects.
|
||||
enabled=false revokes all access; even its otherwise populated scope cannot
|
||||
authorize. No wildcards or implicit note/comment inheritance. A comment
|
||||
requires parts including comment and the requested view. history=true permits
|
||||
older publications subject to a matching share; history=false permits only
|
||||
the current publication. The document is always explicit.
|
||||
|
||||
## 7. Existing Case 2 authority, remote durability and restore
|
||||
Only the existing business-owner human proof may write these policy grants,
|
||||
through the existing trusted human CLI or authenticated write-enabled Console
|
||||
transport, with the existing origin/session/proof checks. No new proof file,
|
||||
caller or exemption. Not tester, agent, viewer or collaborator policy writes.
|
||||
Sharing and scope are different: an authorized collaborator may send context
|
||||
but cannot change grants. expectedRevision is the latest sequence for that
|
||||
business/project/document/subject, zero when none. Compare, append and audit
|
||||
are atomic; conflict returns editor-policy-conflict (409, fixed message).
|
||||
Receipt is `{ok:true,result:{id,revision,previousRevision,scopeDigest}}`, where
|
||||
revision is the appended seq, id is host-generated, and scopeDigest is SHA-256
|
||||
of canonical scope JSON using section 3 serialization. Missing grant denies.
|
||||
|
||||
### 7.1 Access and supporting writes (reopened M1)
|
||||
Each access decision records an authority vector exactly
|
||||
`{restoreEpoch,humanAccess,agentAccess,projectAccess,threadAccess,sharingPolicy,share}`.
|
||||
humanAccess and agentAccess are the respective latest writing_access_events
|
||||
seq values; projectAccess is the existing R5 access revision; threadAccess
|
||||
is the latest O2 conversation membership-event seq; sharingPolicy is the
|
||||
latest writing_policy_events seq; share is the exact writing_shares seq.
|
||||
restoreEpoch is the independent restore-fence epoch in section 7.5.
|
||||
Missing inputs deny rather than zero-default except initial policy explicitly
|
||||
materialized by the human writer as the default. A cursor binds every value;
|
||||
any change invalidates it. Each new lookup reevaluates rights, including a
|
||||
revocation between asynchronous extraction and response emission.
|
||||
|
||||
Reuse existing bound business/project/universe access, live holder/role and source scope.
|
||||
No private O2 conversation ID, per-document access table or send/share token gates a Case 2
|
||||
lookup. Any existing authorized thread/room may use the checked reader and normal library
|
||||
tools. No arbitrary native WordCraft MCP, execute, open/save or raw filesystem/git tool is
|
||||
added. The host store is never mounted in a worker. Rendered/extracted content travels over
|
||||
the ordinary authorized Case 2 tool/message path and existing transcript/run-record policy;
|
||||
the withdrawn private-only transcript/output rule cannot forbid that documented workflow.
|
||||
Grant loss blocks later lookups through existing checks; do not promise erasure of earlier
|
||||
delivered text or rewrite immutable run evidence. This is the D9 choice for PRD approval.
|
||||
### 7.2 One human per context; settings and chip requests
|
||||
|
||||
The existing `library_doc_write`/`library_doc_delete` still require library.write plus the
|
||||
live supporting-repo Gitea grant and supportingPath before git. They operate only on human-
|
||||
marked supporting Markdown, with the existing size/base-CAS/seat-author/push/notice/revert
|
||||
rules. An agent can read a DOCX story and update its character sheet in the same authorized
|
||||
thread. There is no agent write to DOCX, original story, archive or conversion manifest.
|
||||
The archive prefix wins over supporting globs; conflicts refuse mapping. Format/status/type
|
||||
notes in the six creative contracts replace stale 'every item cites lines' wording with
|
||||
free chat plus optional checked line/DOCX citations under requireCitation. No per-document
|
||||
export grant or O2-only session is a hidden prerequisite.
|
||||
An O2 conversation has exactly one owner human and one agent instance. No
|
||||
second human joins it in v1: a thread-share attempt refuses
|
||||
editor-sharing-unsupported (409), rather than borrowing the sender's grants
|
||||
for someone else. A second human starts a separate O2 conversation, supplies
|
||||
their own grant/share and gets no previous content. This is a new O2 mode,
|
||||
not a restriction on existing C2 sharing. Human and agent must still have
|
||||
matching business/project/universe rights. Read-only previews cannot send.
|
||||
|
||||
The cursor authority revision is the server's current project grant, universe/mapping/scope
|
||||
revision, the two human review-settings revisions and format-publication generation for
|
||||
the bound caller/session. It contains no
|
||||
sharing human or writing_access_events/writing_shares sequence. Check the review settings before every part/view/citation/search/index/context attachment,
|
||||
not only on first open. No request boolean can enable them. Recheck before every page,
|
||||
resolution, index hit and message insertion. Scope revocation and format changes invalidate
|
||||
cached authorization; metadata for missing/foreign IDs uses the same refusal as before.
|
||||
Human-only `editor.sharing.set({conversation,mode,expectedRevision})` is
|
||||
available only to that authenticated owner with current write access over
|
||||
the same trusted write transports. mode is off/visible-chip-per-send/
|
||||
conversation-current. It atomically appends writing_policy_events and returns
|
||||
`{ok:true,result:{id,revision,previousRevision,mode}}`. Default is the explicit
|
||||
visible-chip-per-send policy on new conversation creation. Selecting off
|
||||
immediately invalidates **all** prior shares, reads, resolves and cursors for
|
||||
that conversation, not only new sends. Selecting another mode later does
|
||||
not revive shares from the earlier policy revision. No pin changes a mode.
|
||||
|
||||
### 7.2 Selection convenience and ordinary messages
|
||||
In visible-chip-per-send the human sends exactly
|
||||
`{conversation,message,expectedPolicyRevision,chip}` via the human send path.
|
||||
message is human-authored free-chat text; W4b's bounded human API handles it,
|
||||
never an agent-supplied impersonation. chip is null or one of:
|
||||
|
||||
Keep Q233's human-only setting, default a visible removable chip on each send. A chip may
|
||||
attach only a verified committed locator/version, never unsaved selection text. It helps the
|
||||
writer point at a passage; it neither grants nor limits the agent's ordinary project/universe
|
||||
lookup rights and does not freeze a later latest lookup to that older commit. The sent cite
|
||||
remains immutable historical evidence. Multi-human/thread/room behavior follows existing Case 2
|
||||
permission checks, not a new one-human conversation restriction. Saved-context/pin records
|
||||
remain on the bus through their reviewed schema, with no share-token authority layer.
|
||||
- `{kind:"range",document,baseCommit,selection}` where selection is one
|
||||
docx-range/docx-selection locator, with its business/project/document/
|
||||
commit equal to the server-owned conversation/document and baseCommit.
|
||||
- `{kind:"document",document,baseCommit,parts,views,history}` selecting a
|
||||
whole-document **exact publication**, with explicit part/view lists and
|
||||
history boolean constrained by both grants.
|
||||
|
||||
### 7.3 Host-only off-host remote (replaces the ban)
|
||||
Both chips require a server-issued human editor selection token as an
|
||||
additional required `selectionToken` key. It binds authenticated login,
|
||||
conversation, document, baseCommit, saved editor model revision, and either
|
||||
exact locator or whole-document scope digest, expires in 2 minutes and is
|
||||
single-use. The human editor adapter creates it only after exact mapping to
|
||||
the committed projection. The request supplies no draft text or engine path
|
||||
for mapping; mismatched/edited selection fails locally and server-side.
|
||||
A stale base or document change before send refuses, requiring a fresh
|
||||
visible chip; an old citation remains resolvable through its already-sent
|
||||
share while authorized. Null chip sends no editor context. Before readiness,
|
||||
the chip says unavailable and sends no document ID, title, path or selection.
|
||||
|
||||
`editor.remoteURL` is a human-only setting, default unset. No same-host bare/filesystem/loopback
|
||||
remote is the default or qualifies as off-host durability. The human selects an off-stack-host
|
||||
private destination (for example private Gitea) and separately configures host-only auth.
|
||||
Until #431 corrects the real seat helper's shared jason.woltje credential route, the remote
|
||||
must be on a host/account that principal and all seat credentials cannot reach; private
|
||||
visibility alone proves nothing. Prefer a separate off-host instance. An existing-instance
|
||||
owner/org qualifies only after denial through the actual helper credential path proves no
|
||||
jason.woltje access. W3b tests that real helper route and seat identities against an authorized
|
||||
empty fixture remote, without printing/logging credential values; no dummy-token substitute.
|
||||
Only the host process pushes. No seat token can list/fetch/clone/fork/push the writing repo,
|
||||
no seat gets its credential, and no raw Gitea grant or store mount is created. Validate the
|
||||
owned remote, TLS/host policy and off-host storage identity; reject URL userinfo, unexpected
|
||||
remotes/object alternates and non-fast-forward updates. Allow host pushes only to refs/heads/writing-published and
|
||||
refs/heads/import-staging/<operation-id>. A refused candidate retains a private non-reader
|
||||
staging ref and journal status; after publication the staging ref stays as an audit link.
|
||||
No automatic deletion or force-update of staging refs. No silent fallback or force push.
|
||||
A range share permits only editor_resolve_citation for that exact locator
|
||||
(or a range wholly contained within it with newly verified quote/digest).
|
||||
It always refuses editor_read_committed, since rows would expose surrounding
|
||||
text. A document exact share allows paging only its named publication,
|
||||
parts/views; it never grants future saves. No implicit body-to-note/comment
|
||||
or final-to-markup expansion occurs.
|
||||
|
||||
Every writing-store commit pushes there. Agents read the local publication immediately;
|
||||
receipts/UI distinguish local commit and remote pending/failed/synced. Missing remote blocks
|
||||
production opening. Before source archive push, the staged DOCX must have remote acknowledgment.
|
||||
A failed later push keeps local commits, queues bounded host retry/readback and shows failure;
|
||||
it never rolls back the local story. Source archives push through their separate human
|
||||
library writer. The configured retry deadline is human-only (default30s, 1s..300s).
|
||||
Remote/provisioning changes require the normal owner authority, never this design execution.
|
||||
The remote exception is for story git history/manifest; generic stack copies/releases are
|
||||
not story backups and contain no host credentials. Normal Case 2 output/notes follow the
|
||||
existing project's approved storage/sharing policy; the former blanket no-backup/no-remote
|
||||
rule is withdrawn. PRD 1.17 explicitly presents this D7/C4 change to Jason.
|
||||
conversation-current is an explicit human policy with a persistent visible
|
||||
indicator and a required whole-document chip. The same chip fields bind
|
||||
baseCommit and scope, but the share record has versionMode=latest: baseCommit
|
||||
is the acknowledged publication at issuance, not a future-version ceiling.
|
||||
The next authorized latest lookup after Saved follows currentCommit within
|
||||
the granted part/view/history bounds. This exception to exact-version sharing
|
||||
exists only in this explicit mode. Range chips are refused in this mode,
|
||||
not silently upgraded. Pins and earlier exact shares never become latest.
|
||||
|
||||
### 7.4 Conversion events, rollback --restore and irreversible recovery
|
||||
Latest shares expire on explicit policy revocation, owner grant loss, O2
|
||||
conversation close or **execution-session end/clear**. This means the new
|
||||
O2 execution session, not the Console login, bus connection or a C2 thread
|
||||
session. Relaunch/clear requires a fresh chip; a dropped socket/login does
|
||||
not silently widen or prolong one. Exact shares may remain verifiable across
|
||||
execution sessions through their same human conversation while live policy,
|
||||
history permission and grants hold, but never supply replay context to a
|
||||
new agent session without a new share. Their scope is citation verification
|
||||
only after the issuing execution session ends. No cross-human context reuse.
|
||||
|
||||
W7 owns `writing_conversion_events`: a STRICT append-only table with seq INTEGER PRIMARY KEY,
|
||||
at TEXT, business/project/document/operation_id/stage TEXT, record_ref/record_digest TEXT;
|
||||
All fields except seq are NOT NULL; operation_id+stage is unique, no update/delete. The private host journal/record contains source
|
||||
commit/blob/path, archive path/commit, DOCX commit and conversion date; the row is metadata
|
||||
and never prose/credentials. Records are flushed before their bus references. Table rows
|
||||
join stack-release COUNTED and T46 exactly once, so post-backup conversion events contribute
|
||||
to --events and the existing restore approval/counting behavior. No new private conversation,
|
||||
per-document grant or per-send share tables are created for D9.
|
||||
### 7.3 Stored share schema
|
||||
|
||||
Three recovery cases are required: (1) crash after durable DOCX staging but before archive
|
||||
push, (2) crash after archive push before publication, and (3) rollback --restore from a
|
||||
pre-conversion bus backup while archive commits and O2 documents remain. The source is pinned
|
||||
until final publication in cases1/2; after archive push state is visibly pending, never
|
||||
'original untouched'. Case3 drops conversion rows only through the counted/approved restore
|
||||
path. The durable host conversion journal/format registry is not restored from that older bus
|
||||
backup. A compatible controller compares journal, both git histories, remote acknowledgment
|
||||
and restored metadata before lookups/writes resume; it reconstructs missing metadata through
|
||||
new recovery events without reviving Markdown or any old access grant. If reconciliation
|
||||
cannot prove a single current identity/version, refuse that project's lookups and surface the
|
||||
pending operation to the human. Do not replay chapter text into an old index or hide event loss.
|
||||
Incompatible old binaries cannot activate converted projects through the supported controller;
|
||||
raw privileged rollback tampering is outside the guarantee. Never rewrite git/run history.
|
||||
The immutable writing_shares payload is exactly
|
||||
`{version,id,restoreEpoch,business,project,document,conversation,executionSession,message,
|
||||
human,agentInstance,mode,versionMode,baseCommit,blob,extractor,scope,selection,
|
||||
authority,policyRevision,issuedAt}`. version=1; IDs use section 3 grammar;
|
||||
mode is the policy at issuance; versionMode is exact/latest; baseCommit/blob
|
||||
are the acknowledged snapshot and extractor is its exact pinned revision.
|
||||
scope is `{parts,views,history}` as above; selection is the verified locator
|
||||
for a range, otherwise null. authority is the complete revision vector at
|
||||
issuance; its share field equals this row's seq (assigned atomically).
|
||||
restoreEpoch must equal authority.restoreEpoch and the live independent
|
||||
fence. issuedAt is canonical UTC millisecond ISO timestamp. policyRevision equals
|
||||
authority.sharingPolicy. message names a new writing_messages row, not a C2
|
||||
message. A unique `(conversation,message,document)` constraint and operation
|
||||
idempotency key prevent duplicate grants on retry. No user-provided human,
|
||||
agentInstance, authority, issuedAt, blob or share id is accepted.
|
||||
A scope decrease/revocation denies even historical shares; an unrelated new
|
||||
policy revision also invalidates prior policy-bound shares. A human who
|
||||
needs them again explicitly reshares. Missing session/share is no context.
|
||||
|
||||
### 7.4 Dedicated storage and every output surface
|
||||
|
||||
Every turn receiving O2 context runs with the new O2-only allowlist of
|
||||
read/resolve/flag plus its dedicated conversation reply. No generic file,
|
||||
shell, git, supporting-doc writer or arbitrary outgoing tool is available.
|
||||
This narrows only new O2 sessions, never C2 turns. All agent output from such
|
||||
a turn is conservatively O2-derived; it stays in the dedicated conversation
|
||||
with the union of the document provenance/scopes. No semantic quote detector
|
||||
or agent self-labelling supplies this boundary. Human text and titles in that
|
||||
conversation use the same dedicated storage, so a human pasting an O2 passage
|
||||
cannot accidentally route it into an old renderer. O2 replies never enter
|
||||
C2-06 processing. An O2 flag is metadata stored by W5, not a story edit.
|
||||
|
||||
| Existing surface | Required O2 behavior |
|
||||
| --- | --- |
|
||||
| threads/<business>/<thread>/session and claude-projects | no O2 content, prompt, model transcript, title or derived reply |
|
||||
| threads/<business>/<thread>/sessions/<session>/session and claude-projects | same; O2 mode never binds these native stores |
|
||||
| session-workspaces/<business>/<instance>/<session> | no O2 text or exports; no store mount |
|
||||
| launches/<business>/<run> including bundle, turns, runner.log and session | opaque IDs/status only, never O2 prompts/output; host transient O2 adapter instead |
|
||||
| bus events, messages, message_threads and existing sessions rows | no O2 text/titles/quotes; only fixed readiness/status audit with opaque IDs if needed |
|
||||
| Console legacy thread view | no O2 rows; dedicated writing view reauthorizes every read |
|
||||
| CLI thread, talk, follow and raw-session tools | no O2 content; unsupported mode refuses, not generic rendering |
|
||||
| Discord journal.mjs/context.mjs and control-board Discord delivery | no O2 conversation subscriptions/context/output; fixed unavailable notice only |
|
||||
| <dataRoot>/runs write-once evidence | never O2 text, prompts, titles, excerpts, stdout/stderr or derived output; only opaque run status |
|
||||
| bus-host/sessions.json, launch logs and activity projections | IDs/liveness only, no document or conversation title |
|
||||
|
||||
Host-only `<dataRoot>/writing-context/` (0700 owner, no generic mount/remote)
|
||||
holds the immutable dedicated message/share objects defined below, transient
|
||||
O2 adapter material and any unavoidable encrypted native transcript. Object
|
||||
files are regular 0600 owner files, no symlink/hardlink aliases. This is an
|
||||
O2-classified content store; it is never included in the bus release backup,
|
||||
post-attempt store, stack-copy, rehearse input or generic backup. Revocation
|
||||
changes permission at read time, never rewrites a write-once run record. It is keyed to O2 session, not an alias/symlink into seat stores.
|
||||
Release readiness requires an independently tested adapter that prevents
|
||||
plaintext native persistence/logging outside this boundary, including crash
|
||||
and timeout paths. If either Pi or Claude cannot meet it, that harness's O2
|
||||
tools stay off; do not expose the session then attempt post-hoc redaction of
|
||||
write-once run records. Readiness is per adapter. No O2 text enters immutable
|
||||
legacy evidence, even briefly. Stored dedicated replies are authorized afresh
|
||||
at read time; live grant loss hides cached UI content and prevents prompt
|
||||
replay/fork. Revocation cannot erase already delivered knowledge and the UI
|
||||
must not promise that it can.
|
||||
|
||||
O2-derived exports, extraction indexes, caches, previews, source copies and
|
||||
git history retain document classification outside generic roots and C2
|
||||
library mappings. O2 repositories have **no git remotes**. No push, mirror,
|
||||
fork, plaintext backup or remote replication of an O2 repository is allowed,
|
||||
including through human credentials; no seat Gitea account can discover,
|
||||
list, fork or clone one. Backup transport is out of scope and disabled, not
|
||||
an implied exception. Stack-copy/release copies source code and extractor
|
||||
artifacts only, never writing/, writing-context/, writing-control/, O2 content
|
||||
objects, exports or repository object directories. The separate release bus
|
||||
backup retains metadata rows only, under the private-store rules below;
|
||||
it is not a source copy or permission to read context. No raw O2 Gitea grant may be minted.
|
||||
Validate absence of remotes at store creation, before save/read and activation;
|
||||
a new remote or object alternate disables O2, rather than trusting a read-only
|
||||
mount. Symlinks, hardlinks, alternate worktrees and parent mounts must not
|
||||
expose it to generic tools. Native engine APIs remain human-only.
|
||||
|
||||
### 7.5 Named migration and rollback contract
|
||||
|
||||
W4b adds the following new STRICT append-only bus tables (seq INTEGER primary
|
||||
key, at TEXT canonical timestamp, id TEXT unique, all other fields NOT NULL
|
||||
unless stated). Each row also has restore_epoch TEXT NOT NULL, equal to the
|
||||
independent current fence epoch at write time. Every update/delete/replace
|
||||
refuses; latest seq **within that epoch** is state.
|
||||
JSON columns contain the exact canonical schema named above, not arbitrary
|
||||
unvalidated JSON. No existing C2 messages/events/sessions column changes.
|
||||
|
||||
| Table | Additional columns and key |
|
||||
| --- | --- |
|
||||
| writing_access_events | business TEXT, project TEXT, document TEXT, subject_kind TEXT (human/agent), subject_id TEXT, by_human TEXT, previous_revision INTEGER, scope_json TEXT, operation_id TEXT UNIQUE; latest per document/subject |
|
||||
| writing_conversations | business TEXT, project TEXT, owner_human TEXT, agent_instance TEXT; one creation row per id, private O2 identity only |
|
||||
| writing_conversation_events | conversation TEXT FK, op TEXT (close), by_human TEXT; latest membership/close revision, creation seq is initial threadAccess |
|
||||
| writing_execution_sessions | conversation TEXT FK, execution_session TEXT UNIQUE, adapter_revision TEXT; no native transcript path |
|
||||
| writing_session_events | execution_session TEXT FK, op TEXT (end/clear), by_principal TEXT; terminal event invalidates latest shares |
|
||||
| writing_policy_events | conversation TEXT FK, mode TEXT, by_human TEXT, previous_revision INTEGER, operation_id TEXT UNIQUE |
|
||||
| writing_messages | conversation TEXT FK, execution_session TEXT FK nullable for human-only messages, from_kind TEXT (human/agent), from_id TEXT, content_ref TEXT, content_digest TEXT, operation_id TEXT UNIQUE |
|
||||
| writing_shares | conversation TEXT FK, message TEXT FK, document TEXT, payload_ref TEXT, payload_digest TEXT, operation_id TEXT UNIQUE; UNIQUE(conversation,message,document) |
|
||||
|
||||
Each table also has business/project columns if not otherwise listed; FK and
|
||||
application checks require agreement, not just ID existence. The canonical message content object is exactly `{version:1,business,project,row,nonce,body,provenance}`:
|
||||
row is the owning writing_messages row ID; nonce is fresh cryptographically
|
||||
random 32-byte lowercase hex, stored only inside the private object, not its
|
||||
metadata backup. body is
|
||||
the complete human/agent message string; provenance is an array of `{document,share,commit,blob,extractor}`,
|
||||
sorted by document/share; every entry requires live scopes. The share content
|
||||
object is exactly `{version:1,business,project,row,nonce,payload}`, where row
|
||||
is its writing_shares row ID, nonce has the same fresh/private rule, and
|
||||
payload is the full section 7.3 record including any quoted selection.
|
||||
Neither message bodies nor share payloads/quotes enter bus.sqlite, WAL,
|
||||
events, SQL error logs or backup manifests. Human messages with no O2
|
||||
attachment have provenance=[] but use the same private content store.
|
||||
content_ref/payload_ref are opaque 64-hex object names, not paths; their
|
||||
corresponding digest equals SHA-256 of the exact canonical UTF-8 object.
|
||||
Objects reside at writing-context/objects/<business>/<row>/<digest>.json;
|
||||
row/business/project must match both reference metadata and the authorized
|
||||
caller. No cross-business or cross-row deduplication; retention is per owning
|
||||
row and cannot delete another business's object. The private random nonce
|
||||
prevents a metadata digest from confirming a guessed short body. No remote or
|
||||
release-backup copy. Write/flush the immutable object before committing its
|
||||
bus reference. A failed transaction leaves only an inaccessible orphan,
|
||||
never a visible partial message/share; a missing or mismatched object refuses.
|
||||
Object reads always require current authority, not knowledge of a digest.
|
||||
No
|
||||
existing renderer queries these new tables; generic bus recordEvent and
|
||||
recordTask cannot write their rows. Access/policy/shares use only the trusted
|
||||
handlers and atomic transactions. Grant seq values are table-specific, with
|
||||
kind-qualified revision vector fields; never compare unrelated seq counters.
|
||||
|
||||
Sage allocates the schema edge; migration creates these empty tables and
|
||||
triggers, with no backfill/conversion of C2 citations, messages or raw sessions.
|
||||
Production adoption stays refused. The writer registers new documents without
|
||||
creating grants; the owner explicitly sets both human/agent document scopes.
|
||||
O2 session creation and its default policy are one atomic operation behind
|
||||
owner write proof; receipt `{id,executionSession,policyRevision}` contains no
|
||||
content. No legacy thread/session can be attached by guessed ID.
|
||||
|
||||
Old release rollback must not render O2 data. The new store schema version
|
||||
normally makes old binaries refuse opening it, but security does not depend
|
||||
on that: even a restored pre-W4b database reader can see only its unchanged
|
||||
legacy tables with no O2 content. O2 messages/share/provenance remain in new
|
||||
tables, never a tagged body in existing messages. Activating a pre-W4b copy
|
||||
disables O2 context, never converts dedicated conversations to C2 or exports
|
||||
transcripts for compatibility. O2 repositories/stores remain outside copies.
|
||||
A rollback probe uses recognizable secret fixture text in O2-only content objects,
|
||||
then invokes old Console/CLI/Discord/raw-session renderers: none may emit it,
|
||||
its title, quote or prompt. No live rollback is part of acceptance testing.
|
||||
|
||||
#### Release backup, restore counting and independent revocation fence
|
||||
|
||||
The existing release behavior must be explicitly accounted for: at this base
|
||||
`scripts/stack-release.sh:474-493` copies bus.sqlite into
|
||||
`<dataRoot>/backups/stack/<ACT>/bus/`; restore_store at :498ff moves current
|
||||
bus/ to `<dataRoot>/bus.post-attempt-<stamp><suffix>/`. rehearse reads backup
|
||||
stores, and release never prunes them. These paths remain **O2-classified
|
||||
metadata stores** even though content/quotes are outside SQLite. Directories
|
||||
are 0700 owner and files (including SQLite/WAL/SHM if present) 0600 owner,
|
||||
with ownership, symlink/hardlink and generic mount checks. No remote, mirror,
|
||||
seat token access or generic export. rehearse may read metadata for validation
|
||||
only and cannot load content objects, emit quotes or restore readiness.
|
||||
|
||||
Release does not silently delete evidence. A separate explicit human/ops
|
||||
retention action may prune a stopped backup/post-attempt tree only after
|
||||
checking it is not the selected recovery set or needed restore evidence,
|
||||
recording its IDs/digests and a prune receipt. No automatic age-based prune,
|
||||
no editing of retained metadata, no pruning runs/ under this policy. Until
|
||||
that separately reviewed action exists, keep these private local metadata
|
||||
trees indefinitely; it grants no content access. writing-context objects
|
||||
have a distinct reviewed retention process and are never backed up by release.
|
||||
|
||||
W4b owns the release guard changes, not a follow-up: all eight writing_*
|
||||
tables above join COUNTED in scripts/stack-release.sh:420 and the T46 table
|
||||
classification in packages/cli/tests/stack-release.test.mjs:913. They are
|
||||
non-event writers, not classified as ignorable tables or double-counted as
|
||||
generic events. Jason's existing exact --events N confirmation remains
|
||||
required when the combined post-backup row count is nonzero. A restore plan
|
||||
also compares writing row identities with the proposed source store; equal
|
||||
aggregate counts cannot conceal a dropped revocation replaced by another row.
|
||||
A schema with any unclassified writing_* table refuses restore readiness.
|
||||
|
||||
An **independent host-private fence** lives outside bus/ and every backup:
|
||||
`<dataRoot>/writing-control/restore-fence.json`, directory 0700, regular file
|
||||
0600, no links/mounts/remote/source-copy/rehearse access. Its exact schema is
|
||||
`{version:1,epoch,restoreOperation,droppedRowsDigest,confirmedBusinesses}`.
|
||||
epoch is a host-generated opaque ID; restoreOperation is null before the
|
||||
first restore, otherwise a host-generated opaque operation ID; droppedRowsDigest
|
||||
is SHA-256 of the canonical sorted dropped `(table,id)` list (empty allowed);
|
||||
confirmedBusinesses is a sorted unique array of authenticated business IDs.
|
||||
Only the trusted release controller and existing owner-proof handler write
|
||||
it by durable atomic replacement; agents/testers/request paths cannot.
|
||||
It is not a human-editable readiness setting: verified release readiness is
|
||||
still required, and this file is an additional independent deny fence.
|
||||
|
||||
**First creation:** the compatible release controller durably creates the
|
||||
fence as part of W4b's first schema migration, before committing those empty
|
||||
tables or enabling any handler. It uses a fresh epoch, restoreOperation=null,
|
||||
droppedRowsDigest=SHA-256 of the canonical UTF-8 empty JSON array `[]`, and
|
||||
confirmedBusinesses=[]. Every initial writing row uses that epoch. A normal
|
||||
never-restored store does **not** require editor.restore.confirm: the
|
||||
confirmation gate applies only when restoreOperation is non-null. Initial
|
||||
live grants/shares and verified artifact readiness are still mandatory.
|
||||
|
||||
Initialization is allowed only when the pre-migration schema has no writing_*
|
||||
tables/history and there is no existing fence. If a crash leaves a durable
|
||||
initial fence but the schema transaction uncommitted, migration resumes with
|
||||
that same epoch, not another initial state. If writing tables/history exist
|
||||
and the fence is missing/malformed, fail closed; never repair by creating a
|
||||
new initial fence. Creating an initial fence is not a way around a previous
|
||||
restore or revocation. W4b acceptance exercises both migration crash orders.
|
||||
|
||||
Before **any** store swap/restore (not just one with nonzero counts), the
|
||||
current compatible controller durably creates a fresh epoch, records dropped
|
||||
writing rows and clears confirmedBusinesses. If fencing fails, restore
|
||||
refuses before changing bus/. A crash anywhere keeps O2 denied. Old snapshot
|
||||
rows have old restore_epoch; they cannot authorize even if their grant,
|
||||
policy or share says enabled. Automatic failure recovery and rehearse-driven
|
||||
restore use the same fence. Normal code rollback without store restore
|
||||
keeps the fence and leaves O2 off on incompatible code. A pre-W4b controller
|
||||
is not allowed to restore an O2-enabled data root: the active controller's
|
||||
capability preflight refuses it; operations must use the compatible restore
|
||||
controller even when the target runtime is old.
|
||||
|
||||
After restore, the verified W4b runtime remains unavailable for O2 in every
|
||||
unconfirmed business. New human-only
|
||||
`editor.restore.confirm({expectedEpoch})`, under the business owner's existing
|
||||
trusted human proof, acknowledges that business in the independent fence
|
||||
and returns `{ok:true,result:{business,epoch,confirmed:true}}`. It restores
|
||||
**no old grant/share/policy/conversation**. Fresh current-epoch grants,
|
||||
a fresh dedicated conversation/default policy and fresh human chips must
|
||||
be created explicitly. Past rows may be inspected by an authorized recovery
|
||||
operator as metadata, but never used as current authority or prompt replay.
|
||||
Every read/resolve/cursor/share checks matching restore_epoch and, when
|
||||
restoreOperation is non-null, that business's explicit confirmation; old object bytes remaining on disk are not capabilities.
|
||||
Thus even owner confirmation cannot revive a previously revoked old share.
|
||||
Missing/malformed fence state denies O2. This current-epoch file does not
|
||||
claim to detect a privileged manual rollback of both fence and database;
|
||||
the supported restore protocol never restores the fence from a backup and
|
||||
always rotates it first. A same-uid operator bypassing that protocol is not
|
||||
a boundary this file can prove. --events N approves a
|
||||
store operation only and is never a substitute for this recovery protocol.
|
||||
|
||||
Acceptance seeds a secret in a content object and a share, takes a backup,
|
||||
revokes access/sharing, then restores the backup with explicit row-loss
|
||||
approval: the old share stays refused before and after owner confirmation;
|
||||
a new grant/conversation/chip is needed. Sweep bus.sqlite, WAL/SHM, every
|
||||
backups/stack and bus.post-attempt-* tree, rehearse output and release logs:
|
||||
no secret/title/quote/body marker may appear. Only the explicitly named
|
||||
private writing-context content object may contain it. Verify COUNTED/T46
|
||||
covers each table and simulate failure before/after each fence/store step.
|
||||
|
||||
The XML/ZIP parser safety ceilings and absence-of-remote checks apply to
|
||||
human store ingestion too where needed to prevent path escape and remote
|
||||
replication; a projection-unsupported rich feature alone does not reject a
|
||||
valid human save. Parser refusal codes have a fixed human-readable reason
|
||||
category, never raw XML or a misleading empty context. Fields/hyperlinks/
|
||||
text boxes may make real chapters agent-unreadable in v1; the UI explicitly
|
||||
says this and never claims an empty successful projection.
|
||||
|
||||
Every extractor revision referenced by retained publication history or shares
|
||||
is retained as a verified immutable host artifact. An activation missing one
|
||||
refuses O2 readiness (no silent citation downgrade), including rollback to a
|
||||
W4b-era release that drops a revision. A pre-W4b release can run with O2 wholly
|
||||
off and cannot read those records. Retiring revisions requires a separately
|
||||
reviewed retention/prune operation for all dependent O2 history/shares; not
|
||||
an automatic stack-copy cleanup. Artifact caches are not authorization caches.
|
||||
|
||||
Hermes retirement (#423 step5) is a production-opening prerequisite, with old story-write and
|
||||
store-read access removed before any conversion/new-story operation. W7 checks its retirement
|
||||
receipt and host capability preflight; no design seat retires services or accesses Melanie's
|
||||
host. Independent tests use fixtures only. Preserve the existing stack grant/restore behavior;
|
||||
the previous unimplemented W4 private-share restore-fence/table design is withdrawn with M1,
|
||||
not backfilled into legacy rows. Conversion's durable registry is not an authorization token.
|
||||
## 8. Acceptance matrix and release evidence
|
||||
|
||||
These are required future fixture gates, not implementation results.
|
||||
Each row needs a named fixture/test and a mutant or bypass probe in W4b's
|
||||
packet. Independent fixtures construct OOXML directly and recompute Unicode
|
||||
slices/digests outside the production extractor. Security checks fail closed;
|
||||
no fixture-only readiness flag may exist in production APIs.
|
||||
|
||||
| ID | Evidence required | Negative control / bypass |
|
||||
| --- | --- | --- |
|
||||
@@ -623,49 +865,117 @@ These are required future fixture gates, not implementation results.
|
||||
| A03 | XML ID absent/duplicate and ordinal behavior | missing/mismatched paraId cannot silently choose another node |
|
||||
| A04 | Emoji, combining characters, tabs, breaks and split runs | UTF-16 offsets, normalization and whitespace collapse fail |
|
||||
| A05 | Literal split-run wiki forms retained | no path/URL fetch; malformed/cross-paragraph tokens stay text |
|
||||
| A06 | Final/original/markup independently verified with moves/deletions; review settings off by default | final read succeeds; original/markup or comment request/resolve refuses until matching human setting, both needed for original comment; no document rewrite |
|
||||
| A06 | Final/original/markup independently verified with moves/deletions | final-only grant cannot read deletion/comment; no document rewrite |
|
||||
| A07 | Body/note/endnote/comment exact ownership | orphan/dangling/cross-document references and duplicate IDs refuse |
|
||||
| A08 | Package allowlist, aliases, CRC, XML/ZIP limits | external rel, macro, traversal, symlink, entity, ZIP bomb and timeout refuse |
|
||||
| A09 | Commit snapshot and publication ordering | next latest after Saved gets new commit; no worktree/draft fallback |
|
||||
| A10 | Historical resolve stale label | old quote stays old; removed grant/history refuses without text |
|
||||
| A11 | Pagination complete/deterministic and bounded | tampered/foreign/revoked cursor and oversized row refuse |
|
||||
| A12 | Cross-user/business/project/universe denial | foreign project/universe IDs, same blob elsewhere and guessed unpublished objects grant nothing |
|
||||
| A13 | Selection chip points only at committed text; ordinary lookup authority independent | draft mapping refuses; missing chip does not deny an authorized lookup |
|
||||
| A14 | Normal project/universe/source-scope checks in every thread/room | pin, guessed ID, foreign universe and revoked holder cannot grant access |
|
||||
| A15 | Latest after acknowledged save; explicit historical cite stays immutable | chip/per-send snapshot cannot force stale latest results |
|
||||
| A16 | Host-only store and remote credentials; checked reader routes only | no worker store mount, arbitrary git/engine access or seat remote token; actual seat helper credential denied, not just a dummy token |
|
||||
| A17 | Existing Case 2 message/transcript policy | no O2-only/raw-session backdoor that skips existing thread/project checks; no false erasure promise |
|
||||
| A18 | Reader/resolve/flag plus existing Case 2 lookups/supporting writer after gates | native MCP, execute, save, replacement and story comment writes refuse |
|
||||
| A19 | Unconverted/support Markdown unchanged; converted path returns record and DOCX | no stale archive as chapter or DOCX-as-lines; historical line cite verifies by old commit/blob |
|
||||
| A20 | Conversion/new story gates W4b/W7/PRD/post-10-27/Hermes/owner go | agent start, missing readiness, early release, fixture flag and missing first-real-book go refuse |
|
||||
| A12 | Cross-user/business/project/universe denial | guessed IDs, same blob elsewhere and copied document grant nothing |
|
||||
| A13 | Visible range chip exact boundaries and default off before gate | no selection/title/path metadata before readiness; draft mapping refused |
|
||||
| A14 | Every lookup intersects live grant and share | pin, old send, revoked scope and range-to-whole-document escalation fail |
|
||||
| A15 | Whole-document latest share explicit; save rechecked | exact share cannot reveal future edits; persistent policy visible |
|
||||
| A16 | Generic file/search/git/export/index paths denied | source copy, symlink, hardlink, alternate object root and new O2 copy probes |
|
||||
| A17 | Raw session, fork/resume and transcript boundaries | revoked context cannot replay via raw tool or harness history |
|
||||
| A18 | Agent tools exactly read/resolve/flag after gates | native MCP, execute, save, replacement and story comment writes refuse |
|
||||
| A19 | Existing C2 Markdown reads/line cites unchanged | no DOCX-as-lines conversion or redirect following adoption |
|
||||
| A20 | Adoption gate before/after readiness and Q36 disposition | absent/revoked server readiness and required notice readiness refuse |
|
||||
| A21 | Human full-feature save remains separate | unsupported projection yields no agent context, never lossy human save |
|
||||
| A22 | Scope revocation during extraction | final pre-emission check prevents async stale-authority leak |
|
||||
| A23 | Sibling chapter save | currentCommit/stale/cursor for untouched document remain unchanged |
|
||||
| A24 | Full page and oversized paragraph | end before overflow; first oversized row refuses without text; no unreachable subrange claim |
|
||||
| A25 | Every read/write/tool surface uses existing Case 2 isolation | same document ID across business/universe or replayed cursor refuses |
|
||||
| A26 | Host-only off-host remote and per-save push | unset/local-only/helper-readable/unexpected remote refuses opening; real helper denial tested; push outage visible, local next lookup still fresh |
|
||||
| A27 | Conversion restore accounting and durable journal reconciliation | pre-conversion --restore loses counted row but cannot revive Markdown; no lost-event concealment |
|
||||
| A28 | Reopened M1 supports same-thread DOCX-read then supporting-note write | any room/project-authorized caller works; no per-document/share token; story/archive write refuses |
|
||||
| A29 | Readiness is reviewed runtime/extractor plus approved PRD, not caller fields | no request/go/setting can assert release or Hermes retirement |
|
||||
| A25 | No O2 git remote or release copy | seat Gitea list/fork/clone fails; configured remote/object alternate disables O2; copies exclude stores |
|
||||
| A26 | Every section 7.4 store/renderer | secret marker absent in each named legacy store, raw session, CLI/Discord/Console view and runs/ |
|
||||
| A27 | Pre-W4b rollback reader | new O2 rows never rendered through legacy tables; missing retained extractor disables O2 |
|
||||
| A28 | Dedicated mode vs C2 | mixed attachment/resume/fork denied; unchanged C2 supporting writes/replies fixtures pass |
|
||||
| A29 | Two humans/viewer/grant revisions | viewer cannot share; second human cannot join or reuse context; all cursor authority fields checked |
|
||||
| A30 | Canonical schemas and complete selections | unknown keys, inconsistent ranges, xmlId null mismatch and noncanonical story IDs refuse |
|
||||
| A31 | Closed leaf/revision table | every leaf/nested rule; mark-only deletion keeps text in final, mark-only insertion keeps text in original; markup mark spans zero-width; Word -1 separator/0 continuation-separator and older 0/1 reserved layouts validate, references to them refuse |
|
||||
| A32 | Existing grant/restore controls and conversion metadata COUNTED | restore cannot revive grant, current Markdown or uncounted conversion event |
|
||||
| A33 | Staging/archive/publish journal and operation readback | crash/sync at every boundary; pinned source until publish; post-push failure pending, not undone |
|
||||
| A34 | Manifest order, typed index/links and format map | next_chapter never archive; draft scope retained; cross-universe/hybrid exclusions and R4/R5/R6 |
|
||||
| A35 | Per-file fidelity, source history, human controls and missing-folder refusal | all constructs/wiki/front matter preserved; no agent conversion; new DOCX has no Markdown precursor |
|
||||
| A32 | Initial migration and revocation/restore | initial null-operation fence needs no restore confirmation but no grant is implicit; non-null restore fence needs confirmation and fresh authority; all tables counted/classified; old share stays refused; crash/backup-marker sweeps |
|
||||
|
||||
Implementation acceptance includes exact schemas/parser fixtures, live project/universe
|
||||
revocation probes, same-thread supporting writes, remote denial/failure, counted-event restore
|
||||
cases, Hermes gate, pinned-copy/sandbox closure and G1. All skips are named; an unexercised
|
||||
guard blocks enablement. Old O2-only/private-share acceptance wording is replaced, not claimed
|
||||
passed. No test, conversion or runtime experiment ran for this rewrite.
|
||||
Implementation acceptance includes writing, bus, harness, business, WebUI,
|
||||
seat mount/import closure and stack-copy/release fixtures; explicit production
|
||||
adoption refusal/lift tests; independent parser/policy/tool review; G1 and
|
||||
Sage's allocated schema edge. Evidence must name skips and refuse enablement
|
||||
when an acceptance guard was not exercised. W4's review alone lifts nothing.
|
||||
|
||||
## 9. Reopened review and handoff
|
||||
## 9. Handoff and unresolved decisions
|
||||
|
||||
Fenton reviews this policy rewrite against mos-1368's A17–A41/Q1–Q16 and mos-1372 D1–D9,
|
||||
including reopened M1 and A19/A20/A28, host-only remote and C5 restoration. Megavolt's old
|
||||
APPROVE stays historical at the original blob; no author writes its verdict file. The
|
||||
locator/projection facts remain source-backed, but the new authority/tool/runtime contract
|
||||
requires its own independent approval and the Case 2 PRD approval, not a silent addendum.
|
||||
Owner sends both design blobs/commit to Fenton, Mos and Sage. No code, private document,
|
||||
conversion, source build/browser test, queue entry or live change is authorized here.
|
||||
Megavolt reviews this separate document and records a verdict in
|
||||
`docs/reviews/2026-10-22_q195-docx-citations.md`. Revisions repeat direct
|
||||
commit/blob handoff to Megavolt and Sage. Q36 remains with Jason through Mos;
|
||||
no adoption, C2 reader/reply notice change or schema reinterpretation ships
|
||||
on this document. W4b's brief must allocate ownership for every surface in
|
||||
A16/A17 and section 7.4 before code: an unowned enforcement path is a blocker, not a deferred security
|
||||
note. No live files, editor source imports, parser execution or tests were
|
||||
used to write this design.
|
||||
|
||||
## Round 2 changes against review 08fcb8c1c
|
||||
|
||||
B1: exact selection wrapper, canonical paragraph identity, wiki/anchor schema,
|
||||
chip/share and refusal records; ordinal-primary D25 refinement explicit.
|
||||
B2: closed leaves and nested/paragraph-mark revision rules. B3: per-document
|
||||
publication sequence and sibling-save acceptance. B4/B5: concrete append-only
|
||||
authority, human proof, exact/latest share policy, revision vector and
|
||||
single-human dedicated session lifetime. B6: complete-row paging; oversized
|
||||
paragraph unsupported, no circular subrange API. B7: no remotes/backup push,
|
||||
seat token and copy probes. B8: named native stores, renderers and immutable
|
||||
run evidence, with O2-only storage/adapter readiness. B9: tables/columns,
|
||||
rollback separation and extractor retention. M1: dedicated O2 conversations
|
||||
only; mixed C2 context refused, no C2 requirement change. N1-N7 addressed in
|
||||
canonical manifest, bound-business check, ratio/default wording, UI unavailable
|
||||
reason, release-derived readiness and new A23-A31 acceptance rows. These are
|
||||
proposed definitions, not passing implementation tests. Q36/adoption unchanged.
|
||||
|
||||
## Round 3 changes against review 0d627a2b3
|
||||
|
||||
R2-B1: paragraph-mark revisions preserve all run text; markup has a zero-width
|
||||
mark span, never a fabricated whole-paragraph deletion. Reserved note leaves
|
||||
are allowed only in reserved stories; whole-part structural validation is
|
||||
explicit. A31 covers both mark-only cases and reserved notes.
|
||||
R2-B2: bodies/quoted share payloads move out of SQLite into private content
|
||||
objects; backup/post-attempt metadata paths are named with access/retention
|
||||
rules. Every writing_* table is COUNTED/T46-classified. A non-restored deny
|
||||
fence changes epoch before any store swap; owner confirmation cannot revive
|
||||
old-epoch grants/shares. A32 tests revocation/restore and secret-marker sweeps.
|
||||
R2-N1: agent grant subject means agentInstance. R2-N2: structural leaves have
|
||||
one table. This remains design only; no code, restore, parser run or test.
|
||||
|
||||
## Round 4 changes against review 9fb999abf
|
||||
|
||||
R3-B1: reserved-note classification is by type with any canonical signed ID,
|
||||
not sign; normal notes require nonnegative IDs, and references to reserved
|
||||
notes refuse. A31 covers both actual Word layouts. R3-B2: first migration
|
||||
creates the durable initial epoch before schema commit; null restoreOperation
|
||||
needs no recovery confirmation, a restored epoch does. Missing established
|
||||
fences never reinitialize. A32 covers first enable and post-restore states.
|
||||
R3-N1: business/row/private random nonce are part of each content object and
|
||||
its path, so no cross-business deduplication or guessed-body digest oracle.
|
||||
R3-N2: removed the unsupported claim to detect privileged manual rollback
|
||||
of the fence itself. R3-N3: restore_store anchor corrected to :498.
|
||||
|
||||
### Public Word fixture confirmation (not an extractor test)
|
||||
|
||||
Inspected only public Microsoft-maintained test packages in memory using
|
||||
Python ZIP/XML readers, not the O2 parser or a live/user document. No binary
|
||||
was imported into this repository. Source:
|
||||
[OfficeDev/Open-Xml-PowerTools FootNote.docx](https://github.com/OfficeDev/Open-Xml-PowerTools/blob/a9187c5ec4199106562ec91c5ffd9f355251253b/NewDocxDocuments/FootNote.docx),
|
||||
14,214 bytes; DOCX SHA-256
|
||||
`40e2de78f0f70d8acf3db734df13f107c92986f1a40865cdf9772a8b55ab5e20`.
|
||||
Its app properties identify Microsoft Office Word, version 15.0000.
|
||||
footnotes.xml has separator=-1, continuationSeparator=0, normal note=1;
|
||||
endnotes.xml has separator=-1 and continuationSeparator=0.
|
||||
footnotes.xml SHA-256
|
||||
`1da08d9c0a4a1af2706c035dfb176defd179686f81418134b796d49649e850a0`;
|
||||
endnotes.xml SHA-256
|
||||
`f83e7f1c9f91b44db26b147b22ca615a13488bc6a8084febb392ee00a6ca4f5e`.
|
||||
This confirms the reviewer's -1/0 example in a Word-identified fixture.
|
||||
|
||||
An older independent Microsoft SDK fixture confirms why IDs cannot define
|
||||
the class:
|
||||
[Open-XML-SDK ForTestCase/footnote.docx](https://github.com/dotnet/Open-XML-SDK/blob/431ab05cf160248cc3885a4a766026d4f8243792/test/DocumentFormat.OpenXml.Tests.Assets/assets/TestDataStorage/v2FxTestFiles/ForTestCase/footnote.docx),
|
||||
69,706 bytes, SHA-256
|
||||
`26dc62a5d00e2c3c1df60e0d5bf1cac43b142a090ea595b9ad11ce73e3dab268`.
|
||||
It identifies Microsoft Office Word 12.0000 and uses reserved IDs 0/1 in
|
||||
both parts, with normal footnotes 2/3. No claim is made that the whole
|
||||
packages pass v1's otherwise deliberately restrictive projection rules.
|
||||
@@ -624,3 +624,13 @@ Open follow-up: scripts/case1-accept.sh:413 (at candidate3c4c463da) uses `tr < "
|
||||
|
||||
|
||||
Row276 mos-1345 R3 residual remains open: a same-uid process that escapes the session, or a stalled supervisor in that session, can retain descriptors to the private stdout/stderr spool after root unlinks it and keep growing the unlinked file. The outer bound releases the caller because no residual holds its capture pipe. Row276 adds no disk cap and no cross-session kill; this is not a cleanup or closure claim.
|
||||
|
||||
|
||||
## Row207 N1: "refuses at any business" has no test
|
||||
|
||||
Open follow-up (duckworth-0230 N1, sage-3043). Row 207 (#1705) lands on 9bb406c6c without a test that `mosaic access user remove` refuses while the user holds a grant at any business, not only the named one. A mutant adding `&& g.business === business` survives T4: checkAccess still refuses at exit 3, so nothing is written, but the reason and exit code are wrong. The next row that touches packages/cli/tests/access.test.mjs adds one T4 case with the grant at a second business and a mutant that the case kills. N2 (parse ignores stray flags) is pre-existing in grant and revoke and stays out of scope.
|
||||
|
||||
|
||||
## Row270 N6: ledger growth and the whole-file read on session.list
|
||||
|
||||
Open follow-up (duckworth-0228 N6), bound to row 272 (264e, #1777). Row 270 caps what the bus reads per compaction collection (compactionReadBytes, default 64 MiB) but an agent can still grow a session's compaction ledger by about 231 KB per collection, and session.list reads the whole file. Row 272 bounds the ledger or the read on session.list, with a test and a mutant, or records why the cap is enough.
|
||||
+6
-5
@@ -33,7 +33,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
|
||||
|
||||
<!-- mosaic-queue:begin -->
|
||||
|
||||
Generated from `docs/plans/queue.json` revision 3338 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
Generated from `docs/plans/queue.json` revision 3368 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
|
||||
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
@@ -239,11 +239,11 @@ Generated from `docs/plans/queue.json` revision 3338 by `queue render`. Do not e
|
||||
| 200 | stack-release.sh activate --sso for the Q175 phase 2 window (row 192 F3; chained on 192) | launchpad; reviewers webby | #1698 | done | — | Webby approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_stack-release-sso.md` § Brief: stack-release.sh activates a Console with --sso (Q175 phase 2 prerequisite) | Landed 9e1085816 on refactor (base 722136772), pushed. Patch-id 7ebfa6a5 matches git diff 00d4cc1f6..9df40878f on the 3 manifest paths. Manifest b3ead55c, Webby approve r3 29772. Gate on 9df40878f: packages 2675/0, scripts 429/0. Mutants 13/14, M12 ruled equivalent (sage-1446). |
|
||||
| 201 | FED R1: access.json loader and validator, and covers (#414) | gizmoduck; reviewers duckworth | #1699 | done | — | Duckworth approves the manifest; business suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R1, the access.json loader and validator | Landed afad4993f on refactor (squash of gizmoduck ae3c36f24..44e36fc4c, manifest 225597146daf...0c520). patch-id ba604ad5e matches the reviewed diff on all 3 paths. Gate on 44e36fc4c in the slot (gizmoduck-0060): business 173/0, 10 scripts 422/0, mutants 44/44. Duckworth approve 29581. |
|
||||
| 202 | FED R5: mosaic access list, grant and revoke (#414) | quackerjack; reviewers duckworth | #1700 | done | — | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R5, mosaic access list, grant and revoke | Chained on row 201's candidate (Q177), not its done: claimable now, lands after 201 and rebases onto its landing (Sage) |
|
||||
| 203 | FED R2: the federation key in the system config (#414) | quackerjack; reviewers webby | #1701 | briefed | — | Webby approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R2, the federation key in the system config | Chained on row 192's candidate (Q177): starts on 192's candidate, lands after 192 (Sage) |
|
||||
| 203 | FED R2: the federation key in the system config (#414) | quackerjack; reviewers webby | #1701 | done | — | Webby approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R2, the federation key in the system config | Landed a4da4e6df on refactor 23:47Z 10-22, fast-forward from c7b754d7b, 5 commits. Webby APPROVE c30877 rev 3366 on candidate a4da4e6df. Gate on Sage's slot: stack-release.test 219/0, test-config 51/0, M1-M17 killed, cli 895/0, ten scripts green, named skips only. Brief re-pinned 8adfb261f (Webby N2). 270+271 rebase onto this (mosaic-config.mjs conflict) as a new round. |
|
||||
| 204 | 193a: access level check widened to viewer (#1691) | morgana; reviewers duckworth | #1702 | done | — | Duckworth approves the manifest; business suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193a, the access level check widened to viewer | Landed d0bbc5c1c on refactor (base c9f374284). Patch-id 92c18aea matches git diff 44e36fc4c..f869fd062 on the 3 manifest paths. Manifest d38de8c9, Duckworth approve 29595. Gate 2789/0 on 4385b9ec1, mutants 7/7. Unpushed. |
|
||||
| 205 | 193b1a: the message filter (#1703; FED R3 and 193b1b build on it) | morgana; reviewers duckworth | #1703 | done | 196 done | Duckworth approves the manifest; bus suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b1a, the message filter | Landed ce6a96ad6 fast-forward on 0fa7bf421 (3 commits, 4 paths, manifest 858463b1). Duckworth APPROVE r1 c30375 (signed :124 test diff, mos-1262). Gate: Morgana slot205 10 files, bus 652/0, M1 M11-M14 killed, 0 skips; Sage test-*.sh 10/10, 3 by-design skips. N1/N3 to 205b or R3, N2 to 193b2 brief, N4 = #1782 before any user thread. Rebase next: 268 r4, 194, 236, 253, 274. |
|
||||
| 206 | 193b2: thread shares, schema 3k-3l after the #436b build (#1691; live needs a --migrate window with Jason) | launchpad; reviewers duckworth | #1704 | done | 205 done, 234 done, 236 done, 275 done | Duckworth approves the manifest; bus suite, cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b2, thread shares (schema 3l) | Landed 0cbe2e9b3 fast-forward on 17f415792 (10 commits, 22 paths), pushed 19:47Z. Duckworth APPROVE rev 3322 c30795 on 0cbe2e9b3; request rev 3320/3321 c30793 posted as jarvis. Gate (Launchpad, 9538e0fc8, tip differs in BUILD.md only): bus 850/0, cli 869/0, ten scripts/test-*.sh rc=0, touched files whole green, M2-M4 M7-M9 and Q2 killed. One placed fixture red (mos-1272). Schema 3l to 3m; live --migrate is Mos's go under Q173. |
|
||||
| 207 | 193c: mosaic access user verbs and --level viewer (#1691) | megavolt; reviewers duckworth | #1705 | in-progress | 202 done | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193c, mosaic access user verbs and --level viewer | — |
|
||||
| 207 | 193c: mosaic access user verbs and --level viewer (#1691) | megavolt; reviewers duckworth | #1705 | done | 202 done | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193c, mosaic access user verbs and --level viewer | LANDED c4f77373f 22:49Z 10-22 (fast-forward from f0e3dcdf4). Rebuild of gated 9bb406c6c: range-diff 4/4 =, patch-ids equal, 4 manifest blobs equal. Duckworth APPROVE c30849 rev 3356. Gate on 712f79c0a under sage-3041: access.test 31/31, M1-M8 killed, cli 894/894, ten scripts green. N1 DEFERRED (next access.test row). No live change. |
|
||||
| 208 | 193d: Console shared list, share controls and viewer read-only (#1691; also after FED R6 when filed) | morgana; reviewers duckworth | #1706 | queued | 192 done, 206 done | Duckworth approves the manifest; webui suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193d, the Console's shared list, share controls and viewer read-only | — |
|
||||
| 209 | FED R8: the hub window on 10.1.1.54, a recorded acceptance (#414, r4.11 b93f098f0; live window run by Jason and Mos, after the Case 2 install #416, Bean and Chet live and both Hermes gateways stopped) | sage; reviewers duckworth | — | queued | — | Every entry check's output is in the R8 record with who ran it; Jason signs in to the hub and sends one message, Melanie revokes, his next request is refused; Duckworth checks the record (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R8, the hub window on 10.1.1.54 | Stays queued until its entry gates near: Case 2 install on .54 (#416, by 2026-10-27), Bean and Chet live, both Hermes gateways on .54 stopped, R7, #412 phase 2, #419 answer. Filed per mos-1073 after duckworth-0034 (Sage) |
|
||||
| 210 | Agent host access with sudo through the credential store: grant per agent and host, the stack does ssh and sudo, the agent never sees the key (#1707, Vikunja #424, Q183/Q184, due 10-27; chained on row 199's candidate) | gizmoduck; reviewers duckworth | #1707 | done | — | Duckworth approves DESIGN.md, then the manifest; bus, business, cli suites and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_host-credentials.md` § Brief: agent host access with sudo through the credential store (#424) | Landed f5753c0e8 on refactor (base 9d165be8c), pushed. Patch-id 723b1c83 matches git diff ad95c888c..0485384e9 on the 23 manifest paths; landed files pass r5 manifest 688d38b9. Duckworth approve r2 rev 2592, r3 29851. Mutants 52/53, M22b equivalent (rev 2584). Gate on ad95c888c+patch: packages 2796/0, scripts 429/0; landed blobs equal the gate tree. |
|
||||
@@ -306,7 +306,7 @@ Generated from `docs/plans/queue.json` revision 3338 by `queue render`. Do not e
|
||||
| 267 | Case 2 C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | morgana; reviewers filbert | #1772 | queued | 216 done, 217 done, 224 done | Filbert approves the manifest. The brief's tests and the end-to-end crossover run whole plus every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | R8 accepted (sage-2218). Webby's R8 noise note (gosalyn-0127) is for the builder. |
|
||||
| 268 | Case 2 C2-01c playground levels: universe-level prefixes and draft universes | gizmoduck; reviewers webby | #1773 | done | — | Webby approves the manifest. The brief's tests run whole plus the library suite and every touched suite on Sage's slot; the brief's gate grep over packages and docs/case2 finds no library-playground-outside-book. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01c playground levels: universe-level prefixes and draft universes | Landed c27207f6e fast-forward on ce6a96ad6 (1 commit, 9 paths, digest caa0fa4e). r4 is a carry of r3 aca53c4f0: range-diff =, patch-id 2942acc8 equal, blobs equal. Webby APPROVE round 2 on ecd9b35eb stands; carries webby-0112 (r3) and webby-0113 (r4, notes 34e1ab2bf). Gate: Gizmoduck r4 rerun brief 14/14, library 83/83, business 209/209, 0 skips; gate grep empty. 194 rebases next (business.test.mjs). |
|
||||
| 269 | 264b T1 item 7: per-session native store, workspace and single-writer fence | gizmoduck; reviewers duckworth | #1774 | done | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T1 run half, T2 workspace and path mutants; whichever of 191 and 264b lands second wires 191's admission checks (Q5). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | Landed efcc9ea8d fast-forward on 94c33a43c (13 commits, 12 paths, manifest 455c12ee). r3 is a carry of r2 aafcc857c, range-diff = (Duckworth 0194, c30756, rev 3307). Gate r3 18:37-18:50Z all green, 0 skips: bus 834, seat 172, cli 869, case2-unit 43, singles 38. M1-M12 kills carry. Re-pin needs the Console restart by Jason or Mos right after the host. A 264a rollback refuses a v2 sessions.json, see BUILD.md Rollback. |
|
||||
| 270 | 264c T1 item 7: compaction count, exact for Pi | gizmoduck; reviewers duckworth | #1775 | briefed | 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Test T3 Pi half; Claude shows unknown with the Vikunja 466 reason (Q6). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
|
||||
| 270 | 264c T1 item 7: compaction count, exact for Pi | gizmoduck; reviewers duckworth | #1775 | in-review, round 1, request posted | 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Test T3 Pi half; Claude shows unknown with the Vikunja 466 reason (Q6). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
|
||||
| 271 | 264d T1 item 7: clear context through row 194's lifecycle stop | gizmoduck; reviewers duckworth | #1776 | in-review, round 1, request posted | 194 done, 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T4 and T6; rebased onto 194's landing with no 194 code copied (Q4). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
|
||||
| 272 | 264e T1 item 7: mosaic session CLI and Console chooser | huey; reviewers scrooge | #1777 | briefed | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T5 legacy single human and T7 Console half; controls for unlanded verbs refuse with a reason (Q1, Q7). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
|
||||
| 273 | Q175 phase 2: stack-release.sh activate --send and --no-sso (F39, mos-1246) | darkwing; reviewers filbert | #1778 | done | — | Filbert approves the manifest. stack-release.test.mjs and stack-release-external.test.mjs run whole plus scripts/test-release.sh on Sage's slot, and the brief's mutants are killed. (sage) | `docs/plans/2026-10-22_activate-send-nosso.md` § Brief: activate --send and --no-sso | landed 0bbf0f6d1 03:35:55Z, FF of 378a76b85; rebase of cac7d0f67, 6 range-diff pairs =, patch-ids equal, manifest e1031023 unchanged. Gate by Darkwing on 0bbf0f6d1: stack-release.test 218/218, external 25/25, test-release.sh rc 0, T58 c0 pass c1 fail, M1-M7 killed. Filbert APPROVE rev 3111 c30308, carry c30319. Docs 5365adaef (TOOLS.md N1 + BUILD-LOG). #1778. |
|
||||
@@ -327,7 +327,8 @@ Generated from `docs/plans/queue.json` revision 3338 by `queue render`. Do not e
|
||||
| 288 | Case 2 C2-02 agent writes, the notice, revert and her Console saves | honker; reviewers webby | #1793 | briefed | 215 done, 282 done, 283 done, 284 done, 285 done, 286 done | Webby approves the manifest. Library, bus and harness suites and every scripts/test-*.sh green on Sage's gate, mutants killed. A search of the run output for the fixture tokens finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-02 agent writes, the notice, revert and her Console saves | — |
|
||||
| 289 | Case 2 C2-11b Console library pages and her saves (Haiku subagent, decision 129) | researcher; reviewers filbert | #1794 | briefed | 214 done, 288 done | Filbert approves the manifest. The webui suite and every scripts/test-*.sh are green on Sage's gate. Grep for hash pins on bus.js and serve.mjs before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-11b Console library pages and her saves | — |
|
||||
| 290 | Case 2 C2-12b the setup sheet rewrite for free chat, supporting folders and saves | louie; reviewers webby | #1795 | briefed | 217 done, 218 done, 285 done, 291 done | Doc only. Webby approves it against the ruling. Before landing, grep packages/*/tests and scripts/test-*.sh to confirm no test reads the file. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-12b the setup sheet rewrite for free chat, supporting folders and saves | — |
|
||||
| 291 | PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | gosalyn; reviewers webby | #1796 | briefed | 282 done | Webby approves the text. Jason says yes to the exact text (Mos asks him, mos-1328 step 3); no landing before that. requirements.test.mjs and every scripts/test-*.sh that reads the PRD or requirements.json are green on Sage's gate. Mos gets the landing commit. (sage) | `docs/plans/2026-10-22_prd-1-16-brief.md` § PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | — |
|
||||
| 291 | PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | gosalyn; reviewers webby | #1796 | in-review, round 1, request posted | 282 done | Webby approves the text. Jason says yes to the exact text (Mos asks him, mos-1328 step 3); no landing before that. requirements.test.mjs and every scripts/test-*.sh that reads the PRD or requirements.json are green on Sage's gate. Mos gets the landing commit. (sage) | `docs/plans/2026-10-22_prd-1-16-brief.md` § PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | — |
|
||||
| 292 | stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | gander; reviewers duckworth | #1797 | briefed | 203 done, 270 done, 271 done | Duckworth approves the manifest. The touched test files run whole, the cli suite and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-22_config-checks-any-key-brief.md` § stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | — |
|
||||
|
||||
<!-- mosaic-queue:end -->
|
||||
|
||||
|
||||
+1045
-22
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,528 @@
|
||||
# Q195 O2 W4 review: committed DOCX citations and access
|
||||
|
||||
Reviewer Megavolt (Opus 5.5), assigned by Sage (sage-3061, mos-1360), Gitea #1799. Owner Rocko.
|
||||
Reviewed: `docs/plans/2026-10-22_q195-docx-citations.md`, blob 662abdb7c, commit 15e91538f (one
|
||||
docs-only commit on e36bce352). Against: parent blob 322ec50f2 (`docs/plans/2026-10-21_q195-writing-editor.md`)
|
||||
D11, D12, D24-D26, W4 and W4b. Static review only: no tests, parser runs or live files.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
The W4 Gate asks for "no unresolved locator or permission ambiguity" and for every bypass closed.
|
||||
The design gets the core right, but nine items are still open against that bar (B1-B9). One part of
|
||||
the design changes Case 2 behaviour and goes to Mos and Jason (M1). None of these needs a new
|
||||
architecture. Each one needs a definition, a rule or an acceptance row.
|
||||
|
||||
## What holds
|
||||
|
||||
- Snapshot: manifest and DOCX come from one reachable commit. There is no worktree, export or
|
||||
engine-memory fallback, and commit/blob is a locator, not an authority token (section 2).
|
||||
- Identity: a copy or new import gets a new document ID with no inherited grant, share, pin or cache.
|
||||
The same blob in another document is not selectable.
|
||||
- Quote verification: exact part, story and ordinal, a code-point range, literal quote equality,
|
||||
SHA-256 equality, blob and commit membership, and a pinned extractor revision. A stale locator
|
||||
returns old text labelled stale and never relocates.
|
||||
- Write paths: none. There is no native MCP, execute, save, replace, tracked-change or comment write,
|
||||
and flags belong to W5 as metadata.
|
||||
- Case 2: the line-cite schema, library grants and supporting-doc writer are unchanged, and no DOCX
|
||||
paragraph is called a line. Q36 stays open and adoption stays closed (mos-1358). A19 and A20 test
|
||||
this.
|
||||
- Parser: transitional namespace only, closed relationship types, no DTD, entities or external
|
||||
relationships, ZIP bounds before allocation, and refusal rather than a truncated success.
|
||||
- Document text is data: comments, deletions and wiki tokens never become instructions.
|
||||
- Text kept out of generic stores: the design keeps O2 text out of generic raw stores and logs,
|
||||
allows audit events to carry only IDs and digests, and does not let a shared extraction cache act
|
||||
as an authorization cache.
|
||||
|
||||
## Blocking findings
|
||||
|
||||
### B1. Several named records have no exact shape
|
||||
|
||||
Section 3 says "all objects reject unknown keys", but these objects are never specified:
|
||||
|
||||
- **The multi-paragraph citation.** Line 100-101 calls it "an ordered nonempty list of these
|
||||
ranges", but `editor_resolve_citation` takes one `{locator}`. The design needs to give:
|
||||
- the wrapper and its kind;
|
||||
- whether each range carries its own quote and digest;
|
||||
- whether every range must share commit, blob, extractor, view and part;
|
||||
- whether the paragraphs must be contiguous;
|
||||
- the maximum count;
|
||||
- how the resolver and the share chip accept the list.
|
||||
- **wikiTokens entries** (line 120 and section 5). The field names, offsets and forms (`link`,
|
||||
`label`, `heading`, `embed`) are not given.
|
||||
- **Anchor story.** Anchors are "a canonical decimal signed XML ID string" (line 127), but locator
|
||||
stories are nonnegative (line 88). Use one grammar for both.
|
||||
- **`paragraph.xmlId`.** It is "JSON null when absent" (line 89), but also "if provided" (line 97).
|
||||
Two locators for the same range must not both be valid. State that null means "this node has no
|
||||
`w14:paraId`" and that a node with one must carry it, or make it always null.
|
||||
- **Departure from D25.** D25 says "resolve paragraph IDs when present, otherwise an ordinal". The
|
||||
design makes the ordinal primary and the paraId only a check. I agree with that on the merits,
|
||||
since paraId is not unique, but the design should name it as a deliberate departure from D25's
|
||||
wording so that Sage's brief carries it. This is O2-only, not a Case 2 item.
|
||||
- **The share record, the chip request and the refusal envelope.** Section 7 needs:
|
||||
- the fields the WebUI sends to have a selection mapped;
|
||||
- the fields of the stored share record;
|
||||
- the error object's keys (section 6 gives only the codes).
|
||||
|
||||
### B2. Extraction is not closed enough to be deterministic
|
||||
|
||||
Section 5 lists containers and a few leaves, then refuses "unknown text-bearing elements". Offsets
|
||||
depend on how each run-level leaf counts, so two conforming extractors can disagree. The design
|
||||
needs a closed leaf table that gives each leaf's text contribution or refusal. At least these are
|
||||
missing:
|
||||
|
||||
- `w:annotationRef`. Word starts every comment with it, so whether comments are readable at all
|
||||
depends on an unstated choice.
|
||||
- `w:footnoteRef` and `w:endnoteRef`, the number mark inside the note's own story.
|
||||
- `w:noBreakHyphen`, `w:softHyphen`, `w:sym` and `w:ptab`.
|
||||
- `w:moveFromRangeStart/End` and `w:moveToRangeStart/End`, which always come with the moves the
|
||||
design supports.
|
||||
- `w:commentRangeStart/End`, as anchors.
|
||||
- `w:permStart/End`.
|
||||
- `w:fldChar`, `w:instrText` and `w:fldSimple`, as refusals.
|
||||
|
||||
Two revision rules are also unstated:
|
||||
|
||||
- **Nested revisions.** A run is in `final` if and only if no ancestor is `del` or `moveFrom`, and in
|
||||
`original` if and only if no ancestor is `ins` or `moveTo`. The design should say so.
|
||||
- **Paragraph-mark revisions.** For `w:pPr/w:rPr/w:ins|w:del`, state that views never merge or split
|
||||
paragraphs, so that an inserted paragraph shows as empty in `original`. That follows from "ordinals
|
||||
do not change with view" but should be explicit.
|
||||
|
||||
### B3. `currentCommit` and staleness are ambiguous for multi-chapter projects
|
||||
|
||||
D10 puts one DOCX per chapter under one book manifest, and the store is per project
|
||||
(`<dataRoot>/writing/<business>/<project>/`). `stale` is "exactly commit != currentCommit"
|
||||
(line 268). If `currentCommit` is the project's newest commit, then saving chapter 2 marks every
|
||||
chapter 1 citation stale and breaks chapter 1's latest cursors, even though chapter 1's blob did not
|
||||
change.
|
||||
|
||||
Two things need a definition:
|
||||
|
||||
- `currentCommit`: for example, the newest published commit that changed this document's DOCX blob
|
||||
or its manifest entry. Alternatively, define `stale` by blob plus manifest entry.
|
||||
- The document's "published history" (line 49): which ref, and which commits count.
|
||||
|
||||
Add an acceptance row: a save to a sibling document leaves `stale:false`.
|
||||
|
||||
### B4. The permission model is prose, not data
|
||||
|
||||
Section 7 intersects six inputs, but the grant side has no schema. Before W4b can implement this
|
||||
the same way twice, the design must answer:
|
||||
|
||||
- Where do document, part, view and comment scopes live: R5's access.json, a new bus table or the
|
||||
role files? Who writes them, through which human-only verb, with what proof, receipt and revision?
|
||||
Rows 207 and R5 already provide that pattern for business grants.
|
||||
- Who is "the principal" in an agent turn: the human who sent the chip, the seat, or both?
|
||||
- Can a `viewer`-level human share? Can a share ever expose a part or view that the sharing human's
|
||||
own grant does not cover?
|
||||
- In a thread with two humans, whose grants does `conversation-current` intersect?
|
||||
- "Historical-view scope" (line 290) appears once and is undefined. Is it a separate scope, or the
|
||||
live grant plus an exact share?
|
||||
- The cursor binds a "grant revision" (line 282). Which revision counts?
|
||||
|
||||
### B5. Share semantics contradict each other
|
||||
|
||||
- Line 329-331 says a share "does not widen it or authorize a later version". Line 340 says "latest
|
||||
whole-document reads require a whole-document share", and A15 says "whole-document latest share
|
||||
explicit". The design must say what a whole-document chip binds:
|
||||
- one commit or latest;
|
||||
- whether it includes notes, comments and the `original`/`markup` views.
|
||||
- Range shares: rows are whole paragraphs, so `editor_read_committed` must refuse under a range-only
|
||||
share. Only `editor_resolve_citation` works with one. State this.
|
||||
- `off` "revokes future reads". Does it also stop resolution of exact shares already sent?
|
||||
- `conversation-current` lasts "until human revocation/session end". Which session is that: the bus
|
||||
session, the thread session (row 269) or the Console login?
|
||||
|
||||
### B6. Paging rules contradict each other
|
||||
|
||||
- Overflow. The design says "response overflow never returns partial data with success", but paging
|
||||
must end a page before a row that would overflow. State that the page ends before the first row
|
||||
that would exceed 64 KiB, and that only a single row too large on its own refuses.
|
||||
- Paragraphs over 64 KiB. The design says a caller "may request a checked subrange through a
|
||||
separately bounded citation", but a citation needs the quote and its digest first, so such a
|
||||
paragraph is unreachable. Either define a bounded subrange read (ordinal plus a code-point window,
|
||||
returning text and a server-made locator) or declare these paragraphs unsupported in v1.
|
||||
|
||||
### B7. Git grants: the design does not forbid a remote
|
||||
|
||||
Case 2's library has a Gitea remote. Agent supporting-doc writes push through each seat's own Gitea
|
||||
account (Q226, case2-rows.md:1634-1641), and seat tokens carry `write:repository` and can fork
|
||||
(packages/bus/src/gitea-grant.mjs:29 and :297). The design says "no raw O2 git clone/read grant", but
|
||||
nothing stops an O2 project repo from getting a remote, mirror or backup push that any seat account
|
||||
can then list, fork or clone.
|
||||
|
||||
The design must state that O2 store repositories have no remote, and that no mirror, backup or push
|
||||
reaches a host an agent account can access. It also needs a stack-copy/release rule: copies never
|
||||
carry the store. Add an A16 probe: a seat Gitea token cannot list, fork or clone an O2 project.
|
||||
|
||||
### B8. The raw-session surfaces are deferred, not closed
|
||||
|
||||
Line 437-438 hands the surface inventory to the W4b brief. The W4 Gate wants raw sessions closed
|
||||
by this design. The stores already exist and can be named now, each with a rule (no O2 text,
|
||||
opaque reference only, or enforced envelope):
|
||||
|
||||
- seat session stores `threads/<business>/<thread>/session/` and `claude-projects/`,
|
||||
`threads/<business>/<thread>/sessions/<session>/` and `session-workspaces/...`
|
||||
(packages/seat/src/session.mjs:23-29);
|
||||
- the bus store's event and thread-message tables, and row 264's sessions;
|
||||
- the Console thread view;
|
||||
- the CLI's `thread`, talk and follow;
|
||||
- the Discord connector (packages/discord, including journal.mjs and context.mjs) and the
|
||||
control-board Discord path;
|
||||
- `<dataRoot>/runs/` run records.
|
||||
|
||||
Run records need a rule of their own. Invariant 5 makes them write-once, so O2 text written there
|
||||
can't be withdrawn on revocation, only pruned. State that O2 content never enters a run record.
|
||||
|
||||
### B9. The migration and compatibility plan is too thin to review
|
||||
|
||||
W4 asks for "a reviewed migration/compatibility plan". Lines 33-35 say only "new O2 records, a schema
|
||||
edge from Sage, no reinterpretation". The plan needs:
|
||||
|
||||
- **New tables and columns.** Name them, and state whether any existing table (thread messages,
|
||||
events) gains a provenance column.
|
||||
- **Rollback.** After `release.sh rollback` to a pre-W4b copy, the old code does not enforce the
|
||||
envelope. Any O2-derived text stored in an existing table becomes readable through the old thread
|
||||
views. Require that O2-derived content is stored only where pre-W4b code can't render it (a new
|
||||
table, or opaque references in existing tables), and add a rollback probe.
|
||||
- **Extractor revision retention.** Old locators need their old revision. Say whether activating a
|
||||
release that drops a revision is allowed, which turns those citations into
|
||||
`editor-version-unavailable`, or whether every published revision is retained.
|
||||
|
||||
## Mos/Jason item
|
||||
|
||||
**M1. O2-context turns narrow Case 2 behaviour.**
|
||||
|
||||
Section 7, lines 362-371, does two things in any turn that received O2 context:
|
||||
|
||||
- It refuses "generic supporting-doc writes, git writes, exports and arbitrary outgoing tools".
|
||||
- It classifies every output of that turn, and of any resumed or forked descendant, with the O2
|
||||
document scopes.
|
||||
|
||||
In a Case 2 thread, that means:
|
||||
|
||||
- C2-02's supporting-doc write, from the Q226 rows (mos-1309), stops working once the human shares
|
||||
a chip;
|
||||
- the agent's C2-06 replies become invisible to thread members without the O2 grant.
|
||||
|
||||
That changes a Case 2 requirement. Under the W4 Gate it goes to Jason through Mos before code, not
|
||||
as a design choice.
|
||||
|
||||
My recommendation: confine O2 context to O2-dedicated threads or sessions. Case 2 semantics then
|
||||
stay untouched, and the narrowing applies only where it was opted into. Otherwise Jason has to
|
||||
approve the narrowing for mixed threads.
|
||||
|
||||
The rest of the design changes no Case 2 requirement. Q36 and D12 stay open with Jason as before,
|
||||
and the design builds no notice.
|
||||
|
||||
## Non-blocking notes
|
||||
|
||||
- N1. Give the canonical serialization of the extractor build manifest that `extractor.revision`
|
||||
digests, so that the digest can be recomputed independently.
|
||||
- N2. A locator's `business` field must equal the broker-bound business. Otherwise refuse with
|
||||
`editor-access-refused`, the same response as for a foreign ID.
|
||||
- N3. The 100:1 expansion ratio: say whether it is per entry, total, or both.
|
||||
- N4. Line 68-69 says "no implicit defaults except the reading view", but `limit` defaults to 20 and
|
||||
`cursor` to null. Reword.
|
||||
- N5. Hyperlinks, fields and text boxes refuse the projection. Many real chapters will then be
|
||||
agent-unreadable. That is acceptable for v1, but the W4b brief and the human UI should say why a
|
||||
chapter is unavailable.
|
||||
- N6. Readiness should come from the verified release artifact (extractor manifest plus the
|
||||
formatting-property list), not a system-config field, since configs are human-authored
|
||||
(invariant 2).
|
||||
- N7. Add acceptance rows for B3 (sibling save), B6 (oversized paragraph), B7 (seat token against an
|
||||
O2 repo), B8 (each named store, including run records) and B9 (rollback).
|
||||
|
||||
## Next round
|
||||
|
||||
Revise the design for B1-B9 and send the new commit and blob to me and Sage. I re-review only the
|
||||
changed sections and the items above. M1 waits on Mos and Jason. If the design takes the
|
||||
O2-only-thread recommendation, M1 closes without a ruling.
|
||||
|
||||
## Round 2: 019a0ea5b (blob 14dfe92d9)
|
||||
|
||||
Reviewed: Rocko's round 2 commit 019a0ea5b, a fast-forward on 15e91538f, design blob 14dfe92d9.
|
||||
I read the whole file again, not only the changed lines, because the change was large (+453/-120).
|
||||
Static review only.
|
||||
|
||||
### Verdict: CHANGES
|
||||
|
||||
The design now closes B1-B9 and M1 as they were written. Two problems remain. R2-B1 is a
|
||||
projection rule that contradicts D25's "final accepted reading". R2-B2 is an existing backup and
|
||||
restore path that section 7 does not cover. Each needs a rule and an acceptance row, not a new
|
||||
design.
|
||||
|
||||
### Round 1 items
|
||||
|
||||
- **B1, closed.**
|
||||
- The `docx-selection` wrapper: 2..32 consecutive ranges, a shared snapshot, empty intermediate
|
||||
paragraphs, one budget for the whole selection, atomic failure and one canonical form.
|
||||
- `xmlId` is null exactly when the node has no paraId.
|
||||
- The wikiToken and anchor grammars now match the locator.
|
||||
- The chip, share and refusal envelopes are exact.
|
||||
- The departure from D25 is named (lines 108-111).
|
||||
- **B2, closed except for R2-B1.** The closed leaf table and the ancestor veto for nested revisions
|
||||
are in (lines 268-310).
|
||||
- **B3, closed.**
|
||||
- `refs/heads/writing-published` advances first-parent with no force.
|
||||
- Publication commits are defined per document, and `currentCommit` and stale are per document.
|
||||
- A23 covers a sibling save.
|
||||
- **B4, closed.**
|
||||
- The principal is a tuple.
|
||||
- Scope lives in `writing_access_events`, written by the business owner's existing human proof
|
||||
only.
|
||||
- Agent `mayShare` is false, and viewers cannot share.
|
||||
- The authority vector names its sources.
|
||||
- Two-human threads are refused (`editor-sharing-unsupported`).
|
||||
- **B5, closed.**
|
||||
- A range share allows resolve only.
|
||||
- A document chip binds one exact publication.
|
||||
- A latest share exists only under `conversation-current` with a whole-document chip.
|
||||
- `off` and any policy change invalidate all earlier shares.
|
||||
- The share's session is the new O2 execution session.
|
||||
- **B6, closed.** A page ends before the first row that would overflow. A row too large for an
|
||||
empty page is unsupported in v1, and A24 covers it.
|
||||
- **B7, closed.**
|
||||
- O2 repositories have no remotes, mirrors or forks, and the store refuses one.
|
||||
- Copies exclude `writing/` and `writing-context/`.
|
||||
- A25 includes a seat-token probe.
|
||||
- **B8, closed except for R2-B2.** Section 7.4 names every store and renderer I listed, plus
|
||||
`launches/` and `bus-host/sessions.json`. The run-records rule is explicit, and A26 probes each
|
||||
store.
|
||||
- **B9, closed except for R2-B2.**
|
||||
- New tables are named, append-only, with no change to existing columns.
|
||||
- Old renderers never query the new tables.
|
||||
- A rollback probe and an extractor-retention rule are in.
|
||||
- **M1, closed. No Mos/Jason item.** O2 context exists only in new, dedicated O2 conversations and
|
||||
execution sessions. Existing C2 threads cannot be converted, attached, resumed or forked into O2
|
||||
mode. C2-02 supporting-doc writes and C2-06 replies keep their meaning (lines 433-441), and A28
|
||||
tests that. This adds new O2 behaviour and changes no Case 2 requirement.
|
||||
- **N1-N7, addressed.** N1 is the canonical manifest bytes (lines 155-168). N2 is the business
|
||||
check (lines 378-379). N3 is the ratio rule at line 257. N4 is the defaults wording at line 84.
|
||||
N5 is the UI reason (lines 688-690). N6 is release-derived readiness. N7 is A23-A31.
|
||||
|
||||
### R2-B1. Paragraph-mark revisions drop accepted text, and reserved notes would refuse
|
||||
|
||||
**What the design says.** Lines 276-281: "an inserted paragraph mark makes that paragraph's
|
||||
projected text empty in original; a deleted paragraph mark makes it empty in final."
|
||||
|
||||
**What the markup means.** In WordprocessingML, `w:pPr/w:rPr/w:del` marks only the paragraph mark
|
||||
as deleted. Accepting the change merges this paragraph's text into the next one. The text is gone
|
||||
only if its own runs sit under `w:del`, and the ancestor veto already handles that. `w:ins` on the
|
||||
mark works the same way in `original`.
|
||||
|
||||
**Why that breaks the projection.** When a human joins two paragraphs with tracking on, the first
|
||||
paragraph's live text disappears from `final`. A quote taken from the accepted reading then can't be
|
||||
cited. That contradicts D25's "default final accepted reading" and the design's own definition at
|
||||
line 267.
|
||||
|
||||
**Fix.** Paragraph-mark revisions never change text. Runs follow the ancestor rule alone. Since
|
||||
views never merge or split paragraphs, a mark-only revision shows up only as a zero-width
|
||||
whole-paragraph-mark span in `markup`. Extend A31 with a mark-only deletion whose text stays in
|
||||
`final`, and a mark-only insertion whose text stays in `original`.
|
||||
|
||||
**Reserved notes.** The leaf table "is exhaustive; unknown leaves refuse" (line 305). Word writes a
|
||||
separator note and a continuation-separator note into every `footnotes.xml` and `endnotes.xml`,
|
||||
holding `w:separator` and `w:continuationSeparator` (and sometimes `w:continuationNotice`). Line
|
||||
227-229 calls these notes structural, but the table doesn't name those leaves. Read literally, every
|
||||
document that has footnotes refuses. Name the three leaves as allowed only inside reserved notes,
|
||||
structurally validated and never projected. Say whether leaf validation covers the whole part or
|
||||
only the requested story.
|
||||
|
||||
### R2-B2. The release backup and restore path keeps O2 text and can revive revoked access
|
||||
|
||||
**What the design says.**
|
||||
- Section 7.4 forbids "plaintext backup" of O2 material (lines 625-626).
|
||||
- Section 7.5 puts O2 message bodies, share selections (which hold quotes) and every grant and
|
||||
policy event into new tables in the bus store (lines 638-663).
|
||||
|
||||
**What the release script does.** `scripts/stack-release.sh` copies that store, in plaintext, on
|
||||
every activate:
|
||||
- `backup_store` writes `$DATA/backups/stack/<ACT>/bus/bus.sqlite` (lines 474-493).
|
||||
- It never prunes backups (line 18).
|
||||
- `rehearse` reads them (line 22).
|
||||
- `restore_store` moves the current `bus/` into a post-attempt directory and deletes nothing
|
||||
(line 496).
|
||||
|
||||
So O2 text persists in backups and post-attempt directories that section 7.4 does not name.
|
||||
|
||||
**The restore gap.** `rollback --restore` puts back an older store. The guard counts `events` plus
|
||||
the fixed `COUNTED` list (lines 16-17 and 418-420). The design keeps O2 writes out of generic
|
||||
events (lines 659-661), and the new tables are not in `COUNTED`. A restore over revocations made
|
||||
after the backup therefore passes the guard without Jason's `--events N`. It also silently brings
|
||||
back the revoked `writing_access_events`, `writing_policy_events` and session-end rows. That
|
||||
revives revoked grants and shares, against section 7.3's "a scope decrease/revocation denies even
|
||||
historical shares".
|
||||
|
||||
**What the design needs to state.**
|
||||
1. **Backups.** Either O2 bodies and quotes live outside `bus.sqlite` (for example under
|
||||
`writing-context/`) so that release backups hold opaque rows only, or release backups and
|
||||
post-attempt directories are named O2-classified stores in section 7.4, with mode, access and a
|
||||
prune rule.
|
||||
2. **The guard.** Every `writing_*` table joins the restore guard's counted list. The T46 guard
|
||||
already requires every schema table to be classified.
|
||||
3. **After a restore.** A restore that drops any `writing_*` row disables O2 readiness until the
|
||||
owner reconfirms access. It never brings back an older grant, share or policy.
|
||||
4. **Acceptance.** Add a row: a revocation after the backup, then a restore, must leave the revoked
|
||||
share refused. A secret marker must stay absent from every backup and post-attempt path except
|
||||
the named O2-classified ones.
|
||||
|
||||
### Non-blocking
|
||||
|
||||
- R2-N1. Define the agent `subject.id` for `editor.access.set` as the bus agent instance ID (the
|
||||
principal tuple's `agentInstance`), so a grant can't name a role or a seat display name.
|
||||
- R2-N2. Lines 330-332 restate the structural leaves already in the table. Drop the sentence or
|
||||
point it to the table, so there is one list.
|
||||
|
||||
### Next round
|
||||
|
||||
Fix R2-B1 and R2-B2 and send the commit and blob to me and Sage. I'll re-check those two items and
|
||||
any lines they touch.
|
||||
|
||||
## Round 3: 54246cb32 (blob 0c4f55826)
|
||||
|
||||
Reviewed: Rocko's round 3 commit 54246cb32, a fast-forward on 019a0ea5b, design blob 0c4f55826.
|
||||
I read the whole delta and every definition it touches. I checked its citations against the same
|
||||
commit: `COUNTED` is at `scripts/stack-release.sh:420`, T46 is at
|
||||
`packages/cli/tests/stack-release.test.mjs:913`, and the guard counts `WHERE at > STAMP`, which
|
||||
works with the `at` column every `writing_*` table has. Static review only.
|
||||
|
||||
### Verdict: CHANGES
|
||||
|
||||
R2-B1's paragraph-mark rule and all of R2-B2 are closed. One gap from R2-B1 is still open, and the
|
||||
new fence adds a second. Each needs a sentence or two, not a redesign.
|
||||
|
||||
### Round 2 items
|
||||
|
||||
- **R2-B1, paragraph marks: closed.**
|
||||
- A mark-only `w:ins`/`w:del` changes no projected text in any view.
|
||||
- Paragraphs are never merged or split.
|
||||
- Markup records a zero-width `paragraph-mark-insert`/`paragraph-mark-delete` span, and
|
||||
`revisionSpans` allows those kinds with start=end.
|
||||
- A31 covers both cases.
|
||||
- **R2-B1, reserved-note leaves: open.** The table rows and whole-part validation are in (lines
|
||||
301 and 311-318). How a reserved note is recognized is still wrong; see R3-B1.
|
||||
- **R2-B2: closed.**
|
||||
- Message bodies and share payloads leave SQLite for 0600 content objects under
|
||||
`writing-context/objects/`. The bus tables hold only refs and digests.
|
||||
- Backup and post-attempt trees are named O2-classified metadata stores, with modes, link checks,
|
||||
rehearse limits and a receipt-based prune that only a human can run.
|
||||
- All eight `writing_*` tables join `COUNTED` and T46, and row identities are compared.
|
||||
- An independent fence epoch, set before every store swap, stops restored rows from granting
|
||||
access, and owner confirmation can't revive them.
|
||||
- A32 covers the revocation, restore, crash and marker sweep.
|
||||
- **R2-N1: closed.** The agent `subject.id` is the agent instance ID.
|
||||
- **R2-N2: closed.** The structural-leaf list now points to the table.
|
||||
|
||||
### R3-B1. Reserved notes are recognized by negative ID
|
||||
|
||||
Line 228 defines reserved notes as "negative IDs and type separator/continuationSeparator". Word
|
||||
normally writes the separator as `w:id="-1"` and the continuation separator as `w:id="0"`, so the
|
||||
continuation separator is not negative. A `w:type="continuationNotice"` note is a third reserved
|
||||
type, and line 228 leaves it out, although the leaf table names it.
|
||||
|
||||
Read with lines 315-317 ("A separator leaf in a normal note/body/comment refuses"), the id-0
|
||||
continuation separator counts as a normal note. Its `continuationSeparator` leaf then refuses, and
|
||||
every footnoted document Word writes is refused again. ECMA-376 identifies these notes by `w:type`;
|
||||
the ID is only a number. I'm working from Word's usual output here, so a fixture from real Word
|
||||
should confirm it.
|
||||
|
||||
Fix:
|
||||
- Classify a note as reserved by `w:type` in {separator, continuationSeparator,
|
||||
continuationNotice}, with any integer ID.
|
||||
- A normal note has no `w:type` or `w:type="normal"` and a canonical nonnegative ID.
|
||||
- A reference to a reserved note refuses.
|
||||
- Add Word's -1/0 layout to A31.
|
||||
|
||||
### R3-B2. The fence's first state and when confirmation is needed are undefined
|
||||
|
||||
Line 791 says a missing fence denies O2. Only release and the owner-proof handler may write it
|
||||
(lines 762-763), and only "before any store swap" is a write moment defined (line 767). Nothing says
|
||||
who creates the fence when W4b's tables are first created, or with what epoch.
|
||||
|
||||
It is also unclear whether `confirmedBusinesses` is consulted on a store that has never been
|
||||
restored. Read literally, a fresh fence has no confirmed business, so O2 stays off until an
|
||||
`editor.restore.confirm` that nothing calls for. The other reading, confirmation only after a
|
||||
restore, needs a rule saying so.
|
||||
|
||||
Either way this is a gap in an authorization check: it fails closed, but an implementer has to
|
||||
invent the enable path. Fix:
|
||||
- State that the compatible controller creates the fence durably with the W4b migration (or at
|
||||
first O2 enable), with a fresh epoch, `restoreOperation` null and the empty dropped-rows digest.
|
||||
- State whether `confirmedBusinesses` gates O2 always or only after a fence whose
|
||||
`restoreOperation` is non-null.
|
||||
- Add both states to A32.
|
||||
|
||||
### Non-blocking
|
||||
|
||||
- R3-N1. Content objects are named by the SHA-256 of `{body,provenance}`. Two equal objects in
|
||||
different businesses share one file, so a per-business prune or retention action can delete
|
||||
another business's message. The digest kept in metadata backups also lets anyone confirm a
|
||||
guessed short body ("ok"). Put the row `id` (or a random nonce) in the canonical object so each
|
||||
digest is unique and can't be guessed.
|
||||
- R3-N2. Line 791 says rolled-back fence state denies O2, but the fence keeps only its current epoch,
|
||||
so nothing can detect a rollback. Either define the check (for example, a monotonic fence
|
||||
generation that every new row also records, and deny when the bus holds a newer generation than
|
||||
the fence) or drop the claim.
|
||||
- R3-N3. `restore_store` begins at `scripts/stack-release.sh:498`; line 727 cites ":496ff", as my
|
||||
round 2 did. Cosmetic.
|
||||
|
||||
### Next round
|
||||
|
||||
Fix R3-B1 and R3-B2 and send me and Sage the commit and blob. I'll re-check those two items and the
|
||||
lines they touch.
|
||||
|
||||
## Round 4: 994c3f7fb (blob 2af460ab7)
|
||||
|
||||
Reviewed: Rocko's round 4 commit 994c3f7fb, a fast-forward on 54246cb32, design blob 2af460ab7.
|
||||
I read the whole delta and the definitions it touches. Static review only. I did not download the
|
||||
two public fixtures Rocko cites, so their sizes and hashes are Rocko's; what they report (-1/0 in
|
||||
Word 15, 0/1 in Word 12) matches my round 3 claim and also shows that an ID can't identify a
|
||||
reserved note.
|
||||
|
||||
### Verdict: APPROVE
|
||||
|
||||
Every blocker from rounds 1-3 is closed, and M1 closed in round 2 with no Mos/Jason item. The
|
||||
design meets the W4 Gate (spec blob 322ec50f2, lines 806-850):
|
||||
- no locator or permission ambiguity is left;
|
||||
- new O2 copies, generic reads, git grants and raw sessions are closed as bypasses;
|
||||
- quotes verify against a committed blob and an extractor revision;
|
||||
- there is no unsaved-context or agent write path;
|
||||
- Case 2 Markdown authorization is unchanged;
|
||||
- a migration and compatibility plan is present.
|
||||
|
||||
W4b owns the implementation and the A1-A32 evidence. This approval covers the design, not passing
|
||||
tests.
|
||||
|
||||
### Round 3 items
|
||||
|
||||
- **R3-B1: closed.**
|
||||
- Reserved notes are classified by `w:type` in {separator, continuationSeparator,
|
||||
continuationNotice}, with any canonical signed ID (line 227).
|
||||
- A normal note has no type or type normal, plus a nonnegative ID; unknown types refuse.
|
||||
- Any reference to a reserved note refuses, including an id-0 continuation separator.
|
||||
- Duplicate IDs within a part refuse.
|
||||
- A31 covers both the -1/0 and 0/1 layouts.
|
||||
- **R3-B2: closed.**
|
||||
- The first schema migration creates the fence durably before the tables commit, with a fresh
|
||||
epoch, `restoreOperation` null, the `[]` digest and no confirmed businesses (line 781).
|
||||
- Confirmation applies only when `restoreOperation` is non-null (line 819).
|
||||
- A missing or malformed fence over existing `writing_*` tables or history fails closed and
|
||||
never reinitializes (line 790), and a crash resumes on the same epoch.
|
||||
- A32 covers both states.
|
||||
- **R3-N1: closed.**
|
||||
- Each object holds business, project, row and a private 32-byte nonce, and lives under
|
||||
`objects/<business>/<row>/`.
|
||||
- There's no cross-row deduplication, and retention is per owning row.
|
||||
- **R3-N2: closed.** The rollback-detection claim is replaced by an honest limit: a same-uid
|
||||
operator bypassing the protocol is outside what the file can prove.
|
||||
- **R3-N3: closed.** The `:498` citation is fixed.
|
||||
|
||||
### Non-blocking, for W4b
|
||||
|
||||
- R4-N1. Line 790 allows initialization only with no `writing_*` tables and no fence. One case is
|
||||
unnamed: a compatible-controller restore of a backup taken before W4b, then a re-migration. The
|
||||
tables are then absent while a rotated fence with a non-null `restoreOperation` exists. Neither
|
||||
reading opens a bypass, because the restore dropped every `writing_*` row. W4b should still say
|
||||
that re-migration keeps the existing rotated epoch and its confirmation requirement, rather than
|
||||
refusing forever or writing a new null fence. Add that case to A32.
|
||||
@@ -30,7 +30,7 @@ import { fixture } from "./helpers.mjs";
|
||||
const TASK = "vikunja:1/2";
|
||||
|
||||
function gitIn(cwd, ...args) {
|
||||
const r = spawnSync("git", ["-c", "user.name=test", "-c", "[email protected]", "-C", cwd, ...args], { env: gitEnv(), encoding: "utf8" });
|
||||
const r = spawnSync("git", ["-c", "maintenance.auto=false", "-c", "gc.auto=0", "-c", "user.name=test", "-c", "[email protected]", "-C", cwd, ...args], { env: gitEnv(), encoding: "utf8" });
|
||||
assert.equal(r.status, 0, r.stderr);
|
||||
return r.stdout.trim();
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ async function until(fn, ms = 15000) {
|
||||
}
|
||||
|
||||
function gitIn(cwd, ...args) {
|
||||
const r = spawnSync("git", ["-c", "user.name=test", "-c", "[email protected]", "-C", cwd, ...args], { env: gitEnv(), encoding: "utf8" });
|
||||
const r = spawnSync("git", ["-c", "maintenance.auto=false", "-c", "gc.auto=0", "-c", "user.name=test", "-c", "[email protected]", "-C", cwd, ...args], { env: gitEnv(), encoding: "utf8" });
|
||||
assert.equal(r.status, 0, r.stderr);
|
||||
}
|
||||
|
||||
|
||||
@@ -295,9 +295,10 @@ export const KINDS_OLD=KINDS_NEW.replace('\n harness: Object.freeze({','\n "ha
|
||||
assert.notEqual(KINDS_OLD,KINDS_NEW);
|
||||
// Row 200 (#1698): C1 and C take --sso and have the row 192 loader and checkConsole; C's checker
|
||||
// refuses RS256, a stand-in for a target whose rules differ from the repository's. B has the loader
|
||||
// from before row 192, which refuses a `console` key. The other copies have no loader.
|
||||
// from before row 192, which refuses a `console` key. The other copies have no loader. Row 203: B's
|
||||
// loader also refuses a `federation` key, as a pre-203 loader does (its list has no federation).
|
||||
const LOADER=readFileSync(join(repo,'scripts','mosaic-config.mjs'),'utf8'),SSO_CONFIG=readFileSync(join(repo,'packages','webui','src','sso-config.mjs'),'utf8');
|
||||
const LOADER_OLD=LOADER.replace('["configVersion", "environment", "dataRoot", "execution", "console"]','["configVersion", "environment", "dataRoot", "execution"]');
|
||||
const LOADER_OLD=LOADER.replace('["configVersion", "environment", "dataRoot", "execution", "console", "federation"]','["configVersion", "environment", "dataRoot", "execution"]');
|
||||
const SSO_CONFIG_C=SSO_CONFIG.replace("'ES256', 'RS256',","'ES256',");
|
||||
assert.notEqual(LOADER_OLD,LOADER);assert.notEqual(SSO_CONFIG_C,SSO_CONFIG);
|
||||
const ssoFiles=(id)=>id===B?[['scripts/mosaic-config.mjs',LOADER_OLD]]:[C1,C].includes(id)?[['scripts/mosaic-config.mjs',LOADER],['packages/webui/src/sso-config.mjs',id===C?SSO_CONFIG_C:SSO_CONFIG]]:[];
|
||||
|
||||
@@ -1468,7 +1468,7 @@ const SHOW=`show-${CON}.txt`;
|
||||
const setOf=(e,act)=>join(e.state,'stack-rollback',act);
|
||||
const arrivalSet=(e,to)=>setOf(e,logLines(e).findLast((l)=>l.result==='ok'&&l.to===to&&l.from!==to).activation);
|
||||
const ROLLBACK_PLAN=(set)=>`sso: dropped; ${B}'s Console does not support --sso, so the rollback takes the Console flags from ${set}/${SHOW}, with --tester-port as the pointer has it`;
|
||||
const KEY_MSG=(e,to,op,why)=>`${e.cfg} holds a console key, and ${to}'s loader refuses it (${why}); remove the key first (restore the config.json backup from the window's step 1), then run the ${op} again`;
|
||||
const KEY_MSG=(e,to,op,why)=>`${e.cfg} holds a console or federation key, and ${to}'s loader refuses it (${why}); remove the key first (restore the config.json backup from the window's step 1), then run the ${op} again`;
|
||||
const RB_MSG=(why)=>`${B}'s Console does not support --sso, and the Console flags it ran with cannot be read: ${why}`;
|
||||
// Rewrites a file in a rollback set (0700, files kept by cp -p) and, unless sums is false, its SHA256SUMS line.
|
||||
function setEdit(set,name,from,to,sums=true){
|
||||
@@ -1582,6 +1582,19 @@ test('T57 row 200: activate --sso, its refusals before any stop, and the rollbac
|
||||
r=await prun(e,'rollback','acme');assert.equal(r.code,0,r.out+r.err);assert.equal(pointer(e).copy,B);
|
||||
await noLeak(e);
|
||||
});
|
||||
await t.test('row 203: a config whose federation key the target loader refuses stops an activate or a rollback before any stop, naming the first step',async(t)=>{
|
||||
const e=await mk(t);await seedPreview(e);
|
||||
let r=await prun(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
|
||||
// B's loader has no federation key in its list, as a pre-203 loader has none: it refuses the key.
|
||||
const setFed=(fed)=>{const c=JSON.parse(readFileSync(e.cfg,'utf8'));if(fed===undefined)delete c.federation;else c.federation=fed;writeFileSync(e.cfg,JSON.stringify(c))};
|
||||
setFed({self:'hub',peers:[{id:'factory',origin:'https://mosaic.woltje.com',label:'Software factory'}]});
|
||||
const why='mosaic-config: unsupported configuration key: "federation"';
|
||||
await refused(e,['activate',B,'acme'],'config-console-key',KEY_MSG(e,B,'activate',why));
|
||||
await refused(e,['rollback','acme'],'config-console-key',KEY_MSG(e,B,'rollback',why));
|
||||
setFed(undefined);
|
||||
r=await prun(e,'rollback','acme');assert.equal(r.code,0,r.out+r.err);assert.equal(pointer(e).copy,B);
|
||||
await noLeak(e);
|
||||
});
|
||||
await t.test('item 3: a rollback from --sso to a Console without it takes the flags from the set of the activation onto the copy, past a same-copy re-pin (sage-1389)',async(t)=>{
|
||||
const e=await mk(t),base=await seedPreview(e,undefined,['--send','--tester-port','7333']);await ssoConfig(e);
|
||||
let r=await prun(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
|
||||
|
||||
@@ -32,7 +32,7 @@ function tmp(t) {
|
||||
|
||||
// git for the test's own setup, with no user config.
|
||||
function git(cwd, ...args) {
|
||||
const r = spawnSync("git", ["-c", "user.name=t", "-c", "[email protected]", ...args], { cwd, encoding: "utf8", env: gitEnv() });
|
||||
const r = spawnSync("git", ["-c", "maintenance.auto=false", "-c", "gc.auto=0", "-c", "user.name=t", "-c", "[email protected]", ...args], { cwd, encoding: "utf8", env: gitEnv() });
|
||||
assert.equal(r.status, 0, `git ${args.join(" ")}: ${r.stderr}`);
|
||||
return r.stdout.trim();
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ function tmp(t) {
|
||||
}
|
||||
|
||||
function git(cwd, ...args) {
|
||||
const r = spawnSync("git", ["-c", "user.name=t", "-c", "[email protected]", ...args], { cwd, encoding: "utf8", env: gitEnv() });
|
||||
const r = spawnSync("git", ["-c", "maintenance.auto=false", "-c", "gc.auto=0", "-c", "user.name=t", "-c", "[email protected]", ...args], { cwd, encoding: "utf8", env: gitEnv() });
|
||||
assert.equal(r.status, 0, `git ${args.join(" ")}: ${r.stderr}`);
|
||||
return r.stdout.trim();
|
||||
}
|
||||
|
||||
@@ -237,13 +237,66 @@ async function validateConsole(value) {
|
||||
}
|
||||
}
|
||||
|
||||
// FED R2 (#1701, DESIGN.md section 3.1 D1): the optional `federation` key names this instance and its
|
||||
// peers. Ids and origins are labels, not trust: nothing here grants a peer anything (3.1, W1). The
|
||||
// origin must be exactly an https origin, the form `new URL(origin).origin` returns, so a path, a
|
||||
// trailing slash, a port that is the default, or userinfo refuses.
|
||||
const FEDERATION_ID_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
|
||||
const FEDERATION_LABEL_MAX = 80;
|
||||
|
||||
function validateFederationOrigin(value, where) {
|
||||
let url = null;
|
||||
try {
|
||||
url = new URL(value);
|
||||
} catch {
|
||||
url = null;
|
||||
}
|
||||
if (url === null || url.protocol !== "https:" || url.origin !== value) {
|
||||
fail(2, `${where} must be an https origin with no path, query, or userinfo (got ${JSON.stringify(value)})`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateFederation(value) {
|
||||
if (!isPlainObject(value)) {
|
||||
fail(2, '"federation" must be a JSON object');
|
||||
}
|
||||
rejectUnknownKeys(value, ["self", "peers"], "federation");
|
||||
if (typeof value.self !== "string" || !FEDERATION_ID_PATTERN.test(value.self)) {
|
||||
fail(2, `federation.self must match ${FEDERATION_ID_PATTERN} (got ${JSON.stringify(value.self)})`);
|
||||
}
|
||||
if (!Array.isArray(value.peers)) {
|
||||
fail(2, 'federation.peers must be an array');
|
||||
}
|
||||
const ids = new Set([value.self]);
|
||||
const peers = value.peers.map((peer, index) => {
|
||||
const where = `federation.peers[${index}]`;
|
||||
if (!isPlainObject(peer)) {
|
||||
fail(2, `${where} must be a JSON object`);
|
||||
}
|
||||
rejectUnknownKeys(peer, ["id", "origin", "label"], where);
|
||||
if (typeof peer.id !== "string" || !FEDERATION_ID_PATTERN.test(peer.id)) {
|
||||
fail(2, `${where}.id must match ${FEDERATION_ID_PATTERN} (got ${JSON.stringify(peer.id)})`);
|
||||
}
|
||||
if (ids.has(peer.id)) {
|
||||
fail(2, `${where}.id ${JSON.stringify(peer.id)} is already named (by federation.self or an earlier peer)`);
|
||||
}
|
||||
ids.add(peer.id);
|
||||
validateFederationOrigin(peer.origin, `${where}.origin`);
|
||||
if (typeof peer.label !== "string" || peer.label.trim() === "" || peer.label.length > FEDERATION_LABEL_MAX) {
|
||||
fail(2, `${where}.label must be a non-empty string of at most ${FEDERATION_LABEL_MAX} characters`);
|
||||
}
|
||||
return { id: peer.id, origin: peer.origin, label: peer.label };
|
||||
});
|
||||
return { self: value.self, peers };
|
||||
}
|
||||
|
||||
async function validate(document, file) {
|
||||
if (!isPlainObject(document)) {
|
||||
fail(2, "configuration must be a JSON object");
|
||||
}
|
||||
rejectUnknownKeys(
|
||||
document,
|
||||
["configVersion", "environment", "dataRoot", "execution", "console"],
|
||||
["configVersion", "environment", "dataRoot", "execution", "console", "federation"],
|
||||
"configuration",
|
||||
);
|
||||
|
||||
@@ -281,6 +334,9 @@ async function validate(document, file) {
|
||||
// it, so a config without it prints what it always printed.
|
||||
const consoleKey = Object.hasOwn(document, "console") ? await validateConsole(document.console) : undefined;
|
||||
|
||||
// FED R2: the same rule as `console`. The key appears in the output only when the file has it.
|
||||
const federationKey = Object.hasOwn(document, "federation") ? validateFederation(document.federation) : undefined;
|
||||
|
||||
return {
|
||||
configVersion: document.configVersion,
|
||||
environment: document.environment,
|
||||
@@ -292,6 +348,7 @@ async function validate(document, file) {
|
||||
adapter,
|
||||
},
|
||||
...(consoleKey === undefined ? {} : { console: consoleKey }),
|
||||
...(federationKey === undefined ? {} : { federation: federationKey }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1078,12 +1078,12 @@ slot_problem() {
|
||||
# target's own scripts/mosaic-config.mjs validate; its output, the resolved config, is not 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 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=$?
|
||||
[ "$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"
|
||||
@@ -1093,7 +1093,7 @@ process.exit(d!==null&&typeof d==="object"&&Object.hasOwn(d,"console")?0:1)' --
|
||||
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"
|
||||
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"
|
||||
}
|
||||
|
||||
# Row 189 (mos-0972): --no-send drops --send from RUN_CONSOLE, so the unit and the pointer both lose
|
||||
|
||||
@@ -191,6 +191,72 @@ expect_refusal "console.sso slot name outside the pattern exits 2 naming the pat
|
||||
expect_refusal "unknown key under console exits 2 naming the path" \
|
||||
"$(sso_body '{"sso":{"publicOrigin":"https://console.test","providers":['"$(provider)"']},"extra":1}')" 'console.extra is not a supported key'
|
||||
|
||||
# --- FED R2 (#1701, DESIGN.md section 3.1): the optional federation key ---
|
||||
# fed_body FEDERATION-JSON: a config whose federation key is the argument.
|
||||
fed_body() {
|
||||
printf '{"configVersion":1,"environment":"development","dataRoot":"%s","execution":{"backend":"docker","provider":"zai","model":"m"},"federation":%s}' "$DATA_ROOT" "$1"
|
||||
}
|
||||
FED_VALID='{"self":"hub","peers":[{"id":"factory","origin":"https://mosaic.woltje.com","label":"Software factory"}]}'
|
||||
|
||||
cfg fed-valid.json "$(fed_body "$FED_VALID")"
|
||||
OUT="$(MOSAIC_CONFIG="$SANDBOX/fed-valid.json" timeout 20 $CONFIG_OP validate)"
|
||||
[ $? -eq 0 ] && printf '%s' "$OUT" | grep -q '"self": "hub"' && printf '%s' "$OUT" | grep -q '"origin": "https://mosaic.woltje.com"'
|
||||
check "valid federation key loads and is in the output" $?
|
||||
|
||||
# expect_fed_refusal NAME PATH-IN-MESSAGE FEDERATION-JSON: exit 2, and the message names the bad key path.
|
||||
expect_fed_refusal() {
|
||||
cfg fed-bad.json "$(fed_body "$3")"
|
||||
local err rc
|
||||
err="$(MOSAIC_CONFIG="$SANDBOX/fed-bad.json" timeout 20 $CONFIG_OP validate 2>&1 >/dev/null)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 2 ] && printf '%s' "$err" | grep -qF -- "$2"
|
||||
check "$1 (exit $rc)" $?
|
||||
}
|
||||
expect_fed_refusal "federation unknown sub-key exits 2 naming the key" 'unsupported federation key: "extra"' \
|
||||
'{"self":"hub","peers":[],"extra":1}'
|
||||
expect_fed_refusal "federation peer unknown sub-key exits 2 naming the key" 'unsupported federation.peers[0] key: "trust"' \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"https://mosaic.woltje.com","label":"F","trust":"full"}]}'
|
||||
expect_fed_refusal "federation peer origin over http exits 2 naming the key" "federation.peers[0].origin" \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"http://mosaic.woltje.com","label":"F"}]}'
|
||||
expect_fed_refusal "federation peer origin with a path exits 2 naming the key" "federation.peers[0].origin" \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"https://mosaic.woltje.com/console","label":"F"}]}'
|
||||
expect_fed_refusal "federation peer origin with userinfo exits 2 naming the key" "federation.peers[0].origin" \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"https://[email protected]","label":"F"}]}'
|
||||
expect_fed_refusal "federation peer id equal to self exits 2 naming the key" "federation.peers[0].id" \
|
||||
'{"self":"hub","peers":[{"id":"hub","origin":"https://mosaic.woltje.com","label":"F"}]}'
|
||||
expect_fed_refusal "federation peer label empty exits 2 naming the key" "federation.peers[0].label" \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"https://mosaic.woltje.com","label":""}]}'
|
||||
expect_fed_refusal "federation not an object exits 2 naming the key" '"federation" must be a JSON object' '"hub"'
|
||||
expect_fed_refusal "federation self pattern exits 2 naming the key" "federation.self must match" \
|
||||
'{"self":"Hub","peers":[]}'
|
||||
expect_fed_refusal "federation peers not an array exits 2 naming the key" "federation.peers must be an array" \
|
||||
'{"self":"hub","peers":{}}'
|
||||
expect_fed_refusal "federation peer not an object exits 2 naming the key" "federation.peers[0] must be a JSON object" \
|
||||
'{"self":"hub","peers":["factory"]}'
|
||||
expect_fed_refusal "federation peer id pattern exits 2 naming the key" "federation.peers[0].id must match" \
|
||||
'{"self":"hub","peers":[{"id":"Factory","origin":"https://mosaic.woltje.com","label":"F"}]}'
|
||||
FED_LONG_LABEL="$(printf 'x%.0s' $(seq 81))"
|
||||
expect_fed_refusal "federation peer label over 80 characters exits 2 naming the key" "federation.peers[0].label" \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"https://mosaic.woltje.com","label":"'"$FED_LONG_LABEL"'"}]}'
|
||||
expect_fed_refusal "federation duplicate peer id exits 2 naming the key" 'federation.peers[1].id "factory" is already named' \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"https://mosaic.woltje.com","label":"F"},{"id":"factory","origin":"https://other.woltje.com","label":"G"}]}'
|
||||
expect_fed_refusal "federation peer origin with a trailing slash exits 2 naming the key" "federation.peers[0].origin" \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"https://mosaic.woltje.com/","label":"F"}]}'
|
||||
# A missing value: the pattern test coerces undefined to "undefined", which matches, so the typeof half is what refuses.
|
||||
expect_fed_refusal "federation self missing exits 2 naming the key" "federation.self must match" \
|
||||
'{"peers":[]}'
|
||||
expect_fed_refusal "federation peer id missing exits 2 naming the key" "federation.peers[0].id must match" \
|
||||
'{"self":"hub","peers":[{"origin":"https://mosaic.woltje.com","label":"F"}]}'
|
||||
expect_fed_refusal "federation peer label missing exits 2 naming the key" "federation.peers[0].label must be" \
|
||||
'{"self":"hub","peers":[{"id":"factory","origin":"https://mosaic.woltje.com"}]}'
|
||||
|
||||
# Without the key: no federation in the output, and the config still loads (FED R2 boundary).
|
||||
cfg no-federation.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
||||
NO_FED_OUT="$(MOSAIC_CONFIG="$SANDBOX/no-federation.json" timeout 20 $CONFIG_OP validate)"
|
||||
NO_FED_RC=$?
|
||||
[ "$NO_FED_RC" -eq 0 ] && ! printf '%s' "$NO_FED_OUT" | grep -q '"federation"'
|
||||
check "config without federation loads and has no federation output" $?
|
||||
|
||||
# Without the key: the output is exactly what the loader printed before phase 1 (sage-1340, F2).
|
||||
cfg no-console.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
||||
EXPECTED="$(printf '{\n "configVersion": 1,\n "environment": "development",\n "dataRoot": "%s",\n "execution": {\n "backend": "docker",\n "provider": "zai",\n "model": "m",\n "adapter": "pi"\n }\n}' "$DATA_ROOT")"
|
||||
|
||||
Reference in new issue
Block a user