mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-06 23:44:36 +00:00
The owner's audit decisions (decisions.md, "Simplification audit"): cut the reload-only installing state (staging is synchronous) and the static registry cycle edges (the runtime guard stays); retry propagation by a visited set and a hop budget (retry.hops) instead of operation ids and through lists; a pure swap sends holds a fresh bound; one rule after a live failure (every member followed until the admitted commits published); parked_long for any long park, naming the first blocker in declaration order with its state; one seal token per attempt. Keys follow the old instance during a restart's drain like members. src/next: about 255 code lines fewer.
330 lines
27 KiB
Markdown
330 lines
27 KiB
Markdown
# Dependency resolution in Chord v2: one model (decided, 2026-10-29)
|
|
|
|
Status: decided (owner, 2026-10-29); **built for slice 7a** (audit A1, 2026-10-30: `installing` is cut, staging being synchronous; A5: retry dedupe by a visited set and a hop budget; A11: `parked_long` for any long park) (§1-4 and §6-11 without what installs and reloads add:
|
|
§1's `installing`, §4's install-end and reload wakes, §3.9's reload handover and §5's removal, versions and rollout
|
|
are slice 7b's; folded into `design.md` §3, §5, §6, §7, §17, §21's start barrier, §22; `slice-7a.md`). The draft
|
|
history below is kept as it was. Sources: the owner's direction
|
|
("park until resolved"; "no difference between local and remote resolution: a link going is like a plugin
|
|
unloading"), the reviews (`/tmp/dep-mechanism-*.md`, `/tmp/dep-graph-*.md`, `/tmp/barrier-*.md`,
|
|
`/tmp/model/review{1,2}-*.md`), the code at pi `358f7d46a`. **Owner** marks an open decision.
|
|
|
|
Draft 7 (round 6): (A) also retries in lockstep without finding the cycle (Fable); the probe's recovery after a
|
|
suppressed search becomes a choice between (B1) a re-search backoff for probes only (recommended) and (B2) a full
|
|
search lifecycle without timers, since `dead` notices alone need recovery state that eviction may not drop (Astra, Sol).
|
|
Causes are compared by value on wakes (Opus).
|
|
Round 7: a returned search is accepted while its initiator is parked, whatever its round (Astra: re-searches must not
|
|
invalidate searches under way); detection is eventual, not within one backoff period; the local walk stays (Opus).
|
|
|
|
Draft 6 (round 5): cycles across hosts become an owner choice between (A) the owner's slice 4b decision (simultaneous
|
|
starts across hosts time out, no probe message), narrowed to one deadline, and (B) the probe, completed with edge
|
|
tokens, no revisits, and `dead` notices back to the initiator (Astra: a check that dies silently can lose the only
|
|
search that survived dedupe). (A) turned out to either poll chains across hosts again or miss a ring of `waiting`
|
|
attempts, so (B) is recommended, pending its review.
|
|
|
|
Draft 5 (round 4, Sol's phantom cycle): the second round is replaced by a check along the recorded trail of
|
|
`(attempt, edge generation)` hops; a reported ring existed at one moment; probes grow with their path.
|
|
|
|
Draft 4 after round 4 (probe only): round numbers per attempt, forwarded only when newer than the stored one; probes
|
|
honoured on parent and route links, dropped on child links; completeness stated under an eventually stable graph; the
|
|
repair caveat broadened to any concurrent edge removal; a new edge generation resets confirmation; a bounded display
|
|
trail.
|
|
|
|
Draft 4 changes (round 3): the probe starts a fresh round on every new or changed wait edge (a ring closes at its last
|
|
edge, whose source finds it), keeps one seen entry per initiator, accepts only its current round, and states its
|
|
cost honestly; every host drops probes from untrusted links; local forwarding is iterative; no-placement remote
|
|
requests are re-placed when a host's first parent link connects; a cohort object that ends before its first outcome
|
|
settles the barrier; the last-good refinement is marked for the owner.
|
|
|
|
Draft 3 changes (round 2): remote types without placement keep today's fallback (here if served, else the parent);
|
|
hydration stays before parking; no copied `retryAt`; a dependency `ended` breaks its dependent; a reload's handover
|
|
binds new requests before releasing old ones; retry passes only to `broken` and `transient` blockers, once per
|
|
operation; `installing` re-evaluated at every install end (commit or rejection); a registry's last good value dropped
|
|
when one of its contributors leaves; the probe with round ids, bounded seen sets, edge-generation triggers, attempt
|
|
ids on every non-live answer and relays dropping probes from untrusted links; one cycle mechanism, local and remote.
|
|
|
|
## 0. The problem
|
|
|
|
The same situation, "what I need is not there", has five outcomes today, depending only on how it is declared:
|
|
|
|
| Declared as | Provider missing | Today |
|
|
| --- | --- | --- |
|
|
| `self.get(LocalType)` | no facet serves it | `self.get` throws, the factory throws: `broken` |
|
|
| `self.get(RemoteType)` | the worker does not serve it | the hold is refused `broken`; the dependent `transient`, polled 1-60 s forever |
|
|
| `self.registry(R)` | no provider | `transient`, woken at R's first value only |
|
|
| `self.registry(R)`, reader newer | | `broken` |
|
|
| a reload removes the type | | holders get `gone`: ended for good |
|
|
|
|
And the startup barrier deadlocks an acyclic graph across two hosts (Sol, `/tmp/dep-graph-sol-barrier.log`): S's eager
|
|
A needs W's B, B needs S's C; W's hold for C waits for S's whole `load`, which waits for A. Only `readyTimeout` ends it.
|
|
|
|
## 1. Requests and one resolver
|
|
|
|
- **A request** is what a reference stands for: type (or registry), params, principal, the consumer's contract version.
|
|
Its holder makes it and owns it: an attempt (`self.get`, `self.registry`, keyed pins), a call (`ctx.get`), outside
|
|
code (`host.get`, `host.hold`, `host.registry`), a link's peer (`hold`). **It ends only when its holder ends it** (the
|
|
attempt ends, the call settles, `release()`, a getter gives up, the peer's link or origin closes, revocation, the
|
|
holding host closes), never because its provider is missing, went away or does not fit.
|
|
- **The destination by address** (round 1): a `local` type goes here; a `remote` type goes to the address its
|
|
`hostAddress` (computed from the consumer's own contract) gives: here if that is this host's address, whether served
|
|
here or not, else over the route for that kind or the parent (design §3). Every `remote` type declares
|
|
its `hostAddress` (owner, 2026-10-29: the optional `placement` of slice 3a is removed and the field renamed, and with it the fallback "here if
|
|
served, otherwise the parent" and its re-placement at the first parent link). A hold arriving for an address that is not this host's is relayed or
|
|
refused (`invalid/host_address`) as today; a relay never captures a hold because it serves the same name.
|
|
- **Then the selected provider is checked** (served, version or accepted version and fingerprint, `authorize`, the
|
|
provider's own host-address check). Version checks never look at a same-name registration elsewhere.
|
|
- **The publication gate:** a request made on this host, or arriving on its links, before the host's first
|
|
publication (§6) is not placed yet; it shows `loading` and is placed by the publication. The gate lasts only for the
|
|
synchronous setups, never for activation, so it cannot deadlock.
|
|
- **Outcomes of the check:**
|
|
|
|
| The check finds | Request |
|
|
| --- | --- |
|
|
| a fitting provider | bound (to the object here, or to the hold over a path) |
|
|
| not published yet | unbound `unpublished`: `loading` |
|
|
| nobody serves the name at its address | unbound `not_served`: `failed { waiting }` |
|
|
| no fitting version, a fingerprint that differs at the same version, a host address the provider computes elsewhere (code skew) | unbound `version`: `failed { waiting }` |
|
|
| the registry is served, but its first `compose` (or the reader's version mapping) threw | unbound `compose`: `failed { waiting }` |
|
|
| the provider host is installing a revision and the hold does not fit its current code | unbound `installing` at the provider: `loading` |
|
|
| the path has no link (a drop, a route resolving) | on the path: `loading` |
|
|
| `authorize` refused, invalid params | `failed { broken, forbidden \| invalid }`, as today, never indexed |
|
|
|
|
The unbound reasons that wait for a name (`unpublished`, `not_served`, `version`, `compose`) are indexed under it on
|
|
the host that decided them: the consumer's host for a local request; the provider's host for a hold that arrived
|
|
over a link (it already keeps such holds as refused: `remote/hold.ts`). `installing` is the provider host's own,
|
|
re-evaluated at its install's end; `no_link` lives on the path, where the next link holds it again. Indexes drop an
|
|
entry when its request ends; GC-tested.
|
|
|
|
## 2. States
|
|
|
|
`ref.state` keeps its four kinds; `FailureCause` gains one kind, `waiting`:
|
|
|
|
```ts
|
|
type RefState =
|
|
| { kind: "loading" }
|
|
| { kind: "live" }
|
|
| { kind: "failed"; cause: FailureCause; retryAt: number | undefined }
|
|
| { kind: "ended"; error: ChordError };
|
|
|
|
type FailureCause =
|
|
| { kind: "transient"; error: ChordError } // the object's own effect failed (an open, a follow): backoff
|
|
| { kind: "broken"; error: ChordError } // the object's own code, a cycle, or authority: retry() or a reload
|
|
| { kind: "waiting"; error: ChordError }; // not the object's fault: no provider, no fitting version, a
|
|
// dependency failed; retryAt undefined, woken by an event
|
|
```
|
|
|
|
| The request is | `ref.state` | Calls |
|
|
| --- | --- | --- |
|
|
| bound, its object activating or parked with no failed blocker | `loading` | park, bounded |
|
|
| unbound `unpublished` or `installing`, or its path has no link | `loading` | park, bounded |
|
|
| unbound `not_served`, `version` or `compose` | `failed { waiting }` | reject at once |
|
|
| bound, its object failed | `failed` with the object's cause, as today | reject at once |
|
|
| bound, its object live | `live` | run |
|
|
| ended by its holder, revoked, or its host closed | `ended` | reject |
|
|
|
|
**Owner:** `waiting` as a new cause kind (Opus, Fable; recommended: the cause says "not your fault, an event will wake
|
|
you" and a UI can say what is missing) or no new kind: `transient` with no `retryAt` (Astra; today's wire already
|
|
carries a `transient` cause without `retryIn`, `remote-object.ts`), or `loading` with a diagnostic (Sol; parks every
|
|
call on a missing plugin for its full `readyTimeout`).
|
|
|
|
## 3. Dependents: parked until their dependencies resolve
|
|
|
|
A **dependency** is what an attempt declares and needs before it goes live: `self.get` (local or remote),
|
|
`self.registry`, keyed pins (`at(key)` on another object's keyed member). `self.follow` is the attempt's own step (its
|
|
attach runs Durable's code), not a dependency; a follower declares `retryOn` on the registry it reads the harness
|
|
through, or recovers by its own backoff.
|
|
|
|
1. **Order, as today up to the check:** the factory runs; persisted singletons hydrate (the object's documents, kept
|
|
across attempts); the requests are made. The attempt is **parked** while any dependency is not `live` (hydrated):
|
|
|
|
| Its dependencies | The dependent shows |
|
|
| --- | --- |
|
|
| any `loading`, none failed | `loading` |
|
|
| any `failed` | `failed { waiting, "waiting for X: <X's cause>" }`, `retryAt` undefined; the message keeps the nearest blocker and the root cause within the 500-character rule |
|
|
| one failed with `invalid/cycle` (a dependent off the ring) | `broken` with the same error: it would wait on a ring that never heals by itself |
|
|
| one failed with `forbidden` (an `authorize` refused it: authority, §3.6) | `broken` with the same error: only `retry()` or a reload helps (slice 7a fix round 1) |
|
|
| one `ended` (revoked, or its host closed) | `broken` with that error: the request cannot come back; `retry()` or a reload makes new requests (a guard: a dependency ends only with its holder's principal or host, so the dependent ends too; Fable) |
|
|
|
|
2. **The dependency check is the activation boundary:** once every dependency is `live`, the attempt runs its own
|
|
steps (natives in order, follow attaches, member checks, derives), bounded by its own `readyTimeout` deadline, and
|
|
goes live. A dependency that leaves `live` after the check is treated as after live (R18): the attempt goes on. This is
|
|
R18's rule for live objects applied from the check on, not a rule of its own (owner, 2026-10-29: a dependency can
|
|
go away at any point of an object's life; the object stays live, its calls through that reference meet its state). So a parked attempt holds no
|
|
natives, and an attempt that opened natives is never parked.
|
|
3. **No timer while parked:** no dependency deadline, no backoff. The attempt's deadline is paused while parked (as
|
|
eager migrations pause it) and resumes with the budget left.
|
|
4. **Wakes, one rule:** the dependent re-evaluates on every state change of any of its dependencies (live, loading,
|
|
failed, a new cause compared by value, ended; Opus: otherwise a ring of waiting attempts passes its causes round and
|
|
round), updating what it shows; it leaves parking when all are live. The factory never re-runs
|
|
while parked. Wakes are coalesced per object and run after the batch that caused them; a change during the
|
|
attempt's own steps or its teardown is remembered. (As built in 7a, with the parent's note 5: a change while the
|
|
parked attempt checks or loads its pinned keys is remembered and checked again; after the dependency check, during
|
|
the own steps, a change is ignored, §3.2's R18 rule.)
|
|
5. **Kept while parked:** its documents (the object's), its declarations and requests, its seal token; no natives.
|
|
Ended with the object (its last holder; a host-lifetime object when its plugin is removed or the host closes).
|
|
6. **`broken` only for the object's own code** (a throwing factory, a derive throwing at activation, hydration,
|
|
`migrate`, a document fingerprint mismatch, an `open` throwing `broken(...)`), **cycles** (§7) and **authority**
|
|
(`forbidden`, an ended dependency).
|
|
7. **Retry passes on:** `ref.retry()` on a `failed { waiting }` object retries its `broken` and `transient` blockers,
|
|
hop by hop (the existing `retry` message over links), once per retry operation (an id dedupes diamonds); `waiting`
|
|
blockers pass it on; nothing else is touched. Retry of a `broken` or `transient` object starts a new attempt, as
|
|
today.
|
|
8. **`retryOn`** wakes failed objects as decided in slice 8 (a value call retries a `transient` or `broken` object);
|
|
while the object is parked `waiting`, a value call does nothing: its dependencies decide.
|
|
9. **Own-code reload of a parked object:** its plugin's reload replaces the parked attempt with the new factory's,
|
|
as one handover: the new requests are made and bound first, then the old ones released (round 2, Astra N4: an
|
|
unchanged dependency held only by this object must not end in between; today's `#activate` binds before
|
|
`#releasePrevious`).
|
|
10. **After live, R18 unchanged:** a dependency leaving `live` leaves its dependent live.
|
|
|
|
## 4. Wakes: two events
|
|
|
|
| Event | Re-evaluates |
|
|
| --- | --- |
|
|
| **a target changes state**: a dependency's object goes live, loading, fails, gets a new cause or ends; a hold's `bound`, `loading`, `failed` arrive, also after a re-hold on a new link | the requests bound to it, and their dependents (§3.4) |
|
|
| **a name becomes usable, or an install ends, on a host**: a publication (`load`'s setups, a reload's commit, an install's commit) serves a type, a registry, a version; a registry composes or maps a value at a version; an install ends without a commit (rejected) | the unbound requests indexed under those names; the provider's `installing` holds; a refused hold re-runs its start and sends `loading`, then `bound` or `failed` |
|
|
|
|
No new message kind for wakes: `loading`, `bound`, `failed` and `retry` exist. The wire's `failed` carries the new cause
|
|
kind `waiting`.
|
|
|
|
## 5. Removal, versions, rollout
|
|
|
|
- **A reload that removes a type:** its objects end as today (natives close, reason `end`; callers rejected at once;
|
|
handlers drain). Their requests are not ended: each becomes unbound `not_served` and binds again if a later commit
|
|
serves the name.
|
|
- **Locally:** a `Binding` whose target ended by removal becomes unbound and accepts a second `bind`: member sources
|
|
re-bind by name (one frame from the last value, as on a restart), keyed pins re-pin, parked calls flush or
|
|
reject, presence re-lists. The one new piece of machinery.
|
|
- **Over a link:** a `failed` on a bound hold unbinds it (Fable: one message, one rule; the consumer applies it as
|
|
`#startOver` plus `failed`); the provider detaches the hold's sources and drops its unsent pending ops and queued
|
|
`bound`; the ack slots of `bound`/`state` messages already sent stay counted until their `ack` or the link's loss;
|
|
calls in flight on it are rejected and free their slots once.
|
|
- **A reload that stops accepting a version** unbinds only the requests on that version (`version`); the object stays
|
|
(its own plugin's reload still restarts it if it has natives, §21).
|
|
- **A type moved to another host kind:** the request carries the address its own consumer code computed; the old host
|
|
answers `not_served`; the consumer's own reload makes a new request. No redirect.
|
|
- **A consumer whose code moves to a new version** releases its old request and makes a new one (today).
|
|
- **Rollout, without revision fields:** each host knows only its own install. A provider host that installs keeps
|
|
holds that do not fit its current code `installing` (`loading`) and re-evaluates them when the install ends, by its
|
|
commit or by its rejection. A provider host that is queued but not installing yet answers a mismatch `failed
|
|
{ waiting, version }` until its install commits and wakes it: a visible blip of at most one install, accepted (Opus).
|
|
An install never waits for activations (a reverse-order rollout would deadlock); a host whose own install was
|
|
rejected fails its own requests at once with the reason (design §21). The loader keeps its own duties (the desired
|
|
code set, digests, serialized installs).
|
|
- **Old calls across a reload** follow §21 unchanged: a pure swap lets them finish on old handles; a native restart
|
|
drains them first; a removal rejects their callers at once and drains the handlers. Calls are never resent.
|
|
|
|
## 6. Startup: publication, then first outcomes
|
|
|
|
1. **Publication:** `load` runs every facet's setup, then publishes all at once in one batch (types served,
|
|
registries served and composed once, contributions in their tables), then wakes (§4) after that batch. Nothing
|
|
activates inside it.
|
|
2. **No gate beyond it:** holds and `host.hold` wait only for the publication, never for the barrier. This ends the
|
|
startup deadlock: W's hold for C binds as soon as S's setups published; B goes live, then A.
|
|
3. **The barrier** (**Owner:** (a) or (b); recommended (a), three reviewers of four):
|
|
- **(a):** `load` resolves when every host-lifetime object of this `load` has a first outcome:
|
|
`firstOutcome(o) = o.live || o.ownFailure || (o.parked && o.blockers.every((b) => !b.local || firstOutcome(b)))`,
|
|
where `local` means executing on this host (an object here, a keyed pin still loading here), not the contract's
|
|
kind. So `CodeMode → Harness` waits for the harness's outcome; a wait on another host or on a failed dependency
|
|
ends the first pass. One `firstOutcome` signal per object, separate from the readiness dependents wait for (Sol),
|
|
counted once, never reopened by retries. An object of the cohort that ends before its first outcome (its plugin
|
|
removed, the host closing) settles its entry as ended; `host.close` settles the waiting `load` (Astra R3).
|
|
- **What (a) promises:** every host-lifetime object on this host had its first try, and the contributions of those
|
|
that went live are in their tables. **Not:** every tool that will arrive (a parked or slow contributor, tools found
|
|
in `onLive`, MCP; Sol's probe `/tmp/barrier-sol-first-outcome.log`). What a first turn needs is p3's explicit
|
|
admission check.
|
|
- **(b):** `load` resolves after the publication; Chord adds `host.startup`, a watchable of the host-lifetime
|
|
objects' first outcomes with their causes; the app decides (Fable). The same tracker, awaited by the app.
|
|
- p2 today resumes its harness without waiting for all host objects (`apps/p2/src/hosts/worker.ts`), because its
|
|
worker objects that need server types are reachable only after `control.ready()`.
|
|
|
|
## 7. Cycles (decided 2026-10-29: no probe)
|
|
|
|
On one host, cycles are found as today: at `self.get`, following the waiting edges of activating and parked attempts
|
|
(exact). Across hosts, a hold carries the activation chain it was made in, which finds every ring whose objects start
|
|
one after another. The remaining gap, a ring across hosts whose objects start at the same moment, is not detected:
|
|
without a dependency deadline it stays parked. It is made visible: an attempt parked longer than its `readyTimeout` on
|
|
a remote attempt that is itself only `loading` is reported once per parking episode as `HostError` `parked_long
|
|
{ object, waitingFor, since }`; nothing fails or retries. A cycle is a plugin bug; the guide gives plugin authors and
|
|
agents four rules: (1) a factory declares only what the object needs, never a type that needs it back; (2)
|
|
back-references and occasional lookups go into methods and routines (`ctx.get`); (3) data flows up through state and
|
|
registries, not mutual `self.get`; (4) host-lifetime objects on the server never declare worker types in their factory.
|
|
|
|
Rejected (owner, 2026-10-29): the probe (edge-chasing searches with a check along the recorded trail; seven review
|
|
rounds, `/tmp/model/review*-*.md`; complete only with a re-search timer or a full search lifecycle, the most intricate
|
|
code in Chord, for a plugin bug) and the slice 4b timeout narrowed (it polls chains across hosts again, misses a ring of
|
|
`waiting` attempts, and retries in lockstep without finding the ring).
|
|
|
|
## 8. Getters, holds, calls: one rule
|
|
|
|
- **`host.hold`** returns a reference at once; only invalid params throw (also before `load`). A missing local type
|
|
gives `failed { waiting }` after the publication, `loading` before it.
|
|
- **`host.get`, `ctx.get` and `host.registry`** (owner, 2026-10-29, decision 2): resolve when live; wait while `loading`,
|
|
`failed { transient }` or `failed { waiting }`, up to their `readyTimeout` (default 60 s, the caller's choice), then
|
|
reject with the last cause; reject at once only on `failed { broken }`. On
|
|
success, rejection or timeout their temporary wait goes; `host.registry` returns the `RegistryValue` with its
|
|
ordinary watch ownership (no `release()`). A UI holds and shows `ref.state`; code that needs the object now gets it, with the
|
|
patience it passes.
|
|
- **Calls** on a `loading` reference park, each bounded by the reference's `readyTimeout` from when it was made; on a
|
|
`failed` one they reject at once with the cause; sent calls are never resent.
|
|
|
|
## 9. Registries
|
|
|
|
- The same resolver on one host: a reader's request for R at its version is bound while R is served and has a usable
|
|
value at that version (composed; mapped by `accepts[old].value` for an older reader), otherwise unbound
|
|
`not_served`, `version` or `compose`, or `loading` before the publication. Woken at every transition to usable
|
|
(`#valued`'s first-value latch goes). S2's `broken` and S3's backoff go.
|
|
- **Version skew within one host:** a reload of the registry's plugin alone leaves other plugins' contributors and
|
|
readers on their bundled versions; `accepts[old].contribution` and `.value` keep them working, unsupported
|
|
contributions are left out and reported, unsupported readers are `waiting` (`version`).
|
|
- A registry waits for a value, never for a particular contribution (an empty table composes). After live, a later
|
|
`compose` failure changes nothing for the reader: `.value` keeps the last good composition, **but only while every
|
|
contribution it was built from is still in the table** (round 2, Astra N2): when one of them leaves (its
|
|
contributor ended or was reloaded), the last good value is dropped and `.value` throws until `compose` succeeds, so
|
|
it never hands out a native whose contributor ended. `compose` represents a missing contributor as a value.
|
|
- Registries stay host-local (contributions are in-process values).
|
|
|
|
## 10. Keep-alive, presence, revocation, jobs
|
|
|
|
- A request over a link counts once in `host.activity.references` from its arrival to its end, bound or unbound;
|
|
presence (`host.references`) lists only bound references.
|
|
- Revocation ends the requests of a revoked principal, bound and unbound, at every hop; nothing revives them.
|
|
- `gone` stays only for revocation (a provider host closing closes its links and sends nothing; its consumers see a
|
|
drop, `loading`, and hold again on the next link).
|
|
- Persisted job runs of a parked host-lifetime object are loaded with its documents; `run` and `finish` start only
|
|
once it is live; the run count keeps the object as today.
|
|
|
|
## 11. Timeouts after the model
|
|
|
|
| Timeout or backoff | After | Why |
|
|
| --- | --- | --- |
|
|
| `readyTimeout` of an attempt | only its own steps; paused while parked | a slow dependency is bounded where it runs |
|
|
| `readyTimeout` of `host.get`, `ctx.get`, `host.registry` | kept | a caller gets an answer |
|
|
| `readyTimeout` of a parked call | kept | an action must not run long after it was asked for |
|
|
| identity backoff 1-60 s | only for the object's own `transient` failures | nothing else says the disk or network is back |
|
|
| dependency backoff, polling of refused holds | removed | recovery is an event |
|
|
| dependency deadline across links | removed | the simultaneous-cycle gap is a plugin bug, reported by `parked_long` |
|
|
| `parked_long` report | new: once per parking episode after `readyTimeout` on a remote attempt that is only `loading` | a diagnostic; nothing fails or retries |
|
|
| route backoff, heartbeat, `closeTimeout`, routine and `finish` backoffs, `linger` | kept | not dependency waits |
|
|
| rollout install timeout (not built) | a watchdog on the install only | never a deadline for dependencies |
|
|
|
|
## 12. What changes in the design
|
|
|
|
R18 (dependency failures `waiting`, no backoff, the deadline paused while parked, the dependency check as boundary,
|
|
wakes on every change, an ended dependency breaks), S2 and S3 (`waiting`, woken at every usable value), slice 8's
|
|
last-good rule (dropped when a contribution leaves), §3's destination table (by address; the publication gate), §6 (the
|
|
simultaneous-cycle gap: no probe, `parked_long`), §7 (`host.hold`, getters, calls), §21 (no `gone` on
|
|
removal; a dropped version unbinds only its requests; installs never await activations; rollout without revision
|
|
fields; the reload handover), the start barrier (R17, W6), `FailureCause` (+ `waiting`), `parked_long`. Unchanged: what an object's own code reload does (§21), R18 after live,
|
|
`retryOn` outside parking, `self.follow`'s R4, the call window and flow control, revocation, no client providers.
|
|
|
|
## 13. Decisions (owner, 2026-10-29)
|
|
|
|
1. The barrier: (a), `load` resolves at the first outcomes of this host's host-lifetime objects (§6.3).
|
|
2. Getters wait through `waiting` until their `readyTimeout`; calls on `failed` still reject (§8).
|
|
3. `waiting` is a new `FailureCause` kind (§2).
|
|
4. A pure swap whose new factory declares a dependency that is not there parks the object (its gate closes; holders
|
|
see `loading` or `failed { waiting }`; calls park or reject); no overlap of old and new code (§21).
|
|
5. The last good registry value is kept only while every contribution it was built from is still in the table (§9).
|
|
6. Cycles across hosts: no probe; `parked_long` and four authoring rules (§7).
|
|
|
|
1, 3, 4 and 5 are the recommendations, taken when the owner said "just build this" (2, 6 decided in discussion).
|