diff --git a/packages/chord/docs/next/decisions.md b/packages/chord/docs/next/decisions.md index 3d00c73e7..b7f2464ed 100644 --- a/packages/chord/docs/next/decisions.md +++ b/packages/chord/docs/next/decisions.md @@ -1747,6 +1747,12 @@ validation and copies the v2 kernel added around Delta. a throwing `compose` (the owner's bug) is reported (`registry_failed`), its watches get a `failed` call, and `.value` keeps the last good composition (before any, it throws). Derives keep `.value` throwing while failed: state must match its sources, an extension point must stay available. +- **Slice 8 D2 and D3 (owner, 2026-10-27: "yes"):** D2: a `retryOn` source retries a failed object only on a `value` + call; a `failed` call does nothing. D3: `host.root(fn, context, { principal? })` gives `fn` a handler's call + context (the host's principal or the given one, this host's incarnation, `context`'s telemetry and signal linked + until `fn` settles, `ctx.get`); at settle or abort the context is sealed (`ctx.get` rejects `released`, its references + released, their calls reject), so an uncooperative `fn` fails at its next use of Chord; before `load` its gets park; + after `host.close` it rejects `released`. `chord(fn, context)` is p3's helper. Closes design §26 #25. - **The performance and memory bar, and converging reviews (owner, 2026-10-27: "yes"):** supersedes "as good as HEAD in everything" for Chord v2 slices and pi changes. CPU: each hot path (calls, changes, watches, links) within about 5% of HEAD, measured in bursts with alternating processes pinned to one CPU; worse is fixed or goes to the diff --git a/packages/chord/docs/next/design.md b/packages/chord/docs/next/design.md index cd55b3bfb..de9324a66 100644 --- a/packages/chord/docs/next/design.md +++ b/packages/chord/docs/next/design.md @@ -1494,13 +1494,13 @@ export default defineFacet((host, self) => { // `self` (contributions) a ## 17. Registries ```ts -export const Http = defineRegistry("chord.http", { - version: 2, +export const Http = defineRegistry("chord.http", { version: 2 }); // shared: identity only +host.serve(Http, { // the defining plugin's facet: the one provider (slice 8 D1) + compose: (table, { report }) => buildRouter(table, report), // pure; always delivers a value; reports conflicts accepts: { 1: (old: HttpContributionV1): HttpContribution => upgrade(old) }, - compose: (table, { report }) => buildRouter(table, report), // pure; always delivers a value; reports conflicts }); self.contribute(Http, { routes: { … }, wraps: [ … ] }); -const router = host.registry(Http).value; // live +const router = self.registry(Http).value; // live (host.registry(Http) outside objects) ``` - Each contributor (a facet or an object) contributes **at most one value per registry**; the table is an immutable, @@ -2019,7 +2019,6 @@ this document): | 14 | project plugins | 21 | own pass over `plugin-loading.md` | | 16 | a Durable Object profile | 24 | when a DO target becomes concrete | | 24 | the rollout install timeout (60 s) and a per-type drain override: host options or fixed (the routine handover uses `closeTimeout`; a failing job `finish` backs off 1-60 s and blocks after five failures, decided) | 3 | with slice 7 | -| 25 | `host.root` signature (`root(fn, context, { principal? })` proposed) and what an aborted, uncooperative `fn` means for its references (match the handler and `CallContext` rules) | 3, 24 | slice 8 | | 26 | how a migration commit reaches consumers (`[["r", value]]` per changed member, or a fresh `bound`; adapted by `accepts` for older consumers) | 21, 22 | slice 7 | | 28 | Astra's circuit breaker (a hard transport-buffer cap plus a stall deadline); the heartbeat is built, the cap was never decided | 3 | if a real case needs it | | 29 | round-3 cut C11 (`ctx.trace`); C3 and C4 are decided (§14, §15) | 8 | slice 9 | diff --git a/packages/chord/docs/next/slice-8.md b/packages/chord/docs/next/slice-8.md index f18d456ac..303fe2410 100644 --- a/packages/chord/docs/next/slice-8.md +++ b/packages/chord/docs/next/slice-8.md @@ -1,6 +1,6 @@ # Slice 8: registries and `host.root` (spec) -Status: **draft, open points marked "Decision needed"** (2026-10-27), written by the redesign session. Built in +Status: **decided, ready for the spec reviews** (2026-10-27; D1-D3 and the registry failures decided by the owner), written by the redesign session. Built in parallel with 6b (mostly new files). The single source for building 8, together with `design.md` (§7, §8, §11, §16, §17, §18, §24 read in full), the decision log `decisions.md` (R21, R23, R25, Q8, D1 of "Review round 1", "Registries work like Durable's", "Changing contributions, `self.writable`", W4, "Failures are watch calls", "Slice 8 is @@ -96,19 +96,20 @@ has them; a call of any source while the object is failed (`transient` or `broke independently of the attempt: a registry's composed value, another object's `writable` reached through a registry, `constant`; a reference's member is a type error (it would fail with the dependency, R18 handles that). -> **Decision needed (D2): which calls retry.** A registry value's `failed` call (compose threw) is no reason to retry -> the dependent. Proposal: only `value` calls retry; `failed` calls are ignored. +**Which calls retry (D2, owner, 2026-10-27):** only `value` calls: a new value of a source wakes a failed object; a +`failed` call (a throwing `compose`) does nothing, its next good composition retries. ## 4. `host.root(fn, context, options?)` -> **Decision needed (D3): the signature and its guarantees** (design §26 #25). Proposal: `host.root(fn: (ctx: Ctx) -> => Promise, context: Context, options?: { principal?: P }): Promise`: `fn` gets a call context like a -> handler's: the host's principal (or `options.principal`, a trusted same-host caller's choice, as base code acting for -> an authenticated user), this host's incarnation, `context`'s telemetry and abort signal linked until `fn` settles -> (`withLinkedAbortSignal`), and `ctx.get`. When `fn` settles, every reference it took is released and a later -> `ctx.get` throws `released`; an aborted `fn` that does not stop keeps running, but its references are already -> released (as a handler after its caller's cancellation). `host.root` before `load` resolved parks its `ctx.get`s -> like any reference; after `host.close` it rejects `released`. No span of its own until slice 9. +**Signature and guarantees (D3, owner, 2026-10-27; design §26 #25):** `host.root(fn: (ctx: Ctx) => Promise, +context: Context, options?: { principal?: P }): Promise`. `fn` gets a call context like a handler's: the host's +principal (or `options.principal`, a trusted same-host caller's choice, as base code acting for an authenticated +user), this host's incarnation, `context`'s telemetry and abort signal linked until `fn` settles +(`withLinkedAbortSignal`), and `ctx.get`. When `fn` settles or `context` aborts, the call context is sealed: +`ctx.abortSignal` aborted, `ctx.get` rejects `released`, every reference it took released (their calls reject +`released`, their watches end), so an uncooperative `fn` fails at its next use of Chord, as a handler after its caller +cancelled; work outside Chord runs on until then. `ctx.get` before `load` resolved parks like any reference; +`host.root` after `host.close` rejects `released`. No span of its own until slice 9. - `chord(fn, context)` is p3's helper over `host.root` (design §24); Chord ships none.