D214 - Proposals are paired with an oracle; the first one asks for words, not names

decided · 2026-08-24 · directed by the user, who chose the order of work

crates/orbistoun-propose. The callers that let something other than a person turn THE_LOOP.md's steps 17 and 18. orbistoun-llm (D212) supplies proposals and knows nothing about this project; orbistoun-names supplies the oracle and knows nothing about models; this crate is where they meet, which is why neither of them has to.

Named after the oracle, not the source

something proposes  ->  something else disposes  ->  only what survived is kept

The first box is the least important. What makes a proposer safe is the second: an oracle that is cheap, mechanical, and cannot be talked into agreeing. Without one it is a machine for generating plausible wrong answers, which principle 3 forbids in as many words. So each proposer is named after its oracle, and none is built before its oracle exists.

Proposer Oracle One query A wrong proposal
vocabulary the NID hash a sweep, under a minute nothing
stub semantics (next) the guest, re-run one boot, one bit a relaunch
an implementation none - unbounded, so no

Vocabulary is first because its oracle is the best in the project. A hash collision is proof rather than judgement - nothing is consulted and nothing could help - so this is the one corner of the codebase where a model that confidently invents things is harmless: an invented word is discarded by exactly the arithmetic that discards a carefully reasoned one. It is also the loop edge that already compounds (step 7 of THE_LOOP).

Words, never names

The model is asked for vocabulary - Sema, Attr, Prio - and never for an identifier. It is never shown a hash, never told which function is wanted, never given a mapping.

This is the whole provenance argument and not a preference. A name confirmed through the word route is recorded generated at a pattern and an index, so audit re-derives it by evaluating that pattern - the same check every other generated name gets, with nothing taken on trust. Ask for the name directly and the record could say only "something suggested this": nothing could re-derive it, and PROVENANCE.md's answer to "did you work these out yourselves?" would have a hole exactly where its foundation is. It would also need a new provenance category, days after D213 finished removing the one that blurred two claims together.

The word route costs nothing to take, so it is the one taken.

A prompt is a request, not a constraint, so two guards sit behind it: a word must be a single capitalised alphanumeric token, and short enough that an identifier cannot fit. sceKernelAllocateDirectMemory fails the first; SceKernelAllocateDirectMemory fails the second. Both are tested, as is the absence of any hash from the prompt.

The seam that made it testable

Vocabulary::round first took &Llm - a struct that owns real backends and downloads gigabytes - which made the entire round untestable. orbistoun-llm grew an Ask trait, Llm implements it, and the proposer takes &dyn Ask.

Principle 12's own test for whether a seam is premature: "if it pays off only hypothetically it is speculation; if it buys testability now, it is structural." It buys it now, and every proposer written later gets it free.

What that unlocked is the test worth having: a real grammar, a real hasher, a real sweep, a real hash, and only the model faked. Sema is taken out of the vocabulary; a wrong word finds nothing; the right word recovers sceKernelCreateSema; and the record is then handed to solve::verify - the function audit itself runs. The assertion is not "a name appeared" but "the audit re-derives it".

The sweep is a delta, and stops one step short of where it could

Only the shapes that reference the grown vocabulary are swept. Every other shape generates exactly what it generated before, and the caller's ordinary sweep has covered it: 31 million candidates instead of 2.6 billion, an 83x saving, exact rather than approximate.

The next narrowing was written, measured, and reverted, and that is the entry. Restricting the slot itself to only the new words takes a round to about 150,000 candidates - another 200x. It also destroys the record.

An index is a position in a mixed-radix number whose digits are the lengths of that pattern's word lists. Shorten the list and the radix changes, so the recorded index names a different candidate in every grammar anybody actually holds. verify would then refuse names that are perfectly real, and the failure would look like a naming bug rather than an arithmetic one. Filtering patterns is safe for precisely the reason narrowing the slot is not: indices are per-pattern, so dropping a shape cannot move a position inside the ones that remain.

Eighty-three times cheaper with the record intact beats seventeen hundred times cheaper with the record meaningless (principle 11). A test pins it - verify must still accept the record against the grammar as it stands once the word is adopted - so that this cannot be re-optimised by somebody reading the sweep figures and not this entry.

Found by a test, not by review. The assertion that a round sweeps under one per cent failed at 1.2%, which is what sent me looking - and the reason the arithmetic disagreed with the code was that I had costed the design I had not built.

Two things checked rather than assumed

Does any pattern use the learned vocabulary? If none did, every word added would generate nothing - silently, and forever, with each round reporting a clean miss indistinguishable from an exhausted vocabulary. Two shapes use it. That near miss is now an error: Error::SlotUnused refuses a slot no shape references.

A concat! format string cannot capture from scope. The prose guard forbids \ line-continuations because cargo fmt bakes source indentation into the rendered text, so it pushes writers to concat! - and format_args! then refuses {name} capture through a macro expansion. Every concat! format string in this repository must use positional arguments. Cost one build.

What is deliberately not here

Nothing is written. A round returns what it found and what it discarded; persisting is the caller's decision, so changing a tracked file stays in one place rather than being buried inside a search.

And nothing calls this yet. A run-llm entry point that turns the loop with a model and falls back to a person is the next piece; this crate is its foundation, not its delivery.