diff --git a/.project/concepts/app-contract/_debt.md b/.project/concepts/app-contract/_debt.md deleted file mode 100644 index 1816980..0000000 --- a/.project/concepts/app-contract/_debt.md +++ /dev/null @@ -1,7 +0,0 @@ -# Doc-debt — app-contract - -> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file. -> One block = one "big change": `why` + `files` + `verify` (leaves to review). - -## Raw markers (consolidate into blocks, then delete) -- TOUCHED packages/polyfill/src/surface/subscribe.ts @2026-08-20 (session f93872b5-293a-4916-a353-181409a96d42) diff --git a/.project/concepts/app-contract/bug_a-lost-inbox-watch-is-never-retried.md b/.project/concepts/app-contract/bug_a-lost-inbox-watch-is-never-retried.md new file mode 100644 index 0000000..575eb73 --- /dev/null +++ b/.project/concepts/app-contract/bug_a-lost-inbox-watch-is-never-retried.md @@ -0,0 +1,28 @@ +--- +type: bug +severity: major +summary: An inbox watch that failed to open is re-opened only by a narrow set of later events, so a session whose activity does not produce one stays unwatched and never converges +opened: 2026-08-17 +last_checked: 2026-08-17 +--- + +# A lost inbox watch is never retried + +While an identity is connected, every inbox it may read is watched, and a deposit is applied as it lands. When opening one of those watches **fails** — a broker hiccup at sign-in is enough — the failure is reported and the watch is dropped. It is then re-opened only by a later event, and the list of events that qualify is narrower than it looks: + +- creating a document, +- opening an inbox on one, +- a `ReadCap` arriving through an inbox **still** being watched, +- the first read of a document in a public store. + +**An ordinary write does not qualify.** A `sparqlUpdate` on a document the identity already owns fires none of these. So it is not only a read-only session that stays blind — it is any session whose activity stays inside what it already holds. + +There is deliberately no polling: the deferred inbox processing states that as a design choice, and a timer here would be the wrong repair. What is missing is a recovery signal that does not depend on the identity performing a capability-bearing act. + +**The visible consequence.** A document is shared with the affected identity while it sits connected, and nothing arrives. No error is raised, and the application sees an empty result rather than a failure — the same shape as having been given nothing at all. + +**Workaround today:** a fresh `ensureIdentity()` re-enters and re-establishes the watches. + +**Verifying it:** drive an identity to a state where one inbox watch fails to open, let the broker recover, deposit for that identity, and observe that nothing converges while it stays connected. A reproduction existed as a scratchpad probe during the work that found this and did not survive the session; rebuilding it from this description is the honest cost of that. + +Predates the continuous-observation work — the earlier behaviour processed inboxes only at connection, so the same hiccup produced the same silence by a shorter route. diff --git a/.project/concepts/app-contract/bug_an-empty-inbox-list-can-mean-a-failure.md b/.project/concepts/app-contract/bug_an-empty-inbox-list-can-mean-a-failure.md new file mode 100644 index 0000000..1aecfb3 --- /dev/null +++ b/.project/concepts/app-contract/bug_an-empty-inbox-list-can-mean-a-failure.md @@ -0,0 +1,21 @@ +--- +type: bug +severity: normal +summary: When the account record cannot be resolved, the inbox enumeration reports a COMPLETE list holding zero inboxes, so a total failure is indistinguishable from an identity that legitimately has none +opened: 2026-08-17 +last_checked: 2026-08-17 +--- + +# An empty inbox list can mean a failure + +Enumerating the inboxes an identity may read reports, alongside the list, whether that list is **complete** or **short** — and a shortfall is logged so a lost watch is at least visible. + +That signal has a hole. When resolving the account record yields nothing rather than raising, the enumeration reports a list that is **complete and empty**. Every inbox is missing, including the identity's own, and nothing distinguishes it from an identity that genuinely has none yet — which is a real and ordinary state, since a first-visit identity connects with no account. + +So the worst outcome — watching nothing at all — is reported as the most benign one. This is the family this package has closed repeatedly: **a failure wearing the shape of an absence.** + +The fix is not to make the enumeration throw. Reaching the registers is infrastructure and may fail; what is missing is that *resolving nothing* and *there being nothing* are the same answer here, and they are not the same event. + +**Verifying it:** make the account resolution return nothing (rather than reject) and observe that the enumeration reports a complete list with no entries, and that no shortfall is logged. + +Related, and distinct: [[bug_a-lost-inbox-watch-is-never-retried]] — that one is about a watch that failed to open; this one is about never learning there was a watch to open.