Documentation Index
Fetch the complete documentation index at: https://docs.rkat.ai/llms.txt
Use this file to discover all available pages before exploring further.
ADR: Formal Seam Closure
Status: Accepted Date: 2026-03-20Problem
Meerkat’s RMAT model proves individual machines and declared machine-to-machine routes, but does not prove that machine effects requiring owner feedback are fully realized, or that shell-observed outcomes match machine truth. Three specific gaps exist:-
Comms drain seam: Shell code directly mutates
comms_drain_activeoutside the machine authority path. The machine emitsSpawnDrainTask/AbortDrainTaskeffects, but their realization and feedback flow is not protocol-constrained. -
Turn barrier seam:
MeerkatMachinetracks operation completion internally, but the barrier-satisfaction signal (all pending ops complete) previously flowed through ad hoc channels rather than through authority truth. The classification of ops as barrier-blocking vs detached is now machine-owned. -
Terminal outcome alignment:
ExtractionExhaustedtransitions toCompletedphase inMeerkatMachinebut semantically represents a validation failure. Shell code independently decides how to surface outcomes, creating divergence risk between machine truth and API responses.
Decision
Extend the existing composition ontology with typed handoff protocols, obligation tracking, and generated protocol helpers. No second actor system, no second effect transport, no monolithic composition machine.Locked Decisions
- No second actor system. Owner participants extend existing composition
actors with
ActorKind. - No second effect transport. “Owned handoff” is a realization facet
(
handoff_protocol: Option<String>) on existingEffectDispositionRule, not a fourth disposition variant. - Step 10 is real codegen, scoped to seam protocol helpers only. Authorities stay handwritten.
ExtractionExhaustedtransitions to existingFailedphase with newTurnTerminalOutcome::StructuredOutputValidationFailed. No new terminal phase.- Turn-boundary suppression hot path stays lock-free (atomic-read cost).
Proof Model
Three claim types classify every acceptance criterion:Structural Coverage
Protocol exists, realization sites are declared, CI enforces no missing path. Structural claims assert completeness of the model: every effect that crosses an ownership boundary has a declared protocol, every protocol has realization sites, and the closed-world composition validation rejects unprotocolized handoffs. Structural claims are verified by:- Schema validation (
CompositionSchema::validate()) - RMAT audit hard-error rules (
xtask rmat-audit) - CI gate (
make ci)
Safety
No invalid state or transition is reachable. Safety claims assert that the system cannot enter a state where machine truth and runtime behavior diverge. Safety is verified by:- Machine-level TLC model checking (per-machine invariants)
- Composition-level TLC model checking (cross-machine invariants)
- Obligation closure invariants: no open obligations on terminal phases, no feedback without a prior obligation
Liveness
Under explicit fairness assumptions, required feedback eventually occurs. Liveness claims are scoped and conditional. Each liveness claim states its fairness assumption explicitly. Liveness is verified by:- TLC weak/strong fairness annotations on owner-actor processes
- Explicit witness scenarios that exercise the feedback path
Tranche Boundaries
Tranche 1 Proves
Structural coverage for all handoff-annotated effects:- Every effect with
handoff_protocolhas a matchingEffectHandoffProtocolin its composition - Every protocol has at least one realization site
- Protocol feedback enters only through generated helpers
- Terminal mapping uses generated classification helpers
- Comms drain: obligation closure (
NoOpenObligationsOnTerminal,NoFeedbackWithoutObligation), no direct shell mutation ofcomms_drain_activeoutside generated protocol path - Barrier:
ToolCallsResolvedrequiresbarrier_satisfied == true, onlyBarrier-classified ops enter the wait set,Detachedops do not block - Terminal alignment:
ExtractionExhausted->Failedphase withRunFailedeffect, exhaustive generated terminal classifier with no default arm
- Comms drain spawn protocol: “Under task-scheduling fairness, every
SpawnDrainTaskobligation eventually receivesTaskSpawnedorTaskExitedfeedback.”
Tranche 1 Does NOT Prove
- Cross-machine op-barrier correctness/liveness (OpsLifecycle -> TurnExecution barrier satisfaction is wired but not composition-proven)
- Liveness of barrier satisfaction (only safety: barrier cannot be bypassed)
- Liveness of abort acknowledgment (only safety: no feedback without obligation)
Tranche 2 Proves
Structural + safety + scoped liveness for TurnExecution <-> OpsLifecycle barrier composition:- OpsLifecycle
WaitAllSatisfiedeffect routes to TurnExecutionOpsBarrierSatisfiedinput through a declared handoff protocol - Authority-backed barrier satisfaction:
wait_all()routes through authorityWaitAllinput, authority emitsWaitAllSatisfied, shell waits for the authority-emitted effect - Composition-level obligation closure for the barrier seam
- Decision gate: if criteria met, extract
TurnOpBarrierMachineas a separate machine; otherwise keep barrier as enriched TurnExecution input wired to authority-backed OpsLifecycle satisfaction
Execution Order
The dependency line is strict:- Formal layer first: lock the proof model, inventory seam candidates, extend the current actor/effect ontology, add protocol-aware validation and TLA generation, add generated Rust seam helpers, and upgrade RMAT/CI
- Runtime seam conversion second: protocolize comms drain, replace raw operation IDs with typed async-op references, move ops waiting onto the authority-owned wait lifecycle, align terminal outcomes with surfaced failure classes, and preserve producer-derived feedback fields across live handoffs
- Closeout last: backfill the seam model onto the remaining async boundaries, remove legacy realization paths, tighten release gates, and run the final repo-wide seam audit
Ontology Extension — Type Sketches
These are the exact type-level additions tomeerkat-machine-schema, ready for
implementation in steps 5-8. All additions extend existing types — nothing is
forked or duplicated.
Step 5: handoff_protocol on EffectDispositionRule
File: meerkat-machine-schema/src/machine.rs
Current shape:
MachineSchema::validate()):
- If
handoff_protocol.is_some()anddispositionisRouted { .. }, emit error — routed effects don’t need handoff protocols, they have routes. - No other validation at the machine level; the composition validates protocol existence.
EffectDispositionRule constructors in catalog files
get handoff_protocol: None. The local disposition() helper in each catalog
file is updated to pass None. No behavioral change.
Step 6: ActorSchema and ActorKind on CompositionSchema
File: meerkat-machine-schema/src/composition.rs
New types:
CompositionSchema:
CompositionSchema::validate()):
ActorSchemanames are unique (reuse the existingunique_names()helper).- Every
MachineInstance.actorreferences anActorSchemawithkind == Machine. - Every actor referenced by
actor_prioritiesorscheduler_rulesmust exist inactors(this replaces the current implicit actor-id deduction fromMachineInstance.actorfields). ActorKind::Owneractors may participate inactor_prioritiesandscheduler_rules(they need scheduling semantics for fairness modeling).
validate() deduces the
actor set from machines.iter().map(|m| m.actor). After this change, it deduces
actors from actors.iter().map(|a| a.name). The MachineInstance.actor field
still exists and must reference a Machine-kind actor. This means existing
compositions must add actors: vec![...] declarations.
Migration: All 8 canonical compositions get actors declarations matching
their current implicit actor sets, all with ActorKind::Machine.
Step 7: EffectHandoffProtocol on CompositionSchema
File: meerkat-machine-schema/src/composition.rs
New types:
CompositionSchema:
validate() and validate_against()):
- Protocol names are unique within the composition.
producer_instancereferences a validMachineInstance.instance_id.effect_variantexists on the producer machine’seffectsenum (invalidate_against()where schemas are available).- The producer machine’s
EffectDispositionRulefor thateffect_varianthashandoff_protocol == Some(protocol.name)(invalidate_against()). realizing_actorreferences anActorSchemawithkind == Owner.correlation_fieldsare valid field names on the effect variant (invalidate_against()).- Each
FeedbackInputRef.machine_instancereferences a validMachineInstance. - Each
FeedbackInputRef.input_variantexists on the target machine’sinputsenum (invalidate_against()). TerminalClosurerequires the producer machine to have at least one terminal phase (invalidate_against()).
handoff_protocols: vec![]
initially (no protocols until step 12).
Step 8: Closed-World Obligation Validation
File:meerkat-machine-schema/src/composition.rs
New invariant kind:
validate_against(), within the
if self.closed_world { .. } block):
For every MachineInstance in the composition, for every
EffectDispositionRule on that machine’s schema where
handoff_protocol.is_some():
- There must exist an
EffectHandoffProtocolinself.handoff_protocolswhere:protocol.producer_instance == machine_instance.instance_idprotocol.effect_variant == rule.effect_variantprotocol.name == rule.handoff_protocol.unwrap()
- If missing, emit
CompositionSchemaError::MissingHandoffProtocol { .. }.
- Each
FeedbackInputRef.input_variantfield types must be compatible with the correlation fields (checked invalidate_against()).
Concrete Example: Comms Drain (Step 12)
After steps 5-8 are implemented, step 12 applies the ontology to the comms drain seam: Incomms_drain_lifecycle.rs — disposition changes:
compositions.rs — for every composition containing CommsDrainLifecycle:
Re-exports
File:meerkat-machine-schema/src/lib.rs
New types to re-export after steps 5-7:
New Error Variants
MachineSchemaError (step 5):
CompositionSchemaError (steps 6-8):
Generated Protocol Helpers
xtask protocol-codegen generates typed helpers per protocol:
- Obligation record struct (captures correlation fields)
- Executor function (calls
authority.apply(), returns effects + obligation token) - Feedback submitter (consumes obligation token, calls
authority.apply()with feedback input) - Terminal classification helper (exhaustive match, no default arm)
// @generated markers. RMAT audit enforces that
protocol feedback enters only through generated helpers.
Barrier Schema Changes — Type Sketches (Steps 17-19)
These are the schema-level additions toTurnExecutionMachine for barrier-aware
ops. The design replaces raw OperationId sequences with typed async-op
references that carry a wait policy, adds a barrier-satisfaction input, and gates
ToolCallsResolved on barrier clearance.
Step 17: Replace raw op IDs with AsyncOpRef
File: meerkat-machine-schema/src/catalog/turn_execution.rs
Current state field (line ~48):
AsyncOpRef is a named type carrying:
operation_id: OperationId— the raw op IDwait_policy: WaitPolicy—BarrierorDetached
WaitPolicy is a two-variant enum:
Barrier— op blocksToolCallsResolveduntil satisfiedDetached— op runs independently, does not block the turn
RegisterPendingOps field changes from
operation_ids: Seq<OperationId> to op_refs: Seq<AsyncOpRef>.
Derived field: barrier_op_ids — a computed projection of
pending_op_refs.filter(|r| r.wait_policy == Barrier).map(|r| r.operation_id).
This is a read-only view used by guards, not a mutable state field.
Init: pending_op_refs initializes to None.
Step 18: Formalize barrier-classification authority
The classification of an operation asBarrier vs Detached is determined at
RegisterPendingOps time by the shell, but it is represented explicitly in the
typed AsyncOpRef data crossing the dispatcher boundary and enforced by the
turn machine. Classification is immutable once registered; the machine owns the
barrier gate and the runtime waits against authority-owned BeginWaitAll /
WaitAllSatisfied state rather than raw shell watcher timing.
RMAT audit rule: RegisterPendingOps must be the sole entry point for
pending_op_refs mutation. No shell code may directly modify pending_op_refs
outside the machine authority.
Step 19: Extend TurnExecution for barrier membership
New state field:true (no barrier = satisfied). Set to false when
RegisterPendingOps contains any Barrier-policy ops. Set back to true
when OpsBarrierSatisfied is received.
New input:
barrier_token is a correlation field that ties the satisfaction signal to
the specific barrier set. This prevents stale satisfaction from a previous
barrier from clearing a new one.
New guard on ToolCallsResolved:
ToolCallsResolved cannot fire while
barrier_satisfied == false. The machine enforces this — the shell cannot
bypass it.
New transition: OpsBarrierSatisfied from WaitingForOps:
RegisterPendingOps transition: Updates must set
barrier_satisfied to false when any op has wait_policy == Barrier:
Expr::ContainsWhere may require extending the Expr enum if the
existing expression system cannot represent this. If so, a separate boolean
field has_barrier_ops may be carried alongside op_refs, but the machine
must still enforce the gate and RMAT must verify consistency with the typed
AsyncOpRef content.
Composition wiring (Step 28)
OpsBarrierSatisfied is not a direct routed effect. OpsLifecycle emits the
authority-owned WaitAllSatisfied effect, the handoff protocol captures the
obligation, and owner feedback submits TurnExecution.OpsBarrierSatisfied
using explicit field bindings (operation_ids from the obligation, run_id
from owner context).
Shell Boundary Rule
Shell code may:- Capture evidence (observe runtime lifecycle facts)
- Execute mechanics (spawn tasks, manage channels, route messages)
- Classify raw inputs into typed machine inputs
- Perform observation-to-feedback mapping outside protocol constraints
- Directly mutate protected state fields outside generated protocol helpers
- Implement handwritten terminal-outcome classification that duplicates machine truth
- RMAT audit structural seam rules (hard errors in CI)
- Protected field rules in
xtask/src/rmat_policy.rs - Generated protocol helpers that are the only legal path for feedback submission
Verification Strategy
After each phase gate:Consequences
- Every async ownership boundary gains formal protocol coverage
- Shell code is mechanically constrained from inventing parallel semantic state
- Terminal outcome classification is generated and exhaustive — no divergence between machine truth and surface responses
- Owner actors are first-class composition participants, not implicit runtime behavior
- The proof model is closed end-to-end for the current seam inventory rather than staged behind later phases
- CI enforces structural coverage — new handoff-annotated effects without protocols fail the build
