Commit graph Huginn/Cargo.lock
Author SHA1 Message Date
Meta Vens
78f8c6ee2f Cut 11a: the index write side
huginn-mind::index owns what of a document is searchable: index_text is a
pure function per kind, Mind::index_entries builds the entries with the
writing receipt's ordinal, INDEX_TEXT_VERSION says when the rule changed.

huginn-daemon::index owns the projection. A Projector reconciles the mind
against the collection at startup (the only place a point is judged
missing: set difference on point ids, rebuild when the model name, digest,
dimensions or text version differ, refuse a collection that is not this
instance's) and one worker thread embeds and upserts in batches of 32,
retrying with a 30 s to 10 min backoff. IndexSink::committed builds entries
and leaves them in an inbox; admission never waits on the embedder or on
Qdrant. Whoami reports the worker's health as IndexStatus. The Ollama and
Qdrant adapters are the only files holding HTTP or JSON (ureq 3, no TLS).
The daemon now requires --qdrant-url, --ollama-url and --embedding-model;
NoIndex leaves the binary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 08:05:26 +02:00
Meta Vens
eb57bdba40 RS-3 2: the read side is CultNet's selection over the organ's rows
The four hand-written filters, the order and the cap are deleted:
PipelineQuery, PipelineQueryPage, QUERY_LIMIT_MAX, Reader::matches, ordered,
repo_matches. Mind::query(selection, semantic) validates through
cultnet_rs::validate, closes the values door (rows.rs refuse_values, S6),
takes the snapshot from the cursor's own asOf (an asOf beyond the head is
CursorInvalid), builds Reader::at(as_of) so every derivation runs over the
documents admitted at or before it, and evaluates through cultnet_rs::select.
The page carries matched, asOf, the next cursor, headers or whole views by
projection, and typed edges when the selection hops.

rows.rs is the organ's vocabulary: SelectionRow (a Row), Vocabulary (a
RowSet: thirteen schemas, eleven aliases, fifteen roles) and the value door.
huginn-mind links cultnet-rs at the rev the daemon pins. Selection refusals
map totally onto SelectionInvalid, CursorInvalid or Unavailable. The wire's
Query carries CultLib's selection by $ref, and both published schemas are
regenerated. The relations RS-2 unpinned are re-pinned as selections.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 05:56:37 +02:00
Meta Vens
e770fb9f21 BP-2: move Huginn to CultLib 6251cbc and the RS-L leaf 4891f2e5
One pin move: huginn-mind pins cultcache-rs at 6251cbc and the leaf at
4891f2e5; huginn-daemon pins cultnet-rs at 6251cbc. cargo tree shows one
cultcache-rs and one cultnet-rs per crate.

Adopting RS-L, which the new leaf carries:
- Campaign, cut-spec, question and ruling fixtures carry a Title; the
  question and ruling fixtures gain the new required title field.
- The epoch literals in daemon and envelope tests move v1 -> v2. A store
  written at an earlier epoch is refused at open (ForeignEpoch); no key
  migration.
- Derived stewardship, hand-off and resolution keys are now the lowercased
  OrgRepo::identity(); test key literals follow.
- The two published wire schemas are regenerated (Title, OrgRepo bound and
  description); the byte-for-byte pin test passes.

CultLib 6251cbc gives CultNetMessage::Error code and details; the four
construction sites and one pattern in huginn-daemon take None and `..`. No
behaviour change: None is skipped on the wire. mind 70 and daemon 18 pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:56:03 +02:00
Meta Vens
f7e0d020ed Move the epiphany-pipeline pin to the leaf commit carrying S5 and S8
Epiphany codex/eureka-pipeline-state b3ae787b extends the leaf's door
test with the 64-byte whole-name and per-label bounds and NUL, adds
three matching mutation entries, and corrects the entries file header
that still named the deleted repo-local harness path. One cultcache-rs
throughout: epiphany-pipeline, cultnet-rs and huginn-mind all still
resolve to CultLib a0813c6e.

Verified: cargo test --workspace (huginn-mind 65/65, huginn-daemon
20/20, eureka-state 0/0), cargo tree -i cultcache-rs shows one node.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 08:21:56 +02:00
Meta Vens
cc71a569dc Fourth fix batch: fill N1 in its owner, and correct two ledger claims
N1 (medium-low). Moves the epiphany-pipeline pin to the leaf's new commit
(9d3a3efd), which opens Slug::validate_slug. Huginn's own cultcache-rs pin
is untouched; cargo tree -d still carries exactly one cultcache-rs.

Mind::require_instance now validates the declared name's grammar before
comparing identity, so admission's A1 and every read refuse a bad name
identically, through one door. Mind::open and open_with validate the
declared instance before path_for ever runs, so a Slug carrying `..` and a
path separator (Soul's outer/inner probe) is refused before any store is
created, not after it has already escaped the state root.

require_grammatical_instance and its daemon-side branch in daemon.rs are
deleted: the throwaway PipelineInstance wrapper it built is no longer the
only door onto the leaf's grammar. The refusal now names the field a
caller actually held the declared name under (declared, or instance) and
the whole declared name, not the old wrapper's instance.instance field or
a single failing label.

Adds a leaf-escape regression test pinning Soul's probe and asserting no
store is created, and a fullwidth-instance test showing Admit and Query
refuse identically. Updates the cut10 grammar-fold mutant (S3wide) to the
new function shape and the affected daemon test to the new field name.

N5 and N6, prose only. The cut10 entries gain a note by S1 and S1d: neither
mutant "dies on all three" faults its shared fixture carries, contrary to
an earlier claim; S1d exempts the orphan fault by construction and is
killed by the undecodable-document block instead, the next one the test
body reaches. The cut9 header's self-contradiction at lines 18-25 is
resolved (nothing in the suite exercises the door, not "nothing reaches
it"), its self-reference now points at store.rs:18-19, and its line-1
range is corrected to V1-V24L.

All three suites rerun clean after the pin move: cut8 67/67, cut9 38/38,
cut10 65/65, every M0 green, restores hash-verified. cargo test --workspace:
huginn-mind 61/61 (was 60), huginn-daemon 18/18 (was 17).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:14:34 +02:00
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