Place the read-side consumer map, record the residue batch, fix BP-3's orphan claim

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-22 08:23:03 +02:00
commit 9701b8eedf
3 changed files with 1260 additions and 2 deletions

View file

@ -515,7 +515,7 @@ format or JSON schema file (see section 5).
| Add | Owner | Live consumer | Protected invariant | Why an existing owner cannot serve |
|---|---|---|---|---|
| `huginn-mind/src/wire.rs:81-90`: `HuginnMindResponse::Deferred(DeferredAnswer)` with `DeferredAnswer { manifest: CultMeshCdnArtifactManifest }` under Q-BP2 A, or plus the typed summary under B; `status()` (`:97-102`) answers `accepted` for it. A hand-written `JsonSchema` for the manifest inside Huginn, as the selection consumer cut does for `Selection`. `schemas/cultnet/huginn.mind_response.v1.schema.json` is regenerated. **The schema id stays `v1`**, because no client of it exists (Cut 13 is unbuilt) | `huginn-mind` (types only) | the daemon, Cut 13 | the reference rides the response schema, so there is no second vocabulary for a client | `HuginnMindResponse` is the only response a client decodes |
| `huginn-mind/src/wire.rs:81-90`: `HuginnMindResponse::Deferred(DeferredAnswer)` with `DeferredAnswer { manifest: CultMeshCdnArtifactManifest }` under Q-BP2 A, or plus the typed summary under B; `status()` (`:97-102`) answers `accepted` for it. The manifest's schema is embedded through a `#[schemars(schema_with)]` field attribute returning a `$ref` to its published schema, as the selection consumer cut now does for `Selection` (corrected 2026-09-22: a hand-written `impl JsonSchema` for a foreign type is `E0117`, the orphan rule). `schemas/cultnet/huginn.mind_response.v1.schema.json` is regenerated. **The schema id stays `v1`**, because no client of it exists (Cut 13 is unbuilt) | `huginn-mind` (types only) | the daemon, Cut 13 | the reference rides the response schema, so there is no second vocabulary for a client | `HuginnMindResponse` is the only response a client decodes |
| `huginn-daemon/src/bodies.rs` (new), `DeferredBodies`: a map from chunk hash to bytes with reference counts, and a map from content hash to `(chunk hashes, bytes, last_touched)`. `retain(manifest, chunks, now)`, which is idempotent on the content hash and refreshes it; `chunk(hash, now) -> Option<&[u8]>`, which refreshes the owning bodies; `expire(now)`; eviction least-recently-touched first when the total is over budget. Constants: `DEFERRED_CHUNK_BYTES = 256 * 1024`; `MAX_DEFERRED_BODY_BYTES = 64 MiB`; `DEFERRED_BUDGET_BYTES = 256 MiB`; `DEFERRED_TTL = 60 s`, which is twice `session_timeout`, and is a field of `ServeOptions` (`:70-79`) with that default | `huginn-daemon::serve` | `deliver`, `answer` | retention is bounded in bytes and time, and a body is never evicted while its chunks are being served within the TTL | The mind must not hold transport artifacts. `cultnet-rs` owns no retention policy, and should not, because the policy is this service's memory budget |
| `serve.rs`: `deliver(reply, message_id, operation, runtime_id, bodies, now)` replaces `within_window`. It encodes the envelope. If the envelope is `<= MAX_RESPONSE_BYTES`, the reply is sent unchanged. Otherwise it takes the **payload bytes**, the named MessagePack of the response. If they are `<= MAX_DEFERRED_BODY_BYTES`, it calls `pack_content("huginn.mind_response", "package", "", "application/vnd.gamecult.huginn.mind-response+msgpack", now, payload, DEFERRED_CHUNK_BYTES)`, then `bodies.retain`, and answers `Deferred`. Otherwise it answers `Refused(ResponseTooLarge { bytes: payload_len, limit: MAX_DEFERRED_BODY_BYTES })` | `huginn-daemon::serve` | `answer` | **the one owner of how an answer is delivered.** Three outcomes, in that order, and nothing else decides | — |
| `serve.rs:165-194`, `answer`: a new `CultNetMessage::ContentChunkRequest` arm calls `cultnet_rs::answer_content_chunk_request(request, \|h\| bodies.chunk(h, now))`. The error text at `:188-192` names the third family. The signature gains `bodies: &mut DeferredBodies`, and `run` (`:257-305`) owns one and passes it | `huginn-daemon::serve` | the client's fetch | a chunk is answered on the requester's own session, like every other frame | — |
@ -651,7 +651,7 @@ format or JSON schema file (see section 5).
- Removes about 35 lines (`within_window` 35, the stated-limit doc 8) and
8 mutation entries.
- Adds about 190 lines: `bodies.rs` about 110, `deliver` about 35, the
`answer` arm 8, the wire variant with its hand-written `JsonSchema` about
`answer` arm 8, the wire variant with its `schema_with` `$ref` about
35.
- Tests are about +220 net over the rewritten two.
- Entries: 8 removed, about 20 added.

View file

@ -545,6 +545,53 @@ schema-ownership phase inside Epiphany, not because the campaign stops.
restores the bytes it captured at M0, and never checks whether the current
bytes are the mutant it wrote (`eureka-mutations.ps1:163-175, 393, 415`).
Fixed in the Eureka repository in the same batch.
- **The residue batch landed** (Sonnet). Commits:
- Huginn `65c46aa`: seals `path_for`, adds N1 entries on all three sites
(`open_with` now dies), the U+2010 fold, and gate fixtures that vary the
operation and runtime id independently.
- Huginn `f7e0d02`: moves the leaf pin to `b3ae787b`, which carries S5
(64-byte bounds and NUL pinned) and S8. The lockfile also picked up a
`getrandom` 0.4.3→0.3.4 change as a side effect.
- Eureka `5d2bd3e`, H1: the harness saves a mid-run edit as
`.eureka-mutation-overwritten`, prints `EDIT LOST`, and fails the run.
Demonstrated before and after.
Tests: mind 65, daemon 20, leaf 35. Every suite is fully killed:
| Suite | Killed |
|---|---|
| leaf | 5/5 |
| Cut 8 | 67/67 |
| Cut 9 | 38/38 |
| Cut 10 | 77/77 |
There is one `cultcache-rs`. **Soul's check of the residue is
dispatched.** It covers two things. First, `path_for` became
`store_path_for` behind a test-support feature, and a consumer that turns
the feature on might reach the bypass again. Second, the lockfile side
effect.
- **The read-side consumer cut is mapped** at `notes/eureka-read-side-cut.md`
(Imagination, Opus). Cuts in order:
- **P-1 and P-2:** CultLib, before merge. Now folded into selection's
R-G and R-M.
- **RS-L:** leaf epoch v2, titles, and the `OrgRepo` and `Label` doors.
- **RS-2:** subtraction.
- **RS-1:** receipt v2 with ordinals, and the epoch gate moved ahead of
the type gate.
- **BP-2, widened:** one pin move.
- **RS-3:** the consumer, including the value door for S6.
Its appendix carries the summary type, copied from a session scratchpad
that will not survive.
Probed facts:
- The seven old relations each equal one selection.
- Admission order cannot be recovered from the clock: 31 receipts carry
5 distinct seconds.
- Today's opener refuses an old store by the wrong gate.
**Operator questions Q-RS1 (titles) and Q-RS2 (whether `huginn-mind` may
link `cultnet-rs`) are open.**
- *History:* **Cut 10 stays open until the fourth fix batch lands** (Hands, Sonnet): N1
through the leaf check, one generated separator test for N3, fixtures that
vary the operation and the runtime id for N4, and the prose fixes for N5

File diff suppressed because it is too large Load diff