Commit graph Huginn/Cargo.lock
Author SHA1 Message Date
Meta Vens
6b619d6bbd Serve one mind over CultNet, and no rule beside it
`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>
2026-09-16 22:59:33 +02:00
Meta Vens
c4b2a0fa2e Give the mind a wire vocabulary and publish its two schemas
`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>
2026-09-16 22:52:53 +02:00
Meta Vens
5ba7b0c6e5 Refuse a ref that is no ref, rather than answering over it
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>
2026-09-16 22:41:17 +02:00
Meta Vens
3efc0dc82d Take the sequenced keys and make in force mean what it says
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>
2026-09-16 19:28:17 +02:00
Meta Vens
946758f666 Open a mind fail-closed over an owned redb store
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>
2026-09-16 17:04:23 +02:00
Meta Vens
f63c0f2d0b Add the empty Rust workspace
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>
2026-09-16 15:11:05 +02:00