callcenter: D-ODOO-SAV-4 odoo-savant reasoners + audit/Policy fixes#420
Conversation
The jsonl-feature build broke: JsonlAuditSink::new returns Result<_, AuditError>, but with_jsonl_audit was typed io::Result<Self> and no From<AuditError> for io::Error exists, so the `?` couldn't convert (E0277). The default-feature check skips this path; CI builds the feature and surfaced it. Return the honest error type (Result<Self, AuditError>) rather than adding a crate-wide AuditError -> io::Error coercion. Zero callers depend on the old io::Result signature (only a doc-comment mention), so this breaks nothing. cargo check/test -p lance-graph-callcenter --features jsonl clean (137 tests).
…erialization sites Two PREPEND entries to TECH_DEBT.md: - TD-ARIGRAPH-EPISODIC-FIDELITY-1: the Python→Rust AriGraph transcode reimplemented episodic retrieval as the fingerprint-RAG baseline the paper (arxiv 2407.04363) outperforms — eq.1 structural relevance and the episodic edge E_e (the dropped witness_ref/W-slot) are absent; three disconnected episodic/provenance stores exist. Options documented: A (narrow in-place eq.1 fix) vs B (mailbox/W-slot convergence = the already-planned D-CSV-6 WitnessCorpus + D-CSV-7 MailboxSoA W-slot). - TD-JSON-SERIALIZATION-SITES-1: catalogs serde/JSON occurrences under the single-binary no-serialization invariant — outer-boundary ingestion (Cypher/AST, config, JWT, REST, query params) is correct by design; the debt is serde-on-substrate (arigraph orchestrator/sensorium/TruthValue) and JSON audit egress (jsonl_sink + lance_sink JSON-in-column), whose canonical form is the binary canonical_bytes. Reframes TD-SDR-AUDIT-PERSIST-1.
…ack argument + expansion path New plan documenting why the Rust/Raft/lance stack displaces JanusGraph, Cassandra, and Zitadel for multi-server HA/scale-out, plus the conditional expansion architecture: - Displacement: distributed graph + CP via SurrealDB-on-TiKV + lance-graph (vs JanusGraph's JVM 3-system assembly); Cassandra's AP is the wrong consistency model for a belief substrate; authN/IdP gap filled by Ory (Kratos + Hydra), binary stays verification-only. - Multi-server: Raft replicates the belief-delta / episodic LOG; the zero-copy SoA is the per-node state machine; the Rubikon commit_gate is the Raft append point. Resolves TiKV-vs-lance as TiKV-the-log UNDER lance-the-state. - Hard prerequisite (unproven): byte-deterministic NARS apply (reencode_safety / D-SDR-26); next deliverable is the determinism probe. PROPOSAL/expansion-readiness; single-node needs none of it. Indexed in INTEGRATION_PLANS.md.
…-1 + PR_ARC entry Closes the board-hygiene gap: the May 26 signature fix (io::Result -> AuditError, form 1 / W2's instinct) shipped touching only the .rs file; the board still recorded only PR #366's introduction of the constructor. E-AUDIT-1 captures the generalizable lesson (default-feature cargo check masks feature-gated E0277s; error-type honesty over lossy From-coercion). https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
UnifiedBridge::new takes an Arc<lance_graph_rbac::policy::Policy>, but some consumers are barred from a direct lance-graph-rbac dependency (e.g. woa-rs's BBB-barrier allow-list is contract / ontology / callcenter only). Surface the Policy type and the smb_policy() starter factory through the callcenter facade so those consumers can build a UnifiedBridge without naming rbac directly. lance-graph-rbac is already an internal dependency here, so this is a zero-cost re-export. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…-graph-att-k2pHI # Conflicts: # .claude/board/EPIPHANIES.md # .claude/board/INTEGRATION_PLANS.md # .claude/board/PR_ARC_INVENTORY.md
…per ReasoningKind) Implement the lance-graph "thinking" side of the 25-savant delegation: SavantConclusion (suggestion-only, serde-free) + CustomerCategoryReasoner / PostingAnomalyReasoner / NextBestActionReasoner / OtherReasoner — the pinned one-impl-per-ReasoningKind dispatch (PR #419) covering all 25 savants in contract::savants::SAVANTS. Each reasoner resolves the concrete savant from (kind, namespace), selects its QueryStrategy via InferenceType::default_strategy(), and fuses evidence-ref coverage into a NARS (frequency, confidence). woa-rs consumes the conclusion as a native shared type (one-binary contract) and keeps the deterministic AXIS-A guard; this is the ambiguous AXIS-B core only. Dispatch resolution lives in callcenter — the contract stays an untouched inheritance vow (no namespace field on Savant). resolve_savant filters the roster by kind; ambiguous kinds split via DISPATCH_NS (the Other(RECONCILE_MATCH) 19-vs-21 namespace split per #419) then by namespace == savant.name. No serialization in scope: SavantConclusion is a plain in-binary value; JSON exists only at the callcenter<->MedCareV2 FFI boundary. 8 new tests; 137 prior callcenter tests pass; zone_serialize_check no-JSON guard clean. AGENT_LOG updated (board-hygiene). https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
… deny-list) A workspace bouncer that pushes comprehension to the Read tool (not cat/head/ tail), Edit (not sed), and Glob/Read or rg (not grep): - .claude/hooks/forbid-grep-sed-head-tail.sh — PreToolUse(Bash) hook that denies any command whose leading token (per ;|&() segment) is grep/egrep/fgrep/sed/ head/tail. Command-position-only (no false positives on filenames/substrings), fail-open (allows if jq missing), and uses no banned tool itself. - .claude/agents/forbidden-tool-warden.md — audit agent for the hook's blind spots (committed scripts, Makefiles, CI, the hooks dir), files offenders to the board; obeys the ban itself (Glob + Read + rg). - .claude/settings.json — wire the PreToolUse hook + add Bash(grep|sed|head| tail|egrep|fgrep) to the deny list. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…warden + deny-list)" This reverts commit 5358fe6.
📝 WalkthroughWalkthroughAdds a deterministic AXIS-B savant reasoner layer (suggestion-only conclusions, dispatch by kind/namespace, evidence→NARS fusion), exposes reasoners and RBAC policy via the callcenter facade, includes unit tests, and adds multi-server replication proposal and board entries. ChangesSavant Reasoners Implementation and Multi-Server Framework
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 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. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87ab547b03
ℹ️ 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".
| pub struct SavantConclusion { | ||
| /// Roster id of the savant that produced this (SAVANTS.md numbering). | ||
| pub savant_id: u8, | ||
| /// The query strategy the savant dispatches to (from its `InferenceType`). | ||
| pub query_strategy: QueryStrategy, | ||
| /// NARS `(frequency, confidence)` weight of the suggestion. | ||
| pub confidence: NarsTruth, | ||
| /// Human-readable rationale (the AXIS-B decision + evidence summary). | ||
| pub rationale: Cow<'static, str>, |
There was a problem hiding this comment.
Add the missing suggestion payload
When woa-rs consumes one of these reasoners, the conclusion does not contain the actual AXIS-B decision to apply; it only returns the savant id, strategy, confidence, and rationale. The in-repo contract for this deliverable calls for SavantConclusion { suggestion, confidence: NarsTruth, rationale } (.claude/plans/odoo-savant-reasoners-v1.md:80), and individual savant specs require concrete payloads such as ResolveFiscalPosition(partner_id, fiscal_position_id) (.claude/odoo/savants/FiscalPositionResolver.md:84). In that scenario the caller can rank a result but cannot know which fiscal position, date, account, invoice match, etc. was suggested, so the new reasoner layer cannot perform the delegated decision.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
.claude/plans/multi-server-cognition-expansion-v1.md (1)
92-96: 💤 Low valueConsider rewording to improve readability.
Three consecutive list items begin with "Ory". While this is a structured enumeration of Ory components, consider rephrasing for variety:
-- **Ory Hydra** = OAuth2 / OIDC server → token **issuance** (the piece the Raft - stack structurally lacks). -- **Ory Kratos** = identity lifecycle → login, registration, MFA, recovery, user store. -- **Ory Keto** = **not adopted** — authZ stays in-stack (`Policy` RBAC + `TenantId` - Chinese-wall + merkle audit); Keto's Zanzibar model would duplicate it. +- **Hydra** (OAuth2/OIDC server) → token **issuance** (the piece the Raft + stack structurally lacks). +- **Kratos** (identity lifecycle) → login, registration, MFA, recovery, user store. +- **Keto** = **not adopted** — authZ stays in-stack (`Policy` RBAC + `TenantId` + Chinese-wall + merkle audit); Keto's Zanzibar model would duplicate it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.claude/plans/multi-server-cognition-expansion-v1.md around lines 92 - 96, The three list items ("Ory Hydra", "Ory Kratos", "Ory Keto") are repetitive and reduce readability; rephrase each bullet so they don't all start with "Ory" while preserving meaning — e.g., "Hydra — OAuth2/OIDC server (token issuance)", "Kratos — identity lifecycle (login, registration, MFA, recovery, user store)", and "Keto — not adopted — authZ stays in-stack (`Policy` RBAC + `TenantId` Chinese-wall + merkle audit)". Ensure you keep the statements about Hydra providing token issuance and Keto not adopted (with the `Policy` and `TenantId` references) and maintain the original intent and emphasis.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/board/PR_ARC_INVENTORY.md:
- Around line 38-46: The board entry incorrectly claims "not yet a PR" although
this change is part of PR `#420` and violates the file's "Every **merged** PR" and
APPEND-ONLY rules; fix by editing the prepended entry in
.claude/board/PR_ARC_INVENTORY.md: either replace the "not yet a PR" text (the
tuple containing claude/activate-lance-graph-att-k2pHI and commit ea2a378) with
an explicit reference to "PR `#420`" and keep the rest intact, or remove/defer
this new entry entirely and add it in a post-merge housekeeping commit so the
file remains an immutable record.
---
Nitpick comments:
In @.claude/plans/multi-server-cognition-expansion-v1.md:
- Around line 92-96: The three list items ("Ory Hydra", "Ory Kratos", "Ory
Keto") are repetitive and reduce readability; rephrase each bullet so they don't
all start with "Ory" while preserving meaning — e.g., "Hydra — OAuth2/OIDC
server (token issuance)", "Kratos — identity lifecycle (login, registration,
MFA, recovery, user store)", and "Keto — not adopted — authZ stays in-stack
(`Policy` RBAC + `TenantId` Chinese-wall + merkle audit)". Ensure you keep the
statements about Hydra providing token issuance and Keto not adopted (with the
`Policy` and `TenantId` references) and maintain the original intent and
emphasis.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1c7de932-196f-40f5-9a57-21b363ec3a69
📒 Files selected for processing (8)
.claude/board/AGENT_LOG.md.claude/board/EPIPHANIES.md.claude/board/INTEGRATION_PLANS.md.claude/board/PR_ARC_INVENTORY.md.claude/board/TECH_DEBT.md.claude/plans/multi-server-cognition-expansion-v1.mdcrates/lance-graph-callcenter/src/lib.rscrates/lance-graph-callcenter/src/savant_reasoners.rs
| ## callcenter/audit-fix — fix(callcenter): `with_jsonl_audit` returns `Result<Self, AuditError>` (branch work) | ||
|
|
||
| **Status:** On branch `claude/activate-lance-graph-att-k2pHI` (HEAD `ea2a378`, not yet a PR). 1-line `.rs` change + this board record (EPIPHANIES E-AUDIT-1, prepended 2026-05-27). | ||
|
|
||
| - **Added** — nothing new; retypes `UnifiedBridge::with_jsonl_audit` return from `std::io::Result<Self>` → `Result<Self, crate::audit_sink::AuditError>` (`unified_bridge.rs:315`). Resolves an E0277 that only the `--features jsonl` build surfaced (the default `cargo check` skips the feature-gated path). | ||
| - **Locked** — audit constructors return the **domain** error (`AuditError`), not `io::Result`. **No** crate-wide `From<AuditError> for std::io::Error` coercion (rejected: lossy across the non-`Io` variants `ChannelFull`/`Serialize`/`SchemaMigration`/`Lance`/`Arrow`). Optional-feature error paths must be CI-checked under their feature, not just default-feature (E-AUDIT-1). | ||
| - **Deferred** — none. MedCare-rs sprint-2 item 5 (first real caller) consumes the `AuditError` signature directly; any caller needing `io::Result` interop adds a local `map_err`, not a crate-wide `From`. | ||
| - **Docs** — EPIPHANIES E-AUDIT-1. | ||
| - **Confidence (2026-05-27):** working — `cargo check/test -p lance-graph-callcenter --features jsonl` clean at commit time (137 tests); tree clean; zero callers depend on the old signature. |
There was a problem hiding this comment.
Status claim inconsistent with PR context and file governance rules.
Line 40 states "not yet a PR" but this entry is being added as part of PR #420 (per PR objectives). Additionally, the file header (lines 3-6) states this tracks "Every merged PR", yet this entry is being prepended pre-merge.
This violates the historical-record contract:
- Pre-merge entries risk becoming stale if the PR changes or is abandoned
- The APPEND-ONLY rule (line 8) prevents correction beyond confidence annotations
- The "not yet a PR" claim contradicts the current PR
#420context
Recommendation: Either update line 40 to reference PR #420 explicitly, or defer adding this entry until after PR #420 merges. The append-only discipline works best when entries are truly immutable historical facts, not in-flight documentation.
Suggested fix
-**Status:** On branch `claude/activate-lance-graph-att-k2pHI` (HEAD `ea2a378`, not yet a PR). 1-line `.rs` change + this board record (EPIPHANIES E-AUDIT-1, prepended 2026-05-27).
+**Status:** In PR `#420` (branch `claude/activate-lance-graph-att-k2pHI`, HEAD `ea2a378`). 1-line `.rs` change + this board record (EPIPHANIES E-AUDIT-1, prepended 2026-05-27).Alternatively, consider moving this entire entry addition to a post-merge housekeeping commit so it becomes a true historical record per the file's stated purpose.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/board/PR_ARC_INVENTORY.md around lines 38 - 46, The board entry
incorrectly claims "not yet a PR" although this change is part of PR `#420` and
violates the file's "Every **merged** PR" and APPEND-ONLY rules; fix by editing
the prepended entry in .claude/board/PR_ARC_INVENTORY.md: either replace the
"not yet a PR" text (the tuple containing claude/activate-lance-graph-att-k2pHI
and commit ea2a378) with an explicit reference to "PR `#420`" and keep the rest
intact, or remove/defer this new entry entirely and add it in a post-merge
housekeeping commit so the file remains an immutable record.
…d (P1 review on #420) The SavantConclusion dropped the `suggestion` field the plan + per-savant specs require (.claude/plans/odoo-savant-reasoners-v1.md:80, .claude/odoo/savants/FiscalPositionResolver.md:84), so a consumer could rank by confidence but not see *which* decision was delegated. Add `pub enum SavantSuggestion` (callcenter type, contract untouched — the vow stays pure, no serde / no JSON) with variants for the AXIS-B decision shapes across the 25 savants: - SelectFromTable { candidate_table } — top-1 entity (FiscalPosition, Pricelist, Account, AnalyticModel, ProcurementRule, RouteTiebreaker, BankStatementMatch) - Gate — yes/no (Autopost, PaymentToInvoiceMatch, Upsell, BackorderJudge) - Anomaly — suspect key (SequenceGap) - AdvancePeriod — next open period (LockDate, ReorderTiming) - PolicyChoice — fixed-set variant (TaxExigibility, ReportRateType, PartnerTrust, UserCompanyAccess, RemovalStrategy) - Distribution { over_table } — weighted (AnalyticDistribution, ReplenishmentReport) - RankedSet { from_table } — top-N (ReconcileMatch, MoveAssignmentPrioritizer, CurrencySelection) `suggestion_for(savant)` maps every roster id → its variant; reasoners populate it via `build_conclusion`. The row-level value (the concrete fiscal_position_id, etc.) is the consumer's mechanical resolution over rows it holds — the same boundary the 14 NEEDS-INPUT savants already document. The conclusion now CARRIES the AXIS-B decision shape so the delegation is actionable. Tests: 2 new (suggestion_shape_per_savant + every_savant_has_a_suggestion_shape covering all 25 roster ids); 8 prior savant_reasoners tests pass; 137 callcenter tests pass; zone_serialize_check no-JSON guard clean. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
|
Addressed the P1 in
Honest boundary, called out in the doc-comment: the row-level value (the concrete Two new tests (10 total): Generated by Claude Code |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/lance-graph-callcenter/src/savant_reasoners.rs (1)
183-186:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPreserve savant-name fallback when
DISPATCH_NSentry doesn’t match this kind.Line [183]-Line [185] returns early even when the mapped ID is not in
candidates, which bypasses the subsequentnamespace == savant.nameresolution path.Proposed change
- if let Some(&(_, id)) = DISPATCH_NS.iter().find(|(ns, _)| *ns == namespace) { - return candidates.iter().copied().find(|s| s.id == id); - } + if let Some(&(_, id)) = DISPATCH_NS.iter().find(|(ns, _)| *ns == namespace) { + if let Some(s) = candidates.iter().copied().find(|s| s.id == id) { + return Some(s); + } + } candidates.iter().copied().find(|s| s.name == namespace)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/lance-graph-callcenter/src/savant_reasoners.rs` around lines 183 - 186, The current code returns early when DISPATCH_NS maps the namespace to an id even if that id is not present in candidates, skipping the fallback name match; modify the logic in the block that checks DISPATCH_NS so you attempt to find a candidate by the mapped id and only return if that find yields Some(s), otherwise continue to the final lookup that finds by s.name == namespace (i.e., change the early return to conditional return based on whether candidates.iter().copied().find(|s| s.id == id) is Some, and otherwise let the subsequent candidates.iter().copied().find(|s| s.name == namespace) run).
🧹 Nitpick comments (1)
crates/lance-graph-callcenter/src/savant_reasoners.rs (1)
91-121: ⚡ Quick winAvoid silent fallback in
suggestion_forfor unknown savant IDs.Line [119] (
_ => PolicyChoice) masks missing mappings and can return the wrong decision shape when roster IDs evolve.Proposed change
fn suggestion_for(savant: &Savant) -> SavantSuggestion { use SavantSuggestion::*; match savant.id { @@ 25 => RankedSet { from_table: Cow::Borrowed("stock.move") }, 26 => Gate, - _ => PolicyChoice, + id => unreachable!("missing SavantSuggestion mapping for savant id {id}"), } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/lance-graph-callcenter/src/savant_reasoners.rs` around lines 91 - 121, The match in function suggestion_for silently falls back to `_ => PolicyChoice`, which can mask missing mappings; change suggestion_for to return Result<SavantSuggestion, UnknownSavantIdError> (or similar error type) instead of raw SavantSuggestion, remove the `_ => PolicyChoice` arm, and replace it with an explicit `Err(UnknownSavantId(savant.id))` for unmapped IDs; update callers to handle the Result (or alternatively add a distinct SavantSuggestion::Unknown variant and return that instead) so unknown savant IDs are not silently treated as PolicyChoice.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@crates/lance-graph-callcenter/src/savant_reasoners.rs`:
- Around line 183-186: The current code returns early when DISPATCH_NS maps the
namespace to an id even if that id is not present in candidates, skipping the
fallback name match; modify the logic in the block that checks DISPATCH_NS so
you attempt to find a candidate by the mapped id and only return if that find
yields Some(s), otherwise continue to the final lookup that finds by s.name ==
namespace (i.e., change the early return to conditional return based on whether
candidates.iter().copied().find(|s| s.id == id) is Some, and otherwise let the
subsequent candidates.iter().copied().find(|s| s.name == namespace) run).
---
Nitpick comments:
In `@crates/lance-graph-callcenter/src/savant_reasoners.rs`:
- Around line 91-121: The match in function suggestion_for silently falls back
to `_ => PolicyChoice`, which can mask missing mappings; change suggestion_for
to return Result<SavantSuggestion, UnknownSavantIdError> (or similar error type)
instead of raw SavantSuggestion, remove the `_ => PolicyChoice` arm, and replace
it with an explicit `Err(UnknownSavantId(savant.id))` for unmapped IDs; update
callers to handle the Result (or alternatively add a distinct
SavantSuggestion::Unknown variant and return that instead) so unknown savant IDs
are not silently treated as PolicyChoice.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 48ca60b7-6ec3-477f-9713-72a8cb4f7d77
📒 Files selected for processing (2)
crates/lance-graph-callcenter/src/lib.rscrates/lance-graph-callcenter/src/savant_reasoners.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/lance-graph-callcenter/src/lib.rs
…ition over CausalEdge64 + Tactic + role_keys (E-SAVANT-COMPOSITION-1) v1 (shipped PR #420 with a "MED on dispatch shape" caveat) review resolved: the Reasoner trait surface fails CLAUDE.md "P-1 The Click" + "P0 AGI-as-glove" litmus tests verbatim: 1. "new capability lands as a new column, not a new layer" → the Reasoner trait IS a new layer 2. "free function on a carrier's state = reject" → build_conclusion(savant, ctx) is the named anti-pattern 3. "wrap the axes in a new struct = breaks the SIMD sweep" → SavantConclusion + SavantSuggestion duplicate CausalEdge64 v2 routes the canonical path through the agnostic substrate that already exists: - CausalEdge64 (zero-dep crates/causal-edge, v2 layout — the savant's decision IS the emitted edge: SPO palette + NARS truth + Pearl 2³ + inference mantissa) - Tactic trait + 34 kernels (PR #411 — "the Elixir-like recipe layer that later fronts the real fingerprint substrate via cognitive-shader-driver with no change to the 34 call sites") - 33-TSV atom layer (PR #411 contract::atoms::CANONICAL_ATOMS) - Role-key catalogues (I-VSA-IDENTITIES names callcenter/role_keys.rs as future Layer-2 home) Deliverables (Queued): - D-ODOO-SAV-5a: SavantPattern + TacticInvocation + EdgeEmissionSpec + AtomTouchMask primitives in lance-graph-contract (Group D) - D-ODOO-SAV-5b: callcenter/role_keys.rs with 25 disjoint Vsa16kF32 slices + lookup-by-OdooSavant + slice manifest (Group E) - D-ODOO-SAV-5c: 25 typed SavantPattern consts drawn from .claude/odoo/savants/<N>.md slot 1/4 + .claude/odoo/L*.md (Group F) - D-ODOO-SAV-5d: #[deprecated] + legacy-reasoner feature gate + migration pointers on v1 Reasoner surface, per I-LEGACY-API-FEATURE-GATED (Group G) - D-ODOO-SAV-5e: end-to-end test (FiscalPositionResolver SavantPattern → expected CausalEdge64 row, SPO + NARS + v2 signed mantissa) Execution: 5a + 5b parallel (additive, zero churn) → 5c after both → 5d after 5c (migration pointers name real targets) → 5e throughout. woa-rs consumer migration OUT OF SCOPE but UNBLOCKED by 5d. Board hygiene per CLAUDE.md: plan file + INTEGRATION_PLANS PREPEND + STATUS_BOARD section + EPIPHANIES E-SAVANT-COMPOSITION-1 all in this commit. v1 surface (Reasoner trait, 4 *Reasoner impls, SavantConclusion, SavantSuggestion, build_conclusion) stays compiling under the legacy-reasoner feature with #[deprecated] migration pointers until woa-rs migrates its Reasoner::reason() call sites to SavantPattern resolution. Removal in a follow-up PR after the migration. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…OO-SAV-5b 25 savant role keys Two parallel additive deliverables — both zero-churn surfaces opened by the v2 reshape (PR #420 deprecation) + blueprint plan (commits 741a25c + 6d2466e): ## D-ODOO-BP-1a — typed Odoo entity DTOs (lance-graph-ontology::odoo_blueprint) New module `crates/lance-graph-ontology/src/odoo_blueprint.rs` carrying the typed surface the OGIT → OWL → DOLCE → FIBU/FIBO inheritance chain will operate on (replacing today's ad-hoc string-keyed maps against `model_name`). Types: OdooEntity + OdooField + OdooMethod + OdooDecorator + OdooStateMachine + OdooState + OdooTransition + OdooConstraint + OdooProvenance + OdooSourceRef. Enums: OdooFieldKind (17 variants), OdooSemanticRole (12), OdooMethodKind (10), OdooReturnKind (10), OdooDecoratorKind (7), OdooStateSemantic (7), OdooConstraintKind (3), OdooConfidence (3 — Curated/Extracted/Conjecture). Tests: 3 — sample_entity_compiles_as_const (FiscalPosition shape), state_machine_entity_compiles (Invoice draft→posted→cancel), empty_entity_compiles (zero case). Per-lane consts (L1–L15) land in D-ODOO-BP-1b; OGIT/OWL/DOLCE/FIBU wiring lands in 1c/d/e; source extraction in 1f; JITson + recipes in 1g. ## D-ODOO-SAV-5b — 25 savant role keys (contract::callcenter::role_keys) New module `crates/lance-graph-contract/src/callcenter/{mod,role_keys}.rs` per I-VSA-IDENTITIES Layer-2 catalogue doctrine (sibling of `contract::grammar::role_keys`, NOT in the separate lance-graph-callcenter crate — path correction from the v2 plan). Slice layout: 25 savants × 90 dims = 2250 dims in the SMB headroom [14096..16346), with 38 dims of headroom remaining. FNV-64 seeded from each savant's name (lookup matches SAVANTS const order). LazyLock<[RoleKey; 25]> lookup by id or name. Tests: 7 — slices_disjoint_and_in_bounds, savant_zone_fits_in_smb_headroom (2250 dims + 38 headroom = 2288), id_lookup_matches_name_lookup, id_16_absent (the intentional gap), last_savant_is_backorder_judge, deterministic_pseudo_random_bits, no_overlap_with_grammar_slices. ## Supporting change `RoleKey::generate` made `pub` in `contract::grammar::role_keys` so sibling per-domain Layer-2 catalogues (callcenter today; persona, others later) can construct their own role keys with disjoint slice allocations — per I-VSA-IDENTITIES. Documented in the doc-comment. ## Tests + integration - lance-graph-contract: 7 new callcenter::role_keys tests pass; 454 prior contract lib tests unaffected - lance-graph-ontology: 3 new odoo_blueprint tests pass; 43 lib tests total ## Followups - D-ODOO-SAV-5a (BusinessGrammarTemplate primitive) — design pass complete (4-surface integration map: DeepNSM SpoTriple shape + ractor ModuleEntry + AriGraph SpoRecord/TruthValue + ReasoningWitness64). Code lands next, with the 7-field shape: PatternMatchSpec + AtomTouchMask + MailboxSpawnSpec + RecipeComposition + EdgeEmissionSpec + EpisodicWitnessSpec + TemplateProvenance. - D-ODOO-BP-1b — per-lane entity consts, L1–L15 Wave (one subagent per lane). https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…ition over CausalEdge64 + Tactic + role_keys (E-SAVANT-COMPOSITION-1) v1 (shipped PR #420 with a "MED on dispatch shape" caveat) review resolved: the Reasoner trait surface fails CLAUDE.md "P-1 The Click" + "P0 AGI-as-glove" litmus tests verbatim: 1. "new capability lands as a new column, not a new layer" → the Reasoner trait IS a new layer 2. "free function on a carrier's state = reject" → build_conclusion(savant, ctx) is the named anti-pattern 3. "wrap the axes in a new struct = breaks the SIMD sweep" → SavantConclusion + SavantSuggestion duplicate CausalEdge64 v2 routes the canonical path through the agnostic substrate that already exists: - CausalEdge64 (zero-dep crates/causal-edge, v2 layout — the savant's decision IS the emitted edge: SPO palette + NARS truth + Pearl 2³ + inference mantissa) - Tactic trait + 34 kernels (PR #411 — "the Elixir-like recipe layer that later fronts the real fingerprint substrate via cognitive-shader-driver with no change to the 34 call sites") - 33-TSV atom layer (PR #411 contract::atoms::CANONICAL_ATOMS) - Role-key catalogues (I-VSA-IDENTITIES names callcenter/role_keys.rs as future Layer-2 home) Deliverables (Queued): - D-ODOO-SAV-5a: SavantPattern + TacticInvocation + EdgeEmissionSpec + AtomTouchMask primitives in lance-graph-contract (Group D) - D-ODOO-SAV-5b: callcenter/role_keys.rs with 25 disjoint Vsa16kF32 slices + lookup-by-OdooSavant + slice manifest (Group E) - D-ODOO-SAV-5c: 25 typed SavantPattern consts drawn from .claude/odoo/savants/<N>.md slot 1/4 + .claude/odoo/L*.md (Group F) - D-ODOO-SAV-5d: #[deprecated] + legacy-reasoner feature gate + migration pointers on v1 Reasoner surface, per I-LEGACY-API-FEATURE-GATED (Group G) - D-ODOO-SAV-5e: end-to-end test (FiscalPositionResolver SavantPattern → expected CausalEdge64 row, SPO + NARS + v2 signed mantissa) Execution: 5a + 5b parallel (additive, zero churn) → 5c after both → 5d after 5c (migration pointers name real targets) → 5e throughout. woa-rs consumer migration OUT OF SCOPE but UNBLOCKED by 5d. Board hygiene per CLAUDE.md: plan file + INTEGRATION_PLANS PREPEND + STATUS_BOARD section + EPIPHANIES E-SAVANT-COMPOSITION-1 all in this commit. v1 surface (Reasoner trait, 4 *Reasoner impls, SavantConclusion, SavantSuggestion, build_conclusion) stays compiling under the legacy-reasoner feature with #[deprecated] migration pointers until woa-rs migrates its Reasoner::reason() call sites to SavantPattern resolution. Removal in a follow-up PR after the migration. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…OO-SAV-5b 25 savant role keys Two parallel additive deliverables — both zero-churn surfaces opened by the v2 reshape (PR #420 deprecation) + blueprint plan (commits 741a25c + 6d2466e): ## D-ODOO-BP-1a — typed Odoo entity DTOs (lance-graph-ontology::odoo_blueprint) New module `crates/lance-graph-ontology/src/odoo_blueprint.rs` carrying the typed surface the OGIT → OWL → DOLCE → FIBU/FIBO inheritance chain will operate on (replacing today's ad-hoc string-keyed maps against `model_name`). Types: OdooEntity + OdooField + OdooMethod + OdooDecorator + OdooStateMachine + OdooState + OdooTransition + OdooConstraint + OdooProvenance + OdooSourceRef. Enums: OdooFieldKind (17 variants), OdooSemanticRole (12), OdooMethodKind (10), OdooReturnKind (10), OdooDecoratorKind (7), OdooStateSemantic (7), OdooConstraintKind (3), OdooConfidence (3 — Curated/Extracted/Conjecture). Tests: 3 — sample_entity_compiles_as_const (FiscalPosition shape), state_machine_entity_compiles (Invoice draft→posted→cancel), empty_entity_compiles (zero case). Per-lane consts (L1–L15) land in D-ODOO-BP-1b; OGIT/OWL/DOLCE/FIBU wiring lands in 1c/d/e; source extraction in 1f; JITson + recipes in 1g. ## D-ODOO-SAV-5b — 25 savant role keys (contract::callcenter::role_keys) New module `crates/lance-graph-contract/src/callcenter/{mod,role_keys}.rs` per I-VSA-IDENTITIES Layer-2 catalogue doctrine (sibling of `contract::grammar::role_keys`, NOT in the separate lance-graph-callcenter crate — path correction from the v2 plan). Slice layout: 25 savants × 90 dims = 2250 dims in the SMB headroom [14096..16346), with 38 dims of headroom remaining. FNV-64 seeded from each savant's name (lookup matches SAVANTS const order). LazyLock<[RoleKey; 25]> lookup by id or name. Tests: 7 — slices_disjoint_and_in_bounds, savant_zone_fits_in_smb_headroom (2250 dims + 38 headroom = 2288), id_lookup_matches_name_lookup, id_16_absent (the intentional gap), last_savant_is_backorder_judge, deterministic_pseudo_random_bits, no_overlap_with_grammar_slices. ## Supporting change `RoleKey::generate` made `pub` in `contract::grammar::role_keys` so sibling per-domain Layer-2 catalogues (callcenter today; persona, others later) can construct their own role keys with disjoint slice allocations — per I-VSA-IDENTITIES. Documented in the doc-comment. ## Tests + integration - lance-graph-contract: 7 new callcenter::role_keys tests pass; 454 prior contract lib tests unaffected - lance-graph-ontology: 3 new odoo_blueprint tests pass; 43 lib tests total ## Followups - D-ODOO-SAV-5a (BusinessGrammarTemplate primitive) — design pass complete (4-surface integration map: DeepNSM SpoTriple shape + ractor ModuleEntry + AriGraph SpoRecord/TruthValue + ReasoningWitness64). Code lands next, with the 7-field shape: PatternMatchSpec + AtomTouchMask + MailboxSpawnSpec + RecipeComposition + EdgeEmissionSpec + EpisodicWitnessSpec + TemplateProvenance. - D-ODOO-BP-1b — per-lane entity consts, L1–L15 Wave (one subagent per lane). https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…ition over CausalEdge64 + Tactic + role_keys (E-SAVANT-COMPOSITION-1) v1 (shipped PR #420 with a "MED on dispatch shape" caveat) review resolved: the Reasoner trait surface fails CLAUDE.md "P-1 The Click" + "P0 AGI-as-glove" litmus tests verbatim: 1. "new capability lands as a new column, not a new layer" → the Reasoner trait IS a new layer 2. "free function on a carrier's state = reject" → build_conclusion(savant, ctx) is the named anti-pattern 3. "wrap the axes in a new struct = breaks the SIMD sweep" → SavantConclusion + SavantSuggestion duplicate CausalEdge64 v2 routes the canonical path through the agnostic substrate that already exists: - CausalEdge64 (zero-dep crates/causal-edge, v2 layout — the savant's decision IS the emitted edge: SPO palette + NARS truth + Pearl 2³ + inference mantissa) - Tactic trait + 34 kernels (PR #411 — "the Elixir-like recipe layer that later fronts the real fingerprint substrate via cognitive-shader-driver with no change to the 34 call sites") - 33-TSV atom layer (PR #411 contract::atoms::CANONICAL_ATOMS) - Role-key catalogues (I-VSA-IDENTITIES names callcenter/role_keys.rs as future Layer-2 home) Deliverables (Queued): - D-ODOO-SAV-5a: SavantPattern + TacticInvocation + EdgeEmissionSpec + AtomTouchMask primitives in lance-graph-contract (Group D) - D-ODOO-SAV-5b: callcenter/role_keys.rs with 25 disjoint Vsa16kF32 slices + lookup-by-OdooSavant + slice manifest (Group E) - D-ODOO-SAV-5c: 25 typed SavantPattern consts drawn from .claude/odoo/savants/<N>.md slot 1/4 + .claude/odoo/L*.md (Group F) - D-ODOO-SAV-5d: #[deprecated] + legacy-reasoner feature gate + migration pointers on v1 Reasoner surface, per I-LEGACY-API-FEATURE-GATED (Group G) - D-ODOO-SAV-5e: end-to-end test (FiscalPositionResolver SavantPattern → expected CausalEdge64 row, SPO + NARS + v2 signed mantissa) Execution: 5a + 5b parallel (additive, zero churn) → 5c after both → 5d after 5c (migration pointers name real targets) → 5e throughout. woa-rs consumer migration OUT OF SCOPE but UNBLOCKED by 5d. Board hygiene per CLAUDE.md: plan file + INTEGRATION_PLANS PREPEND + STATUS_BOARD section + EPIPHANIES E-SAVANT-COMPOSITION-1 all in this commit. v1 surface (Reasoner trait, 4 *Reasoner impls, SavantConclusion, SavantSuggestion, build_conclusion) stays compiling under the legacy-reasoner feature with #[deprecated] migration pointers until woa-rs migrates its Reasoner::reason() call sites to SavantPattern resolution. Removal in a follow-up PR after the migration. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…OO-SAV-5b 25 savant role keys Two parallel additive deliverables — both zero-churn surfaces opened by the v2 reshape (PR #420 deprecation) + blueprint plan (commits 741a25c + 6d2466e): ## D-ODOO-BP-1a — typed Odoo entity DTOs (lance-graph-ontology::odoo_blueprint) New module `crates/lance-graph-ontology/src/odoo_blueprint.rs` carrying the typed surface the OGIT → OWL → DOLCE → FIBU/FIBO inheritance chain will operate on (replacing today's ad-hoc string-keyed maps against `model_name`). Types: OdooEntity + OdooField + OdooMethod + OdooDecorator + OdooStateMachine + OdooState + OdooTransition + OdooConstraint + OdooProvenance + OdooSourceRef. Enums: OdooFieldKind (17 variants), OdooSemanticRole (12), OdooMethodKind (10), OdooReturnKind (10), OdooDecoratorKind (7), OdooStateSemantic (7), OdooConstraintKind (3), OdooConfidence (3 — Curated/Extracted/Conjecture). Tests: 3 — sample_entity_compiles_as_const (FiscalPosition shape), state_machine_entity_compiles (Invoice draft→posted→cancel), empty_entity_compiles (zero case). Per-lane consts (L1–L15) land in D-ODOO-BP-1b; OGIT/OWL/DOLCE/FIBU wiring lands in 1c/d/e; source extraction in 1f; JITson + recipes in 1g. ## D-ODOO-SAV-5b — 25 savant role keys (contract::callcenter::role_keys) New module `crates/lance-graph-contract/src/callcenter/{mod,role_keys}.rs` per I-VSA-IDENTITIES Layer-2 catalogue doctrine (sibling of `contract::grammar::role_keys`, NOT in the separate lance-graph-callcenter crate — path correction from the v2 plan). Slice layout: 25 savants × 90 dims = 2250 dims in the SMB headroom [14096..16346), with 38 dims of headroom remaining. FNV-64 seeded from each savant's name (lookup matches SAVANTS const order). LazyLock<[RoleKey; 25]> lookup by id or name. Tests: 7 — slices_disjoint_and_in_bounds, savant_zone_fits_in_smb_headroom (2250 dims + 38 headroom = 2288), id_lookup_matches_name_lookup, id_16_absent (the intentional gap), last_savant_is_backorder_judge, deterministic_pseudo_random_bits, no_overlap_with_grammar_slices. ## Supporting change `RoleKey::generate` made `pub` in `contract::grammar::role_keys` so sibling per-domain Layer-2 catalogues (callcenter today; persona, others later) can construct their own role keys with disjoint slice allocations — per I-VSA-IDENTITIES. Documented in the doc-comment. ## Tests + integration - lance-graph-contract: 7 new callcenter::role_keys tests pass; 454 prior contract lib tests unaffected - lance-graph-ontology: 3 new odoo_blueprint tests pass; 43 lib tests total ## Followups - D-ODOO-SAV-5a (BusinessGrammarTemplate primitive) — design pass complete (4-surface integration map: DeepNSM SpoTriple shape + ractor ModuleEntry + AriGraph SpoRecord/TruthValue + ReasoningWitness64). Code lands next, with the 7-field shape: PatternMatchSpec + AtomTouchMask + MailboxSpawnSpec + RecipeComposition + EdgeEmissionSpec + EpisodicWitnessSpec + TemplateProvenance. - D-ODOO-BP-1b — per-lane entity consts, L1–L15 Wave (one subagent per lane). https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…ition over CausalEdge64 + Tactic + role_keys (E-SAVANT-COMPOSITION-1) v1 (shipped PR #420 with a "MED on dispatch shape" caveat) review resolved: the Reasoner trait surface fails CLAUDE.md "P-1 The Click" + "P0 AGI-as-glove" litmus tests verbatim: 1. "new capability lands as a new column, not a new layer" → the Reasoner trait IS a new layer 2. "free function on a carrier's state = reject" → build_conclusion(savant, ctx) is the named anti-pattern 3. "wrap the axes in a new struct = breaks the SIMD sweep" → SavantConclusion + SavantSuggestion duplicate CausalEdge64 v2 routes the canonical path through the agnostic substrate that already exists: - CausalEdge64 (zero-dep crates/causal-edge, v2 layout — the savant's decision IS the emitted edge: SPO palette + NARS truth + Pearl 2³ + inference mantissa) - Tactic trait + 34 kernels (PR #411 — "the Elixir-like recipe layer that later fronts the real fingerprint substrate via cognitive-shader-driver with no change to the 34 call sites") - 33-TSV atom layer (PR #411 contract::atoms::CANONICAL_ATOMS) - Role-key catalogues (I-VSA-IDENTITIES names callcenter/role_keys.rs as future Layer-2 home) Deliverables (Queued): - D-ODOO-SAV-5a: SavantPattern + TacticInvocation + EdgeEmissionSpec + AtomTouchMask primitives in lance-graph-contract (Group D) - D-ODOO-SAV-5b: callcenter/role_keys.rs with 25 disjoint Vsa16kF32 slices + lookup-by-OdooSavant + slice manifest (Group E) - D-ODOO-SAV-5c: 25 typed SavantPattern consts drawn from .claude/odoo/savants/<N>.md slot 1/4 + .claude/odoo/L*.md (Group F) - D-ODOO-SAV-5d: #[deprecated] + legacy-reasoner feature gate + migration pointers on v1 Reasoner surface, per I-LEGACY-API-FEATURE-GATED (Group G) - D-ODOO-SAV-5e: end-to-end test (FiscalPositionResolver SavantPattern → expected CausalEdge64 row, SPO + NARS + v2 signed mantissa) Execution: 5a + 5b parallel (additive, zero churn) → 5c after both → 5d after 5c (migration pointers name real targets) → 5e throughout. woa-rs consumer migration OUT OF SCOPE but UNBLOCKED by 5d. Board hygiene per CLAUDE.md: plan file + INTEGRATION_PLANS PREPEND + STATUS_BOARD section + EPIPHANIES E-SAVANT-COMPOSITION-1 all in this commit. v1 surface (Reasoner trait, 4 *Reasoner impls, SavantConclusion, SavantSuggestion, build_conclusion) stays compiling under the legacy-reasoner feature with #[deprecated] migration pointers until woa-rs migrates its Reasoner::reason() call sites to SavantPattern resolution. Removal in a follow-up PR after the migration. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…OO-SAV-5b 25 savant role keys Two parallel additive deliverables — both zero-churn surfaces opened by the v2 reshape (PR #420 deprecation) + blueprint plan (commits 741a25c + 6d2466e): ## D-ODOO-BP-1a — typed Odoo entity DTOs (lance-graph-ontology::odoo_blueprint) New module `crates/lance-graph-ontology/src/odoo_blueprint.rs` carrying the typed surface the OGIT → OWL → DOLCE → FIBU/FIBO inheritance chain will operate on (replacing today's ad-hoc string-keyed maps against `model_name`). Types: OdooEntity + OdooField + OdooMethod + OdooDecorator + OdooStateMachine + OdooState + OdooTransition + OdooConstraint + OdooProvenance + OdooSourceRef. Enums: OdooFieldKind (17 variants), OdooSemanticRole (12), OdooMethodKind (10), OdooReturnKind (10), OdooDecoratorKind (7), OdooStateSemantic (7), OdooConstraintKind (3), OdooConfidence (3 — Curated/Extracted/Conjecture). Tests: 3 — sample_entity_compiles_as_const (FiscalPosition shape), state_machine_entity_compiles (Invoice draft→posted→cancel), empty_entity_compiles (zero case). Per-lane consts (L1–L15) land in D-ODOO-BP-1b; OGIT/OWL/DOLCE/FIBU wiring lands in 1c/d/e; source extraction in 1f; JITson + recipes in 1g. ## D-ODOO-SAV-5b — 25 savant role keys (contract::callcenter::role_keys) New module `crates/lance-graph-contract/src/callcenter/{mod,role_keys}.rs` per I-VSA-IDENTITIES Layer-2 catalogue doctrine (sibling of `contract::grammar::role_keys`, NOT in the separate lance-graph-callcenter crate — path correction from the v2 plan). Slice layout: 25 savants × 90 dims = 2250 dims in the SMB headroom [14096..16346), with 38 dims of headroom remaining. FNV-64 seeded from each savant's name (lookup matches SAVANTS const order). LazyLock<[RoleKey; 25]> lookup by id or name. Tests: 7 — slices_disjoint_and_in_bounds, savant_zone_fits_in_smb_headroom (2250 dims + 38 headroom = 2288), id_lookup_matches_name_lookup, id_16_absent (the intentional gap), last_savant_is_backorder_judge, deterministic_pseudo_random_bits, no_overlap_with_grammar_slices. ## Supporting change `RoleKey::generate` made `pub` in `contract::grammar::role_keys` so sibling per-domain Layer-2 catalogues (callcenter today; persona, others later) can construct their own role keys with disjoint slice allocations — per I-VSA-IDENTITIES. Documented in the doc-comment. ## Tests + integration - lance-graph-contract: 7 new callcenter::role_keys tests pass; 454 prior contract lib tests unaffected - lance-graph-ontology: 3 new odoo_blueprint tests pass; 43 lib tests total ## Followups - D-ODOO-SAV-5a (BusinessGrammarTemplate primitive) — design pass complete (4-surface integration map: DeepNSM SpoTriple shape + ractor ModuleEntry + AriGraph SpoRecord/TruthValue + ReasoningWitness64). Code lands next, with the 7-field shape: PatternMatchSpec + AtomTouchMask + MailboxSpawnSpec + RecipeComposition + EdgeEmissionSpec + EpisodicWitnessSpec + TemplateProvenance. - D-ODOO-BP-1b — per-lane entity consts, L1–L15 Wave (one subagent per lane). https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…d (P1 review on #420) The SavantConclusion dropped the `suggestion` field the plan + per-savant specs require (.claude/plans/odoo-savant-reasoners-v1.md:80, .claude/odoo/savants/FiscalPositionResolver.md:84), so a consumer could rank by confidence but not see *which* decision was delegated. Add `pub enum SavantSuggestion` (callcenter type, contract untouched — the vow stays pure, no serde / no JSON) with variants for the AXIS-B decision shapes across the 25 savants: - SelectFromTable { candidate_table } — top-1 entity (FiscalPosition, Pricelist, Account, AnalyticModel, ProcurementRule, RouteTiebreaker, BankStatementMatch) - Gate — yes/no (Autopost, PaymentToInvoiceMatch, Upsell, BackorderJudge) - Anomaly — suspect key (SequenceGap) - AdvancePeriod — next open period (LockDate, ReorderTiming) - PolicyChoice — fixed-set variant (TaxExigibility, ReportRateType, PartnerTrust, UserCompanyAccess, RemovalStrategy) - Distribution { over_table } — weighted (AnalyticDistribution, ReplenishmentReport) - RankedSet { from_table } — top-N (ReconcileMatch, MoveAssignmentPrioritizer, CurrencySelection) `suggestion_for(savant)` maps every roster id → its variant; reasoners populate it via `build_conclusion`. The row-level value (the concrete fiscal_position_id, etc.) is the consumer's mechanical resolution over rows it holds — the same boundary the 14 NEEDS-INPUT savants already document. The conclusion now CARRIES the AXIS-B decision shape so the delegation is actionable. Tests: 2 new (suggestion_shape_per_savant + every_savant_has_a_suggestion_shape covering all 25 roster ids); 8 prior savant_reasoners tests pass; 137 callcenter tests pass; zone_serialize_check no-JSON guard clean. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…att-k2pHI callcenter: D-ODOO-SAV-4 odoo-savant reasoners + audit/Policy fixes
…ition over CausalEdge64 + Tactic + role_keys (E-SAVANT-COMPOSITION-1) v1 (shipped PR #420 with a "MED on dispatch shape" caveat) review resolved: the Reasoner trait surface fails CLAUDE.md "P-1 The Click" + "P0 AGI-as-glove" litmus tests verbatim: 1. "new capability lands as a new column, not a new layer" → the Reasoner trait IS a new layer 2. "free function on a carrier's state = reject" → build_conclusion(savant, ctx) is the named anti-pattern 3. "wrap the axes in a new struct = breaks the SIMD sweep" → SavantConclusion + SavantSuggestion duplicate CausalEdge64 v2 routes the canonical path through the agnostic substrate that already exists: - CausalEdge64 (zero-dep crates/causal-edge, v2 layout — the savant's decision IS the emitted edge: SPO palette + NARS truth + Pearl 2³ + inference mantissa) - Tactic trait + 34 kernels (PR #411 — "the Elixir-like recipe layer that later fronts the real fingerprint substrate via cognitive-shader-driver with no change to the 34 call sites") - 33-TSV atom layer (PR #411 contract::atoms::CANONICAL_ATOMS) - Role-key catalogues (I-VSA-IDENTITIES names callcenter/role_keys.rs as future Layer-2 home) Deliverables (Queued): - D-ODOO-SAV-5a: SavantPattern + TacticInvocation + EdgeEmissionSpec + AtomTouchMask primitives in lance-graph-contract (Group D) - D-ODOO-SAV-5b: callcenter/role_keys.rs with 25 disjoint Vsa16kF32 slices + lookup-by-OdooSavant + slice manifest (Group E) - D-ODOO-SAV-5c: 25 typed SavantPattern consts drawn from .claude/odoo/savants/<N>.md slot 1/4 + .claude/odoo/L*.md (Group F) - D-ODOO-SAV-5d: #[deprecated] + legacy-reasoner feature gate + migration pointers on v1 Reasoner surface, per I-LEGACY-API-FEATURE-GATED (Group G) - D-ODOO-SAV-5e: end-to-end test (FiscalPositionResolver SavantPattern → expected CausalEdge64 row, SPO + NARS + v2 signed mantissa) Execution: 5a + 5b parallel (additive, zero churn) → 5c after both → 5d after 5c (migration pointers name real targets) → 5e throughout. woa-rs consumer migration OUT OF SCOPE but UNBLOCKED by 5d. Board hygiene per CLAUDE.md: plan file + INTEGRATION_PLANS PREPEND + STATUS_BOARD section + EPIPHANIES E-SAVANT-COMPOSITION-1 all in this commit. v1 surface (Reasoner trait, 4 *Reasoner impls, SavantConclusion, SavantSuggestion, build_conclusion) stays compiling under the legacy-reasoner feature with #[deprecated] migration pointers until woa-rs migrates its Reasoner::reason() call sites to SavantPattern resolution. Removal in a follow-up PR after the migration. https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
…OO-SAV-5b 25 savant role keys Two parallel additive deliverables — both zero-churn surfaces opened by the v2 reshape (PR #420 deprecation) + blueprint plan (commits 741a25c + 6d2466e): ## D-ODOO-BP-1a — typed Odoo entity DTOs (lance-graph-ontology::odoo_blueprint) New module `crates/lance-graph-ontology/src/odoo_blueprint.rs` carrying the typed surface the OGIT → OWL → DOLCE → FIBU/FIBO inheritance chain will operate on (replacing today's ad-hoc string-keyed maps against `model_name`). Types: OdooEntity + OdooField + OdooMethod + OdooDecorator + OdooStateMachine + OdooState + OdooTransition + OdooConstraint + OdooProvenance + OdooSourceRef. Enums: OdooFieldKind (17 variants), OdooSemanticRole (12), OdooMethodKind (10), OdooReturnKind (10), OdooDecoratorKind (7), OdooStateSemantic (7), OdooConstraintKind (3), OdooConfidence (3 — Curated/Extracted/Conjecture). Tests: 3 — sample_entity_compiles_as_const (FiscalPosition shape), state_machine_entity_compiles (Invoice draft→posted→cancel), empty_entity_compiles (zero case). Per-lane consts (L1–L15) land in D-ODOO-BP-1b; OGIT/OWL/DOLCE/FIBU wiring lands in 1c/d/e; source extraction in 1f; JITson + recipes in 1g. ## D-ODOO-SAV-5b — 25 savant role keys (contract::callcenter::role_keys) New module `crates/lance-graph-contract/src/callcenter/{mod,role_keys}.rs` per I-VSA-IDENTITIES Layer-2 catalogue doctrine (sibling of `contract::grammar::role_keys`, NOT in the separate lance-graph-callcenter crate — path correction from the v2 plan). Slice layout: 25 savants × 90 dims = 2250 dims in the SMB headroom [14096..16346), with 38 dims of headroom remaining. FNV-64 seeded from each savant's name (lookup matches SAVANTS const order). LazyLock<[RoleKey; 25]> lookup by id or name. Tests: 7 — slices_disjoint_and_in_bounds, savant_zone_fits_in_smb_headroom (2250 dims + 38 headroom = 2288), id_lookup_matches_name_lookup, id_16_absent (the intentional gap), last_savant_is_backorder_judge, deterministic_pseudo_random_bits, no_overlap_with_grammar_slices. ## Supporting change `RoleKey::generate` made `pub` in `contract::grammar::role_keys` so sibling per-domain Layer-2 catalogues (callcenter today; persona, others later) can construct their own role keys with disjoint slice allocations — per I-VSA-IDENTITIES. Documented in the doc-comment. ## Tests + integration - lance-graph-contract: 7 new callcenter::role_keys tests pass; 454 prior contract lib tests unaffected - lance-graph-ontology: 3 new odoo_blueprint tests pass; 43 lib tests total ## Followups - D-ODOO-SAV-5a (BusinessGrammarTemplate primitive) — design pass complete (4-surface integration map: DeepNSM SpoTriple shape + ractor ModuleEntry + AriGraph SpoRecord/TruthValue + ReasoningWitness64). Code lands next, with the 7-field shape: PatternMatchSpec + AtomTouchMask + MailboxSpawnSpec + RecipeComposition + EdgeEmissionSpec + EpisodicWitnessSpec + TemplateProvenance. - D-ODOO-BP-1b — per-lane entity consts, L1–L15 Wave (one subagent per lane). https://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
Summary
crates/lance-graph-callcenter/src/savant_reasoners.rs):SavantConclusion { savant_id, query_strategy, confidence: NarsTruth, rationale }(suggestion-only, serde-free) + the 4 one-impl-per-ReasoningKindreasoners —CustomerCategoryReasoner/PostingAnomalyReasoner/NextBestActionReasoner/OtherReasoner— covering all 25 savants incontract::savants::SAVANTS, per the dispatch pinned in docs(odoo-savants): 25 AXIS-B evidence contracts (carve-out) + dispatch decision #419. Each resolves the concrete savant from(kind, namespace), selects itsQueryStrategyviaInferenceType::default_strategy(), and fuses evidence-ref coverage into a NARS(frequency, confidence).namespacefield added toSavant).resolve_savantfilters the roster by kind; ambiguous kinds split viaDISPATCH_NS(theOther(RECONCILE_MATCH)19-vs-21 namespace split per docs(odoo-savants): 25 AXIS-B evidence contracts (carve-out) + dispatch decision #419) then bynamespace == savant.name.with_jsonl_audit→Result<Self, AuditError>(wasio::Result): feature-gated audit errors surface as the domain error, not a lossyio::Errorcoercion.Policy+smb_policyso consumers barred from a directlance-graph-rbacdependency can build aUnifiedBridgethrough the facade alone.Architecture: one-binary contract —
SavantConclusionis a plain in-binary value that woa-rs consumes as the same struct; nothing is serialized between them. JSON exists only at the callcenter↔MedCareV2 FFI boundary, never on these types. lance-graph does the thinking (the ambiguous AXIS-B core); woa-rs keeps the deterministic AXIS-A guard and consumes the suggestion.Scope note: all 25 savants dispatch through the 4 impls today; the 14
NEEDS-INPUTsavants are blocked on woa-rs evidence feeds, not on the impl — their row-level column fusion lands when woa-rs supplies materialized evidence (v1 fusion is coverage-based + monotone).Test plan
cargo test -p lance-graph-callcenter --features jsonl— 8 newsavant_reasonerstests + 137 prior passzone_serialize_check(no-JSON guard) clean — confirms the module is serde-freehttps://claude.ai/code/session_017gZ6sPRXYPj5n7uJ7NBtRv
Generated by Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation