feat(soa+ogar): cycle-aware write contract + DO-arm ActionDef/ActionInvocation + OGAR consumer API#538
Conversation
… sanity harness
Inc 2 of the cycle-aware write contract (S2.5), default-OFF behind
mailbox-thoughtspace; the substrate-sanity harness runs on the default build.
write deinterlacing (mailbox_soa.rs):
- write_row(row, cycle, &WriteCell) -> WriteOutcome{Accepted,Stale,Future},
the ONE cycle-aware mutator. Wrap-aware gate (current_cycle.wrapping_sub +
half-range) so a post-u32-wrap straggler is Stale, not mis-read Future.
- last_write_cycle:[u32;N] (2nd stamp, separate from last_active_cycle
consumption stamp; cleared in reset_row). stale_write_count telemetry
(drop-with-telemetry = Strict disposition). Infallible WriteOutcome
(ownership compile-proven; stale/future is an outcome, not a failure).
- WriteCell field-presence staging (only Some fields applied).
shim wiring (backing.rs):
- BackingStoreWrite::write_row routes the Mailbox arm through the gate;
the Singleton(&BindSpace) arm is cycle-blind BY CONSTRUCTION (no
current_cycle; gate is a Mailbox-only guarantee until W7 deletes BindSpace).
kanban / planner / SurrealQL cycle-awareness (contract):
- KanbanMove::cycle() accessor over witness_chain_position (already stamped
= current_cycle in both real recording paths), so the move + the planner
that consumes it + a ExecTarget::SurrealQl read-as-of are cycle-aware off
ONE source of truth, without growing the <=16B airgap baton. soa_view
FakeSoa stamps witness=cycle for consistency. No inter-mailbox emission
(the mailbox writes to itself in place, #477 three-tier model).
substrate sanity harness (tests/substrate_sanity.rs, 8 tests):
- NaN: qualia f32 projection finite over full i4 range; energy finite
through consume.
- Tautology: gate discriminates current/stale/future + is wrap-aware;
field-presence honoured; distinct writes stay distinct; qualia projection
discriminates; zero-edge fabricates no energy.
Verified: contract + driver lib/doc tests green; harness 8/8; w2_differential
4/4 under mailbox-thoughtspace.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
The DO arm of the OGAR IR — the Perdurant complement to the Endurant field-set (class_view/codegen_manifest). Both the probe (runtime-archaeologist) and the merged cross-repo PR survey agreed this was the one missing wire: the THINK arm (classid->ClassView, has_function->MethodSig) is converged + merged across ruff/OGAR/lance-graph/openproject/tesseract, but the DO-arm ActionInvocation/ActionDef type was ABSENT. action.rs (new module): - ActionDef (static, const-constructible like MethodSig): predicate (=has_function method), object_class (classid), exec (ExecTarget incl SurrealQl), guard (KausalSpec::StateGuard), required_role (RBAC), overrides (OGAR classid->ClassView inheritance). - ClassActions + actions_for (zero-fallback) — action-axis sibling of ClassMethods/methods_for. - effective_actions(parent, child) — OGAR inheritance: a class's DO surface is its parents' + its own, child overrides parent by predicate. This is the "inherit the adapters/interfaces/core libs to make DO work" mechanism. - ActionInvocation (dynamic, Copy): lifecycle (Pending->Committed/Failed/ Cancelled), S2.5 cycle stamp, idempotency/trace keys, HLC emit stamp. - ActionInvocation::commit gates the egress: RBAC FIRST (auth::ActorContext must hold required_role or be admin -> else Failed), THEN MUL impact assessment (mul::GateDecision: Flow->Committed+stamped, Hold->stays Pending/escalate, Block->Cancelled). This IS the "commit to the external consumer (odoo/openproject/woa/tesseract) after the cycle decides sound" egress. Terminal states sticky. 5 tests green: const manifest + zero-fallback; OGAR inheritance override; RBAC-then-MUL-Flow commit; unauthorized fails before impact; Hold stays Pending / Block cancels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
- docs/OGAR_CONSUMER_API.md — the consumer contract for odoo-rs / openproject-nexgen-rs / woa-rs / tesseract-rs: THINK arm (classid-> ClassView, MethodSig, ValueTenant, EdgeBlock) + DO arm (ActionDef/ ActionInvocation, effective_actions inheritance, RBAC+MUL commit gate, UnifiedStep/ExecTarget dispatch), the harvest->generate recipe, the Core-First iron rules, and the minimal AR->DO existence proof. - LATEST_STATE.md Contract Inventory: D-DO-ARM-1 action module entry (board hygiene — contract type added in same commit). - cargo fmt wrap of two long lines (action.rs, backing.rs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
|
Warning Review limit reached
More reviews will be available in 50 minutes and 29 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds the OGAR "DO arm" action IR to ChangesOGAR DO Arm — Action IR Contract
MailboxSoA Cycle-Aware Write Infrastructure
Sequence Diagram(s)sequenceDiagram
participant Consumer
participant ActionInvocation
participant ActorContext
participant GateDecision
participant UnifiedStep
Consumer->>ActionInvocation: pending(classid, target_guid, action_predicate)
Consumer->>ActionInvocation: commit(def, actor, gate_decision, guard_val, now_millis)
ActionInvocation->>ActorContext: check admin || required_role
ActorContext-->>ActionInvocation: authorized / denied (→ Cancelled)
ActionInvocation->>ActionInvocation: optional StateGuard field/value check (→ Cancelled on mismatch)
ActionInvocation->>GateDecision: evaluate Flow / Hold / Block
GateDecision-->>ActionInvocation: Committed (stamp emitted_at) / Pending / Cancelled
ActionInvocation-->>Consumer: ActionState
Consumer->>UnifiedStep: dispatch(ExecTarget) if Committed
sequenceDiagram
participant Planner
participant BackingStoreWrite
participant MailboxSoA
Planner->>BackingStoreWrite: write_row(row, cycle, &WriteCell)
alt mailbox-thoughtspace feature enabled
BackingStoreWrite->>MailboxSoA: write_row(row, cycle, cell)
MailboxSoA->>MailboxSoA: wrap-aware cycle compare vs current_cycle
alt cycle == current_cycle
MailboxSoA-->>BackingStoreWrite: WriteOutcome::Accepted (fields applied, last_write_cycle stamped)
else cycle < current_cycle
MailboxSoA-->>BackingStoreWrite: WriteOutcome::Stale (stale_write_count++)
else cycle > current_cycle
MailboxSoA-->>BackingStoreWrite: WriteOutcome::Future (no mutation)
end
else singleton arm
BackingStoreWrite->>BackingStoreWrite: apply present cell fields via per-field setters
BackingStoreWrite-->>Planner: WriteOutcome::Accepted
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9292652009
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let Some(role) = def.required_role { | ||
| let authorized = actor.is_admin() || actor.roles.iter().any(|r| r == role); |
There was a problem hiding this comment.
Reject mismatched action definitions before RBAC
When commit is called with an ActionDef that does not match this invocation's object_class/predicate, the method still authorizes against that unrelated definition's required_role. With a registry containing a guarded action_confirm and an unguarded action_cancel, a caller can build an action_confirm invocation, pass the cancel definition, and reach Committed under GateDecision::Flow without the confirm role. Since these fields are documented as identifying the realized ActionDef, validate the definition match before applying RBAC/MUL.
Useful? React with 👍 / 👎.
| self.state = match impact { | ||
| GateDecision::Flow => { | ||
| self.emitted_at_millis = Some(now_millis); | ||
| ActionState::Committed |
There was a problem hiding this comment.
Enforce state guards before committing actions
For actions with guard: Some(StateGuard { field: "state", value: "draft" }), commit has no object-state input and proceeds solely from RBAC plus MUL, so a guarded action can be committed even when the target record is in a non-eligible state such as sent or cancelled. The new API docs and ActionDef comments say guards mean the action fires only when field == value; the egress should either evaluate the guard before Flow -> Committed or refuse guarded actions until a checked state is supplied.
Useful? React with 👍 / 👎.
#538) Codex #538 review: - P1: commit authorized against whatever ActionDef was passed without checking it identified the invocation -> passing the unguarded, no-role action_cancel def to an action_confirm invocation reached Committed under Flow without the confirm role. Fix: reject a def whose object_class/predicate mismatch BEFORE RBAC/guard/MUL -> Failed. - P2: StateGuard (field==value) was documented as "fire only when field==value" but commit had no object-state input. Fix: commit takes guard_field_value: Option<&str> (the instance's current value of the guarded field, caller-supplied -- Core holds no object state); a guard present + unsatisfied/unknown -> Cancelled (not eligible). Order is now: def-match -> RBAC -> state guard -> MUL impact. Docs (OGAR_CONSUMER_API.md) updated to the 5-arg signature. +2 tests (mismatched-def rejected; guarded action refused in wrong/unknown state); action module 7/7 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
… dedup, doc gates 8-agent 5+3 on the DO arm. iron-rule: YIELDS-ALL (clean). Fixes: - CATCH-CRITICAL (baton-handoff): ActionInvocation.object_instance was u32 — cannot address a node outside the default basin (identity is u24 + panicking assert; basin needs family). A consumer reconstructing the target would panic (forbidden in woa-rs/arcgis) or silently mutate the WRONG external record. -> object_instance is now a full NodeGuid (classid + HHTL + family + identity); pending() debug_asserts guid.classid() == object_class. - P1 (brutally-honest): ActionInvocation was Copy -> committing a copy silently dropped the state mutation + defeated idempotency. -> dropped Copy, kept Clone. - P1 (brutally-honest): effective_actions didn't dedup WITHIN a slice -> duplicate harvest predicates double-dispatched. -> dedup + debug_assert has_duplicate_predicate. - P1 (brutally-honest): documented that Hold->Pending re-commit idempotency/replay dedup is the caller/dispatch-layer's job (commit is policy-only). - convergence nit: added ActionDef::is_override() for MethodSig parity. - baton P1 docs: required_role/exec are consumer-private (not portable); predicates must be normalized at harvest (cross-consumer identity is not a contract guarantee). action module 7/7 green; clippy -D warnings + fmt clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
…t feature-wired Operator pointed to surrealdb/core/src/kvs/lance (real code, not a scaffold): the backend MODULE is implemented (schema/tx_buffer/timeline/ background_optimizer/tests = the 19-method Transactable scaffold), but it is not yet feature-wired (no kv-lance in core Cargo.toml, not in kvs/mod.rs, no lance dep) and the Lance-integration TODOs remain. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
…t-commit) Capture the "lite unified" bet as a CONJECTURE plan to test behind a feature gate (NOT a default-build change): collapse the two query engines (datafusion + SurrealQL) + two stores (lance + rocksdb) to ONE store (lance-KV) + ONE primary query surface (SurrealQL/AR-API); datafusion feature-gated for analytical SQL; rocksdb dropped; DO-arm ExecTarget::SurrealQl becomes the primary exec path. Win for graph/AR/cognitive (Cypher->SurrealQL is a better lowering than Cypher->datafusion-SQL); downgrade for analytical SQL (datafusion kept feature-gated). Falsifier: datafusion_planner query-shape coverage in SurrealQL. Blockers: kv-lance not feature-wired, polyglot->SurrealQL lowering missing. Gated on a convergence+cross-domain+truth-architect probe before any promotion. Board: INTEGRATION_PLANS prepended. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
…ng frame the click epiphanies reference E-AR-DO-WIRING (ruff static + ARM dynamic meet at SPO; AR Class splits along DOLCE into THINK+DO; consumers land off the harvest) was stranded on a stale branch (claude/ar-do-wiring-epiphany) cut from a pre-#538 main — its diff against current main showed #538's files as deletions. The only real content was this single docs-only epiphany (+53 on EPIPHANIES.md), which the particle/wave click epiphanies in this PR cross-reference 5 times. Placed after E-OGAR-ROUTER-ENCODER (chronological: it is the prior-art frame the router/encoder + Excel + chess + perturbation entries build on). Makes this PR's arc self-contained — no dangling refs to an abandoned branch. The stale branch can be deleted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
What
Implements the S2.5 cycle-aware write contract + the OGAR DO arm (the one wire both the 4-agent
sale_orderprobe and the merged cross-repo PR survey agreed was missing), with a consumer-facing OGAR API doc. Stacked on #537 (the plans this implements); base retargets tomainwhen #537 merges.All
mailbox-thoughtspacework is default-OFF; the substrate-sanity harness runs on the default build. Green: contract + driver lib/doc tests, harness 8/8,w2_differential4/4 under the feature.1. Write deinterlacing (
mailbox_soa.rs)write_row(row, cycle, &WriteCell) -> WriteOutcome{Accepted,Stale,Future}— the one cycle-aware mutator. Wrap-aware gate (current_cycle.wrapping_sub+ half-range) so a post-u32-wrap straggler isStale, not mis-readFuture.last_write_cycle: [u32; N](second stamp, separate from thelast_active_cycleconsumption stamp; cleared inreset_row).stale_write_counttelemetry. InfallibleWriteOutcome(ownership compile-proven — stale/future is an outcome, not a failure).WriteCellfield-presence staging (onlySomefields applied).BackingStoreWrite::write_rowroutes the Mailbox arm through the gate; theSingleton(&BindSpace)arm is cycle-blind by construction (nocurrent_cycle; the gate is a Mailbox-only guarantee until W7 deletesBindSpace).2. Kanban / planner / SurrealQL cycle-awareness
KanbanMove::cycle()accessor over the already-stampedwitness_chain_position— the move, the planner that consumes it, and aExecTarget::SurrealQlread-as-of are cycle-aware off one source of truth, without growing the ≤16 B baton.3. NaN / tautology harness (
tests/substrate_sanity.rs, 8 tests)Verdict: the substrate is NaN-free and non-tautological on every surface tested — qualia f32 projection finite over the full i4 range; energy finite through consume; the write gate genuinely discriminates current/stale/future and is wrap-aware; field-presence honored; distinct writes stay distinct; zero-edge fabricates no energy.
4. The OGAR DO arm (
lance-graph-contract::action)The Perdurant complement of the Endurant field-set:
ActionDef(static,const-constructible likeMethodSig):predicate(= harvestedhas_function),object_class(classid),exec(ExecTargetinclSurrealQl),guard(StateGuard),required_role(RBAC),overrides(OGARclassid→ClassViewinheritance).ClassActions+actions_for(zero-fallback);effective_actions(parent, child)= OGAR inheritance (child overrides parent by predicate).ActionInvocation(dynamic,Copy): lifecyclePending→Committed|Failed|Cancelled, S2.5cyclestamp, idempotency/trace keys, HLCemitted_at_millis.commitgate (the egress): RBAC first (auth::ActorContextmust holdrequired_roleor be admin → elseFailed), then MUL impact (mul::GateDecision:Flow→Committed+stamped,Hold→Pending/escalate,Block→Cancelled). This is "commit to the external consumer (odoo/openproject/woa/tesseract) after the cycle decides sound." Dispatched viaUnifiedStep/ExecTarget, never a per-crate endpoint.5.
docs/OGAR_CONSUMER_API.mdThe consumer contract for odoo-rs / openproject-nexgen-rs / woa-rs / tesseract-rs: THINK + DO surfaces, the harvest→generate recipe, the Core-First iron rules, and the minimal AR→DO existence proof.
Board hygiene
LATEST_STATE.mdContract Inventory updated (D-DO-ARM-1) in the same commit as the type.Honest remaining (not in this PR)
ClassActionsfrom each model'shas_function(codegen, downstream, likeClassMethods).ClassView::{compute_dag, constraints}extension (the named Core gap for computed-field recompute/validation dispatch).🤖 Generated with Claude Code
https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
Generated by Claude Code
Summary by CodeRabbit
New Features
Tests
Documentation