loco: the shared basin codebook — one FnIndex for a whole palette - #295
Conversation
Measured first: the loco domain range 0x90..=0xFF is 112 function slots per vocabulary (ogar-ro mints 22 from 0x90; recipe_vocab mints 34). A 144-entry relation palette cannot be FnIndex bytes -- 144 > 112, and no carving makes it fit. A large palette is therefore a VALUE table: one FnIndex for the whole thing, with the operand byte indexing a codebook. The gap that blocked that. Two mechanisms each had one of the two properties a shared operand table needs: ConstantPool u8 1..=255 classid(4)+12 V3 facet OWNED, per-function ValueCodebook u8 id no shape (advisory) SHARED, basin-scoped ValueCodebook's contract is that the same call in sibling basins may resolve its operand against different codebooks -- but it carries no table, so nothing could actually resolve. ConstantPool carries exactly the right table and is owned by one function, so a vocabulary-wide target set held there would be copied into every function that names it. crates/ogar-loco/src/basin.rs is the join: the pool's facet arithmetic under the codebook's ownership. ConstantPool::locate and ::slot_payload_offset are CALLED, not reimplemented -- pool.rs says the defects live in that arithmetic, so a second copy would be a second place for them to live. Why this is not the shared-mutable sink pool.rs refused: that objection is about mutability, not sharing, and it is answered structurally rather than by convention. BasinCodebookBuilder is the only thing that can intern; seal() consumes it and yields a BasinCodebook with no &mut self method at all. One writer at mint time, zero afterwards. Sharing an immutable table is not a sink. BasinCodebooks::resolve_operand(vocab, call, i) is the seam that makes Vocabulary::value_codebook load-bearing instead of advisory: it asks the vocabulary which codebook the call's function declares, then resolves that byte against it. Before this, the declaration named a table nothing could reach. What the entry classid buys: an entry is classid(4) + 12, the V3 content-blind register, and the classid names the reading -- 6x(u8:u8), 4x(u8:u8:u8), 3x(u8:u8:u8:u8) or 12xu8 over the same bytes. That is what makes an index into this table different in kind from an enum ordinal: a flat ordinal has no metric, while a quantizer code answers distance by table lookup. Compiling meaning into a codebook IS minting the entry classid. No classid is minted here. As in pool, the classids stay PARAMETERS supplied by the caller, because minting a concept is an operator decision with a ledger entry. loco stays vocabulary-agnostic: it owns the ABI, the shapes, the pool and now this table, and knows nothing about what any byte means. A thinking IR is a caller that plugs its vocabulary and codebooks in through the same seams. 8 tests, each disable-verified rather than assumed. Two were VACUOUS on first writing and were found by their own falsifier: - id-keyed resolution passed while "return the first book" was substituted, because both basins listed the same table first. Strengthened to hold two tables in ONE basin with different bytes at index 1; the substitution now fails. - the capacity guard passed while removed, because u8::try_from returns Full anyway -- but without the guard the 256th entry is PUSHED before rejection. Same error value, corrupted state. Strengthened to assert the table length is unchanged after a refusal; the substitution now fails. The other six falsifiers all failed as intended: dropping classid from entry identity, treating 0 as a real index, writing the payload over the classid, letting a duplicate codebook id shadow, and ignoring the vocabulary's declaration in resolve_operand. Open underneath, unchanged: W-RO-5 (LaneShape and CascadeShape are bit-for-bit the same carving in two repos). A shared rail codebook is written in one and read in the other, which makes it the first thing actually blocked by that duplication rather than merely annoyed by it. cargo test -p ogar-loco: 63 passing (55 pre-existing + 8). cargo clippy --all-targets --no-deps: clean.
…pt domain
Operator flag: 0x9x is now health domains. Checked, and there is no collision
-- but the hazard is real and the workspace already has a live instance of it.
Three address spaces carry overlapping numerals:
FnIndex (call codebook) u8 0x90 = DOMAIN_FLOOR, first
vocabulary-local opcode
ogar_vocab concept domain u16 hi byte 0x90XX = ROOT of the domain reference
tree; 0x90..=0x9D is Disease,
Phenomenology, Lab, Imaging,
Substance, Procedure, Form
ValueCodebook::id u8 which codebook a basin plugged in
They never meet. loco's own concepts are 0x1701/0x1702 in the Blocks domain
and basin.rs mints none at all; its test classids are the deliberately invalid
0xDEAD_100x, and its test opcodes are FnIndex bytes. So the code is correct as
written.
What was wrong was the prose: basin.rs and the ledger entry both said 'the
domain range is 0x90..=0xFF' repeatedly without naming the axis, and a reader
who knows 0x90XX as the reference-tree root would read a health-domain claim
into a sentence about function indices.
ogar_vocab's own domain table already documents exactly this overlap one slot
along: '0x9E is NOT claimed: it is has_phenotype in ogar-ro's PREDICATE space,
a different axis that overlaps these numbers' -- and ogar-ro does mint 0x9E, as
an opcode. Both readings are correct on their own axis, which is precisely why
the axis has to be named rather than inferred.
basin.rs now carries the three-space table and states that every 0x9x below it
is an opcode byte. The DISCOVERY-MAP entry says the same inline.
No behaviour change. cargo test -p ogar-loco: 63 passing. clippy clean.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_e26c6282-aafb-4f6b-8e30-90699bd85228) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc94be7dc2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| pub fn resolve_operand<V: Vocabulary>( | ||
| &self, | ||
| vocab: &V, | ||
| call: &Call, | ||
| which: usize, | ||
| ) -> Option<&Constant> { | ||
| let declared = vocab.value_codebook(call.function)?; |
There was a problem hiding this comment.
Resolve operands from the validated vocabulary table
When a caller passes a raw Vocabulary—as this module's new test does—this method re-reads value_codebook from live, unvalidated trait methods. An implementation can override the composed method for shared-core functions or return changing domain_value_codebook answers, causing operands to resolve against a table different from the frozen semantics in CheckedVocabulary/VocabularyTable; the registry also retains only that frozen table. Require a checked vocabulary or perform the lookup directly against VocabularyTable, as the other downstream semantic traversals do.
Useful? React with 👍 / 👎.
Paired with the lance-graph semantic-family recovery (2026-09-02, E-SIX-SEMANTIC-FAMILIES-MUST-NOT-IMPERSONATE-EACH-OTHER-1). The register the 0x87..0x8B calls operated on aliased episodic loci, qualia magnitude and population-basin geometry into one type. Removed - FnIndex BELNAP_JOIN / INFO_GAIN / SIGMA_TENSION / ACCUMULATE / STANCE_ENTROPY (0x87..0x8B), their arity and name rows; census re-pinned 101 -> 96. The slots return to reserved core (reserve, don't reclaim). - crates/ogar-epistemic (the fixed 24-axis basis, concept 0x0334) and its workspace member line. Kept - TERNLOG 0x86: a generic 3-input mask combinator whose value byte is the truth table, independently justified by ndarray's ternlog. - BasinCodebook (#295) untouched. Ledger: D-EPISTEMIC-BAND-RETRACTED appended to docs/DISCOVERY-MAP.md. Tests: ogar-loco 55 green; clippy -D warnings + fmt clean; workspace metadata resolves without the removed member. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PFnYKqw6d7TTiB9cT8eFdK
The measurement that decided the shape
The loco domain range
0x90..=0xFFis 112 function slots per vocabulary.ogar-romints 22 (0x90..=0xA5, read from therelation_palette!table, contiguous by design);recipe_vocabmints 34. A 144-entry relation palette therefore cannot beFnIndexbytes — 144 > 112, and no carving makes it fit.So a large palette is not a set of functions. It is one function whose operand byte indexes a codebook.
The gap that blocked that
Two mechanisms each had one of the two properties a shared operand table needs, and neither had both:
ConstantPoolu81..=255classid(4) + 12V3 facetValueCodebooku8id{id, name}ValueCodebook's own contract is that the same call in sibling basins may resolve its operand against different codebooks — but it carries no table, so nothing could actually resolve.ConstantPoolcarries exactly the right table and is owned by ONE function, so a vocabulary-wide set held there would be copied into every function that names it.crates/ogar-loco/src/basin.rsis the join: the pool's facet arithmetic under the codebook's ownership.ConstantPool::locateand::slot_payload_offsetare called, not reimplemented —pool.rssays the defects live in that arithmetic, so a second copy would be a second place for them to live.Why this is not the shared-mutable sink
poolrefusedpool.rsrejects holding constants in the Inventory SoA because "Inventory is shared by every function, so a per-function pool living there is a shared-mutable sink with N writers." That objection is about mutability, not sharing — and it is answered structurally rather than by convention:BasinCodebookBuilderis the only thing that can intern;seal()consumes it and yields aBasinCodebookwith no&mut selfmethod at all. One writer at mint time, zero afterwards. A body resolving an operand borrows&BasinCodebookand cannot reach a mutation it does not have.The seam
BasinCodebooks::resolve_operand(vocab, call, i)asks the vocabulary which codebook the call's function declares, then resolves that byte against it. This is what makesVocabulary::value_codebookload-bearing rather than advisory — before it, the declaration named a table nothing could reach.What the entry classid buys
An entry is
classid(4) + 12— the V3 content-blind register — and the classid names the READING:6×(u8:u8),4×(u8:u8:u8),3×(u8:u8:u8:u8)or12×u8over the same bytes. That is what makes an index into this table different in kind from an enum ordinal: a flat ordinal has no metric (nothing about 71 says it is near 72), whereas a quantizer code answers distance by table lookup.No classid is minted here. As in
pool, the classids stay PARAMETERS supplied by the caller, because minting a concept is an operator decision with a ledger entry.Axis discipline
0x90names three different things in this workspace, and this module touches two:0x90namesFnIndex(call codebook)u8DOMAIN_FLOOR, first vocabulary-local opcodeogar_vocabconcept domainu16hi byte0x90XX, ROOT of the domain reference tree (0x90..=0x9D: Disease, Phenomenology, Lab, Imaging, Substance, Procedure, Form)ValueCodebook::idu8They never meet — loco's own concepts are
0x1701/0x1702in Blocks, and this module mints none. Butogar_vocab's domain table already documents a live instance of the overlap (0x9E=has_phenotypeinogar-ro's predicate space, whichogar-rodoes mint, as an opcode). The second commit states which axis every0x9xinbasin.rsis on, so a reader who knows0x90XXas the reference-tree root does not read a health-domain claim into a sentence about function indices.Tests — 8, each disable-verified
Two were vacuous on first writing and were caught by their own falsifiers:
u8::try_fromreturnsFullanyway — but without the guard the 256th entry is pushed before rejection. Same error value, corrupted state. Now asserts the table length is unchanged after a refusal; the substitution now fails.The other six falsifiers failed as intended: dropping classid from entry identity, treating
0as a real index, writing the payload over the classid, letting a duplicate codebook id shadow, and ignoring the vocabulary's declaration inresolve_operand.Scope
Additive.
ogar-locostays vocabulary-agnostic — it owns the ABI, the shapes, the pool and now this table, and knows nothing about what any byte means. A thinking IR is a caller that plugs its vocabulary and codebooks in through the same seams; nothing in this module knows one exists.Ledger:
D-BASIN-CODEBOOKindocs/DISCOVERY-MAP.md(append-only,[G]/CODED).Open underneath, unchanged
W-RO-5 —
LaneShape(Pairs/Triples/Quads) andCascadeShape(G6D2/G4D3/G3D4) are bit-for-bit the same carving in two repos, deliberately duplicated because loco is zero-dep. A shared rail codebook is written in one and read in the other, which makes it the first thing genuinely blocked by that duplication rather than merely inconvenienced. Operator call, not this PR's.Verification
cargo test -p ogar-loco— 63 passing (55 pre-existing + 8 new)cargo clippy -p ogar-loco --all-targets --no-deps— cleancargo check -p ogar-loco -p ogar-ro -p ogar-r2il --all-targets— clean🤖 Generated with Claude Code
Generated by Claude Code