# Context pack: map (eureka-substrate, 2026-10-08) Imagination pass `imagination-context-pack`, session `self-2026-10-08`, for ruling `eureka-substrate:ruling:context-pack-resolving-anchors`. Evidence: `F:\Projects\eureka-toolcall-patterns-2026-10-08.md` (Eyes facts) and `F:\Projects\eureka-context-pack-prior-art-2026-10-08.md` (prior art). The cut_specs and the question are the typed record; this page holds body facts, the model page and rationale only. Pinned heads: Eureka main `0bb414d92728db61d2d587d749c94970ed2e5582` (also the head of `hands/toolcall-relief`, whose changes are uncommitted in `C:\eureka-toolcalls`); CultLib `05e67014`; Huginn `6b496eba`; Epiphany `4006b14b`; Aetheria `e75083fe`. ## Body facts Scratch: `C:\Users\Meta\AppData\Local\Temp\claude\F--Projects-CultLib\8ee3cab1-a20c-498d-a46b-e503a25ac030\scratchpad\cp\` (`anchor-map.sh` prototype, `mindview.py`, `mindq.py`, `drift.py`, `all.json`). This scratchpad is volatile; Hands re-derives from the specs, not from it. B1. Diff-hunk line mapping resolves a (base, path, start, end) anchor onto any later tree with git alone. Prototype `anchor-map.sh` parses `git diff -U0 -- ` hunk headers: hunks wholly before the range shift it (MOVED), a hunk overlapping it marks it TOUCHED and widens the range by its net lines, no hunk is SAME; `git diff -M --name-status` gives rename (RENAMED) and delete (GONE). Target may be a commit or the working tree (`git diff ` with no second commit). Dependencies: git and awk; no ctags, rg or tree-sitter. B2. Real spec, real drift: `cultmath-tapes:cut_spec:cut-affine-shaders.r2` (base `a7966142`) mapped onto `05150acd` (the merge the affine-shaders Hands started from). Result: `CultMath.hlsl:509-510` MOVED to 510-511 (the same two `#include` lines, checked by `git show`); `GlslMirrorTests.cs:317-319` TOUCHED, mapped to 336-340, which is the end of `Families` with snoise-tie's two new families inside it (checked); five anchors SAME. The spec's own prose carried the workaround: "(GlslMirrorTests.cs:317-319 at a7966142; the lines move with the merge)". `cut-huginn-persona-state.r2` carries another: "Hands re-takes the anchor at the merged 13a/13b tip". B3. Anchor accuracy at base: the four affine-shaders anchors read at `a7966142` show exactly the code each `change` names (`cultmath_iv_snoise_ball(float3, float)` at Interval.hlsl:83-89, the `InlineData` list at HlslMirrorTests.cs:64-68, the presence asserts at HlslSourceCompatibilityTests.cs:34-35, the Interval.hlsl lines in build-unity-package.ps1:86-87). Imagination's line numbers are right at base; what goes wrong is drift. B4. Drift across the whole mind (`drift.py` over `all.json`: 203 cut_specs, 143 cut_reports, 1,260 file_changes). For every spec with a report and a local repo, each file_change was mapped from `spec.base` to the first report's `range.base`, the tree Hands started on. 557 anchors: SAME 444 (79.7%), MOVED 64 (11.5%), TOUCHED 17 (3.1%), path absent at base 32 (5.7%). All 32 absent paths exist at the report head: they anchor files the cut creates (none were typos). No base was unreachable. By repo: gamecult-site 55 of 125 drifted (44%), CultLib 17 of 245, Aetheria 5 of 127, Idunn 3 of 24, Bifrost 1 of 19, Epiphany, gamecult-ops and Huginn none. So about one anchor in seven was stale when Hands first read it, concentrated in campaigns whose cuts land in parallel. B5. Worktree, rename, delete and failure probes (scratch clone of Eureka at `0bb414d`): two lines inserted at 100 and line 185 edited, uncommitted: `briefs.md:50-60` SAME, `180-190` TOUCHED to 182-192, `300-310` MOVED to 302-312. A staged `git mv` reads SAME+RENAMED; a staged `git rm` reads GONE. Unreachable base prints `UNRESOLVED base not in this clone: git fetch origin `; path absent at base prints `UNRESOLVED absent at base`. Limit: an unstaged delete plus an untracked new file is not detected as a rename (git's rename detection needs both sides in the index or a commit). Windows Git Bash prints an LF/CRLF warning per diff on stderr; the tool must discard stderr of these calls. Line numbers are unaffected by CRLF. B6. Cost: rename detection over a whole tree takes about 0.5-1 s per call on Aetheria (`git diff -M --name-status`), so the drift script, which ran it per anchor, took over 10 minutes for 557 anchors. The tool runs it once per invocation and one `-U0` diff per distinct path. B7. Symbol by name alone is not an identity. `git grep -W` with git's csharp driver (supplied without touching the repo through `git -c core.attributesFile=`) prints the whole enclosing function, but the probe name `Dispose` matched several members in one file (`CultMeshContentSessions.cs`), and a bare name has no namespace. A symbol is usable as a check that the resolved window contains it, and as the key for a caller list, not as the anchor itself. Git's built-in funcname drivers cover csharp, rust, python, kotlin and cpp (usable for HLSL/GLSL), and none covers TypeScript. B8. Tool availability. Starfire: Exuberant Ctags 5.8 (Strawberry Perl; no TypeScript, Kotlin or HLSL), rg (Codex's copy), no tree-sitter, Python 3.13 via `py -3` (no `tree_sitter` module), git 2.53, gawk, perl. Yggdrasil host: git 2.47, python3, awk, perl; no ctags, no rg. So git plus the Python standard library is the only toolset common to both. B9. Caller lists: `git grep -n -w -F ` takes about 0.4-0.6 s on CultLib and Aetheria. `cultmath_iv_snoise_ball` has 20 hits in 8 files, including docs and fixtures. `Dispose` has 386 hits, so a caller list needs a cap, and above the cap it needs per-file counts rather than lines (SWE-agent: more than 50 results returns none and asks for a narrower query). B10. Unity YAML: across the three most-modified Aetheria prefabs, fileIDs (`--- !u! &`) are unique within each file and survive edits (Djinni.prefab at 122e9292: 440 of 446 ids shared across a 91-hunk commit; Inventory 116 of 116; Longinus 107 of 107). Diff-hunk mapping works on YAML as on code. `&` is a good symbol for a YAML anchor. B11. The spec can be read without the model copying it. `mindview.py` spawns `~/.eureka/bin/eureka-state.exe` (the binding in `~/.claude.json` `mcpServers.eureka-state`: command plus `EUREKA_INSTANCE` and `HUGINN_ENDPOINT`), speaks MCP JSON-RPC over stdio (initialize, initialized, tools/call view) and prints the document, in 1.3 s. eureka-state has no CLI mode of its own (`crates/eureka-state/src/main.rs:298`, stdio MCP only). B12. Schema facts for a typed home. `CodeLocation { path: Short, line: u32, end_line: Option }` (Epiphany `epiphany-pipeline/src/lib.rs:463`) is shared by cut_spec file_changes, finding and follow_up locations, and mutation records. The epoch doc (`lib.rs:879-886`) says evolution is additive at `epiphany.pipeline.epoch.v2`: "a new named field with a serde default". No `#[serde(default)]` exists in the leaf yet, so the `value_types!` macro has never carried one. Huginn pins the leaf by git rev (`crates/huginn-mind/Cargo.toml:22`, `ef956865`), so a field reaches the mind only after a leaf change, a Huginn re-pin and a daemon plus eureka-state redeploy. B13. The Hands brief already asks for a hand-made pack: "Orient in one script. One Bash call prints git status, the base and the spec's anchor ranges (`sed -n`)" (briefs.md:276-278 at 0bb414d). That reads the frozen line numbers, so it reads the wrong lines for the one anchor in seven that has drifted (B4). B14. Prose `path:line` references in spec text (first, adds, keeps_moves, change): 224 across 99 of 203 specs. Imagination already writes read anchors inline, untyped (for example "as iv_frustum_ball's FrustumBall check (GlslMirrorTests.cs:191) does"). ## Model page | Kind | Identity | Lifecycle | Authority | |---|---|---|---| | Edit anchor | `(cut_spec id, file_changes index)`; content `(spec.repo, spec.base, path, line, end_line)`. Namespace: the spec's repo at its base commit, which is injective: one path and range at one commit. A short `base` is resolved with `git rev-parse --verify ^{commit}`, and ambiguity is a loud failure. | Created with a spec revision and immutable. Changed only by revision N+1 with a superseding resolution. Its position at a later tree is derived at read time, never stored. A path absent at base is a file the cut creates (B4); it belongs in `adds`, and the tool reports it as NEW. | Imagination writes it (admits the spec). Hands and the tool never rewrite it. A wrong anchor is a deviation in Hands' report and a revision by Imagination. | | Read anchor (code to see, not to change) | **Empty cell.** It has no typed home. Today it is prose `path:line` (B14) with no namespace (basenames), no base and no check. | None: it is not a document. | None. This is question `context-pack-read-anchors`. | | Symbol (a check and a caller key) | **Empty cell.** A name is not injective (B7). Its only sound role is a needle that the resolved window must contain, plus a caller-list key. | Would be the anchor's. | Same question. | | Resolved anchor and window (the expanded pack) | `(spec id, worktree HEAD, working-tree state)`. Derived and never persisted. | Printed into Hands' context. Stale once Hands edits inside it, and re-expanded for edited anchors only with `--since `. No cache, no pack file. | `tools/context-pack.py` derives it. Nothing stores it. A stored pack would be a cache pretending to be truth. | | The reader tool | `tools/context-pack.py` in GameCult/Eureka. | Lands by Forgejo PR after a Soul pass. Pruned when no brief names it (tools/INDEX.md rule). | Eureka repo. Owns "what Hands sees of a spec's anchors at its tree". It does not own anchors and does not write the mind. | | Measurement baseline | The Eyes report above and its scripts (`cls.py`, `an2/an3/an4.py`) under this session's scratchpad `eyes-toolcalls\`. | **Volatile.** The scratchpad is session-scoped, so the scripts can vanish before the re-measure. | Self (follow_up `context-pack-measurement`). | ## Rationale **Anchor grammar: base-pinned line range, resolved by diff-hunk mapping.** The candidates were: symbol paths (`File::Type.Member`), git funcname (`git grep -W`, `git log -L :fn:file`), ctags, tree-sitter queries, LSP document symbols, and a text needle plus line hint. Every one of them except the line range needs a per-language parser, and most need a dependency that is missing on Yggdrasil or on both hosts (B8). Unity YAML has no symbol grammar in any of them, and a bare symbol name is not injective (B7). The spec already pins a full coordinate, `spec.base` plus `path:line-end_line`, and git can carry that coordinate onto any later tree, the working tree included (B1, B5). This is the way Gerrit ports review comments across patch sets and GitHub marks a comment outdated: the anchor is stored where it was made, and diffs carry it forward. It is language-agnostic, needs only git, and fails loudly on every path that cannot be carried (base unreachable, path absent, range past EOF, file deleted). One anchor in seven needed carrying in practice (B4), and the `sed -n` orient step reads those wrong today (B13). **Why the edit anchors are the v1 pack.** file_changes already hold the planner's localization at base, accurate when mapped (B3), which is Agentless's and ORACLE-SWE's edit-location handoff. The tool turns them into SWE-agent-shaped output: one status table first (lost-in-the-middle: the summary at the top), then windows of at most 100 lines (the SWE-agent ablation optimum; full-file views lost 5.3 points), then caller lists capped at 50 lines with per-file counts beyond that (the iterative per-hit search lost 6 points). An output cap keeps the pack from becoming the 20%-more-steps AGENTS.md overview (Gloaguen et al.). Re-expansion with `--since` answers the re-read finding (54%, 1,198 repeats with no edit in between): the edited windows come back fresh and nothing else is reprinted, as in SWE-agent's viewer auto-refresh and CORVUS's current-contents registry. **Why read anchors and symbols are a question rather than a cut.** Three homes exist: a typed field (an additive `CodeLocation.symbol` and a `reads` list on cut_spec, three passes across Epiphany, Huginn plus a deploy, and Eureka, B12); a `Pack:` line convention in `first` (free, but structure carried in prose); or a sidecar pack file. The sidecar was rejected outright: it is a second store with its own lifecycle that goes stale the day a spec is revised. Which of the first two, and whether now or after measuring v1, decides schema work in the mind's own leaf. The question records options, evidence and a recommendation. Nothing in cuts context-pack-tool and context-pack-briefs depends on the answer. **Base for the Eureka cuts.** Both cuts edit `tools/test-tools.sh` and `references/briefs.md`, which `hands/toolcall-relief` is changing (uncommitted in `C:\eureka-toolcalls`; its branch head equals main `0bb414d`). The specs pin `base 0bb414d` and anchor at it. Hands starts from main after that PR merges, and the first thing context-pack-briefs does is resolve its own briefs.md anchors through the tool that context-pack-tool lands (the +39 lines shift them). If the PR has not merged, Hands stops. **Subtraction.** Removed: the Hands orient step's `sed -n` of frozen ranges (briefs.md:276-278) and the "reading a file twice is a smell" advice that the tool replaces. Imagination's prose drift notes ("the lines move with the merge", "re-take the anchor") are no longer needed and the map brief says so. Not added: a parser per language, a cache, a pack file, or a mind field. ## Read anchors and symbols in the schema (2026-10-08, imagination-read-anchors) Imagination pass `imagination-read-anchors`, session `self-2026-10-08`, for operator ruling `eureka-substrate:ruling:context-pack-read-anchors` (typed-fields-now). Cuts: `leaf-read-anchors` (Epiphany), `huginn-read-anchors` (Huginn), `ops-read-anchors-upgrade` (gamecult-ops), `context-pack-reads` (Eureka). Pinned heads: Epiphany origin/main `ef9568655e75bb6cb362a038e2739a5ac5e777ee` (the rev Huginn pins; the local `F:\Projects\Epiphany` main is behind it at `2bf39eef`), Huginn main `6b496eba8c8cfde7b93bb8bc46b1961abca52fd4`, gamecult-ops main `7e233877dfb21f75d5bbe994641781f718098eac`, Eureka main `0bb414d92728db61d2d587d749c94970ed2e5582`. Probes ran in detached worktrees under this session's scratchpad `ra\`, removed afterwards. ### Body facts B15. Stored payload encoding. `PipelineDocument::prepare` calls CultLib `prepare_entry_named` (cultcache-rs at `8fc74c7`, `lib.rs:2244-2259`), which is `rmp_serde::to_vec_named`: every value struct is a MessagePack map keyed by field name. `decode` is `rmp_serde::from_slice` (leaf `lib.rs:745-751`). No `deny_unknown_fields` exists in the leaf. Consequences: (a) an old document lacking a new key decodes when the field is `Option` or carries `#[serde(default)]`; (b) an old daemon reading a new document ignores the unknown key, so after a rollback it serves such documents without the field while the stored bytes keep it. B16. Re-encoding is load-bearing. Admission prepares every write afresh (`admission.rs:143-157`) and the receipt digest is over those payloads (`receipt.rs:123-126`); A9 compares it with stored receipts to answer `AlreadyAdmitted` (`admission.rs:164-166`). So if a document without the new fields encoded differently after the change (for example `symbol: null` or `reads: []` written out), replaying any pre-upgrade batch would stop answering `AlreadyAdmitted`, and target invariant `stored-documents-valid` ("No change re-keys, re-encodes or invalidates a document already in the mind") would break. Hence `skip_serializing_if` on both fields, not only `serde(default)`. B17. The macro. `value_types!` (`lib.rs:415-436`) emits a struct whose fields carry only an optional `#[schemars(extend("maxItems"))]`; it has no way to put a serde attribute on a field. `Bounded for Option` exists (`lib.rs:79-83`); `Vec` is validated only through the `[max]` list form (`list`, `lib.rs:110`). `=> extra` is a struct-level validation hook (`campaign_repos_are_distinct`, `lib.rs:452-468`). Refusal variants are pinned at five by the schema test (`lib.rs:2717`), so new bounds must reuse `FieldBound` and `InvalidFormat`. B18. Who constructs the changed structs. Epiphany: `CodeLocation {` 3 sites and `PipelineCutSpec {` 1 test sample, all in `lib.rs`; no other Epiphany crate builds them. Huginn: `CodeLocation {` in `huginn-mind/src/fixtures.rs:88`, `huginn-daemon/src/daemon.rs:360`, `eureka-state/tests/client.rs:59`; `PipelineCutSpec {` in `fixtures.rs:192`, `daemon.rs:361`, `client.rs:44` (`admission.rs:432` uses `..spec.clone()` and needs no edit). Published schemas that embed `CodeLocation`: Epiphany `schemas/cultnet/epiphany.pipeline.{cut_spec,cut_report,finding,follow_up}.v2.schema.json`; Huginn `schemas/cultnet/huginn.mind_{request,response}.v1.schema.json`, pinned byte for byte by `wire.rs:276-280`. The leaf's own check is `pipeline_published_schemas_match_derivation` (`lib.rs:2723`), which writes the derived copies to a temp dir on failure. B19. Size calibration. `daemon.rs:349-356`'s `cut_spec` claims "every list the leaf bounds filled to its maximum"; `client.rs:220-236` calibrates `fits` (180 file changes) under `MAX_RESPONSE_BYTES` and `wide` (256) over it. Filling the new fields in the daemon builder keeps its claim true; filling them in the client builder would move the calibration. B20. Admission has no tree. Huginn holds no repository checkout; admission checks bounds, formats, keys and cross-document rules only. "The symbol is in the window at base" cannot be an admission rule. Its owner is the reader tool, run by Imagination at base after admitting (cut context-pack-briefs) and by Hands at its tree. B21. Index text. `huginn-mind/src/index.rs:42-70` indexes a cut_spec's title, first, adds and keeps_moves only, not file_changes. Leaving `reads` out keeps the index text unchanged, so no reindex follows the upgrade. B22. Deploy owner. Huginn is not an Idunn target: `runbooks/huginn-yggdrasil.md` (gamecult-ops) calls it "an interim deployment: a hand-installed systemd unit", retired by Idunn only in Huginn's 14-I, and says "No brake gates deployment of this unit". Target r2 names the same runbook as the deploy's canonical implementation. The live release is `releases/107552dc4b27b8856b043616ae74c2ebe7908b66` (read-only `readlink` on Yggdrasil, unit active); Huginn main since then changes docs only (`git diff --stat 107552dc..6b496eba -- crates Cargo.toml Cargo.lock` is empty). The client is `C:\Users\Meta\.eureka\bin\eureka-state.exe`, whose MCP `admit` input schema is derived at run time from the leaf types through schemars (`eureka-state/src/main.rs:73`), so a rebuilt client publishes the new fields with no hand edit; an agent session sees them only after it starts anew. The runbook's rollback paragraph (`:109-113`) is specific to the 64-byte key widening. B23. The harvest. Target r2's canonical implementations put the leaf at Huginn `crates/eureka-pipeline`, and Huginn `docs/eureka-self-cut.md:435` (harvest H1) copies it there "from Epiphany head `4006b14b`", which is 5 commits behind `ef956865`. H1 has no cut_spec yet (eureka-body has only questions and rulings). If H1 copied from `4006b14b` after this change merges, it would drop both leaf-key-bound and the new fields. ### Rationale **Where the fields land.** The leaf still lives in Epiphany and Huginn pins it by git rev, so the field goes into Epiphany's leaf, then Huginn re-pins, as the ruling's option text names. Waiting for H1 would make this one Huginn cut instead of two, but H1 is unscheduled. The operator accepted the three-pass cost in her words. Follow-up `leaf-harvest-copies-head` records the constraint on H1. If H1 merges before `leaf-read-anchors` starts, Self stops the cut and Imagination re-maps it onto `crates/eureka-pipeline` (one Huginn cut, no re-pin). **Shape.** `CodeLocation.symbol: Option` everywhere a location is (file changes, findings, follow-ups, mutations). It means the same thing in every one: a name the location's lines must contain. `PipelineCutSpec.reads: Vec[16]` with `ReadAnchor { location: CodeLocation, why: Short }`. It is a distinct type, not `FileChange`, because a read changes nothing and a field named `change` would lie. Reads are in the spec's repo at its base, like file_changes. A read in another repo stays prose in `first`, because a read cannot pin another repo's commit. `Symbol` is a new bounded text: 1-128 bytes, no whitespace or control character. It is new, so no stored document can fail it. A read's window is bounded by an `=> extra` hook on `ReadAnchor`: `line >= 1`, `end_line >= line` (`InvalidFormat`), and span at most 100 lines (`FieldBound`, limit 100). 100 is the window cap the tool already uses. No new bound touches `CodeLocation`'s existing fields, because stored findings would then stop validating. **The macro owns "additive".** `value_types!` gains a per-field marker written after the type and any list maximum, for example `symbol: Option = absent` and `reads: Vec[16] = absent`. It emits `#[serde(default, skip_serializing_if = "Absent::absent")]`. Trait `Absent` is implemented for `Option` and `Vec` only, and the validate expansion names `<$ty as Absent>::$marker`, so a marker word other than `absent`, or the marker on any other type, is a compile error. The epoch doc and the schemas README then say this marker is how a field is added at v2. This replaces the hope that every future author remembers both attributes (B16). **No admission rule in Huginn.** Bounds are the leaf's, and Huginn applies them through `validate` on every write. The symbol-in-window check needs a tree (B20) and belongs to `context-pack.py`, which exits nonzero on `SYMBOL_MISSING`. **Deploy.** The owner is the gamecult-ops runbook's Upgrade section. It is not Idunn: Idunn does not own this unit, and no brake applies (B22). The ops cut generalises the rollback paragraph for additive fields and then executes the Upgrade steps. One addition: the read-back test also asserts that every stored document re-prepares to its stored payload bytes. That checks B16 on the live mind, not only in a unit test. **Eureka.** One new cut after both v1 cuts and the deploy. The v1 specs stay as they are, because v1 must not wait on the deploy. **Subtraction.** Removed: prose `path:line` read references in specs for the spec's own repo (224 today, B14), replaced by checked reads; Imagination's "re-take the anchor" notes; Hands' first-pass grep for callers of named symbols. Added: one trait, one text type, one struct, one macro marker, two fields, one tool section. Not added: an admission rule, a Huginn type, an index change, or a second store. B24. The admit door drops unknown keys silently. The batch that admitted these specs had one file_change location with a stray `"symbol": null`. It was admitted as Committed (receipt `mind-commit-c528cc24...`), and a view of `cut-context-pack-reads.r1` shows that location without the key. This is B15(b) at the JSON door: a session that started before the client swap will drop `reads` and `symbol` and still report success. Follow-up `gap-admit-drops-unknown-keys` (owner: Huginn eureka-state). Meanwhile the ops cut makes agents restart, and context-pack-reads stops if its admit schema lacks `reads`. Admitted (receipts `mind-commit-c528cc24...`, `mind-commit-5d97a6ad...`): `eureka-substrate:cut_spec:cut-leaf-read-anchors.r1`, `cut-huginn-read-anchors.r1`, `cut-ops-read-anchors-upgrade.r1`, `cut-context-pack-reads.r1`; follow-ups `leaf-harvest-copies-head` and `gap-admit-drops-unknown-keys`. Order: leaf, then Huginn, then ops (deploy), then Eureka. The Eureka cut also waits for both v1 cuts.