Explicit claude-fable-5 design APPROVE, no blockers, frozen e065b888bc9fef6cdcaaf2a0ca457d4923dd9d16. Both shared TryLock and TeamMode marker/release-floor amendments approved. A final clarification revision will pin response abort/client retry and empty local delivery-list behavior plus the nonblocking advisories before implementation. Full review:
APPROVE
I reviewed e065b88 against the inline Docs main sources, the approved principal design/implementation, the approved roster contract, and the frozen roster module at 667dc82. No blockers. Both explicit deltas are correct in direction and mechanically feasible. Findings by lens, then advisories.
The TryLock amendment (delta 1) — approved. The inversion is real and source-backed: publish, UpdateRecord and the generation-fence path in fetch all hold Cache.mu across Durable.Save, and Lease.Valid/Remaining take that same mutex. With Durable over Docs' single-connection write pool, an in-transaction lease check and a publishing refresh wait on each other. The fix is minimal and safety-preserving in the only direction that matters: a true from Valid still requires acquiring the lock and running the unchanged version/usable/deadline checks — and, importantly, observe() still runs on every successful acquisition, so the wall-clock fence is never bypassed on any path that answers true. Contention can only produce false/0, which routes to the existing retryable 503 with the save queue intact (the exact failure shape HasGrant's doc comment exists to protect). Rejecting released-lock persistence is right: publish currently relies on version/digest/deadline re-checks under the same lock that spans save; releasing it means a serial-persistence protocol plus compare/fence/republish — a change to the reviewed atomic-cache contract, not a Docs integration detail. The deterministic regression test (real writer held, refresh's Save genuinely waiting, lease calls in-transaction, mutation restoring blocking Lock must fail) is the right shape and will fail for the right reason. Grant-backed writes correctly bypass the lease entirely, so contention never blocks guests.
Mode marker + release floor (delta 2) — approved. The narrowing of the principal design's rollback paragraph is a genuine correction, not a contradiction: the old/new/old/new drill proved data compatibility, and the 3ed7665-lineage rollback argument addressed Home-only impersonation — but inline app.go/grants.go show that lineage grants any allowlisted local person full OwnerID scope with no roster, so a removed local member regains everything under rollback. The marker is honestly scoped (refuses new binaries booting modeless on an activated DB; explicitly cannot constrain old binaries; hence the mandatory binary floor), fails closed on malformed/unreadable, and pins bindings rather than generation, so legitimate generation changes don't trip it. The issuer census claim checks out against the R0.23 Session struct (no issuer field) — census + immutable marker + exact-issuer lookups is the correct closure.
Current-integration claims are source-accurate. Every "current main" assertion I could check against inline evidence holds exactly: ApplyUpdate's pre-replay check, out-of-band snapshot transaction, post-commit updated_at bump under owned(doc.OwnerID); readable/writable OwnerID inclusion; connect-time-only SSE authorization; the Home resolver's empty-address refusal; updatesPost's 404-on-ErrNotWritable. The remedies target each precisely, and moving the updated_at bump inside the transaction under the actor scope strictly tightens D4 rather than weakening it.
Nil-address, grant-only scope, standalone separation, streams — consistent with both binding contracts. Nil emitting no grant clause (rather than comparing empty) is the right defense against malformed historical grants and matches the principal contract verbatim. The single scope constructor with OwnerID disabled under NotMember/Unknown implements the roster contract's "no global has-any-grant gate" ruling and my carried-forward advisory. The Unknown-vs-NotMember response split (503 vs 404, no fabricated empty authoritative list) preserves both non-disclosure and the save queue. SSE per-event rechecks, deadline timer, re-arm-from-new-decision, and no manufactured grant timer match the module's own Lease comment; Remaining()==0 on contention degrades to the expiry path, which fails closed.
Advisories (non-blocking):
- Pin
updatesPost's status for definite NotMember. "Return 503 beforeupdateIsClientFaultclassification" and "404 for known nonmembership" can both be read to apply to a fresh-snapshot-removed writer; the difference decides whether their queued batch is retained (503, retried forever by a permanently revoked client) or dropped (404). Both readings are commit-safe; decide one and test it before branch 3. - Add the existence-oracle test pair. The invariant is stated; the test isn't: in each standing state, a nonexistent sqid and an existing-inaccessible sqid must answer identically — especially both-503 under Unknown, where a 404/503 split would be an oracle.
- Pin marker ordering against first-activation crash. Commit the marker before the listener serves any request, making "no Home-only principal ever exists on a marker-less DB" a theorem rather than a walkthrough (today the crash window leaves only inert schema, but nothing pins that).
- Give the marker row the principal ledger's immutability triggers (refuse UPDATE/DELETE). Old binaries ignore the table and raw SQL is excluded by fiat, but future tooling isn't.
- State the deadlock mechanism precisely in the amendment. With a context-honoring adapter the blocking-Lock cycle resolves at FetchTimeout as a repeated whole-writer stall; with any non-context-honoring wait — or
UpdateRecordcalled with an unbounded ctx once runtime reload lands — it is permanent. TryLock is correct either way; the amendment's test ("return immediately") already asserts the right thing. - Land the unknown-
kidnil-key guard (my prior module advisory 1) in the same amendment revision, since the module is being revised anyway. - Canary the contention rate deliberately. Publish's Save still waits ≤5s on the writer under edit load; sustained writer saturation can expire the cache and 503-storm members. The design names this; make the canary threshold explicit before branch 4.
The two absolutes hold end to end: a removed member's standing dies within the measured 60-second ceiling on every path including silent streams and in-flight transactions, and no contention, refusal, or uncertainty ever widens access — every ambiguous state resolves to refuse-and-retry with the editor's bytes intact. Implementation remains gated on the amended module's own CI and Fable code review before Docs pins it, per the design's own sequencing.