docs: close C2-04 tracker publish and state publication gaps (row 215)
This commit is contained in:
1 parent
6a508804f0
commit
2522025afc
2 files changed
+111
-9
No files matched your search
@@ -31,6 +31,18 @@ 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
|
||||
@@ -81,6 +93,43 @@ Exports consumed by row 222:
|
||||
| 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:
|
||||
|
||||
@@ -91,13 +140,40 @@ Host adapter exports for this row:
|
||||
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, the lock, the timer and state
|
||||
publication. It rebuilds problems with existing readLibrary({repo, commit,
|
||||
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, test run, suite, image build, live operation
|
||||
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.
|
||||
@@ -16,7 +16,7 @@ the broker's sandbox import closure. Sync and HTTP routes belong to later rows.
|
||||
No dependencies. node_modules remains the plain canonical symlink.
|
||||
|
||||
**Spec:** Pinned C2-04 brief; Gosalyn's approved design 1.1, 1.3, 2.1,
|
||||
M4/M9 and 7.2; BUILD.md's sage-1848/1852 scope and shared-state contract.
|
||||
M4/M9 and 7.2; BUILD.md's sage-1848/1852/1860 scope and shared-state contract.
|
||||
|
||||
## Constraints and review focus
|
||||
|
||||
@@ -47,7 +47,8 @@ the result and human-only admission (T4).
|
||||
| packages/business/src/instance-settings.mjs | Skip library before projectFilePath (:142), nothing else |
|
||||
| packages/seat/src/checkout.mjs | Skip library entries in ensureCheckouts (:315-327) |
|
||||
| packages/cli/src/launcher.mjs | Pass only code entries to ensureCheckouts (:354) |
|
||||
| packages/project/src/paths.mjs | Skip library entries in root overlap loop (:41-44); keep default projectRepo location |
|
||||
| packages/cli/src/config.mjs | Skip library entries in trackerFor's root loop (:61-63), per sage-1860 |
|
||||
| packages/project/src/paths.mjs | Skip library entries in overlap loop (:41-44); default record use refuses library, mirrorPath internally opts into allowLibrary |
|
||||
| packages/library/src/mirror.mjs | New bare init, checked state and served-commit file read module |
|
||||
| packages/library/src/index.mjs | Export mirror module interface and SYNC_STATE_FILE |
|
||||
| packages/bus/src/broker.mjs | New injected library adapter, human-only admission and two async verbs |
|
||||
@@ -61,7 +62,7 @@ scripts/stack-copy.sh already copies packages/library/src and needs no edit.
|
||||
|
||||
Test paths to modify: business/tests/business.test.mjs, cli.test.mjs,
|
||||
resolve.test.mjs, instance-settings.test.mjs; seat/tests/checkout.test.mjs;
|
||||
cli/tests/launcher.test.mjs; project/tests/project.test.mjs;
|
||||
cli/tests/launcher.test.mjs, trackers-boot.test.mjs; project/tests/project.test.mjs;
|
||||
bus/tests/project-runtime.test.mjs. All paths are under packages/.
|
||||
|
||||
New tests: packages/business/tests/library-projects-protected.test.mjs;
|
||||
@@ -78,13 +79,22 @@ row when implementation begins. Sage owns docs/SESSIONS.md.
|
||||
|
||||
- [ ] Add failing business tests: accept a frozen library mapping without a
|
||||
root, retain mixed/only-library documents, and propagate C2-01's mapping
|
||||
and playground refusal codes. Existing code validation remains unchanged.
|
||||
and playground refusal codes. A role publish naming a library project
|
||||
refuses at load, even with an otherwise valid fork identity and remote.
|
||||
Existing code validation remains unchanged.
|
||||
- [ ] Add a separate regression for each consumer: CLI validate and its
|
||||
credential exclusion roots; CLI resolve --project books has no project
|
||||
layer; resolveInstance finds a later real project; instance settings still
|
||||
writes an unrelated setting; ensureCheckouts returns no library checkout;
|
||||
launcher creates no books workspace clone; projectRepo skips a library
|
||||
while still rejecting overlap with a real code root. Put books first.
|
||||
- [ ] Add the real bus boot regression in cli/tests/trackers-boot.test.mjs:
|
||||
tracker.baseUrl is set and books is a library entry; boot succeeds, books
|
||||
produces no tracker, and a later real tracker project still works.
|
||||
- [ ] projectRepo(context, {allowLibrary = false} = {}) refuses a library
|
||||
entry by default with project-invalid. Only mirrorPath opts in. Test that
|
||||
ordinary human and tester record initialization leave no directory at the
|
||||
future mirror path; no public verb can supply allowLibrary.
|
||||
- [ ] Add runtime's protectedRoots consumer regression in T4's real runtime
|
||||
fixture. A valid credential outside code/data roots still loads with a
|
||||
library entry, and existing credential protections still refuse unsafe files.
|
||||
@@ -121,6 +131,17 @@ as the design's skip requires. It does not fabricate project vars or a root.
|
||||
state/ref, valid state, malformed JSON, unknown keys/types, branch mismatch,
|
||||
invalid times/IDs/problem shape, symlink/hardlink/non-private file and
|
||||
missing or mismatched served ref. Assert reads change no bytes.
|
||||
- [ ] Add shared-lock tests: withLibraryLock(mirror, operation) waits at most
|
||||
5000 ms, refuses library-sync-busy on timeout and releases after a throw.
|
||||
A reader arriving between the publisher's served move and state write
|
||||
waits, then reads the consistent pair. Cover first sync and fast-forward
|
||||
in independent processes, same-process independent acquisitions and
|
||||
release after owner death. A callback-scoped reader expires on release.
|
||||
- [ ] Implement the lock by async spawn of flock on the parent's inherited
|
||||
fd 3, as pinned in BUILD.md. Await acquisition-child exit before calling
|
||||
operation. Never spawnSync during acquisition, which could freeze the bus
|
||||
for 5 s. A parallel ordinary bus read remains responsive while a library
|
||||
read waits. Only the publication/read lock is shared; fetch is outside it.
|
||||
- [ ] Add served-read tests against local fixtures containing spaces,
|
||||
capitals, a .canvas and unmapped top-level note. Seed a served ref and state
|
||||
without fetching. A newer unrelated HEAD does not change the returned
|
||||
@@ -152,6 +173,9 @@ approved library-state-invalid code. State reads are bounded and private.
|
||||
- [ ] Inject a separate library adapter via runtime, initialize only library
|
||||
mirrors, add the two async request paths, and include them in server's
|
||||
awaited list. Do not import library or fs into broker.
|
||||
- [ ] Unsafe or pre-existing non-mirror initialization refuses bus boot,
|
||||
closes opened resources and never publishes a ready socket. Add that
|
||||
runtime regression in library.test.mjs.
|
||||
- [ ] Wire main and tester Console callbacks beside project-file callbacks.
|
||||
No HTTP route is shipped. The new CLI test captures the startServer
|
||||
options with a test-only module loader and calls the supplied callbacks
|
||||
@@ -178,6 +202,8 @@ approved library-state-invalid code. State reads are bounded and private.
|
||||
## Self-review
|
||||
|
||||
Every brief acceptance item is in T1-T4. Added host support paths match
|
||||
sage-1848. State filename, types and authority match sage-1852 and Megavolt's
|
||||
agreement. Fetch, timers, locks for sync and HTTP stay with their later rows.
|
||||
sage-1848/1860. BUILD.md records the complete 14-hit root audit. State shape
|
||||
and authority match sage-1852; the shared read/publication lock follows
|
||||
sage-1860 and Megavolt's agreement. Fetch, timers, outer sync serialization
|
||||
and HTTP stay with their later rows.
|
||||
This is a plan only; no product edits or test execution precede approval.
|
||||
Reference in new issue
Block a user