diff --git a/.project/concepts/app-contract/_debt.md b/.project/concepts/app-contract/_debt.md deleted file mode 100644 index 2514d19..0000000 --- a/.project/concepts/app-contract/_debt.md +++ /dev/null @@ -1,14 +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/use-shape-query.ts @2026-09-21 (session f93872b5-293a-4916-a353-181409a96d42) -- TOUCHED packages/polyfill/src/index.ts @2026-09-21 (session f93872b5-293a-4916-a353-181409a96d42) -- TOUCHED packages/polyfill/src/surface/orm.ts @2026-09-21 (session f93872b5-293a-4916-a353-181409a96d42) -- TOUCHED packages/polyfill/src/surface/read-document.ts @2026-09-21 (session f93872b5-293a-4916-a353-181409a96d42) -- TOUCHED examples/notebook/app.ts @2026-09-21 (session f93872b5-293a-4916-a353-181409a96d42) -- TOUCHED packages/polyfill/src/surface/subscribe.ts @2026-09-24 (session f93872b5-293a-4916-a353-181409a96d42) -- TOUCHED examples/notebook/package.json @2026-09-24 (session f93872b5-293a-4916-a353-181409a96d42) -- TOUCHED docs/api-contract.md @2026-09-24 (session f93872b5-293a-4916-a353-181409a96d42) diff --git a/.project/concepts/app-contract/knowledge_what-an-app-deletes-at-migration.md b/.project/concepts/app-contract/knowledge_what-an-app-deletes-at-migration.md index 68a23a0..e5486b4 100644 --- a/.project/concepts/app-contract/knowledge_what-an-app-deletes-at-migration.md +++ b/.project/concepts/app-contract/knowledge_what-an-app-deletes-at-migration.md @@ -7,11 +7,13 @@ summary: The two fates a published symbol can have, and why the deletion list is Every published symbol has exactly one of two fates, and knowing which is the whole point of this boundary. -**Replaced in place.** The build alias resolving `@ng-eventually/polyfill` is removed, the import resolves to the real SDK, and the application's code is unchanged. This is almost everything: `ng`, `useShape`, `useShapeQuery`, `init`, `initNg`, `getObjects`, `insertObject`, `subscribeDoc(s)`, `docs.*`, `inbox.*`, `storeRegistry.*`, and the types. +**Replaced in place.** The build alias resolving `@ng-eventually/polyfill` is removed, the import resolves to the real SDK, and the application's code is unchanged. This is almost everything: `ng`, `useShape`, `useShapeQuery`, `init`, `initNg`, `getObjects`, `insertObject`, `subscribeDoc(s)`, `docs.*`, `inbox.*`, `storeRegistry.*`, and every type a published signature actually uses - a type that no signature uses has the third fate below, not this one. -**A third fate exists and is not one of the two: withdrawn before migration.** `readUnion` and `UnionSubject` were listed above as replaced in place, and they were never going to be: a batch over N anchored queries has no counterpart at any level, so nothing would have resolved to it. They left the surface in `1.0.0-dev.8`, and an application reads one document with `docs.sparqlQuery` instead. That is the reason this leaf exists — a symbol whose fate is asserted rather than checked is how the list goes wrong. +**A third fate exists and is not one of the two: withdrawn before migration.** `readUnion` and `UnionSubject` were listed above as replaced in place, and they were never going to be: a batch over N anchored queries has no counterpart at any level, so nothing would have resolved to it. They left the surface in `1.0.0-dev.8`, and an application reads one document with `docs.sparqlQuery` instead. `Schema` and `DeepSignalSet` followed in `1.0.0-dev.12`, for a different reason worth keeping apart: not that the target lacks a counterpart - it has both - but that no published signature here used them, so they were surface an application could name and never needed. That is the reason this leaf exists - a symbol whose fate is asserted rather than checked is how the list goes wrong, and the two withdrawals show the question is asked twice: does the target carry it, and does our own surface need it. -**Deleted.** The symbol has no counterpart at any level of the target, exists only because one shared wallet hosts every user, and goes with its call site. Today that is `configure` and its config type. +**Deleted.** The symbol has no counterpart at any level of the target, exists only because one shared wallet hosts every user, and goes with its call site. Today that is `configure` and `EventuallyConfig`, the five types that describe that config's fields, and `ShapeObservable`. + +`ShapeObservable` is the instructive one, because its fate is COUPLED rather than independent. It is what `useShapeQuery` returns, and it is not a framework adapter: it carries the capability filter behind `getSnapshot` and the capability-arrival re-publish keyed on the held-caps signal. A capability arrives mid-session only because the wallet is shared, so in the target there is nothing to filter and no arrival to react to - and an announced hook returning a snapshot has nowhere to hang such a subscription because it needs none. It therefore goes exactly when the shared wallet goes, not before and not on its own. Read the deletion list as naming one migration there, not four removals. `ensureIdentity` sits between the two and is worth stating precisely: its **substance** is scaffolding — a barrier that hands out a shared wallet file and takes an identifier, a step that exists only because users share a wallet — while its **call site survives**, because an application still awaits a session before it renders. Its signature was designed for that: it takes no identifier, so the line does not change the day the wallet supplies the identity and the barrier stops appearing. @@ -29,4 +31,4 @@ The trade was deliberate and it is a real reduction in enforcement: a documentar ## The direction of travel -The polyfill-era list only shrinks. It has gone from four published calls to one, and each removal was a symbol an application should never have had: naming its own identity, awaiting the connection, wiring a second bootstrap, reaching a machinery accessor. A symbol added to that block is a promise to delete it later — so the question at every addition is whether the application genuinely needs it, or whether the library is passing on a problem of its own. +The polyfill-era CALLS only shrink - from four to one, each removal a symbol an application should never have had: naming its own identity, awaiting the connection, wiring a second bootstrap, reaching a machinery accessor. The BLOCK does not: it grew by six type entries in `1.0.0-dev.13` and `-dev.14`, when five config field types and `ShapeObservable` were found sitting in the SDK-shaped section while honestly annotated as having no counterpart. That was a correction, not a regression - the block got longer because it got true - and the distinction matters, because reading monotonicity as the invariant is how a misplaced symbol stays misplaced to protect a trend. A symbol added to that block is a promise to delete it later — so the question at every addition is whether the application genuinely needs it, or whether the library is passing on a problem of its own. diff --git a/.project/concepts/app-contract/polyfill-surface/usage_ng-helpers.md b/.project/concepts/app-contract/polyfill-surface/usage_ng-helpers.md new file mode 100644 index 0000000..737e1db --- /dev/null +++ b/.project/concepts/app-contract/polyfill-surface/usage_ng-helpers.md @@ -0,0 +1,94 @@ +--- +type: usage +summary: The polyfill entries the indexing layer and its end-to-end application stand on - placement by store reference, one anchored write, the deposit that becomes an entry, the one-shot read, and one shape query +against: "@ng-eventually/polyfill@1.0.0-dev.6 (pulled 77e78ec81de81959e90e40da8d302e274b025c51)" +pulled_from: git@gitea.reconnexion.apps.gueraud.net:Sylvain/ng-helpers.git/.project/concepts/indexing/polyfill-surface/usage_ng-helpers.md +pulled_version: 71667ca14eae86bf8a327fae09383a4d4e7541dd +pulled_at: 2026-10-01 +--- + +# usage_ng-helpers - `@ng-helpers/indexing` on `polyfill-surface` + +The consumer is the indexing layer: an index is an ordinary public document that declares a field, anyone may hand it a reference by depositing into it, and its entries appear because the layer below applies that deposit. NextGraph has no indexing concept, so nothing of what a deposit MEANS belongs upstream - the polyfill's inbox stays generic and carries an opaque payload. + +Two callers here, and they consume different things. The **library** (`src/`) reaches the polyfill from exactly ONE file, `src/polyfill-adapter.ts`: everything else is written against an internal port, so a change to the engagement breaks that one file and nothing else. The **end-to-end application** (`e2e/indexing-app.ts`, `e2e/bridge.ts`) is written the way a consumer of `@ng-helpers/indexing` writes one, and consumes the polyfill directly for its bootstrap and for reading - which is the point: if something is awkward there, it is awkward for every application. + +## Consumed surface + +**Library - placement, write, inbox** (`src/polyfill-adapter.ts`) + +- `storeRegistry.publicStore()` then `storeRegistry.createEntityDoc(store)` - the index document, in its creator's public store. A store is named by the REFERENCE the accessor hands back, and the argument is never omitted: omitting it places the document in the private store, and an index nobody but its owner can open is not an index. This is the call that was still passing the withdrawn string `"public"` against the previous version. +- `storeRegistry.openDocumentInbox(doc)` - called once, at creation, under the owner. The address it returns is deliberately dropped. +- `docs.sparqlUpdate(sessionId, update, anchor)` - the only write this package makes, always an anchored `INSERT DATA` of one triple. +- `inbox.postToDocument(doc, { payload })` - the deposit, where the payload IS a bare `did:ng:` reference. +- Types `Nuri` and `NuriLike`, type-only, so they are literally the published ones rather than a copy that can drift. + +**End-to-end application - bootstrap, reading, and one observation** (`e2e/indexing-app.ts`, `e2e/bridge.ts`) + +- `configure({ ng, useShape, ormSubscription, init, initNg, sharedWallet })`, this package's own `init(callback, true, [])` for the `session_id` its callback delivers, this package's `initNg(ng, session)` once that session has arrived, and `ensureIdentity()`. `ormSubscription` is `RdfOrmSubscription.getOrCreate` and the injected `initNg` is the SDK's, both from `@ng-org/orm@0.1.2-alpha.21`, passed as they stand; the `ng` and `init` handed to `configure` are `@ng-org/web@0.1.2-alpha.14`'s. +- The package's own published `ng`, as the argument `initNg` is given. +- `readUnion([doc])` and the type `UnionSubject` - how an index is read, and how a referenced document is read. See `## Needs`. +- `docs.sparqlQuery(sessionId, query, undefined, anchor)` - an anchored SELECT, as the second of the two ways anyone reads an index. +- `useShapeQuery(shape, { graphs: "did:ng:i" })`, `ShapeObservable`'s three members (`getSnapshot`, `subscribe`, `refetch`), the three states of `ShapeQuery` and its `data`, and the types `Scope` and `ShapeType`. ONE use, and it is an observation rather than a business read: after a failure is injected into the inbox step of `create`, this is how the suite finds the document that was left behind. The shape is hand-written and names one predicate - the descriptor `create` writes - which is what makes an index document matchable at all. + +**Not consumed**, and safely evolvable as far as this repository is concerned: `useShape` (a function that REFUSES is handed to `configure`, and nothing calls it - see `## Frictions`), `subscribeDoc` / `subscribeDocs`, `docs.docCreate`, `storeRegistry.privateStore` / `protectedStore`, `inbox.share` / `post` / `read` / `readForDocument` / `readSynced` / `readSyncedForDocument` / `processInbox` / `watch`, and the types `Deposit` and `PrincipalId`. + +## Constraints + +**A bare reference deposited into a document's inbox becomes an entry on that document.** This is the single guarantee the whole layer rests on: `add` deposits a `did:ng:` reference and nothing else, precisely because that shape is this engagement's own and it acts on it. Nothing of indexing's meaning travels in the payload, and there is no second shape to fall back on. + +**An index is created in its creator's PUBLIC store, named by that store's reference.** The public store is what serves an index's read key to whoever asks, and that is the whole reason a stranger can reach one from its reference alone. The unit suite pins both halves - where the document landed, and that the store travelled as the reference an accessor produced - so the private placement that omitting the argument would give is red rather than silent. + +**One handle is one identity.** The session is one user's and no call takes an identifier, so an `Indexing` handle is one person's. Two users mean two handles - which is also what keeps the multi-actor end-to-end journeys honest: a depositor obtains the index reference the way an application does. + +**`sessionId` is relayed, never converted.** It is carried at the engagement's own `string | number` and handed back untouched. + +**The ORM is the APPLICATION's to wire, and the library never touches it.** `src/` performs no shape read of any kind, so a consumer of `@ng-helpers/indexing` that wants none wires nothing and declares no `@ng-org/*` dependency of its own. Only the end-to-end application here wires it, and only because one observation has no other door. + +**A reactive read's load state is read before its data, and a pending read is bounded.** An empty `data` means "this scope holds nothing" only under `isSuccess`; under anything else it means nobody answered, and the two never reach a check as one value - the state travels out to the report beside the result. The bound exists because a shape query the engine refuses never finishes loading, so without one that failure would be a hang rather than an observation. + +**The write is add-only, and structurally so.** There is no delete builder anywhere in this package and the only statement it can compose is an anchored `INSERT DATA`; the unit suite executes that SPARQL against an engine that refuses anything else, so a removal is unrunnable rather than merely absent. What this asks of the engagement is exactly one write primitive and no more. + +**The document is named once, as the anchor.** The statement carries no `GRAPH <...>` wrapper, because the anchor already scopes the write to that repo's default graph. Naming it twice would be two chances to disagree. + +**A document's inbox is opened from ONE place** - `create`, under the owner, at the moment the document is created - and no address is held anywhere above that call. The non-guarantee about `openDocumentInbox` not coalescing across pages is therefore respected by construction rather than by care. + +**This package declares the polyfill a PEER dependency**, so the application above remains the one place it is named. The single-copy rule is the one packaging mistake the surface cannot protect anyone from, and a library that declared it a direct dependency would be manufacturing exactly that. + +**Every NURI is validated before it reaches a query or a deposit**, because no type guard is published (see `## Frictions`). + +**This layer stays out of `urn:ng-eventually:` entirely.** Its predicates are `urn:ng-helpers:` and the subjects it writes are always `did:ng:` references, so nothing it writes falls under the reserved prefix and nothing it writes is dropped on read. + +**An empty read is never concluded from.** Where the end-to-end suite reads an index it polls to a bound and reports what the index held instead, rather than reading once and calling an empty answer an answer. + +**The identity `ensureIdentity()` returns is opaque here** - checked for non-emptiness, carried, printed in a run report, never parsed or rendered as a name. + +## Frictions + +**The acquisition channel carries no version, so the Change policy has nothing to bite on.** The engagement asks for a pinned version and says the channel by which the source reaches an application is agreed with that application; nothing has been agreed here. This repository reaches the package as a filesystem link and declares the peer range `*`, so what it compiles against is whatever is checked out at the other end - uncommitted edits included. That is not theoretical: while this declaration was being rewritten the linked tree moved from `1.0.0-dev.6` to work heading for `1.0.0-dev.7`, and the end-to-end suite's result moved with it, mid-session, with no pin able to prevent it. The `against:` line above pins the last COMMIT, which is the only pin that exists on this side. A tag-addressable, fetchable source would turn the whole semver discipline from a description into a mechanism. + +**At the pinned version the one shape read here never starts, and the engagement says it cannot be told apart from a shape written wrong.** `useShapeQuery` is wired exactly as the Deployment requirements describe - `ormSubscription: RdfOrmSubscription.getOrCreate`, the SDK's `initNg` injected, this package's `initNg(ng, session)` called once the session has arrived - and against the broker the ORM's call into `ng` fails with `orm_start_graph is not a function` on the console. Per `## Non-guarantees` NextGraph swallows that and the read stays `isPending` for ever, so the observation it feeds reports `observed=pending` and the two end-to-end checks that rest on it fail. Reproduced on three consecutive runs, 2026-09-21. Measured through the published surface: the SDK's own `ng` answers that member, and the `ng` an application hands `initNg` makes no difference because the call substitutes one of its own. Nothing on this side can work around it - the withdrawn `listMyEntityDocs` had no other replacement - so this is carried as a failing check rather than hidden behind a looser one. + +**`configure` requires `useShape`, and a frameworkless application has nothing to give it.** `@ng-org/orm` publishes `useShape` only through its react / svelte / vue adapters; a page with no framework can produce none of them. The field is not optional in `EventuallyConfig`, so this application hands over a function that THROWS if anything ever calls it - better than the `() => undefined` it carried before, which a caller could not tell from an empty read-filtered view, but still a value invented to satisfy a signature. Making the field optional, or saying in the engagement what a frameworkless application should pass, removes it. + +**A shape has to be hand-written, and the engagement carries no example.** `ShapeType` demands `readablePredicate`, `minCardinality` and `maxCardinality` on every predicate, plus a `schema` keyed by the shape's own IRI and a type carrying `@id` and `@graph`. The Change policy names three of those requirements in one clause and shows none of the shape. What was written here was assembled from the SDK's own type declarations - a read this contract exists to make unnecessary. Three lines of example in `## Surface` would cover every application that has no generated shape to hand. + +**`initNg`'s first argument has no effect.** The Deployment requirements say to call `initNg(ng, session)`, and `## Surface` says of the published `ng` that it is the one to call instead of the one `configure` was given - so an application has a choice to make. Measured through the published surface: the call substitutes an `ng` of its own and the argument is discarded, so both readings behave identically and neither can be told apart from the outside. Saying so - or dropping the parameter - saves the next application the experiment. + +**No published NURI type guard.** `## Guarantees` says so plainly, so this layer carries its own. It is a guess at what the engagement considers a valid `Nuri`, and a wrong guess either rejects a legitimate reference or lets one through into a query. + +**No published escaping helpers.** The engagement lists no `escapeIri` / `escapeLiteral`, and this package composes SPARQL against `docs.sparqlUpdate` - so it carries its own escaping rather than reach into the provider. That is a security-relevant duplication: two implementations of one rule, and the one running here is not the one the provider tests. + +**No published way to write triples above the raw SPARQL primitive.** `docs.sparqlUpdate` is the level this layer had to align on, which is where the two frictions above come from. An "add these triples to this document" entry would delete the query composition, the escaping and the NURI validation from this package in a single move. + +**`readUnion` has no stated behaviour for a document it cannot read.** `## Guarantees` says what it returns for a document it can, and it says elsewhere that a rejection means "unknown, never absent" - but not whether an unreadable document inside the list comes back as a rejection or is swallowed into the result. The worst is assumed (swallowed, therefore indistinguishable from empty) and every read path here is written defensively around that guess. One sentence in `## Guarantees` would replace it. + +## Needs + +**A one-shot read of a named document's subjects - `readUnion` as it stands, or something that answers the same question for a document whose key the reader does not yet hold.** The ORM is wired here now, so `useShapeQuery` is reachable and the earlier objection - that this application wires none - has fallen. It still does not replace this read, and the reason is the engagement's own: **neither shape read fetches a public store's key**, so a public document read nowhere else appears in neither, and the way to make it appear is to read it once by naming it, with `readUnion` or an anchored SELECT. An index IS a public document and its central case is a STRANGER reaching it from its reference alone - precisely the case no shape read serves. Three smaller costs sit on top: a shape read is reactive and three-stated where a page-load read is one-shot; it projects only the predicates a shape declares, so an index's entries and the index's own declaration take two shapes where `readUnion([index])` takes one call; and it pushes the ORM, a hand-written shape and the `useShape` friction above onto every consumer of `@ng-helpers/indexing`, which today needs none of them. This package publishes to its own consumers that an index is an ordinary document, read with `readUnion([index])` and recognised by two IRIs; withdrawing it leaves the anchored SELECT as the only fallback, which puts every reader of an index back on raw SPARQL - where three of the frictions above come from. + +**A way to tell which store holds a document whose reference is already held.** The withdrawn `listMyEntityDocs` had one use here, and the shape read now covers most of it: after a failure is injected into the inbox step of `create`, the suite finds the document left behind by asking for every index document this identity HOLDS and diffing. What that cannot say is WHERE it sits, so the check it feeds now establishes that a document survived, and no longer that it survived in the public store. This is not the question the Change policy declined: it enumerates nothing, it names a document the caller already holds, and a store's graph is said to hold exactly the containment triples that would answer it. As it stands an application cannot verify its own placement at all - a document written to the wrong store is undetectable until some reader fails to open it. + +**A way to tell a shape the ENGINE refused from a shape the application wrote wrong.** `## Non-guarantees` says a refused shape query is caught upstream, written to the console and nothing more: the set stays empty and its readiness never settles. From an application both faults look identical - an `isPending` that does not end - and the console line is not something a page can act on. That is the whole diagnostic surface for the one thing an application must hand-write itself (see `## Frictions`), so the first shape anybody writes has no way of telling "the engine would not take this" from "the ORM was never started". Anything that separates them - a refusal that reaches `isError`, or a state that says "never opened" - turns a silent hang into something an interface can report. + +**A way to tell "examined and discarded" from "not examined yet".** A deposit this engagement applies is consumed whether or not it produced an entry, and a refusal goes to the provider's own log stream - nothing an application can observe, and by the engagement's own words there is no way to ask what was lost. The cost here is concrete: the journey that checks that an object carrying nothing for an index's field is NOT indexed cannot be waited for, because no reading distinguishes "the deposit was examined and skipped" from "the deposit has not been examined yet" - and this repository's suite says so in its own header rather than hiding it, declaring that check weaker than its name. Anything observable from the application - a per-deposit outcome, or merely a count that moves when a queue has been drained - would make it a real one. This layer holds nothing of its own on purpose: an index only ever grows, and the reference is the whole of what is deposited. diff --git a/.project/concepts/e2e-harness/ng-e2e-helpers/usage_ng-helpers.md b/.project/concepts/e2e-harness/ng-e2e-helpers/usage_ng-helpers.md new file mode 100644 index 0000000..b777507 --- /dev/null +++ b/.project/concepts/e2e-harness/ng-e2e-helpers/usage_ng-helpers.md @@ -0,0 +1,55 @@ +--- +type: usage +summary: What this repository's end-to-end suite calls from ng-e2e-helpers - bounds, profiles, the minted wallet, the broker crossing, and the run report +against: "ng-e2e-helpers@1.0.0-dev.2 (pulled d44063961682f84d4b804e5b349cd0a791dd9550)" +pulled_from: git@gitea.reconnexion.apps.gueraud.net:Sylvain/ng-helpers.git/.project/concepts/indexing/ng-e2e-helpers/usage_ng-helpers.md +pulled_version: 71667ca14eae86bf8a327fae09383a4d4e7541dd +pulled_at: 2026-10-01 +--- + +# usage_ng-helpers - `@ng-helpers/indexing`'s end-to-end suite on `ng-e2e-helpers` + +The consumer is this repository's end-to-end suite: one run that builds a small indexing application, serves it, gets two real people into it through the real broker, and drives depositing and curating between them. + +Everything generic - the wallet lifecycle, the broker crossing, per-run profiles, bounds, the report shape, the recognition of the known browser failure modes - comes from the engagement and is **used, never reimplemented**. What is specific to this repository is the page that carries the application, the name its runs mint, and the journeys themselves. Two files touch the engagement: `e2e/harness-page.ts` and `e2e/run.ts`. + +## Consumed surface + +- **Bounds** - `within`, `armSuiteDeadline`, `closeQuietly`, `firstLine`. +- **Measurement** - `measured`. +- **Browser and profiles** - `launchWatchedContext`, `closeContext`, `newPage`, and the type `RunProfile`. +- **Wallet** - `mintWalletProfile` and the type `WalletCredentials`. Each run mints its own. +- **Broker crossing** - `setupBrokerPage`. +- **Serving** - `serveOnEphemeralPort`, for the application bundle. +- **Known failure modes** - `browserTrouble`, wired as the suite's `diagnose`. +- **Report** - `declareSuite` and the type `Prerequisite`; on the handle it returns, all three of `check`, `journey` and `finish`. The `JourneyDeclaration`, `JourneySpec` and `SuiteOptions` shapes are consumed structurally, through those calls, rather than imported. +- **Constants** - `BROKER_ROUND_TRIP_MS`, `NEW_PAGE_MS`, each used as a term of a bound this suite composes. +- **Browser types** - `BrowserContext`, `Page` and `Frame`, obtained by DERIVING them from the helpers that return them (`Awaited>`) rather than by importing them. See `## Constraints`. + +**Not consumed**, and safely evolvable as far as this suite is concerned: `completeBrokerLogin`, `brokerRedirectFor`, `BROKER_SCREENS` and the screen inventory types, `WALLET_APP` / `WALLET_CREATION` / `WALLET_IMPORT`, `emptyProfileContext`, `importWalletFile`, `exportWalletBytes` / `exportWalletFile`, `mintWalletBytes`, `mintWalletProfileKeepingContext`, `createWalletInContext`, `newRunProfile`, `isAlive`, `browserLost`, `lossDeclared`, `enclosingBound`, `frameTrouble`, `record`, `timingsWanted`, `printTimings`, `DEFAULT_WALLET_NAME`, the exported error classes `DeadlineExceeded` and `BrowserGone`, the `ng-mint-wallet` executable, and the remaining `*_MS` constants. + +## Constraints + +**The browser types are DERIVED from the helpers, never imported from `playwright` here.** The engagement declares Playwright a peer dependency and this package reaches this repository as symlinked files, so TypeScript resolves the helpers' `playwright` from where those files really live. Importing the driver in this repository as well produced two structurally different copies of `BrowserContext`, and a context this suite had opened could not be handed back to the helper that opens contexts. Taking the types from the calls that return them leaves exactly one set. The engagement now re-exports those same three types from its own entry point, which reaches the same single-set guarantee by a shorter road; what must keep holding either way is that there is exactly ONE resolution of the driver in this repository. + +**One wallet per run, minted, never carried.** The run's wallet name is stable and its identity is not; nothing survives a run, and no result depends on a previous one. That is also what lets this suite run beside another repository's at the same time, against the same broker, without a lock. + +**Nothing generic is reimplemented here.** Where a helper exists, it is called. That is a standing rule for this suite, not a preference - the crossing alone has cost days of misdiagnosis upstream, and a local copy of it would not carry those lessons. + +**Every wait is entered bounded.** No page or frame operation runs outside `within` or a helper that bounds it itself. + +**No failure text is parsed.** What `browserTrouble` answers and what a deadline carries are reported to a human reading the run and never matched on or branched on; a failed check carries an unexpected VALUE, and that is what the suite decides from. + +**Two suites never share this run's machinery.** One suite deadline is armed and one timings table is kept, and nothing here assumes either can be shared - the engagement offers no concurrency guarantee and this suite asks for none. + +## Frictions + +**The acquisition channel carries no version, so the Change policy has nothing to bite on.** The engagement asks for a pinned version; this repository reaches the package as a filesystem link, so what it compiles and runs against is whatever branch happens to be checked out at the other end - and the `ref:` pinned for this contract is a branch, for the same reason. The `against:` line above is the only pin that actually exists on this side. A tag-addressable, fetchable source would turn the semver discipline from a description into a mechanism. + +**Nothing bounds a call into the application iframe.** `frame.evaluate` carries no timeout of its own, so this suite wraps every bridge call itself. The engagement offers no way to obtain that bound, so each consumer re-derives the same wrapper - and the derivation is not free: the calls this suite reached for outside its own wrapper are exactly the ones that can still hang a run. A bounded `evaluate` here would delete the wrapper and close the gap in one move. + +**"Measured and bounded" is one intent and two calls.** Sizing a bound from its own measurement is the discipline the engagement itself prescribes, yet every step in this suite has to compose `measured(what, ms, (bound) => within(what, bound, task))` by hand, in a local helper written for that purpose alone. Two consumers writing the same three-line helper is the tell that the pair belongs on the engagement. + +## Needs + +**A bounded read-until-it-holds that reports what it saw.** Convergence in NextGraph is asynchronous everywhere - nothing on any surface says "it has arrived" - so a check that reads the instant after an act reads too early, and this suite has to carry its own loop: read, test, sleep, and on expiry throw naming what never arrived AND what was held instead, so the journey's remaining checks are reported unreached with an observation rather than a bare timeout. That is not specific to indexing; it is what any application's suite needs against a system that converges, and it sits exactly where the engagement's bounds already sit. What must not be lost if it moves onto the engagement: the assertions run ONCE on the reading handed back, never re-run inside the loop - a loop that retried the assertions would turn a flapping result into a green one. diff --git a/.project/concepts/sign-in/_debt.md b/.project/concepts/sign-in/_debt.md deleted file mode 100644 index 01ef1c0..0000000 --- a/.project/concepts/sign-in/_debt.md +++ /dev/null @@ -1,8 +0,0 @@ -# Doc-debt — sign-in - -> 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/shared-wallet/bootstrap.ts @2026-09-21 (session f93872b5-293a-4916-a353-181409a96d42) -- TOUCHED packages/polyfill/src/shared-wallet/account-registry.ts @2026-09-21 (session f93872b5-293a-4916-a353-181409a96d42)