`huginn-daemon` becomes a library and a binary. `Daemon` holds one mind and
dispatches a `HuginnMindRequest` to the `Mind` method that answers it, with no
socket and no clock: `now` is the caller's. Every read that names an instance
is put to `Mind::require_instance` before the method runs, so ruling 14 is
checked once, by its owner, whichever side of the wire the request came from.
`IndexSink` is where Cut 11 attaches; its error is logged and the outcome is
what the mind decided.
`envelope` carries the vocabulary in CultNet's `cultnet.operation_request.v0`
and `cultnet.operation_response.v0` as base64 of named MessagePack, which is
what the C# reference and Eve's plugin ABI speak. The envelope's operation
string is checked against the decoded payload rather than trusted. A message
that does not decode is answered with a typed failure naming its code, and
reaches no mind.
`serve` binds one non-blocking socket on CultNet's operation connection id and
answers each frame on the session it arrived on. A refusal is an answer with a
`rejected` status, never a transport error. The two wire schemas go out on a
schema catalog request. A hostile datagram and a departed peer are logged and
served past. `startup` is the one place the mind opens and the socket binds,
in that order, so a mind that will not open leaves the port free.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`huginn-mind::wire` names one request per `Mind` method plus `whoami`, and one
response per method plus the read side's refusal. The payloads are the
admission and read types unchanged, so a client speaks the organ's own
vocabulary rather than a projection of it. `operation()` and `instance()` say
what the envelope carries; `status()` derives the envelope's status from the
response, and a refusal is data in that response rather than a transport
failure.
`Mind::status` answers `whoami` from the image every time it is asked: the
instance, the schema epoch, and how many pipeline documents and receipts the
mind holds. Nothing stores a status and no configuration supplies one.
`schemas/cultnet/` publishes the JSON schema of each wire type, derived from
the type and pinned to it byte for byte by a test, so the schema catalog
cannot advertise a shape the crate does not speak.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ruling 14's comparison moves out of `admit_steps` into
`Mind::require_instance`, so admission and the CultNet surface's reads ask one
function rather than two copies of one rule. A1 calls it where the inline
comparison was and behaves as before.
The leaf derives `Serialize`, `Deserialize` and `JsonSchema` for
`PipelineRefusal` at the pinned revision, so serde's remote definition of it
is deleted and `MindRefusal::Document` wraps the leaf's value directly.
`PipelineAdmissionBatch` derives the same three and loses the doc sentence
that claimed it could not.
The crate re-exports `epiphany_pipeline` and the owned redb store type, so the
daemon and the client name the leaf's types and the store through one
dependency entry at one revision instead of declaring the git dependency
twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pin moves to Epiphany `5cda0886`, where `PipelineRef::validate_ref` opens
the leaf's grammar to a reader. `view` and `history`'s subject scope ask it
before they look, so a ref whose kind and id disagree is refused instead of
reporting the nothing a lookup finds. `query`'s `campaign`, `cut` and `repo`
filters take a `Slug`, a `Label` and an `OrgRepo`, not refs, and are unchanged.
The refusal is `MindRefusal::Document`, the leaf's `InvalidFormat` wrapped
where the organ already wraps the leaf's four. A new variant would be a second
name for one rule, and the grammar it names lives in the leaf.
`a_ref_whose_kind_and_id_disagree_is_refused_by_both_doors` pins both doors on
a disagreeing kind and on a local no writer composes, and pins that absence is
still absence: a well-formed id of a document this mind does not hold answers
`None` and an empty history as it always did. The history test's malformed
scope asserts the refusal where it asserted an empty answer.
`V24` and `V24L` in `tools/eureka-cut9-mutations.psd1` are the validation
dropped and the validation applied at `view` alone. `V10L` leaves the suite
with its reason in the header: a subject compared by `id` alone can no longer
differ from one compared whole, since both sides' kind is now a function of
the id, so the weakening is unreachable and a mutation that cannot fail is not
a proof.
`cargo test -p huginn-mind --lib` 57 passed, 0 warnings; `cargo check
--workspace` clean; Cut 8 and Cut 9 suites green-and-killed through the
harness. The lock moves the one pin.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cut 9's entries file recorded three weakenings as unfailable. Two of them
are reachable in this Body, so they are entries now:
- The `semantic` check below `Reader::new` changes the answer for a store
the reader refuses -- a doubled receipt, a row that does not decode --
from the semantic refusal to the integrity one. `V8L` is that move, and
the semantic test asks a semantic query of a mind whose receipts name one
write twice.
- `resolutions_of` comparing a subject by `id` alone is reachable because
the read side validates no `PipelineRef` it is handed. `V10L` is that
comparison, and the subject-history test asks for a `Ruling` ref carrying
a question's id after that question has ten resolutions.
`assignments_of` ignoring the instance stays recorded as unfailable, with
A5 and the derivations scoped to this mind as the reason.
`specs_without_report` compares the citation by exact id, but every fixture
report cited `.r1`, so the test could not tell an exact id from a cut
prefix. cut-9 now carries a report of the revision it superseded and cut-12
is a superseded revision no report names, so standing and citation are each
still visible on their own, and `V13L` compares by cut prefix.
Fork, not closed here: Soul asked that `view` and `history` validate the
ref through the leaf and refuse a malformed one typed rather than answering
empty. The leaf's `Bounded` is `pub(crate)` at the pinned rev d5a36c2, so
`PipelineRef::validate` cannot be called from this crate and the fix is an
Epiphany change. The history test pins today's empty answer and names the
reason.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`tools/eureka-cut9-mutations.psd1`, V1-V23: every rule the views decide,
with the edit that removes it and the test that must fail while it is
applied. A rule with two weakenings gets two entries, `Vn` for the rule
absent and `VnL` for the rule weakened rather than removed, so a suite
that only catches the crude form does not pass for the subtle one. 34
entries; M0 green and every one killed.
V1 is the proof that in force has one owner: its mutant lands in
`docs.rs`, and V1L is H41's own edit run against the views instead of
the rules. One mutant, two suites.
Four tests grew to put a mutant in reach of a real failure, which is the
point of writing the entries before trusting the suite:
- Two receipts naming one write, forged and planted, so the integrity
check that refuses them is not dead text.
- A cut spec whose withdrawal is itself withdrawn, so `base` has two
steps to walk and stopping at one is visible.
- A `cut-1` spec beside `cut-9` and `cut-10`, so the prefix that ends at
the dot is not the same answer as the prefix that does not.
- A second report of one cut, judged by nothing, so "this cut has a
verdict" and "this report has one" come apart.
Three weakenings the map names have no entry and say why in the file's
header: the `semantic` check moved below the filters still refuses;
`resolutions_of` ignoring a subject's kind cannot matter while an id
carries its own kind segment; `assignments_of` ignoring the instance
cannot matter while A5 refuses a stewardship naming another. A mutation
that cannot fail is not a proof, and listing it would only make the
suite look thorough.
Nine cut-8 entries moved with their code in the first commit; that suite
is green as well, H1-H67, M0 included.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`query.rs` is the read side, over a `&Mind<S>` with no store file, no
daemon and no index. A `PipelineDocumentView` is four fields: the id,
because a `PipelineDocument` does not carry its key and a client must
never derive one; the document; the facts of its admission; and its
status.
The facts come from the commit receipts and from their `writes` alone,
so a document a later batch cited as a strong read keeps the receipt
that wrote it, and `admitted_at` is that receipt's `committed_at` -- the
`now` admission was passed -- never the store's `stored_at`. A pipeline
document the image holds and no receipt wrote is an integrity fault and
refuses; the store has one writer and the receipt is its proof.
Status is derived per call through `docs::Docs`, the same owner the
admission rules ask, so a withdrawn resolution reopens its subject for a
reader exactly when it reopens it for a rule. `closing_resolution` is
`in_force_unless`'s own body asked for the resolution rather than the
bool, so there is no second derivation to drift.
Four reads: `view` by id, `query` over the filters (root, repo, cut,
kinds, in force, faculty, an admission window), `open_items` for one
campaign's unanswered questions, standing findings, follow-ups,
unreported specs and unjudged reports, and `history` for every
resolution a subject ever had or every assignment of a repo to this
mind, withdrawn ones included with their reasons.
A query is ordered by `(admitted_at, id)`, capped at 200, and carries
`matched`, the count before the cap, so a truncation is never a short
page that reads like the whole answer. `semantic` is accepted by the
type and refused typed until Cut 11 owns the index; no substring filter
lives here to become a second answer to "the text of a document".
`cargo test -p huginn-mind --lib`: 56 passed, 0 failed, 0 warnings
(46 + the ten tests). `cargo check --workspace` clean. No dependency, no
target, no kind, no schema, no epoch; `Cargo.toml` and `Cargo.lock`
untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`Docs`, `Staged`, `Held`, `kind_of_type` and `later_than` leave
`admission.rs` whole and land in `src/docs.rs`, so the read side of Cut 9
can reach the derivations without admission exporting its internals. One
owner for in force: the views will call the same `Docs` the rules do,
never a second copy.
Three edits ride along, each an extraction the views need and admission
now runs through:
- `closing_resolution_unless` is `in_force_unless`'s body, returning the
resolution that closes a document rather than a bool; `in_force_unless`
is `.is_none()` over it.
- `resolutions_of` and `assignments_of` are the scope selectors
`latest_resolution`, `latest_stewardship` and `stewardships_of` already
spelled out one at a time.
Nothing else changes: same bodies, same call sites, `admission.rs` keeps
every rule and every `docs.` call reads as before. Its module doc says
where in force lives now.
Nine cut-8 entries re-anchor because their code moved: H33, H41, H62,
H63, H65, H66 and H67 by file, H47 and H60 on the scope selectors the
extraction introduced. Their rules are unchanged, and H49's mutant still
compiles because `Docs`'s fields are `pub(crate)`.
`cargo test -p huginn-mind --lib`: 46 passed, 0 failed, 0 warnings.
`eureka-cut8-mutations.psd1` through Epiphany's harness: M0 green,
H1-H67 all killed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A hand-off's derived withdrawal read whichever stewardship `stewardships_of`
listed first. While the withdrawal is being derived the hand-off's own is
excluded from what stands, so two assignments are in force at once: the one
this hand-off withdrew, and whatever stewards the repo now. Key order does not
tell them apart -- once a repo has changed hands ten times, `n11` sorts before
`n2` -- so replaying an old hand-off derived a second withdrawal of a standing
assignment and died on the identity collision instead of answering with its
first receipt. `stewardship_of` now takes the stewardship whose withdrawal
already carries this hand-off's key, the same content match the ruling's answer
and the receiving side's assignment already use, and falls back to the one in
force. The comment claiming at most one is ever in force said the opposite of
what the exclusion does; it is gone.
`later_than`'s resolution arm is deleted. Nothing could reach it: the Q19 cap
refuses a withdrawal whose subject is a resolution of a resolution before the
reinstatement rule asks what stands later, so the arm was dead and the entry
Soul wrote against it could not fail. Dead code with a test that cannot reach
it is what subtraction is for -- an arm no caller enters is a rule nobody can
break and nobody can rely on, and leaving it would have bought a green mutation
line for a branch the machine never runs. The two doc sentences that named the
resolution scope go with it.
Four fixtures close gaps where the code was right and the suite could not tell
it from something weaker: a ruling answering a reopened question takes the next
sequence rather than reproducing the withdrawn answer's; a cut spec's revisions
are read within its own cut, so cut B's second does not block reinstating cut
A's first; `later_in_force` counts only what stands, and reads the batch, so an
assignment derived beside a withdrawal still blocks it.
Entries H62-H67 pin all six. H1-H67 killed by their own tests, M0 green;
46 tests pass with no warnings; `cargo check --workspace` clean.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both anchored on the `latest_resolution`/`latest_stewardship` call `derive`
no longer makes, so the harness stopped at H45 with no verdict rather than
running. The rule each entry states is unchanged -- a derived record takes
the next sequence of its scope, not always the first -- so each moves to the
line that now decides it and keeps its killer.
H1-H61 all killed, M0 green.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
H52-H61, each restoring one permissiveness the follow-up closed and naming
the test that must fail while it is applied.
H52-H54 revert the three derivation sites to an unconditional `latest + 1`,
one per derived kind, killed by the two replay tests. H55 and H56 drop the
reinstatement check against the retaken repo and the superseded revision.
H57 and H58 let each sequence rule accept a gap, which only a test whose
sequence is above the expected one can tell from the rule. H59 reads the Q19
cap's subject from the image alone, H60 `latest_resolution` from the image
alone, and H61 sets the hand-off's derived withdrawal to sequence 1 -- the
three Soul's second pass could restore with the suite still green.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two rules Soul's follow-up pass falsified, both in admission.
A batch that derives a write could not be replayed. `derive` set every
derived sequence to `latest + 1` over an image that already held the first
admission's derived record, so the second admission produced a different
document, A8 refused it -- `AlreadyResolved` on the source side, and
`AlreadyStewarded` on the receiving one -- and A9 never saw the digest it
would have recognised. `derive` now reproduces the record it already made
when image or batch holds one, found by what the derivation writes into it: a
resolution `Answered` by this ruling, a withdrawal whose reason is this
hand-off's key, an assignment whose note is. The same document comes back
byte for byte, A9 answers with the first receipt, and only a batch that never
landed takes `latest + 1`.
A withdrawal could re-raise an earlier record over a later one, which is the
rationale Q19 A's cap rests on and the cap alone does not carry. Withdrawing
a resolution puts its subject back in force, so a repo handed away and handed
back could have the hand-off's withdrawal withdrawn and stand under two
assignments at once; a supersession withdrawn while the revision that
superseded it stood left two revisions in force, which is what the finding
rule reads for its invariant vocabulary. `WouldReinstateOverLater` refuses
that: the record the withdrawal would reinstate, against anything in force
and later in the scope that record is sequenced in -- `(instance, repo)` for
a stewardship, the subject for a resolution, the campaign for a target and
the cut for a cut spec.
Four tests, one per fix and kind: the ruling's resolution and both sides of a
hand-off replayed to `AlreadyAdmitted` with the first receipt and no second
receipt in the store; the retaken repo and the superseded revision refused,
with the later record still the only one in force.
Four more tests for rules the code already held and the suite could not tell
from weaker ones: a sequence gap refuses on both sequenced kinds rather than
only a taken sequence; the latest sequence counts the batch as well as the
image; the Q19 cap reads a chain landing in one batch; and the hand-off's
second withdrawal of one assignment takes the next sequence, not `1`.
41 tests, 0 warnings.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two tests for the rules the last commit landed.
`a_withdrawn_resolution_reopens_its_subject_and_stays_readable` withdraws a
closure, admits the next one at `n2`, reads the first back with its outcome
intact, refuses a third while `n2` stands, refuses the withdrawal of the
withdrawal, and has a ruling answer the reopened question -- the derived
resolution lands at `n2`, not `n1`.
`a_repo_is_stewarded_once_at_a_time_and_again_after_a_transfer` refuses a
second assignment while the first stands and a second claiming the taken
sequence, hands the repo away, finds nothing stewarding it, hands it back and
gets `n2` with the second `handed_on`, the first still readable by key.
Entries H40-H46 carry the mutants: the two sequence rules, the recursive
in-force, `AlreadyStewarded`, the Q19 cap, and `derive`'s two sequences.
Then the six places Soul found the code right and the suite blind, each a
fixture and an entry (H47-H51): a handed-away repo is not stewarded, so
`stewardship_of`'s in-force half is pinned; a document two batch documents
cite is pinned once rather than cancelled; what a derived write cites in the
image is pinned, which is H32's surviving half; a finding may cite the
vocabulary of the target landing beside it; `Answered` coherence is checked
against a ruling in the image as against one in the batch; a branch differing
only in case is a mismatch.
H32 is re-anchored rather than left to rot. Its A4 half is unreachable now: a
derived write takes the sequence after the batch's own records, so a derived
identity cannot collide with one the batch holds. What survives is that
derived writes are validated, staged and resolved like any other, and the
transfer test's strong read is what fails when they are not.
README stops claiming the two stub crates do anything.
H1-H51 all killed, M0 green. 33 tests, 0 warnings; `cargo check --workspace`
clean; `git diff --stat 4094e68 -- .voidbot` empty.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The pin moves to Epiphany `d5a36c2a`, where a resolution's key ends in its
per-subject sequence and a stewardship's in its per-(instance, repo) one. Two
fields arrive with it, so `stewardship` and `resolution` in the fixtures set
`sequence: 1` and `stewardship_n`/`resolution_n` say otherwise when a test
needs a second record. Every literal key that gained `.n1` moved with it,
`id_of` included.
The rules the sequence was for:
- `in_force` is recursive. A document is in force when no resolution that is
itself in force names it, so a withdrawn resolution stops closing its
subject. The old one-level version counted a withdrawal as if it still
stood, which is half of why a reopened subject could not be resolved again.
- The other half was A10. `refuse_collisions` no longer has a `Resolution`
arm: a resolution key is not one-per-subject any more, so a collision on it
means what it means for every other kind, and whether a subject is closed
is A8's question.
- A8 asks it, for both sequenced kinds, in the same two steps. The sequence
is the writer's and must be the previous plus one over image and batch,
counting every record of that scope but this document --
`ResolutionOutOfSequence`, `StewardshipOutOfSequence`. Then no record of
that scope may still be in force -- `AlreadyResolved`, `AlreadyStewarded`.
With both, at most one stewardship of a repo stands on a mind, which is
what `stewardship_of` has been assuming.
- Q19 A is enforced rather than described: a resolution whose subject is a
resolution of a resolution is `IncompatibleResolution`. A withdrawal cannot
be withdrawn; a closure is reinstated by resolving the subject again.
- `derive` sets a sequence on each write it makes, from the same image the
rule reads.
Two existing tests move with the rules. `subject_resolves_at_most_once` is
now `a_subject_with_a_resolution_in_force_refuses_another` and separates the
two refusals. The hand-off test's last case used to be an A4 collision
between a batch's own stewardship and the derived one; the derived write now
takes the sequence after the batch's, so there is nothing left to collide and
the batch is refused as out of sequence instead.
31 tests, 0 warnings; `cargo check --workspace` clean.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Soul's pass found the suite claimed more than it proved: H1-H20 killed
twenty mutations, and nineteen further permissivenesses could be restored
with every test still green. A rule no test reaches is a rule the next
pass may delete by accident.
Two of them were about the receipt's atomicity and its reach, and needed
new machinery. A store in `receipt.rs`'s tests records the replacement set
of every swap it is asked for, which lets one test say what no assertion
over the resulting image could: a commit is one compare-and-swap, and the
documents and their receipt go in it together. Splitting the receipt into
a second swap now fails. Separately, a batch citing three image documents
is pinned against all three, so truncating the strong reads to the first
of them fails.
The rest are one fixture apiece, each closing a row the per-kind rules
already held: stewardship looked up in image as well as batch; the cut
report's repo as well as its branch; a promise measured by the claim that
names that label; the invariant vocabulary taken from the in-force target
and not a superseded one; a question that is answered or withdrawn but
never superseded, and a target never answered; the derived stewardship
dated by the hand-off; derived writes passing A3-A7, so a derived identity
the batch already holds is A4's refusal and not a store error; A4 and A2
themselves; a question's two distinct options and its recommendation among
them; unique labels on a question's options and a target's invariants; a
finding's locations; and an `Answered` naming the ruling that actually
answers its subject.
`tools/eureka-cut8-mutations.psd1` grows to H1-H39. H21 and H22 pin the
opener's epoch key and count, H23 `EmptyRepos`; the rest are Soul's own
survivors, adopted as entries so the thing that found them is the thing
that guards them.
Harness: M0 green at 31 tests, H1-H39 all killed by their own test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The previous commit carried Self's default reading of Q17, that a subject
resolves at most once and a resolution is not itself a subject. The
operator ruled the other way: a withdrawn resolution is kept, attached to
the subject it withdrew, and the subject may then be resolved again.
So the matrix row comes back exactly as it was and
`the_resolution_matrix_is_admissions` returns to admitting the withdrawal
of a resolution. Only the comment above `matrix` is new, and it says whose
ruling this is and what it still needs: a second resolution of one subject
needs a key the first does not already hold, which is a sequence in the
leaf's resolution key and is mapped elsewhere.
`EmptyRepos` and the opener's epoch record stand.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Soul's Cut 8 pass found three rules the code only half held and one
document that promised more than the crate does.
The opener's step 3 said "exactly one epoch record at the current epoch"
and checked neither the count nor the key: a record keyed anything at all
was read as the epoch record, and a second one was never looked at, so a
current record beside a foreign one passed or refused on nothing but the
order the store returned them in. Step 3 now counts before it reads,
refuses a record under any key but the epoch, and names the defect in
`found`: the stored epoch, the offending key, or the count.
The campaign row minted `PipelineRefusal::FieldBound` for an empty
`repos`. The organ does not get to invent the leaf's refusals; an empty
`repos` is admission's own rule and is now `MindRefusal::EmptyRepos`.
The matrix admitted `Withdrawn` on a resolution, which made a subject
resolvable twice over: withdraw the resolution and the subject is open
again. A subject resolves at most once, and a resolution is not itself a
subject, so `resolution` joins `campaign`, `cut_report`, `verdict`,
`instance` and `hand_off` as not resolvable. The key grammar still spells
a resolution of a resolution; admission refuses it here. Self's default
ruling on Q17, standing until the operator rules.
`README.md` and `AGENTS.md` claimed CultNet publication and a Qdrant
connection in the present tense and called the store `.cc`. The daemon is
a stub, nothing connects to Qdrant, and the live crate writes
`mind.redb`. They say so now.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cut 8, Huginn half, commit 4 of 4: `tools/eureka-cut8-mutations.psd1`,
entries H1-H20, each the exact source change that restores one
permissiveness this cut closed and the one test that must fail while it
is applied. The suite runs through Epiphany's `tools/eureka-mutations.ps1`
with `-Repo F:\Projects\Huginn`; Huginn holds only the entries.
H5 is a four-edit entry: `Mind::open` is switched to the transient redb
store, which needs a `MindStore` impl the live crate deliberately lacks,
so the entry supplies it in `store.rs`. That makes `store.rs` a fourth
harness target beyond the three the spec names; the file's header says
so. H7 moves the replay check above the per-kind rules rather than above
A7, since the digest it looks up needs A7's strong reads to exist.
Run 2026-09-16 against `0bd7133`: M0 green with 26 tests, every entry
killed by its named test, every target restored byte for byte.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cut 8, Huginn half, commit 3 of 4: `admission.rs`. `Mind::admit`
prepares typed documents through the leaf and calls `admit_prepared`,
the single commit path for this cut, Cut 10's sink, Cut 12's hand-off and
import, and Cut 13's tools. The steps run in order and the first failing
step is the outcome: the batch names this mind (A1), one to sixty-four
envelopes (A2), the leaf's bounds, formats and key recomputation (A3),
unique identities (A4), every instance-carrying document names this mind
(A5), an empty mind's first batch carries exactly one `instance` document
and a batch carrying one derives the epoch record (A6), every reference
resolves in image or batch by `(kind, id)` (A7), the derived writes pass
A3-A7 themselves, the per-kind rules and the resolution matrix (A8), an
exact replay answers with the stored receipt only after validation (A9),
no write collides with the image (A10), the commit (A11).
Derived writes: a ruling that answers derives the `Answered` resolution
of its question; a hand-off derives the stewardship withdrawal on its
source side and the stewardship assignment on its receiving side. "In
force" is computed at rule time from the resolutions in image and batch
and never stored; the two derivations are the only resolutions a rule
ignores when judging its own document.
Two places the leaf is thinner than the spec assumed. `FindingWithoutRange`
is unreachable: a finding envelope without `range` fails the leaf's decode
as `Document(InvalidFormat { field: "payload" })` before any organ rule,
and the test asserts that. A campaign with no repos has no refusal of its
own in the spec's list; it is refused as `Document(FieldBound)` on
`campaign.repos` with a minimum of one. `PipelineAdmissionBatch` cannot
derive `JsonSchema` because the leaf's `PipelineDocument` does not; the
outcome and refusal types do.
The epoch record is derived whenever the batch carries the `instance`
document, which only the first batch may, so a replay of the first batch
digests to the same receipt and answers `AlreadyAdmitted` instead of
colliding.
Tests: 26 (the spec's 25 plus the replay-planting test, the receipt's
deliberate second construction site). `cargo check -p huginn-mind --lib
--tests` and `cargo test -p huginn-mind --lib` pass with zero warnings;
`cargo check --workspace` passes. README says what the crate does now.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cut 8, Huginn half, commit 2 of 4: the commit primitive in `receipt.rs`.
`candidate` is the one construction site of a receipt: its id is the
digest of `(instance, strong_reads, writes)`, each list in identity order,
computed before anything is stored so admission can look up an exact
replay. `replay` finds a stored receipt with that id and checks its reads
and writes equal the candidate's. `commit` is one compare-and-swap over
the store, the strong reads as its expectation and the writes plus the
receipt as its replacements; the image is re-pulled either way, and a lost
swap is diffed against the fresh image and typed `Conflict`.
Provenance and `committed_at` are on the receipt and outside the digest,
so the same bytes replayed by another faculty in another session answer
with the first receipt. The store requires every expected identity to be
replaced, so a strong read is re-inserted unchanged beside the writes, as
Epiphany's receipt path does.
Builds: `cargo check -p huginn-mind --lib --tests`, `cargo test -p
huginn-mind --lib` (4 tests). Dead-code warnings remain on the primitive
and the refusing test store until admission consumes them in the next
commit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cut 8, Huginn half, commit 1 of 4: the store port, the opener and the
identity rules. `huginn-mind` gains its dependencies (`cultcache-rs` at
Epiphany's pin `a0813c6`, `epiphany-pipeline` at `a65c6420`, the Epiphany
half of this cut), `store.rs` with the one narrow `MindStore` trait that
injects compare-and-swap, `mind.rs` with `Mind::open` and `open_with`, and
`refusal.rs` with the organ's refusal vocabulary.
The opener refuses in order and attaches nothing until every gate passes:
a foreign type, a foreign epoch, a missing identity, an identity naming
another instance. The path under the state root is derived from the slug;
the `instance` document is the authority, so a store copied into another
instance's directory is refused. The owned store's exclusive lock is the
single-writer mechanism: a second `Mind::open` of the same path is
`MindAlreadyOwned`.
The receipt type and the two provenance shapes (`Faculty`,
`PipelineProvenance`) land here rather than in the later commits because
the opener registers the receipt's type id, and their only consumer is the
receipt; the commit primitive itself is the next commit. `MindRefusal`
carries a serde remote definition of the leaf's `PipelineRefusal`, which
derives neither `Serialize` nor `JsonSchema` at the pinned rev; it is
compiled against the leaf's type so a leaf change breaks the build rather
than drifting.
`Cargo.lock` is regenerated and gains 90 packages. The map predicted a
CRLF-to-LF rewrite of the whole file; the file was already LF in the index
and stays LF, so the diff is additions only.
README and AGENTS now say `huginn-mind` is live and the other two crates
are stubs.
Builds: `cargo check -p huginn-mind --lib --tests` and `cargo test -p
huginn-mind --lib` (4 tests) pass. This commit carries dead-code warnings
on the store and cache accessors and the refusing test store; commits 2 and
3 consume them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
README carried the caveat that every crate is an empty stub filled by a
named Eureka cut; AGENTS described ownership, admission, CultNet
publication and a Qdrant dependency in the present tense. Add the same
caveat to the Body section and name Studio as the .cc inspection owner
in one sentence instead of a list of things Huginn does not do.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
README and AGENTS now state what Huginn is: the single writer of each
instance's mind as typed CultCache state, published over CultNet, depending
on Qdrant directly and refusing loudly without it. Generic .cc inspection is
CultCache Studio's. The docs name the Rust workspace, the F: drive body, and
the cut map that owns what each crate does next.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three stub crates following Odin's layout: huginn-mind and eureka-state as
libraries, huginn-daemon as the binary. Edition, license and publish are
workspace-owned. .gitignore gains target/ and drops the four entries that
named build outputs no longer produced here.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Huginn is the memory organ. Nothing consumed the @gamecult/huginn package,
the huginn CLI, or buildHuginnEveDsl; generic .cc inspection belongs to
CultCache Studio, and CultLib owns the inspector. Delete the package, its
tracked build output, and its toolchain config outright. No code moves.
Eve's huginn-cc-surface fixture is Eve's own lowering conformance material
and stays in Eve.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>