Files
ng-eventually/.tasks/todo/T01.i-a-failure-that-reads-as-an-absence.md
T
2026-09-21 14:17:42 +02:00

49 lines
4.8 KiB
Markdown

---
id: T01.i
title: Record the failure-reads-as-an-absence chain, and clear two leftovers
parent: T01
depends_on: [T01.h]
created: 2026-09-21
claimed_by: null
claimed_at: null
blocked: false
---
## Description
Three leftovers from T01.f. The first is the substantial one; the other two are mechanical.
### 1. Open a `bug_` leaf for a read that answers empty where it should fail
This repo has closed the "failure disguised as an absence" family repeatedly and it has two open `bug_` leaves on it already (`bug_a-lost-inbox-watch-is-never-retried`, `bug_an-empty-inbox-list-can-mean-a-failure`). This is another member, and it spans three layers. Write ONE leaf in `.project/concepts/app-contract/`, naming the chain rather than any single symptom.
**The upstream fact that makes it a divergence and not a heritage - VERIFIED, do not re-derive it, and do NOT go read `nextgraph-rs` for it.** An anchored SPARQL query on a repo the verifier does not know REJECTS. The deciding line is `engine/verifier/src/request_processor.rs:269`: `let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?;` inside `resolve_target_for_sparql`. It crosses into JS as a REJECTED PROMISE carrying the string `"RepoNotFound"` (`sdk/js/lib-wasm/src/lib.rs:403` for nodejs, `:606` for browser). Upstream's observable partition is "in `self.repos`" vs "not in `self.repos`": "does not exist" and "exists but my session does not know it" are INDISTINGUISHABLE, while "known but unsynced" collides with "genuinely empty". No read-capability check exists anywhere on that path - `resolve_target_for_sparql` reads only `nuri.target`.
**The three layers of our chain**, each VERIFIED by reading the code in this working copy:
- `fetchReadCap` (`packages/polyfill/src/emulated-verifier/public-store.ts:190`) collapses three unrelated situations into one `false`: the emulation is not in force (`:196`), the network could not be asked (`:223`), and the document exposes no cap (`:225`). Its own comment at `:220` states the collapse out loud. The function BELOW it, `downloadReadCap`, deliberately distinguishes resolving from rejecting and documents why - so the distinction exists and is destroyed one line later. Worse, NO caller reads the boolean at all: `ensureRepoOpen:208`, `docs.sparqlQuery:155`, `branch-registers.ts:316` and `fetchReadCaps:277` all call it as a bare statement. The information is not merely flattened, it is unrecoverable.
- The read then proceeds on a document that was never opened.
- `useShapeQuery` answers a SUCCESS holding zero objects.
**What the target says the answer should be.** `useShapeQuery`'s return type is modelled on TanStack Query's `useQuery`, which distinguishes purely by whether the query function REJECTS: a rejection gives `isError` with the error, a resolution gives `isSuccess` with whatever data, empty included. That maps exactly onto upstream's `RepoNotFound`. So "the document is not reachable" must surface as `isError`, and `isSuccess` with zero objects must mean "it is there and holds nothing".
**Also record, because it is the same family and currently unowned:** `useShapeQuery` never opens the documents named in `scope.graphs`, so an application that names an unopened document gets an empty success with no error. The `ensureReposOpen` barrier was removed from that path by T01.d. A fix for it was written during T01.f and REVERTED on instruction, because it was justified by a misdiagnosis (see the parent's digest); the defect itself is real and stands.
**Severity is yours to argue in the leaf**, from the two existing bug leaves' bar. State what an application observes, not what the code does.
**Explicitly OUT OF SCOPE - do not implement a fix.** Changing `fetchReadCap`'s return type to name the three cases, or making a read fail where it currently answers empty, is a DESIGN CHANGE the owner has not arbitrated. This task RECORDS the defect. It does not repair it.
### 2. Remove `ensureReposOpen`, now dead
T01.f removed its last caller. Verify that claim with a grep before deleting - if a caller survives, say so and leave it. `ensureRepoOpen` (singular) is NOT dead and must stay.
### 3. The example's typecheck
`examples/notebook/tsconfig.json` fails typecheck. T01.f reports this as pre-existing and verified as such. The owner has asked for the example to be repaired. Make it typecheck, without weakening the config - if the only way through is to loosen a compiler option, stop and report instead.
**Hard gate:** `bun test` -> 0 fail; `bunx tsc --noEmit -p packages/polyfill/tsconfig.json` -> exit 0; `bunx tsc --noEmit -p packages/polyfill/e2e/tsconfig.json` -> exit 0; the example's own typecheck -> exit 0; `python3 ~/.claude/skills/concept/lint.py` -> 0 ERROR. Anti-cheat: no `any`, no `@ts-ignore`, no `as unknown as`, no deleted or skipped test, no weakened assertion, no loosened compiler option.
## Progress
## Result