11 KiB
type, summary, last_checked
| type | summary | last_checked |
|---|---|---|
| knowledge | Openness is the state of the document's ONE real subscription, asked and never remembered - the sweep that established it counted eleven pieces of state where the problem had named three, and the lesson is to count exhaustively, including what merely implies the fact, before designing | 2026-10-01 |
One answer to whether a document is open in this session
"Is this document open in this session" must have exactly ONE answer. It had several, and they disagreed - six rounds of work on the predecessor task kept finding the same defect in a narrower corner before anyone named the cause. This leaf records the invariant that closed it, why each displaced record had to GO rather than be repaired, and the counting discipline that is the transferable part.
The invariant, and where it is written
The answer is the state of the document's ONE real subscription - docChannel(nuri) in surface/subscribe.ts - and emulated-verifier/open-repo.ts records only the bootstrap open it ASKED FOR: the listener it took out, which fan-out that listener joined, and the bounded wait around it. Never whether that listener became a live channel or crossed a barrier. So no answer in open-repo can outlive the channel it describes, because none is kept.
It is written three times, each beside the state it governs: open-repo.ts's header block, subscribe.ts's header under This module OWNS "is this document open in this session", and on the DocChannel type. A branch upstream holds exactly one subscriber - a second doc_subscribe EVICTS the first - which is why the fan-out exists at all and why it is the physical truth: a listener exists or it does not.
docChannel answers from the fan-out and nothing else. establishing says an attempt STANDS (left true after success, so on its own it cannot tell "asked" from "open"), realUnsub says that attempt's call ANSWERED, lastState is the barrier, incarnation says WHICH fan-out stands so a caller holding a listener can tell its entry was replaced under it, and attempt qualifies the rest so a late outcome can tell whether it is still the one being waited on.
The count was ELEVEN, and the gap is the finding
The problem was stated with THREE records, and that number is still in the published contract's 1.0.0-dev.10 entry - it is the count of the opening hypothesis, not of the finding, and the gap is the point. An inventory pass over every module-level Map/Set in src, plus every reader, then checked by hand, found eleven.
Five in open-repo.ts, all deleted: held ("this session holds a live bootstrap subscription"), syncState (the barrier), inFlight ("an open is under way", read as a short-circuit), settleInFlight (the same set of documents with a different payload - a second record of inFlight), and registryEpoch plus each open's myEpoch (a record ABOUT the records, which existed only because the records did). The last two merely IMPLIED openness through a short-circuit and had never been counted by anyone, including six adversarial rounds on the predecessor task.
Five in surface/subscribe.ts, which survive as the single answer: fanOuts, establishing, realUnsub, lastState and attempt. lastState turned out to have been a second barrier record all along, and the correct one - it is forgotten when the channel is released, when the session changes under it, and when the attempt that produced it fails, which is exactly when a barrier stops meaning anything.
The eleventh is boundSessionId, counted and KEPT: it is not per-document, and says which session the registry belongs to.
Also counted and ruled OUT, each with its reason, because ruling out is part of counting: inbox-observer's obs.inboxes (a different fact - watched by the observation, not open in the verifier - and its teardown is already paired with the one that broke the others), public-store's attempted (nominally a cap memo, but a memo hit skips an ensurePhysicalRepoOpen inside it, and nothing resets it on configure - left alone and reported), and the in-flight or being-serviced maps in branch-registers, inbox-drain, inbox-processor, connect, surface/inbox and account-registry. ORM land holds no openness record at all: orm_start_graph is deliberately unused, and there is no graph registry or refcount anywhere in src.
Why two records of one fact cannot be reconciled
They disagreed in both directions, and each failure was measured:
- A reset cleared one and left the other standing.
configure()is PUBLISHED and callsresetDocSubscriptions(), which drops every fan-out listener and tellsopen-reponothing.heldkept a dead entry andsyncStatekept"synced", so the short-circuit skipped the re-open for ever: after a secondconfigure, a namedshape readover a previously synced document made zerodoc_subscribecalls and published{isPending:false, isSuccess:true, data:[]}- an empty success asserting AUTHORITY for a document the session had never subscribed, unrecoverable even through a refetch. - A subscribe the broker REJECTED was recorded as opened, blocking the retry the subscription layer already offered through a channel nothing had wired. Measured with a
doc_subscriberejecting at 200ms against a 20ms fallback: one subscribe call after the retry where the claimed behaviour needs two.
The repair is therefore deletion, not notification: nothing outside subscribe.ts records openness, so teardown is safe BY CONSTRUCTION and no cross-module notification was added. A guard at the READER cannot fix a fact that is wrong at the source - the first draft put the channel question in getSyncState instead of forgetting the barrier at the source, which only MASKED a stale barrier while the fan-out went on replaying a dead State to the next joiner.
A record is written when the fact becomes TRUE, not when it is requested
held was written the moment subscribeDocUnguarded returned - which is BEFORE the doc_subscribe behind it has been placed, since establish awaits the session id first - so it meant "we asked": the exact looser meaning an opened set had been deleted for one round earlier. That is a claim about what a call's return implies, written and never run; see justify-a-return-value-by-running-it.
BootstrapOpen.release is held to the same rule and says only "am I still listening". It cannot answer "is this document open", because resetDocSubscriptions drops a listener from under this module without a word - so stillStanding asks three questions, every one of them of the thing that owns the answer: do I hold a listener at all, is it in the fan-out that stands NOW (channel.incarnation !== open.incarnation), and does an attempt stand behind it. An attempt that has not ANSWERED yet counts as standing, deliberately: a second doc_subscribe cannot be placed while one is in flight, so a fresh attempt buys nothing and costs the caller the whole bounded fallback again.
The four sync states, and why "unknown" is authoritative
getSyncState DERIVES all four on every call and stores none. "synced" - a State has arrived on the channel standing NOW; the truth signal, so a zero-row read MEANS zero. "syncing" - inside the bounded wait. "timed-out" - opened here with no barrier to show for it, whether the wait gave up, the subscribe was refused, or the channel was taken away; never "unknown", which is why a reset DISOWNS a document rather than forgetting it. "unknown" - never opened here, or the no-doc_subscribe path with no State semantics.
"unknown" passes as authoritative, deliberately: where there are no State semantics there is no barrier to have missed. Refusing it instead is an inert-case regression that has shipped twice. Its two readers in src disagree only in the direction each must be safe in - index-deposit's noRowsMeansAbsent accepts "synced" and "unknown", and use-shape-query's namedDocumentsArrived refuses "timed-out" and "syncing". inbox-observer reads NEITHER; it keeps its own watch record, and counting is what settled that (see bug_a-lost-inbox-watch-is-never-retried).
Every teardown path, and what each leaves behind
configure()andresetConfig()(PUBLISHED) callresetDocSubscriptionsandstopObservingInboxes, and NOTresetOpenedRepos. They leaveopen-repo's records holding a listener whose fan-out is gone - now inert by construction:stillStandingsees a changedincarnationand re-opens, andgetSyncStatereads"timed-out"off a channel in state"none".resetDocSubscriptions()clears every listener, releases each fan-out and empties the map. It leavesobs.inboxes,public-store.attemptedandseenByInbox.resetOpenedRepos()- ONE caller insrc,syncSessionon a session-id change - releases each bootstrap listener, ENDS the abandoned wait (without which a caller hangs the full fallback on a subscription that no longer exists), replaces each record with a disowned one, clearsboundSessionId, then callsresubscribeDocs. It leaves the same three.resubscribeDocs()re-opens each channel ON THE SAME entry, forgettinglastStatebecause the barrier belonged to the session that is gone, and keeps the keys and listeners on purpose so aninbox.watchsurvives the change. This is why entry identity cannot answer which attempt is current, andattemptmust.releaseFanOut()takes one entry out of the map with its barrier;reportSetupFailure()clearsestablishing(retryable) andlastState(a barrier belongs to the attempt that produced it) for the CURRENT attempt only - a superseded one is logged and nothing else.setCurrentUser()callsstopObservingInboxesonly, and correctly: an identity change is not a session change. There is no sign-out orsession_stoppath insrc;setCurrentUser(null)is the disconnection.forgetEverything()(TEST-ONLY) never clearsseenByInbox,running,openInboxInFlightorconnect.inFlight.
The lesson, which is the transferable part
Count exhaustively FIRST - including anything that merely INFERS the fact - and write the count down before designing anything. Two attempts failed by fixing a WRITER without knowing who read it, and the two records nobody had counted were both short-circuits that implied openness rather than stating it. Enumerate the readers and the teardown paths from the code and verify the list rather than trusting it; the count is what produced the invariant, not the other way round.
The caller-facing consequence is in knowledge_what-a-failed-read-answers-upstream (its third rule) and in polyfill-surface/contract_polyfill-surface at 1.0.0-dev.10, where the pending-until-the-barrier guarantee lost its session-scoped qualification. The other half of what a shape read may show is knowledge_what-stands-between-identities: this leaf decides whether a named document is an answer, that one bounds what the answer may contain.