From 373fbcc706686225c5c4f25cbecf95bae3d7811e Mon Sep 17 00:00:00 2001
From: Mario Zechner
Date: Thu, 29 Oct 2026 07:37:56 +0000
Subject: [PATCH] docs(chord): dependency model: hostAddress
---
packages/chord/docs/next/dependencies/book.md | 41 ++++++++++---------
.../chord/docs/next/dependencies/index.html | 39 +++++++++---------
.../chord/docs/next/dependencies/model.md | 14 +++----
3 files changed, 48 insertions(+), 46 deletions(-)
diff --git a/packages/chord/docs/next/dependencies/book.md b/packages/chord/docs/next/dependencies/book.md
index 15665a434..1bafbc4a2 100644
--- a/packages/chord/docs/next/dependencies/book.md
+++ b/packages/chord/docs/next/dependencies/book.md
@@ -166,22 +166,22 @@ and a reload that removes a type sends `gone`. Both go.
A request goes to one place, and the address decides which. For a `local` type, the place is this
host. For a `remote` type, the consumer computes the address from the params with its own contract's
-placement function (design §3): `Notes { session: "S1" }` is placed on `Session { id: "S1" }`. If that is this host's
+`hostAddress` function (design §3; `placement` until 2026-10-29): `Notes { session: "S1" }` is placed on `Session { id: "S1" }`. If that is this host's
address, the request goes here; otherwise over the route for that kind, or over the parent link.
Today the code checks something else first: whether the type is registered here. A type placed on this host but not
registered yet is sent over the parent link, which is wrong twice. Sol's example: the server has address `Server`,
-the catalog type is placed on `Server`, and no facet serves it yet. Today's `Placement.reference` sends the server's
+the catalog type is placed on `Server`, and no facet serves it yet. Today's `Placement.reference` (`host/placement.ts`) sends the server's
own request over a parent link the server never had. And when the catalog plugin is installed later, nothing wakes
that request, because it was never registered as waiting here.
-:::flow wide | Placement by address. The destination follows from the address alone; only then is the provider found there checked.
+:::flow wide | The destination by address. The destination follows from the address alone; only then is the provider found there checked.
row "Where" accent
-box addr "the request's address" "from the consumer's own placement function" accent
+box addr "the request's address" "from the consumer's own `hostAddress`" accent
box here "here" "a local type, or this host's address"
box away "a route or the parent" "every other address" blue
row "Then" muted
-box check "check the provider here" "served? fitting version? authorize, own placement"
+box check "check the provider here" "served? fitting version? authorize, own host address"
box hold "hold over the path" "the provider host checks it the same way" blue
arrow addr -> here
arrow addr -> away
@@ -189,13 +189,14 @@ arrow here -> check
arrow away -> hold
:::
-The rule, from the reviews (Sol B1, Astra F1): placement never looks at what is registered when the type names an
+The rule, from the reviews (Sol B1, Astra F1): the destination never depends on what is registered when the type names an
address. A request placed here whose provider is missing is **unbound here**, waiting for the name. A version check
compares the consumer with the provider actually selected, never with a same-name registration on the consumer's own
-host (today `#assertRoute` does that before placement, so worker W1 serving `Notes` v1 rejects a `Notes` v2 request
+host (today `#assertRoute` does that before choosing the destination, so worker W1 serving `Notes` v1 rejects a `Notes` v2 request
placed on W2, which serves v2).
-Every `remote` type declares its placement (owner, 2026-10-29). Until then `remote()` allowed leaving it out, a
+Every `remote` type declares its `hostAddress` (owner, 2026-10-29, who also renamed `placement` to it). Until then
+`remote()` allowed leaving it out, a
leftover of slice 3a, which built remote objects over one link before host kinds existed; such a type went "here if
served, otherwise the parent", which needed its own fallback and a re-placement when a tab that loads before it
connects gets its first parent link (Opus, Sol). Opus and Fable proposed making it required; the owner agreed.
@@ -203,7 +204,7 @@ connects gets its first parent link (Opus, Sol). Opus and Fable proposed making
## What the check finds
Once placed, the provider found there is checked: served, a fitting version (the current one or an accepted older one,
-with the fingerprint computed from the provider's own history), `authorize`, and the provider's own placement check.
+with the fingerprint computed from the provider's own history), `authorize`, and the provider's own host-address check.
The request is then **bound** (to an object here, or to a hold over a path), or **unbound** with a reason:
| The check finds | The request | Shows |
@@ -211,7 +212,7 @@ The request is then **bound** (to an object here, or to a hold over a path), or
| a fitting provider | bound | its object's state |
| this host has not published its facets 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 placement the provider computes elsewhere | unbound `version` | `failed { waiting }` |
+| no fitting version, a fingerprint that differs at the same version, a host address the provider computes elsewhere | 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, 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) | waits on the path | `loading` |
@@ -598,9 +599,9 @@ has natives (§21), as for any reload.
## A type that moved
-A plugin can move a type from the worker to the server by changing its placement. A consumer's request carries the
+A plugin can move a type from the worker to the server by changing its `hostAddress`. A consumer's request carries the
address its own code computed, so the provider appearing on the server cannot wake it: the request is not waiting
-there. The old host answers `not_served`; the consumer's own reload, which brings the new placement, makes a new
+there. The old host answers `not_served`; the consumer's own reload, which brings the new `hostAddress`, makes a new
request. The model adds no redirect: moving state between hosts is a deliberate migration, not something Chord guesses.
## Rollouts across hosts
@@ -1216,7 +1217,7 @@ scenarios against five drafts; the catalog in the chapters before this one is a
| R18, the boundary | "live" means every dependency was live at the end of activation | the dependency check is the boundary: after it, a dependency leaving `live` is treated as after live |
| `self.get` of a missing `local` type | throws in the factory: `broken` | unbound `not_served`: `failed { waiting }`, woken by the publication that serves it |
| a remote hold the provider cannot serve | refused `broken`; the dependent polls by backoff | refused and kept at the provider, re-run at its publication; `failed { waiting }` |
-| design §3, placement | "served here" decides "here"; `placement` optional on `remote()` | the address decides; every remote type declares its placement (owner, 2026-10-29) |
+| design §3, the destination | "served here" decides "here"; `placement` optional on `remote()` | the address decides; every remote type declares its `hostAddress`, renamed from `placement` (owner, 2026-10-29) |
| the start barrier, `pendingLoad` | holds wait for all of `load` | holds wait only for the publication; the barrier is (a) or (b) |
| S2, a registry reader newer than its provider | `broken` | `failed { waiting, version }` |
| S3, a registry without a value | `transient`, woken at the first value only | `failed { waiting }`, woken at every transition to usable |
@@ -1314,14 +1315,14 @@ A needs W's B, B needs S's C; W's hold for C waits for S's whole `load`, which w
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.
-- **Placement by address** (round 1): a `local` type goes here; a `remote` type with a placement goes to the address
- its placement (computed from the consumer's own contract) gives: here if that is this host's address, whether served
+- **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 placement (owner, 2026-10-29: the optional placement of slice 3a is removed, and with it the fallback "here if
+ 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/placement`) as today; a relay never captures a hold because it serves the same name.
+ 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 placement check). Version checks never look at a same-name registration elsewhere.
+ 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.
@@ -1332,7 +1333,7 @@ A needs W's B, B needs S's C; W's hold for C waits for S's whole `load`, which w
| 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 placement the provider computes elsewhere (code skew) | unbound `version`: `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` |
@@ -1646,7 +1647,7 @@ ring that never existed, `/tmp/model/sol-probe4.log`).
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 placement table (by address; the publication gate), §6 (the
+last-good rule (dropped when a contribution leaves), §3's destination table (by address; the publication gate), §6 (the
simultaneous-cycle gap: (A) one narrowed deadline or (B) the probe), §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`), `loading`/`failed` with the
diff --git a/packages/chord/docs/next/dependencies/index.html b/packages/chord/docs/next/dependencies/index.html
index 656a4f6fa..81c7ae1fe 100644
--- a/packages/chord/docs/next/dependencies/index.html
+++ b/packages/chord/docs/next/dependencies/index.html
@@ -314,26 +314,27 @@ and a reload that removes a type sends gone. Both go.
The address decides where
A request goes to one place, and the address decides which. For a local type, the place is this
host. For a remote type, the consumer computes the address from the params with its own contract's
-placement function (design §3): Notes { session: "S1" } is placed on Session { id: "S1" }. If that is this host's
+hostAddress function (design §3; placement until 2026-10-29): Notes { session: "S1" } is placed on Session { id: "S1" }. If that is this host's
address, the request goes here; otherwise over the route for that kind, or over the parent link.
Today the code checks something else first: whether the type is registered here. A type placed on this host but not
registered yet is sent over the parent link, which is wrong twice. Sol's example: the server has address Server,
-the catalog type is placed on Server, and no facet serves it yet. Today's Placement.reference sends the server's
+the catalog type is placed on Server, and no facet serves it yet. Today's Placement.reference (host/placement.ts) sends the server's
own request over a parent link the server never had. And when the catalog plugin is installed later, nothing wakes
that request, because it was never registered as waiting here.
-Figure 2. Placement by address. The destination follows from the address alone; only then is the provider found there checked.
-
The rule, from the reviews (Sol B1, Astra F1): placement never looks at what is registered when the type names an
+Figure 2. The destination by address. The destination follows from the address alone; only then is the provider found there checked.
+
The rule, from the reviews (Sol B1, Astra F1): the destination never depends on what is registered when the type names an
address. A request placed here whose provider is missing is unbound here, waiting for the name. A version check
compares the consumer with the provider actually selected, never with a same-name registration on the consumer's own
-host (today #assertRoute does that before placement, so worker W1 serving Notes v1 rejects a Notes v2 request
+host (today #assertRoute does that before choosing the destination, so worker W1 serving Notes v1 rejects a Notes v2 request
placed on W2, which serves v2).
-
Every remote type declares its placement (owner, 2026-10-29). Until then remote() allowed leaving it out, a
+
Every remote type declares its hostAddress (owner, 2026-10-29, who also renamed placement to it). Until then
+remote() allowed leaving it out, a
leftover of slice 3a, which built remote objects over one link before host kinds existed; such a type went "here if
served, otherwise the parent", which needed its own fallback and a re-placement when a tab that loads before it
connects gets its first parent link (Opus, Sol). Opus and Fable proposed making it required; the owner agreed.
What the check finds
Once placed, the provider found there is checked: served, a fitting version (the current one or an accepted older one,
-with the fingerprint computed from the provider's own history), authorize, and the provider's own placement check.
+with the fingerprint computed from the provider's own history), authorize, and the provider's own host-address check.
The request is then bound (to an object here, or to a hold over a path), or unbound with a reason:
@@ -359,7 +360,7 @@ The request is then bound (to an object here, or to a hold over
failed { waiting }
-
no fitting version, a fingerprint that differs at the same version, a placement the provider computes elsewhere
+
no fitting version, a fingerprint that differs at the same version, a host address the provider computes elsewhere
unbound version
failed { waiting }
@@ -721,9 +722,9 @@ object then; Sol and Astra caught it: the object, its documents, its jobs and it
fine. Only the requests on version 1 become unbound version. The object's own plugin reload still restarts it if it
has natives (§21), as for any reload.
A type that moved
-
A plugin can move a type from the worker to the server by changing its placement. A consumer's request carries the
+
A plugin can move a type from the worker to the server by changing its hostAddress. A consumer's request carries the
address its own code computed, so the provider appearing on the server cannot wake it: the request is not waiting
-there. The old host answers not_served; the consumer's own reload, which brings the new placement, makes a new
+there. The old host answers not_served; the consumer's own reload, which brings the new hostAddress, makes a new
request. The model adds no redirect: moving state between hosts is a deliberate migration, not something Chord guesses.
Rollouts across hosts
A rollout installs a revision on the server first, then on the workers, then on the tabs (design §21). Between those
@@ -1929,9 +1930,9 @@ owner's decisions.
refused and kept at the provider, re-run at its publication; failed { waiting }
-
design §3, placement
+
design §3, the destination
"served here" decides "here"; placement optional on remote()
-
the address decides; every remote type declares its placement (owner, 2026-10-29)
+
the address decides; every remote type declares its hostAddress, renamed from placement (owner, 2026-10-29)
the start barrier, pendingLoad
@@ -2099,15 +2100,15 @@ code (host.get, host.hold, host.registry)
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.
-
Placement by address (round 1): a local type goes here; a remote type with a placement goes to the address
-its placement (computed from the consumer's own contract) gives: here if that is this host's address, whether served
+
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 placement (owner, 2026-10-29: the optional placement of slice 3a is removed, and with it the fallback "here if
+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/placement) as today; a relay never captures a hold because it serves the same name.
+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 placement check). Version checks never look at a same-name registration elsewhere.
+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
@@ -2134,7 +2135,7 @@ synchronous setups, never for activation, so it cannot deadlock.
unbound not_served: failed { waiting }
-
no fitting version, a fingerprint that differs at the same version, a placement the provider computes elsewhere (code skew)
+
no fitting version, a fingerprint that differs at the same version, a host address the provider computes elsewhere (code skew)
unbound version: failed { waiting }
@@ -2565,7 +2566,7 @@ once it is live; the run count keeps the object as today.
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 placement table (by address; the publication gate), §6 (the
+last-good rule (dropped when a contribution leaves), §3's destination table (by address; the publication gate), §6 (the
simultaneous-cycle gap: (A) one narrowed deadline or (B) the probe), §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), loading/failed with the
diff --git a/packages/chord/docs/next/dependencies/model.md b/packages/chord/docs/next/dependencies/model.md
index 2b838eb79..bc3d606c8 100644
--- a/packages/chord/docs/next/dependencies/model.md
+++ b/packages/chord/docs/next/dependencies/model.md
@@ -61,14 +61,14 @@ A needs W's B, B needs S's C; W's hold for C waits for S's whole `load`, which w
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.
-- **Placement by address** (round 1): a `local` type goes here; a `remote` type with a placement goes to the address
- its placement (computed from the consumer's own contract) gives: here if that is this host's address, whether served
+- **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 placement (owner, 2026-10-29: the optional placement of slice 3a is removed, and with it the fallback "here if
+ 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/placement`) as today; a relay never captures a hold because it serves the same name.
+ 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 placement check). Version checks never look at a same-name registration elsewhere.
+ 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.
@@ -79,7 +79,7 @@ A needs W's B, B needs S's C; W's hold for C waits for S's whole `load`, which w
| 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 placement the provider computes elsewhere (code skew) | unbound `version`: `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` |
@@ -393,7 +393,7 @@ ring that never existed, `/tmp/model/sol-probe4.log`).
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 placement table (by address; the publication gate), §6 (the
+last-good rule (dropped when a contribution leaves), §3's destination table (by address; the publication gate), §6 (the
simultaneous-cycle gap: (A) one narrowed deadline or (B) the probe), §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`), `loading`/`failed` with the