Skip to content

feat(persona): inbox→admission bridge runner (#1121 PR-3)#1143

Merged
joelteply merged 1 commit into
canaryfrom
feat/persona-inbox-admission-wiring
May 14, 2026
Merged

feat(persona): inbox→admission bridge runner (#1121 PR-3)#1143
joelteply merged 1 commit into
canaryfrom
feat/persona-inbox-admission-wiring

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Summary

Closes the e2e admission loop on top of the storage types (#1129 PR-1) and gate machinery (#1134 PR-2) by giving callers ONE pure-Rust object — InboxAdmissionRunner — that wraps the recipe + config + trust mapping for a persona. Single method runner.admit(&inbox_msg, ...) returns the typed AdmissionDecision.

Card

continuum#1140.

What ships

  • InboxAdmissionRunner<R: IsMemorable> — generic per-persona runner. Convenience constructors: default_v1() (HeuristicIsMemorable + permissive config + permissive trust) and strict_v1() (same recipe + strict config + strict trust).
  • TrustMapping — configurable SenderTypeTrustState map. default_v1(): Human=IntragridMember, Persona/Agent=ApprovedPeer, System=SelfTrust. strict_v1(): demotes Persona+Agent to Authenticated for SOC governance.
  • Pure convertersinbox_message_to_candidate(msg, mapping), inbox_message_to_origin(msg) (always Chat-origin for v1), content_hash_sha256(s) (canonical "sha256:<hex>" format).

Scope (sliced)

  • ⏭️ PR-4: call-site integration with PersonaInbox::drain_frame() from the cognition path
  • ⏭️ PR-5+: ORM persistence path for admitted engrams + AIRC envelope origin converter (separate slice — AIRC events carry signature/proof material InboxMessage doesn't)

Validation

$ cargo test -p continuum-core --features metal,accelerate persona::inbox_admission
test result: ok. 16 passed; 0 failed; 0 ignored

16/16 unit tests covering:

  • content_hash_sha256 — canonical format + deterministic + distinguishing
  • TrustMapping — default + strict variants pin documented values
  • Pure converters — origin always Chat, candidate carries full provenance, trust varies by SenderType
  • Runner end-to-end — admit well-formed, drop short, drop duplicate, strict-admit System via SelfTrust, strict-reject Persona at trust boundary, custom recipe via generic, accessors expose configured state, seam-emission invariant across outcomes

npm run build:ts clean. cargo clippy clean (156 warnings vs baseline 163 — 7 BELOW). Hooks ran without --no-verify.

Test plan

  • CI label-pr / validate / WIP green
  • PR-4 (PersonaInbox call-site integration) cleanly composes on top
  • No regression in 408 → 424 persona test count

🤖 Generated with Claude Code

Closes the e2e admission loop on top of the storage types (PR-1, #1129)
and the gate machinery (PR-2, #1134) by giving callers ONE pure-Rust
object — `InboxAdmissionRunner` — that wraps the recipe + config +
trust mapping for a persona, exposing a single `admit(&inbox_msg, ...)`
method that returns the typed `AdmissionDecision`.

What ships:

- `InboxAdmissionRunner<R: IsMemorable>` — generic per-persona runner.
  Convenience constructors: `default_v1()` (HeuristicIsMemorable +
  permissive config + permissive trust mapping) and `strict_v1()` (same
  recipe + strict config + strict trust mapping).
- `TrustMapping` — configurable map from `SenderType` (Human/Persona/
  Agent/System) to `TrustState`. `default_v1()`: Human=IntragridMember,
  Persona/Agent=ApprovedPeer, System=SelfTrust. `strict_v1()`: demotes
  Persona+Agent to Authenticated for SOC governance contexts.
- `inbox_message_to_candidate(msg, mapping)` — pure converter.
  Synthesizes a `ChatMessageRef` origin (internal Continuum chat is
  Chat-origin, not AIRC; AIRC envelope path lands in PR-5 alongside
  the AIRC event converter that carries signature/proof material the
  inbox doesn't).
- `inbox_message_to_origin(msg)` — pure helper (always Chat for v1).
- `content_hash_sha256(s)` — canonical hash format `"sha256:<hex>"`
  used by the converter so dedup keys are consistent across all
  admission paths.

What this PR does NOT ship (deferred):

- Call-site integration with `PersonaInbox::drain_frame()` — PR-4
  adds the actual call from the cognition path.
- Engram persistence — admitted engrams come back from the runner;
  caller stores them. PR-5+ adds the ORM persistence path.
- AIRC envelope origin converter — separate slice; AIRC events carry
  signature/proof material `InboxMessage` doesn't.

Tests: 16/16 covering content_hash_sha256 (canonical format,
deterministic, distinguishing), TrustMapping (default + strict), pure
converters (origin always Chat, candidate carries full provenance,
trust varies by SenderType), runner end-to-end (admit well-formed,
drop short, drop duplicate, strict-admit System via SelfTrust, strict-
reject Persona at trust boundary, custom recipe via generic, accessors,
seam-emission invariant across outcomes).

Card: continuum#1140. Builds on continuum#1129 + continuum#1134
(both merged on canary).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@joelteply joelteply merged commit 897f176 into canary May 14, 2026
3 checks passed
@joelteply joelteply deleted the feat/persona-inbox-admission-wiring branch May 14, 2026 01:30
@joelteply
Copy link
Copy Markdown
Contributor Author

Substantive review (claude tab #2). Reviewed PR-2 (#1134) earlier; this is the natural PR-3 closing the inbox→admission loop on top of it.

Architecture — consistent with PR-1 + PR-2

InboxAdmissionRunner owns Recipe + Config + TrustMapping; oracles injected per admit() call. Same compositional shape as AdmissionGate::admit from PR-2 — state that lives across calls is owned, state that varies per call is injected. Generic over <R: IsMemorable> keeps custom-recipe paths zero-overhead (no dyn dispatch in the v1 sync hot path).

The split between inbox_message_to_candidate (pure conversion, public) and runner.admit() (compose-with-oracles) is right. PR-4 call-site integration can use the converter directly when it needs to reuse a candidate (e.g., for retry semantics) without owning a runner.

What I verified

  • Type alignment: InboxMessage fields (id, room_id, sender_id, sender_name, sender_type, content, timestamp) all map cleanly into ChatMessageRef (message_id, room_id, sender_id, posted_at_ms, content_hash). Spot-checked against persona/types.rs:44 + persona/engram.rs:259. ✓
  • Always-emit-seam invariant carried through: runner.admit() is a thin wrapper over AdmissionGate::admit; the forensic invariant from PR-2 holds. Test runner_emits_one_seam_per_call_across_outcomes enshrines it across Admit + Drop + Err. ✓
  • Send + Sync propagation: IsMemorable requires Send+Sync per PR-2; InboxAdmissionRunner<R> inherits these whenever R does. The "per-persona shareable across tokio tasks" claim in the docstring holds. ✓
  • Strict mode is real, not decoration: runner_strict_rejects_persona_messages_at_trust_boundary proves Authenticated < IntragridMember actually rejects at the trust boundary BEFORE the recipe runs. Defense-in-depth carries through. ✓
  • content_hash_sha256 matches between candidate + origin: the converter computes the hash twice (once for cand.origin.content_hash, once for cand.content_hash); the test candidate_carries_full_provenance_from_message pins the equality. Future refactor that splits these would break the dedup-key invariant — caught at test time. ✓
  • default_v1 / strict_v1 version-anchored: matches the _v1 discipline from B1, PR-1, PR-2. Future tweaks need a v2 alongside, not a silent change to v1. ✓

Things to consider (none blocking)

  1. content_hash_sha256 allocates 32 small Strings per call via format!("{:02x}", byte) in the inner loop. For a per-message hot path this adds up. Cheap fix:

    use std::fmt::Write;
    let mut hex = String::with_capacity(7 + 64);
    hex.push_str("sha256:");
    for byte in digest { write!(hex, "{:02x}", byte).unwrap(); }

    Saves the small per-byte allocs without changing the public API or the test's hex format check. Nit.

  2. No test asserts quarantine decision propagates through the runner. Sibling's feat(persona): IsMemorable Recipe + admission gate (#1121 PR-2) #1134 covers it on the gate directly (recipe_quarantine_decision_propagates). The runner is a thin wrapper so the invariant carries by construction, but a one-test sanity check is cheap insurance against a PR-4 wiring change that accidentally swallows quarantines. Add to PR-4's coverage if not here.

  3. recall_keys: vec![msg.sender_name.clone()] — v1 minimal recall key set. The test pins exactly one element, which is good — a future PR-5 enrichment (room name, mentioned users, topic) breaks the test and forces an explicit design conversation rather than silent expansion. Pattern works; just flagging that the minimalism is intentional.

  4. use super::admission::HeuristicIsMemorable; appears mid-file rather than with the other use super::admission::{...} block at top. Functionally fine in Rust. Style nit if there's a cleanup pass.

  5. AdmissionContext::new called per admit() — recreates the context each call. Cheap (bundles refs), but the hot path could benefit from one-time construction if the runner's lifetime allowed it. Premature opt for v1.

  6. Custom-recipe test (AlwaysAdmit) validates the generic shape with an outlier — exactly the project's "build outliers to prove the interface" methodology from CLAUDE.md. Particularly nice that it asserts the custom recipe overrides the heuristic length-drop, proving the heuristic is BYPASSED when a custom recipe takes over. Good. ✓

  7. No AIRC-origin path in this PR — converter explicitly always returns Chat origin (test inbox_origin_is_always_chat enshrines). PR description correctly defers to PR-5+. Worth noting that this PR's runner CANNOT process AIRC envelopes today; PR-4 call-site integration must only feed it inbox messages, not AIRC events.

What I particularly like

  • "What this catches" preambles on every test — same discipline I noted on feat(persona): IsMemorable Recipe + admission gate (#1121 PR-2) #1134. Reads as protect-this-contract rather than improve-coverage. Failures will be actionable.
  • clippy 156 vs baseline 163 — net negative; sibling's TS-ratchet pattern from test(generated): ts-rs export sync ratchet (#1132 PR-2) #1137 applied to Rust warnings. Reduces drift, not just stops it.
  • Public converter functionsinbox_message_to_candidate, content_hash_sha256, inbox_message_to_origin exposed for PR-4 reuse. Right encapsulation; the runner is a convenience for the common case, not a gate.
  • Convenience constructors named default_v1 / strict_v1 explicitly — when a future SOC-strict tightening lands as strict_v2, the v1 callers don't silently change posture.

Recommendation

LGTM to merge. Architecture is consistent with PR-1 + PR-2; tests are surgical with regression-anchor preambles; scope honestly delivers what PR-3 should. The 7 nits are polish or PR-5+ territory, none blocking.

The hot-path allocation in content_hash_sha256 (nit #1) is the only one worth doing before merge if quick — every admission does this once, and it's the most-called function in the new module. The others can wait.

Thanks for the consistent shape across the engram→admission lane (PR-1, PR-2, PR-3 all read like one design). Makes PR-4 + PR-5 review much cheaper.

joelteply added a commit that referenced this pull request May 14, 2026
…#1185)

Per task #71 — survey of every .json under src/system/recipes/.

Findings: the 28 split into 3 pipeline shapes (15 static-view, 10
single-persona-chat, 1 full multi-persona) plus 2 outliers (gan,
academy-training). The 10 single-persona-chat are missing 6 steps
that multi-persona-chat has (loop-risk, fast-respond, training-mode,
record-interaction, chat/send, cooldown). NO recipe currently
integrates the engram admission gate shipped on canary in #1129/
#1134/#1143/#1155/#1163.

5 identified gaps with concrete next-sprint cards:
1. Engram integration in Shape B + C (11 recipes need cognition/
   admit-inbox-message + cognition/recall-engrams)
2. Resolve academy-training half-migrated state
3. Document gan orphan intent
4. Shape B → Shape C decision (or shared inheritance)
5. version field discipline across all 28

Pure docs PR. Output at docs/cognition/RECIPE-AUDIT-2026-05-14.md.

Closes #71.

Co-authored-by: Test <test@test.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant