The business file accepts a kind "library" project entry, and each consumer that assumed a project root skips it. An organization write whose projects differ from the file's is refused (M9). The bare mirror is created at the projectRepo path with no remote. library.file and library.status are human only and refuse an agent session with human-required. Candidate herb/row215-on197dde3445abona36523713, Filbert APPROVE rev 2924.
22 KiB
Row 215 build record
Owner: Herb. Reviewer: Filbert. Claim: herb-215-start, rev 2753.
Brief: docs/plans/2026-10-20_case2-rows.md, C2-04, pin
16847e94ae. Design: Gosalyn r11,
sections 1.1, 1.3, 2.1, M4, M9 and 7.2.
Base: a2c72ac952, row 212's reviewed
candidate, under Q177. Branch: herb/row215. Rebuild on the combined 212+197
landing when Sage names it. Do not replay or revert landed changes.
Scope rulings (sage-1848)
- Add packages/bus/src/runtime.mjs for library adapter injection and the library skip in protectedRoots. The skip has its own consumer test. Change nothing else there.
- Add packages/library/src/mirror.mjs, named mirror rather than store. It creates a bare mirror without a remote, reads at the served commit and reads sync state. Export one state filename constant, and export this module through packages/library/src/index.mjs. Row 222 imports the constant.
- Add packages/bus/src/server.mjs only to await library.file and library.status, with socket tests proving their replies are results, not {}.
- HTTP exposure is deferred. Row 225 owns the library.status route; C2-11b owns library pages. Leave serve.mjs alone. webui/src/cli.mjs gets only callback wiring next to the project-file callbacks on both listeners.
- Reuse existing refusal codes where possible.
- Build on
a2c72ac95until Sage supplies the 212 landing SHA.
Additional ruling, sage-1852: library-state-invalid is approved for malformed, unsafe or mismatched state, including a missing file while the served ref exists. The version-1 shared contract below is approved. Row 222 also owns runtime.mjs for its sync and timer wiring, recorded in that row's manifest.
Additional rulings, sage-1860, after Filbert's CHANGES on 6a508804f:
- B1 adds packages/cli/src/config.mjs and cli/tests/trackers-boot.test.mjs. trackerFor skips library entries. A real bus boot with tracker.baseUrl and a library entry succeeds and gets no tracker from that entry.
- B2 refuses a role publish grant naming a library project in the loader.
- B3 readers use the sync lock. mirror.mjs exports withLibraryLock, with a 5000 ms wait bound and library-sync-busy on timeout. Sync locks only the served-ref move and state publication, never the fetch.
- N3, git-socket doPush/recordPush accepting a library ID, is a later row. Sage records it in DEFERRED.md when 215 lands. No fix here.
Shared contract with row 222
Agreed in herb-0048, megavolt-0081/0082 and sage-1852. This section is the contract Filbert reviews and Megavolt cites for Dewey. It is not a sync writer.
Module: packages/library/src/mirror.mjs. Export
SYNC_STATE_FILE = 'mosaic-library-sync.json'. The full path is
join(mirror, SYNC_STATE_FILE), inside the bare repository. Only sync.mjs
writes it, using a sibling temp file, fsync, rename, and directory fsync.
Version 1 has exactly these keys:
{
"version": 1,
"branch": "main",
"served": null,
"lastFetch": null,
"lastError": null,
"diverged": null,
"problems": []
}
- branch is the configured mapping branch and must match the reader's branch.
- served is null or a full lowercase 40-hex commit ID. refs/heads/served is authority. The state value must equal that ref, which must name a commit.
- lastFetch is null or a canonical ISO UTC timestamp.
- lastError is null or exactly {code, at}. code matches [a-z-]{1,64}; at is canonical ISO UTC. It never holds stderr, prose or a credential value.
- diverged is null or exactly {commit, at}, with the same commit/time rules.
- problems uses C2-01's exact {code, path, message, line?}. code is R1-R7, message is that code's fixed RULES text, line is a positive safe integer when present. No file body or arbitrary message is accepted.
- An absent file and absent served ref return the empty object above with the configured branch. An absent file with an existing served ref refuses.
- Unknown keys, bad types, unsafe filesystem metadata, invalid JSON and a mismatch refuse library-state-invalid. Reads use a bounded regular owned private file without following links. The read never repairs state.
- refs/remotes/library/ is not duplicated in this file. Row 222 may read it when it needs the fetched tip.
Exports consumed by row 222:
| Export | Contract |
|---|---|
| mirrorPath({business, project, dataRoot}) | Require a library entry; return projectRepo's checked path |
| ensureMirror(context) | Promise of the mirror path; race-safe idempotent bare init; no remote or fetch |
| servedCommit(mirror) | Promise of full commit ID or null; refs/heads/served only |
| readSyncState(mirror, {branch}) | Promise of checked version-1 state; compare with served ref |
| withLibraryLock(mirror, operation) | Await the same exclusive lock as readers, then await operation({readSyncState}); always release |
The lock is a stable private regular file, mosaic-library-sync.lock inside the mirror. It is never unlinked. A kernel flock is held by the caller's file descriptor. Acquisition waits at most 5000 ms and refuses library-sync-busy on timeout. Unsafe lock metadata refuses library-state-invalid.
There is no fs.flock API. The exact protocol is: open the stable file with
O_NOFOLLOW, check regular/private/owned/single-link metadata, pass its fd as
child FD 3 to flock --exclusive --wait 5 --conflict-exit-code 75 3 using
asynchronous spawn, await
that child's exit 0, then run the callback while the parent still holds its
original descriptor. Both descriptors share the open file description, so
the lock survives acquisition-child exit. Finally close the parent's fd.
Process death also closes it. No holder child stays alive in the callback,
no stale file is deleted, and unrelated operation children inherit only 0-2.
Acquisition never uses spawnSync: the bus must serve other calls while a
library reader waits (megavolt-0087).
A scratch-only probe on node v26.8.1 at 2026-10-21T08:34:00Z confirmed this: acquisition child exited 0; a second flock -n returned 1 while the parent fd stayed open; after closeSync it returned 0. The probe removed its own root. This was a design probe, not a package test, suite, Docker command or proof.
readSyncState takes that lock around reading the served ref and state file. The callback-scoped readSyncState({branch}) supplied by withLibraryLock reads under the already-held lock and does not reacquire it. The helper awaits asynchronous callbacks and releases in finally. A callback-scoped reader used after its callback ends refuses; it cannot escape its lock lifetime.
Row 222 fetches without this lock, then calls withLibraryLock across the served-ref move and atomic state write. Its tests hold publication between those steps and prove a concurrent reader waits, then sees the new consistent pair, including first sync. Row 215 tests that publication shape and timeout. A file read releases the lock after snapshotting a consistent served SHA, then reads that immutable commit. Row 222's separate outer sync lock spans fetch to prevent two sync operations interleaving (megavolt-0086).
Host adapter exports for this row:
- libraryStatus(context): return {project, ...state}, with no repository path or content. Do not fetch or rebuild on this read.
- readLibraryFile({...context, path}): read a mapped, readable file at the served commit, returning {project, path, commit, content}. No caller-picked commit, working-tree fallback, fetch or write. C2-01's R1/R2/R7 exclusions apply. A never-synced mirror refuses library-commit for a file read.
Row 222 owns syncCommand, sync, writeSyncState, its outer sync lock, the timer and state publication. Row 215 owns the shared publication/read lock per sage-1860. Sync rebuilds problems with existing readLibrary({repo, commit, mapping}). It does not need a second rebuild function from this row.
Root consumer audit and reviewer notes
On a2c72ac95, ran exactly the sage-1860 audit:
git grep -n 'entry\.root\|\.root)' -- 'packages/*/src/*.mjs'.
It has 14 hits. runtime.mjs:95, business/cli.mjs:61,72,78,118,
instance-settings.mjs:142-143, resolve.mjs:44, cli/config.mjs:62-63 and
project/paths.mjs:42 all get the library guard in this row. business.mjs:233
and :242 are code-project validation after the library dispatch, not an
unguarded consumer. queue.mjs:1144 compares queue genesis roots and never
iterates business.projects; it is safe and unchanged. A second search for
projectFilePath/business.projects/b.projects found no additional root reader.
ensureCheckouts destructures root rather than spelling .root; its guard and
the launcher's filter remain in T1. Bare projectFilePath/loadProject remain
strict and unchanged.
Filbert N1 is handled at the existing owned paths.mjs boundary: projectRepo defaults to record-project use and refuses a library entry with project-invalid. Only mirrorPath's internal call opts into allowLibrary. Thus ordinary human and tester project init cannot occupy an uninitialized library mirror path. No record API or bus argument exposes that internal option.
Filbert N2: ensureMirror failure refuses bus boot. No socket becomes ready. The runtime closes resources it opened, following its existing failure path. A T4 test uses an unsafe/pre-existing mirror and proves the refusal, rather than serving a half-configured library adapter.
Verification record
Planning stage. No product code, package test, suite, image build, live operation or push for row 215 has happened. Product implementation awaits Filbert's manifest approval. Sage owns suite and full-gate grants. Row 197's proof commit is separate and is not part of this row's changes.
Implementation ledger
Plan: agents/herb/work/queue-215/MANIFEST.md at 2522025af, approved by
Filbert in the reply to herb-0056. Execution is inline by Herb under the
executing-plans and test-driven-development skills. No agent delegation.
Pre-flight: T1's validated mapping feeds T2's protected document and T3's mirror context; T3's lock/state helpers feed T4's injected host adapter. The shared contract is pinned above, with no interface conflict found.
Sage-1867 overrides COMMON's singles allowance: every test and executable probe needs an explicit slot grant. No further probe/test has run since that reminder. Read-only inspection and test authoring continue while the slot is held. The first red execution is pending; product code is unchanged.
Filbert's approved-manifest build notes: ensureMirror creates the lock in its staged directory before publication; a missing lock in an existing mirror refuses library-state-invalid. The top-level metadata allowlist permits only normal bare Git entries plus the state and stable lock files. Tests cover an unknown entry. The host needs /usr/bin/flock; ENOENT yields a handled library-state-invalid refusal, never an unhandled child error. Lock acquisition uses /usr/bin/flock directly and asynchronous spawn.
T1: consumer and loader regression tests authored, not run. T2: direct M9 write and serialization regression tests authored, not run. No completion or passing claim is made for either task.
Update, sage-1869 focused slot, 2026-10-21T08:47:48Z to 08:51:07Z: the busy check was CLEAR before the first test. T1's mapping regression failed 0/1 on unsupported kind as expected, then passed 2/0 with the publish refusal included. Other focused greens: business CLI 1/0, resolver 1/0, instance settings 1/0, seat checkout 1/0, project paths 1/0, sandboxed launcher 1/0, and trackers-boot whole 2/0. T2's direct writer case failed 0/1 on the missing refusal, then its whole new file passed 4/0. Green total 14/0 with no skips or cancellations. Logs and counts: granted-t1/. The final /proc scan found no test process or fixture cwd/argv reference; its private root was removed. Clear sent as herb-0062. Existing files' other cases have not been run yet, so T1/T2's full-file acceptance is still pending.
Sage-1874 directs continued authoring and implementation while the landing gate/proof holds the slot. T3/T4 tests were written first. Their initial red runs cannot execute during that hold; implementation proceeds under that owner direction, with no claim of a witnessed red or green for T3/T4. Their baseline red and candidate checks need a later explicit grant.
Implementation ruling: initialization uses GNU /usr/bin/mv --no-clobber --no-target-directory for its bounded staging rename, rather than Node's renameSync, which could replace an empty directory made by a competing actor. This preserves pre-existing directories and needs no new package on the Linux/coreutils bus host. The host also needs /usr/bin/flock. Neither command has been executed for T3/T4 yet. Publication errors fail closed. Sync-state reads are capped at 8 MiB, including a checked extra byte; an oversized or concurrently changed file refuses library-state-invalid.
T5 static dependency audit found two partial pinned-copy fixtures that copy business/src but not its newly imported library mapping validator: cli/tests/stack-release.test.mjs:1772 and stack-release-fixture.mjs:309 on the base. Each now includes packages/library/src in the existing copy list. This changes fixture closure only, no assertion or timeout. The complete stack-release file and each test file importing that shared fixture require a later granted single-file run. They are outside the four-file T3/T4 slot.
Sage-1879 places the four-file T3/T4 focused run after their 212+197 proof, before Gizmoduck, and requires their explicit clear before starting. No run has occurred after herb-0062. Row 197's N7 base-image-tag finding is left to Sage's DEFERRED entry; no row 197 source change is made here.
Focused T3/T4 grant sage-1882, 2026-10-21 09:13-09:18Z: busy check
CLEAR. Final whole-file results: mirror 16/0, bus library 6/0, WebUI
library-cli 1/0, project-runtime 3/0, all with zero skips/cancellations.
Syntax checks passed for the four files and five changed host modules.
Logs, all intermediate failures, counts and empty /proc cleanup scan are
in granted-t34/. Baseline a2c72ac95: three focused bus cases 0/3
(unknown-verb), CLI 0/1 (missing callbacks). Mirror baseline 0/1 is an
import refusal for the new module, not a witnessed behavioral red.
An initial whole baseline bus attempt left a runtime after its expected
failure; its exact runner was stopped, no process remained, and the
baseline was narrowed to the three bounded broker/socket cases.
Candidate fixture setup errors and a mock-child timeout were corrected
before the final whole-file passes. No suite or Docker/image/proof ran.
Filbert's 09:09 publication note: no dependence on mv -n's skipped-rename exit status across coreutils versions. Save staging dev/ino before mv; only its disappearance plus the same dev/ino at the mirror proves a win. A remaining original staging inode means a loss, requiring a valid winner. Only that owned original staging inode is removed. Numeric no-clobber statuses 0 and 1, including a diagnostic, are tested with a competing winner. Spawn errors, signals/timeouts and inconsistent outcomes refuse. Coreutils must provide atomic --no-clobber --no-target-directory semantics; it need not use any particular skipped-rename status convention.
Landing update: prepared herb/row215-on212 on fefd54e13 (includes row 212
landing ee7b2f137), without history replay or stash. Only BUILD-LOG.md and
cli/tests/stack-release.test.mjs overlap the a2c72ac95-to-landing change
set. BUILD-LOG preserves all landed entries and appends the existing Herb
entry. stack-release preserves the landing plus only the library/src copy
list addition. No test has run on this landing yet. All further execution
requires a Sage grant (supersedes the original COMMON singles rule).
Sage-1979 early singles on fefd54e13, stopped after file10 per sage-1983:
- packages/bus/tests/library.test.mjs: 6/0
- packages/bus/tests/project-runtime.test.mjs: 3/0
- packages/business/tests/business.test.mjs: 18/0
- packages/business/tests/cli.test.mjs: 10/0
- packages/business/tests/instance-settings.test.mjs: 12/0
- packages/business/tests/library-projects-protected.test.mjs: 4/0
- packages/business/tests/resolve.test.mjs: 11/0
- packages/cli/tests/launcher.test.mjs: 30/0
- packages/cli/tests/stack-release-external.test.mjs: 25/0
- packages/cli/tests/stack-release.test.mjs: 196/0
Total315/0, zero skips/cancellations. This is an early read only, not gate evidence for a merged candidate. The sequential driver was paused during file10 so it could not start file11, then terminated after file10 exited0. No file11 log exists. Results/logs are in granted-singles/. Later landed overlaps require a new branch and all16 singles again after their merge.
Current merge preparation per sage-1983/1987: new branch
herb/row215-on-f827 on f8276c5eb3.
Nine landed overlaps are recorded in current-MERGE.json and the exact
candidate deltas against the landing are in current-merge-only.patch.
Two textual conflicts were resolved manually: Broker keeps hostKeys and
adds library; business.mjs keeps CREATIVE_DEFINITIONS and adds checkMapping.
The other seven merges preserve the landing plus the original C2-04 edits.
All other product/test paths match the early215 tree byte for byte. The
landing's BUILD-LOG is preserved and the existing Herb entry is appended.
No history replay, stash, force push, branch deletion or source commit.
No test/probe/syntax check ran on the merged tree after the slot was cleared.
All16 whole singles need a new Sage grant before candidate commit/review.
The10 old-base315/0 results are early reads only, never merged gate evidence.
Source candidate and sage-2008 receipts
Source commit: e333af4dc7, on
f8276c5eb3. SOURCE.json records all30
changed source/phase-log paths with blob SHA256 and Git mode. The next
commit contains only this packet. No product change follows the tests.
Sage-2008: start13:15:15.548943Z; end13:27:27.279728Z, 2026-10-21. Each of the16 TEST-FILES.txt files ran whole, sequentially. All451 passed, with0 failed/skipped/cancelled. Per-file outputs are in granted-2008/; EVIDENCE.json records their counts, Node durations and log hashes.
| File | Pass/fail |
|---|---|
| packages/bus/tests/library.test.mjs | 6/0 |
| packages/bus/tests/project-runtime.test.mjs | 3/0 |
| packages/business/tests/business.test.mjs | 19/0 |
| packages/business/tests/cli.test.mjs | 10/0 |
| packages/business/tests/instance-settings.test.mjs | 13/0 |
| packages/business/tests/library-projects-protected.test.mjs | 4/0 |
| packages/business/tests/resolve.test.mjs | 11/0 |
| packages/cli/tests/launcher.test.mjs | 31/0 |
| packages/cli/tests/stack-release-external.test.mjs | 25/0 |
| packages/cli/tests/stack-release.test.mjs | 196/0 |
| packages/cli/tests/stack-upgrade.test.mjs | 21/0 |
| packages/cli/tests/trackers-boot.test.mjs | 2/0 |
| packages/library/tests/mirror.test.mjs | 16/0 |
| packages/project/tests/project.test.mjs | 15/0 |
| packages/seat/tests/checkout.test.mjs | 78/0 |
| packages/webui/tests/library-cli.test.mjs | 1/0 |
All29 product/test hashes equal their pre-run values. The final /proc cwd and argv scan was empty; the private test root was removed. Clear sent as herb-0082 at13:28:38Z. No further runs occurred. No image, Docker, lane, proof or full suite was part of this grant.
Before commit, checked f8276c5eb..8a04f4aa4 on every215 source path and all16 test paths: no overlap. Row248's grant-executor test is outside them. The current root-consumer grep still returns14 sites, all classified by the earlier audit: business code roots after dispatch, guarded library skips at consumers, and queue's unrelated genesis-root invariant.
Sage-2016 authorizes the source commit, packet receipts and seat-only push, then Filbert's candidate review. The landing gate stays with Sage. Earlier focused and old-base receipts are archived in granted-t1/, granted-t34/ and granted-singles/, with their limits kept above. Full package/shell landing checks and implementation approval remain pending. No HTTP route, remote fetch, sync writer, timer or live operation was added.
Raw Node error logs retain their original space-only lines. Packet whitespace checks exclude *.log so the recorded output bytes stay unchanged; source and other packet files pass the ordinary diff check.
Merge-scoped round 2 after rows 249 and 197 (sage-2054)
Row197 landed as a36523713 after proof6 passed 8/8 gates. Row249's
broker fix is also in this base. Source a6101e21c5, single parent a365237133,
is on the new herb/row215-on197 branch. The old branch stays untouched.
No force push, branch deletion or stash was used. The merge was clean.
Only broker.mjs and stack-release.test.mjs changed upstream among the30 source/phase-log paths. Both retain every landed hunk. The broker keeps row249's installed flag and hostLineLeft handling. The stack-release test keeps row197's leasePort import, after hook and closed-port lease helper; 215 adds only its library fixture copy. The other28 blobs and all modes match the prior source. All30 paths have exactly the same -U0 added and removed lines as f8276c5eb..e333af4dc. BUILD-LOG.md is append-only.
Stable patch-id, old and new: d191e7e9586cd3b7c18310daacdf0c1fdbc8be01. r2-range-diff.txt reports only the commit message changed (!); its patch is unchanged. r2-MERGE.json records every path; r2-merge-only.patch contains both merged source diffs against the new base. SOURCE.json names the new source.
Filbert approved round1 in comment30071, rev2897. This round requests merge-only review. The earlier451/0 receipts belong to the prior base; no test, probe, syntax check, Docker command or lane ran in this round. Merged-tree evidence remains pending Sage's explicit clear. r2-TEST-FILES.txt proposes all16 original whole singles plus all66 bus test files whose local import closure reaches broker.mjs, deduplicated to80 whole files. This includes shared fixture and runtime imports. Sage's slot and row196 landing gate come first. The full landing gate remains Sage's. No library behavior, assertion or timeout changed.