diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index ef0b517d..e281827c 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,7 @@ +## 2026-06-11 — tombstone commit: emission artifacts removed per PR #477 follow-up + +**Main thread (Fable, session splat3d-cpu-simd-renderer).** Executed the PR #477 documented follow-up (the "what does NOT exist" table → source reality): removed `CollapseGateEmission` from `lance-graph-contract::collapse_gate` (+ lib.rs re-export; `MailboxId`/`MergeMode`/`GateDecision` survive), removed `MailboxSoA::emit()`, renamed `last_emission_cycle` → `last_active_cycle`, added in-place `consume_firing(row)` successor (same threshold + same-cycle-idempotency semantics, no carrier object), reworded 4 stale doc references (kanban/episodic_edges/witness_tombstone/mailbox_soa header), superseded the CLAUDE.md Baton-scoping block, fixed cycle-coherent-soa-snapshot-v1 D-SOA-SNAP-1/2 to generic `SnapshotProvider::Column` (closes #477 CodeRabbit Critical — contract stays zero-dep), closed TD-COLLAPSE-GATE-SMALLVEC-1 as moot. Verified #477 codex P2 (`verify_layout` ColumnOutOfBounds) already fixed on main with regression test. Tests: contract 594 (−8 emission, +2 gate/merge), driver 85 (emit tests → consume tests, +1 OOB), clippy clean, workspace check clean. Commit: in PR. + ## 2026-06-09 — plan addendum: left-prefix parsing confirmed + D-PG-7 deterministic foveated tree-builder **Main thread (Fable).** User direction validated against identity.rs octets: GUID left half (class+tree) is order-preserving plain bytes ⇒ Cypher label/subtree patterns = byte-prefix predicates on FixedSizeBinary(16) via Lance zone-maps; similarity leg (RaBitQ/CAM-PQ/Binary16K) rides the same row. Two caveats recorded (namespace-first ordering; ≤4-nibble GUID prefix). New M6 + D-PG-7: NiblePath assignment computable by deterministic hierarchical partition ("deterministic Louvain" → concretely ndarray CLAM pole-split, 16-way, capacity-bounded ⇒ foveation), with the iron requirement APPEND-STABLE (bootstrap once; minted paths never move; layout_version gates changes). Query-time twin noted (cascade / bgz-tensor HHTL cache). Plan §8 + STATUS_BOARD row. Commit: this. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 8a4b6487..cdf145c3 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,59 @@ +## 2026-06-12 — E-OUTER-BOUNDARY-IS-ORM-1 — there is only one boundary, and it is ontology-mediated + +**Status:** FINDING (PR #487 tombstone commit makes this source-true; OGAR class + `SoaEnvelope` + Lance columnar I/O is the realized triangle). +**Confidence:** High — every prior candidate inner boundary has now been removed or recast as ownership transfer; no surviving call-site asserts otherwise. + +**The reframing.** `CollapseGateEmission` looked like a wire format for an +inter-mailbox seam. It was, in fact, the workspace's last hand-written DTO +asserting an **inner** boundary that does not exist. Between mailboxes there is +only ownership transfer (Rust move semantics — `E-CE64-MB-4` makes UB a compile +error); within a mailbox there are only in-place bytes (`SoaEnvelope` geometry). +The only real boundary is the **outer** one — where the SoA meets persistence +and meaning — and that boundary is **ontology-mediated, not DTO-mediated**. + +**This is exactly the ORM pattern.** + +| ORM | This substrate | +|---|---| +| Table schema | OGAR class (label, fields, tools, templates) | +| Column mapping | `SoaEnvelope` + `ColumnDescriptor` (byte geometry) | +| Active record | register-bank slice wrapped by the class view | +| SQL writer | Lance columnar I/O (writes LE bytes from the in-place store) | +| Hand-rolled row DTO | `CollapseGateEmission` — **the anti-pattern** | + +In an ORM you don't write a bespoke struct per table-crossing; the mapping +derives the persisted shape from the schema. Here likewise: the OGAR class +supplies the semantics, the envelope supplies the geometry, Lance does the +writing — and any independent carrier struct at that seam is **schema drift by +definition** (per #477's "every DTO is a derived view of an OGAR class"). The +emission type was not just unused; it was a second, ontology-bypassing +description of data the class layer already described. + +**Why `MailboxId` / `MergeMode` / `GateDecision` survive.** They are vocabulary +*of* the ontology side — addressing (which register bank), merge policy (how +overlapping writes compose), gate decision (apply / block / hold). They are +not parallel descriptions of row data; they are the operational verbs the +ontology binds. + +**Consequences (the test for any future PR):** + +- **Inner seams are ownership transfers**, never carrier types. If a proposed + type looks like "DTO crossing from mailbox A to mailbox B", it is wrong by + construction — the SoA is the DTO; the move is the crossing. +- **The outer seam has exactly one description.** OGAR class on one side, + `SoaEnvelope` on the other, Lance in between. A new "wire format" at this + seam is the same anti-pattern by a different name — propose a new column or + a class-template specialization instead. +- **Hand-rolled active-record structs are ORM-bypass.** If you find yourself + serializing-then-deserializing fields the class already names, the class + template + `ClassView` + `FieldMask` is the right reach. + +**Cross-ref:** PR #477 (three-tier model + "what does NOT exist" table); PR +#487 (tombstone commit — emission artifacts removed; `consume_firing` is the +in-place ownership successor); `docs/architecture/soa-three-tier-model.md` +(register-file analogy + ORM mapping); `E-OGAR-NORTHSTAR-1` (the class spine +this boundary binds to); `I-LEGACY-API-FEATURE-GATED` (legacy carrier paths +must route to the canonical mapping or be removed — the tombstone is removal). ## 2026-06-10 — E-PROBE-MANTISSA-1 — golden-mantissa centroid placement measured: beats uniform-random on coverage AND pile-up; PHASE-1 bit-exactness green **Status:** FINDING (probes run first-hand: `crates/helix/tests/probe_mantissa_fill.rs`, 4/4 green) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index e70b8d71..7e6b27af 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -131,7 +131,7 @@ Types live in `crates/cognitive-shader-driver/src/wire.rs` behind `--features se **Sprint-11/12 D-CSV substrate types (2026-05-16, PRs #383-#389)**: - `lance-graph-contract::qualia`: `QualiaI4_16D` (16-dim signed-i4, 9× compression vs `[f32; 18]`), `QualiaI4Column` (sibling SoA column in cognitive-shader-driver). -- `lance-graph-contract::collapse_gate`: `CollapseGateEmission` (Vec-of-`(u16 target, CausalEdge64)` wire format; zero-dep — SmallVec optimization deferred as TD-COLLAPSE-GATE-SMALLVEC-1). +- `lance-graph-contract::collapse_gate`: ~~`CollapseGateEmission`~~ **REMOVED 2026-06-11** (PR #477 three-tier model tombstone commit — zero-copy SoA, no inter-mailbox handoff type; TD-COLLAPSE-GATE-SMALLVEC-1 closed as moot). `MailboxId` / `MergeMode` / `GateDecision` remain. - `lance-graph-contract::mailbox` / `attention_mask`: `MailboxId` (canonical id type), `MailboxSoA` (SoA mailbox with W-slot + plasticity accumulator + `apply_edges`), `AttentionMaskSoA`, `AttentionMaskActor`, `AttentionMaskBackend` trait. - `lance-graph-contract::sigma_tier`: `SigmaTierBands`, `SigmaTierRouter` (Rubicon-resonance ΔF + threshold dispatch), `DispatchOutcome`, `RestReason` (Σ-tier crate surface). - `lance-graph-contract::witness`: `WitnessCorpus` (CAM-PQ-indexed; D-CSV-6a partial in PR #386, full 6b sprint-12), `WitnessEntry`, `WitnessId`, `WitnessIndexHashMap` (anchor + chain invariant). @@ -212,7 +212,7 @@ Types live in `crates/cognitive-shader-driver/src/wire.rs` behind `--features se **Sprint-12/13 explicit deferrals (2026-05-16):** -- **TD-COLLAPSE-GATE-SMALLVEC-1** — SmallVec optimization for `CollapseGateEmission` (currently Vec to preserve contract zero-dep invariant). Revisit only if profiling shows the heap allocation is hot. +- **TD-COLLAPSE-GATE-SMALLVEC-1** — CLOSED 2026-06-11 as moot: `CollapseGateEmission` removed entirely (PR #477 tombstone commit), nothing left to optimize. - **TD-SIGMA-TIER-THRESHOLDS-1** — Σ10 VAMPE-coupled Jirak-derived threshold refinement (D-CSV-15). Hand-tuned acceptable through sprint-12 per `I-NOISE-FLOOR-JIRAK`; principled Jirak 2016 derivation forwarded to sprint-13+ VAMPE coupled-revival track. - **ndarray `parallel`-feature `par_*` rayon variants** — productized substrate ships sequentially in PR #147; rayon work-stealing wraps deferred to sprint-14+ behind an opt-in feature gate. diff --git a/.claude/board/TECH_DEBT.md b/.claude/board/TECH_DEBT.md index c3ef7040..fad7dca3 100644 --- a/.claude/board/TECH_DEBT.md +++ b/.claude/board/TECH_DEBT.md @@ -936,6 +936,8 @@ Cross-ref: `docs/TYPE_DUPLICATION_MAP.md`; `crates/lance-graph-contract/src/mul. ## 2026-05-16 — TD-COLLAPSE-GATE-SMALLVEC-1: `CollapseGateEmission` uses `Vec` instead of `SmallVec`; zero-dep constraint was the tradeoff +**Status: CLOSED 2026-06-11 (moot)** — `CollapseGateEmission` was removed entirely per the PR #477 three-tier model (zero-copy SoA, no inter-mailbox handoff type; tombstone commit). No carrier, no Vec, nothing to optimize. + **Status:** Open **Priority:** P3 **Scope:** crate:lance-graph-contract domain:perf diff --git a/.claude/plans/cycle-coherent-soa-snapshot-v1.md b/.claude/plans/cycle-coherent-soa-snapshot-v1.md index 755ac15d..476c9379 100644 --- a/.claude/plans/cycle-coherent-soa-snapshot-v1.md +++ b/.claude/plans/cycle-coherent-soa-snapshot-v1.md @@ -86,21 +86,46 @@ row-scale and byte-scale deinterlace use the same clock. ## Deliverables -### D-SOA-SNAP-1 — `MailboxSoaSnapshot` type in lance-graph-contract +### D-SOA-SNAP-1 — `MailboxSoaSnapshot` type in lance-graph-contract -A `MailboxSoaSnapshot` struct: `cycle: u32`, `cols: Vec>`. -Snapshot is `Send + Sync`. No reference to the originating `MailboxSoa`. -This is a point-in-time read — immutable after creation. +> **REVISED 2026-06-11 (PR #477 CodeRabbit Critical):** the original sketch +> (`cols: Vec>` directly in contract) would create a +> `lance-graph-contract → ndarray` dependency — `MultiLaneColumn` is an +> ndarray type, and the contract crate is zero-dep by iron invariant. The +> snapshot type is therefore **generic over the column type**; the concrete +> binding happens in lance-graph, never in contract. + +A `MailboxSoaSnapshot` struct: `cycle: u32`, `cols: Vec>`. +Snapshot is `Send + Sync` (when `C: Send + Sync`). No reference to the +originating `MailboxSoa`. This is a point-in-time read — immutable after +creation. Contract never names `MultiLaneColumn`; it only carries the +generic parameter. ### D-SOA-SNAP-2 — `SnapshotProvider` trait in lance-graph-contract ```rust +// In lance-graph-contract (zero-dep — no ndarray import): +pub struct MailboxSoaSnapshot { + pub cycle: u32, + pub cols: Vec>, +} + pub trait SnapshotProvider { - fn snapshot(&self) -> MailboxSoaSnapshot; + type Column; + fn snapshot(&self) -> MailboxSoaSnapshot; } + +// In lance-graph (the binding side): +// impl SnapshotProvider for MailboxSoa { +// type Column = ndarray::simd::MultiLaneColumn; +// fn snapshot(&self) -> MailboxSoaSnapshot { … } +// } ``` -Zero deps in contract. `MailboxSoa` in lance-graph implements it. +Zero deps in contract — the associated type defers the column choice to the +implementor. `MailboxSoa` in lance-graph binds `Column = MultiLaneColumn`; +ndarray never learns the snapshot exists, contract never learns ndarray +exists, lance-graph binds them (same triangulation as `SoaEnvelope`). ### D-SOA-SNAP-3 — Arc-swap write path in `MailboxSoa::advance_phase` diff --git a/CLAUDE.md b/CLAUDE.md index 8afd3ab5..9cab5d31 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,6 +60,20 @@ VSA carrier). > emissions + AriGraph SPO-G quads + BindSpace SoA columns. The bundle MATH > here (§I-SUBSTRATE-MARKOV Chapman-Kolmogorov, §I-VSA-IDENTITIES) is > **untouched** — only the cross-boundary carrier is scoped out. +> +> **2026-06-11 supersession (PR #477 three-tier model + tombstone commit):** +> the Baton/emission framing above is itself now SUPERSEDED. There is **no +> inter-mailbox handoff type at all** — `CollapseGateEmission`, +> `MailboxSoA::emit()`, and `wire_cost_bytes() = 13 + 10·baton_count` were +> removed from source (the tombstone commit). The ratified invariant: every +> SoA envelope is **zero-copy from creation to Lance tombstone**; Lance's own +> columnar I/O writes LE bytes from the in-place backing store described by +> `SoaEnvelope`; nothing is serialized or transmitted between mailboxes. +> `last_emission_cycle` was renamed `last_active_cycle` (in-place consumption +> stamp). Canonical reference: `docs/architecture/soa-three-tier-model.md`. +> What survives from this block: Vsa16kF32-never-crosses-boundaries, the +> mailbox-as-owner compile-time safety argument (E-CE64-MB-4), and the bundle +> math — all untouched. ``` Sentence → FSM → RoleKey_fp × content_fp → vsa_bundle (Σ) with ρ^d braiding diff --git a/crates/cognitive-shader-driver/src/mailbox_soa.rs b/crates/cognitive-shader-driver/src/mailbox_soa.rs index 4028c1c1..f32e3f50 100644 --- a/crates/cognitive-shader-driver/src/mailbox_soa.rs +++ b/crates/cognitive-shader-driver/src/mailbox_soa.rs @@ -1,39 +1,47 @@ -//! MailboxSoA: spatial-temporal accumulator for per-row baton receipts. +//! MailboxSoA: spatial-temporal accumulator for per-row edge receipts. //! //! Per plan §9 — mailboxes are NOT message queues. Each row is a -//! single neuron with many incoming synapses; multi-source batons land -//! via `apply_edges`; the row's energy integrates; when threshold crosses, -//! the row emits via the receiving CollapseGate. +//! single neuron with many incoming synapses; multi-source `CausalEdge64` +//! deliveries land via `apply_edges`; the row's energy integrates; rows at or +//! above `threshold` are "firing" (see `pending_count`) and are consumed by +//! the owner during its phase advance. //! //! D-CSV-7 scope (sprint-11 Phase B): core types + W-slot referencing + -//! per-row plasticity accumulator + apply_edges baton receipt. NO ractor +//! per-row plasticity accumulator + apply_edges edge receipt. NO ractor //! wrap, NO AttentionMask/LRU, NO cross-cycle rollup — those are W6's //! orthogonal concerns / sprint-12 SigmaTierRouter integration. //! -//! Migration target (design, NOT yet wired): this type is the per-mailbox, -//! mailbox-owned, *ephemeral* "thoughtspace" — the BindSpace surrogate. The -//! shared singleton `Arc` dissolves *onto* mailboxes (each owns its -//! own LE-contract SoA columns: edges/qualia/meta/entity_type — minus the -//! deprecated `Vsa16kF32` plane), it is NOT copied per mailbox. The only -//! cross-boundary state stays the LE baton `(u16, CausalEdge64)` (E-BATON-1); -//! ownership makes no-alias/no-race a compile error (E-CE64-MB-4). Column map + -//! gated steps: `.claude/plans/bindspace-singleton-to-mailbox-soa-v1.md`. +//! ## Zero-copy lifecycle (PR #477 three-tier model) +//! +//! This type is the per-mailbox, mailbox-owned Tier-1 SoA — the BindSpace +//! surrogate. The shared singleton `Arc` dissolves *onto* +//! mailboxes (each owns its own LE-contract SoA columns: +//! edges/qualia/meta/entity_type — minus the deprecated `Vsa16kF32` plane), +//! it is NOT copied per mailbox. **There is no emission and no inter-mailbox +//! handoff type** — the SoA is zero-copy from creation to Lance tombstone; +//! Lance's columnar I/O writes LE bytes from this in-place store +//! (`SoaEnvelope` describes the geometry). The former `emit()` / +//! `CollapseGateEmission` path was removed per the ratified model +//! (`docs/architecture/soa-three-tier-model.md`). Ownership makes +//! no-alias/no-race a compile error (E-CE64-MB-4). Column map + gated steps: +//! `.claude/plans/bindspace-singleton-to-mailbox-soa-v1.md`. use causal_edge::CausalEdge64; use lance_graph_contract::cognitive_shader::MetaWord; -use lance_graph_contract::collapse_gate::{CollapseGateEmission, MailboxId, MergeMode}; +use lance_graph_contract::collapse_gate::MailboxId; use lance_graph_contract::qualia::QualiaI4_16D; -/// Spatial-temporal accumulator for per-row baton receipts. +/// Spatial-temporal accumulator for per-row edge receipts. /// /// `N` is the maximum number of neuron rows this mailbox can serve. -/// Each row accumulates `energy` from incoming `CausalEdge64` batons -/// (via `apply_edges`) and emits when the magnitude crosses `threshold`. +/// Each row accumulates `energy` from incoming `CausalEdge64` deliveries +/// (via `apply_edges`); rows whose magnitude crosses `threshold` are +/// "firing" and are consumed in place by the owner (no emission). /// /// # W-slot invariant /// -/// All accepted batons must have `edge.w_slot() == self.w_slot`. -/// Mismatched batons are silently dropped in `apply_edges`. +/// All accepted edges must have `edge.w_slot() == self.w_slot`. +/// Mismatched edges are silently dropped in `apply_edges`. /// `w_slot` must be < 64 (6-bit limit per plan §6 L-6); the constructor /// panics with a clear message if this is violated. /// @@ -51,13 +59,16 @@ pub struct MailboxSoA { pub energy: [f32; N], /// Per-row Hebbian integration counter (saturating u8 per W6 §4.4). - /// Incremented once per accepted baton, never decremented within a cycle. + /// Incremented once per accepted edge, never decremented within a cycle. pub plasticity_counter: [u8; N], - /// Per-row last-emission cycle stamp. - /// Guards the "never emit on the same cycle twice" invariant: - /// if `last_emission_cycle[row] == current_cycle`, emission is suppressed. - pub last_emission_cycle: [u32; N], + /// Per-row last-active cycle stamp (renamed from `last_emission_cycle` + /// per PR #477 — there is no emission; the stamp marks in-place + /// consumption). Guards the "never consume the same firing row twice in + /// one cycle" invariant: the owner stamps `last_active_cycle[row] = + /// current_cycle` when it consumes a firing row during phase advance; + /// rows already stamped this cycle are skipped. + pub last_active_cycle: [u32; N], // ── NEW: migrated thoughtspace columns (per-mailbox owned, D-MBX-A1) ── @@ -86,12 +97,13 @@ pub struct MailboxSoA { pub current_cycle: u32, /// 6-bit W-slot value this mailbox represents. - /// Incoming batons with `edge.w_slot() != self.w_slot` are rejected. + /// Incoming edges with `edge.w_slot() != self.w_slot` are rejected. /// Must be < 64 (plan §6 L-6). pub w_slot: u8, - /// Emission threshold (default 1.0; tunable). - /// A row emits when `energy[row].abs() >= threshold`. + /// Firing threshold (default 1.0; tunable). + /// A row is firing when `energy[row].abs() >= threshold` + /// (see [`Self::pending_count`]). pub threshold: f32, } @@ -114,11 +126,11 @@ impl MailboxSoA { mailbox_id, energy: [0.0f32; N], plasticity_counter: [0u8; N], - // u32::MAX is the "never emitted" sentinel. current_cycle starts at 0 + // u32::MAX is the "never consumed" sentinel. current_cycle starts at 0 // and advances via wrapping_add(1); in practice it never reaches - // u32::MAX during a session, so the first emit on any cycle is always - // permitted (u32::MAX != any valid cycle stamp). - last_emission_cycle: [u32::MAX; N], + // u32::MAX during a session, so first consumption on any cycle is + // always permitted (u32::MAX != any valid cycle stamp). + last_active_cycle: [u32::MAX; N], current_cycle: 0, w_slot, threshold, @@ -130,20 +142,20 @@ impl MailboxSoA { } } - /// Accept a batch of `(target_row, CausalEdge64)` batons. + /// Accept a batch of `(target_row, CausalEdge64)` deliveries. /// - /// For each baton: + /// For each delivery: /// - Rows out of `[0, N)` are silently dropped. - /// - Batons whose `edge.w_slot()` differs from `self.w_slot` are silently + /// - Edges whose `edge.w_slot()` differs from `self.w_slot` are silently /// dropped (wrong corpus). Downstream telemetry can count rejections by - /// comparing the return value against `batons.len()`. - /// - Accepted batons: `energy[row] += mantissa/8.0 * confidence`; + /// comparing the return value against `deliveries.len()`. + /// - Accepted edges: `energy[row] += mantissa/8.0 * confidence`; /// `plasticity_counter[row]` is incremented (saturating). /// - /// Returns the count of accepted (non-dropped) batons. - pub fn apply_edges(&mut self, batons: &[(u16, CausalEdge64)]) -> usize { + /// Returns the count of accepted (non-dropped) deliveries. + pub fn apply_edges(&mut self, deliveries: &[(u16, CausalEdge64)]) -> usize { let mut accepted = 0; - for &(target, edge) in batons { + for &(target, edge) in deliveries { let row = target as usize; if row >= N { continue; @@ -163,38 +175,30 @@ impl MailboxSoA { accepted } - /// Scan all rows and emit batons for those whose `|energy|` meets or - /// exceeds `threshold`, subject to the same-cycle idempotency guard. + /// Consume one firing row in place: stamp `last_active_cycle[row]` to the + /// current cycle and reset its energy, subject to the same-cycle + /// idempotency guard. /// - /// For each emitting row: - /// - A baton is appended to `emission` with `target = row`. - /// - The edge carries the clamped mantissa and the mailbox's `w_slot`. - /// - `last_emission_cycle[row]` is stamped to `current_cycle`. - /// - `energy[row]` is reset to `0.0` (single-shot per cycle). + /// This is the zero-copy successor of the removed `emit()` path (PR #477 + /// three-tier model): nothing is packaged or transmitted — the owner + /// reads the row's columns in place (edge/qualia/meta/entity_type) and + /// then calls this to mark the row consumed for this cycle. /// - /// Rows that already emitted this cycle (`last_emission_cycle[row] == current_cycle`) - /// are skipped regardless of current energy. - pub fn emit(&mut self, source: MailboxId) -> CollapseGateEmission { - let mut emission = - CollapseGateEmission::new(source, self.current_cycle, MergeMode::Bundle); - for row in 0..N { - if self.energy[row].abs() < self.threshold { - continue; - } - if self.last_emission_cycle[row] == self.current_cycle { - continue; // already emitted this cycle - } - // Encode accumulated energy as a signed 4-bit mantissa (-8..+7). - let mantissa = - (self.energy[row].clamp(-1.0, 1.0) * 7.0).round() as i8; - let edge = CausalEdge64::ZERO - .with_inference_mantissa(mantissa) - .with_w_slot(self.w_slot); - emission.push_baton(row as u16, edge.0); - self.last_emission_cycle[row] = self.current_cycle; - self.energy[row] = 0.0; // reset on emission (single-shot per cycle) + /// Returns `true` if the row was consumed; `false` if the row is out of + /// range, below threshold, or already consumed this cycle. + pub fn consume_firing(&mut self, row: usize) -> bool { + if row >= N { + return false; } - emission + if self.energy[row].abs() < self.threshold { + return false; + } + if self.last_active_cycle[row] == self.current_cycle { + return false; // already consumed this cycle + } + self.last_active_cycle[row] = self.current_cycle; + self.energy[row] = 0.0; // reset on consumption (single-shot per cycle) + true } /// Advance to the next cycle. @@ -207,7 +211,7 @@ impl MailboxSoA { /// Reset one row to its zero-initialised state. /// - /// Clears `energy`, `plasticity_counter`, and `last_emission_cycle` + /// Clears `energy`, `plasticity_counter`, and `last_active_cycle` /// for `row`. Out-of-range `row` values are silently ignored. /// Cross-cycle plasticity rollup is outside this scope (W6 §4.4 Zone 2). pub fn reset_row(&mut self, row: usize) { @@ -216,9 +220,9 @@ impl MailboxSoA { } self.energy[row] = 0.0; self.plasticity_counter[row] = 0; - // Restore the "never emitted" sentinel so the row can emit immediately + // Restore the "never consumed" sentinel so the row can fire immediately // on the next cycle without triggering the same-cycle guard. - self.last_emission_cycle[row] = u32::MAX; + self.last_active_cycle[row] = u32::MAX; // ── NEW thoughtspace columns reset (D-MBX-A1) ── self.edges[row] = CausalEdge64::ZERO; self.qualia[row] = QualiaI4_16D::ZERO; @@ -340,8 +344,8 @@ mod tests { /// New mailbox must have all per-row state initialised correctly. /// - /// energy and plasticity_counter are zero; last_emission_cycle is u32::MAX - /// (the "never emitted" sentinel so cycle 0 can emit without false guard). + /// energy and plasticity_counter are zero; last_active_cycle is u32::MAX + /// (the "never consumed" sentinel so cycle 0 can consume without false guard). #[test] fn test_mailbox_soa_new_zero() { let mb: MailboxSoA<8> = MailboxSoA::new(7, 5, 1.0); @@ -357,9 +361,9 @@ mod tests { "plasticity_counter[{row}] should be 0" ); assert_eq!( - mb.last_emission_cycle[row], + mb.last_active_cycle[row], u32::MAX, - "last_emission_cycle[{row}] should be u32::MAX (never-emitted sentinel)" + "last_active_cycle[{row}] should be u32::MAX (never-consumed sentinel)" ); } } @@ -439,83 +443,72 @@ mod tests { } } - // ── test 6: emit above threshold ──────────────────────────────────────── + // ── test 6: consume firing row above threshold ──────────────────────────── - /// A row above threshold must emit one baton; energy resets; cycle stamp set. + /// A row above threshold must be consumable in place: energy resets and + /// the cycle stamp is set. No emission object exists (PR #477). #[test] - fn test_mailbox_soa_emit_above_threshold() { + fn test_mailbox_soa_consume_firing_above_threshold() { let mut mb: MailboxSoA<8> = MailboxSoA::new(10, 1, 1.0); // Directly set energy[3] to 1.5 (above 1.0 threshold) mb.energy[3] = 1.5; - let emission = mb.emit(10); - - assert_eq!(emission.baton_count(), 1, "should emit exactly 1 baton"); - let batons = emission.batons(); - assert_eq!(batons[0].0, 3u16, "baton target must be row 3"); - // mantissa = (1.5.clamp(-1,1) * 7.0).round() as i8 = (7.0).round() = 7 - let emitted_edge = CausalEdge64(batons[0].1); - assert_ne!( - emitted_edge.inference_mantissa(), - 0, - "emitted edge mantissa must be non-zero" + assert_eq!(mb.pending_count(), 1, "exactly one row should be firing"); + assert!(mb.consume_firing(3), "firing row must be consumable"); + assert_eq!( + mb.energy_at(3), + 0.0, + "energy[3] must reset to 0 after consumption" ); - assert_eq!(mb.energy_at(3), 0.0, "energy[3] must reset to 0 after emit"); assert_eq!( - mb.last_emission_cycle[3], + mb.last_active_cycle[3], mb.current_cycle, - "last_emission_cycle[3] must equal current_cycle" + "last_active_cycle[3] must equal current_cycle" ); } - // ── test 7: emit below threshold — no baton ────────────────────────────── + // ── test 7: below threshold — not consumable ───────────────────────────── - /// Row energy below threshold must NOT produce a baton. + /// Row energy below threshold must NOT be consumable. #[test] - fn test_mailbox_soa_emit_below_threshold_no_baton() { + fn test_mailbox_soa_consume_below_threshold_rejected() { let mut mb: MailboxSoA<8> = MailboxSoA::new(5, 2, 1.0); mb.energy[3] = 0.5; // below 1.0 threshold - let emission = mb.emit(5); - - assert_eq!(emission.baton_count(), 0, "below-threshold row must not emit"); + assert!(!mb.consume_firing(3), "below-threshold row must not consume"); assert_eq!(mb.energy_at(3), 0.5, "energy[3] must be unchanged"); } - // ── test 8: double emit same cycle is idempotent ───────────────────────── + // ── test 8: double consume same cycle is idempotent ────────────────────── - /// Second emit() on the same cycle (no tick) must produce 0 batons for - /// rows that already emitted. + /// Second consume_firing() on the same cycle (no tick) must be blocked + /// for rows already consumed, even if energy re-accumulated. #[test] - fn test_mailbox_soa_emit_double_call_same_cycle_idempotent() { + fn test_mailbox_soa_consume_double_call_same_cycle_idempotent() { let mut mb: MailboxSoA<8> = MailboxSoA::new(7, 0, 1.0); mb.energy[1] = 2.0; - let first = mb.emit(7); - assert_eq!(first.baton_count(), 1, "first emit must produce 1 baton"); + assert!(mb.consume_firing(1), "first consume must succeed"); // Re-accumulate energy so energy[1] is above threshold again — - // but last_emission_cycle guard must block re-emission. + // but the last_active_cycle guard must block re-consumption. mb.energy[1] = 2.0; - let second = mb.emit(7); - assert_eq!( - second.baton_count(), - 0, - "second emit same cycle must be blocked by last_emission_cycle guard" + assert!( + !mb.consume_firing(1), + "second consume same cycle must be blocked by last_active_cycle guard" ); } - // ── test 9: tick then re-accumulate then emit ──────────────────────────── + // ── test 9: tick then re-accumulate then consume ────────────────────────── - /// After tick(), a row can re-emit in the new cycle. + /// After tick(), a row can be consumed again in the new cycle. #[test] - fn test_mailbox_soa_tick_then_emit_after_re_accumulation() { + fn test_mailbox_soa_tick_then_consume_after_re_accumulation() { let mut mb: MailboxSoA<8> = MailboxSoA::new(3, 1, 1.0); mb.energy[4] = 1.5; - // First emission - let first = mb.emit(3); - assert_eq!(first.baton_count(), 1); + // First consumption + assert!(mb.consume_firing(4)); assert_eq!(mb.energy_at(4), 0.0); // Advance cycle @@ -523,18 +516,26 @@ mod tests { // Re-accumulate enough energy let edge = make_edge(1, 7, 255); // w_slot=1 matches mb.w_slot=1 - let batons = vec![(4u16, edge)]; - mb.apply_edges(&batons); + let deliveries = vec![(4u16, edge)]; + mb.apply_edges(&deliveries); // energy[4] = (7/8.0) * (255/255.0) ≈ 0.875 — below 1.0 threshold. // Let's set it directly to be safe. mb.energy[4] = 1.2; - // Second emission must succeed in the new cycle - let second = mb.emit(3); - assert_eq!( - second.baton_count(), - 1, - "after tick, row 4 must be able to emit again" + // Second consumption must succeed in the new cycle + assert!( + mb.consume_firing(4), + "after tick, row 4 must be consumable again" ); } + + // ── test 10: out-of-range row not consumable ────────────────────────────── + + /// consume_firing on a row >= N must return false and not panic. + #[test] + fn test_mailbox_soa_consume_out_of_range_rejected() { + let mut mb: MailboxSoA<8> = MailboxSoA::new(1, 0, 1.0); + assert!(!mb.consume_firing(8), "row == N must be rejected"); + assert!(!mb.consume_firing(100), "row > N must be rejected"); + } } diff --git a/crates/lance-graph-contract/src/collapse_gate.rs b/crates/lance-graph-contract/src/collapse_gate.rs index b20b85f2..02893c4f 100644 --- a/crates/lance-graph-contract/src/collapse_gate.rs +++ b/crates/lance-graph-contract/src/collapse_gate.rs @@ -99,305 +99,50 @@ impl GateDecision { } } -// ── D-CSV-4: CollapseGateEmission ──────────────────────────────────────────── +// ── CollapseGateEmission: REMOVED (PR #477 three-tier model) ───────────────── // -// Plan §8.1 specifies `SmallVec<[(u16, CausalEdge64); 8]>` for the baton list. -// Resolution (pre-ratified by main thread): use `Vec<(u16, u64)>` instead. -// • Keeps contract zero-dep (SmallVec would require the `smallvec` crate). -// • u64 is the raw CausalEdge64 packing; receivers wrap it back via -// `CausalEdge64(raw)` using the causal-edge crate's tuple-struct accessor. -// • Defers the SmallVec inline-storage optimisation to a sprint-12+ pass. -// • No `#[repr(C)]` — that was a wire-format aspiration; with `Vec` we are -// heap-indirect anyway and repr(C) buys nothing here. +// The D-CSV-4 `CollapseGateEmission` carrier (Vec-of-`(u16 target, u64 edge)` +// batons, `wire_cost_bytes() = 13 + 10·baton_count`) was a code artifact of +// the superseded baton/emission design. Per the ratified zero-copy invariant +// (`docs/architecture/soa-three-tier-model.md`): every SoA envelope is +// zero-copy from creation to Lance tombstone — nothing is serialized or +// transmitted between mailboxes, so there is no inter-mailbox handoff type. +// Lance's own columnar I/O writes LE bytes from the in-place backing store +// (`SoaEnvelope` describes the geometry); the store itself is never packaged. // -// Wire-cost (plan §8.2): -// header = 4 (source_mailbox) + 4 (chain_position) + 1 (merge_mode tag) = 9 B -// But plan §8.2 states the header is 13 bytes; accounting is: -// source_mailbox u32 4 B -// chain_position u32 4 B -// merge_mode u8 1 B -// padding / future headroom 4 B (reserved for sprint-12+ fields) -// ───────────────────────────────── -// header total 13 B (matches §8.2 exactly) -// per baton: u16 (2 B) + u64 (8 B) = 10 B -// Total = 13 + 10 × baton_count -// 8-baton inline budget → 93 B (§8.2 "Total inline emission: ~93 bytes"). -// -// No-analog-bucket rebuttal (plan §8.3): -// Vec<(u16, u64)> IS the bundle decomposed. Vsa16kF32 does NOT cross -// mailbox boundaries. Receiver's `apply_edges` re-superposes via energy -// addition — same algebra, zero encode/decode at the boundary. +// `MailboxId` (below) predates and survives the removal — it is the mailbox +// addressing handle, not an emission concept. /// Canonical handle for the W-slot corpus-root / mailbox addressing surface. /// A `MailboxId` is the unique u32 identity of one spatial-temporal meaning -/// accumulator in `MailboxSoA`. It is used as the provenance anchor in -/// `CollapseGateEmission` so receivers can locate the source row without an -/// explicit cycle-id field (per plan §8.1: provenance via source + chain_position). +/// accumulator in `MailboxSoA`. Consumers use it as the provenance anchor +/// wherever a mailbox must be referenced by value (witness tables, Kanban +/// moves, SoA views). pub type MailboxId = u32; -/// Discrete baton emission from one CollapseGate to downstream consumers. -/// -/// Implements the gapless-baton model (plan §4.2 + §8): -/// - No `Vsa16kF32` envelope — the baton list IS its own wire format. -/// - No encode/decode at mailbox boundaries; the tuple is the wire. -/// - Provenance is implicit: `(source_mailbox, chain_position)` together -/// identify this emission uniquely within a discourse corpus. -/// -/// # Deviation from §8.1 sketch -/// -/// Plan §8.1 specifies `SmallVec<[(u16, CausalEdge64); 8]>`. This -/// implementation uses `Vec<(u16, u64)>` (zero-dep, heap-indirect): -/// - `u64` is the raw CausalEdge64 packing; receivers reconstruct via -/// `CausalEdge64(raw)`. -/// - The SmallVec inline-storage optimisation is deferred to sprint-12+. -/// - `#[repr(C)]` is omitted (heap indirection via Vec makes it moot). -/// -/// # Wire-cost (§8.2) -/// -/// ```text -/// header = 13 bytes (source_mailbox u32 + chain_position u32 + merge_mode u8 + 4 B reserved) -/// per baton = 10 bytes (u16 target + u64 edge raw) -/// total = 13 + 10 × baton_count -/// 8 batons → 93 bytes (§8.2 budget) -/// ``` -/// -/// # No analog bucket (§8.3) -/// -/// `Vsa16kF32` does not cross mailbox boundaries. Three candidate needs -/// that would have forced it — compound bundles, Markov ±5 braiding, -/// continuous strength values — are all satisfied by discrete tuple lists. -/// Receiver's `apply_edges` re-superposes via energy addition (same algebra). -/// -/// # Cycle ID -/// -/// There is NO `cycle_id` field. Provenance is fully determined by -/// `(source_mailbox, chain_position)` per plan §8.1. Consumers locate the -/// source mailbox row and its position in the witness chain without an -/// explicit cycle reference. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CollapseGateEmission { - /// Per-target discrete batons. Each `(target, edge_raw)` tuple is one - /// neuron-to-neuron delivery. `edge_raw` is the packed u64 of a - /// `CausalEdge64`; receivers reconstruct via `CausalEdge64(edge_raw)`. - /// - /// Uses `Vec` rather than `SmallVec<[…; 8]>` to keep contract zero-dep. - /// SmallVec optimisation deferred to sprint-12+. - batons: Vec<(u16, u64)>, - - /// Source mailbox identity — the W-slot corpus-root handle that produced - /// this emission. Together with `chain_position` this is the full provenance. - source_mailbox: MailboxId, - - /// Position of this emission in the source mailbox's witness chain. - /// Encodes the temporal axis structurally (plan §4.4 — "temporal causality - /// is structural, not stored"). No wall-clock timestamp; relative order - /// is chain_position; absolute anchor is AriGraph `Triplet.timestamp`. - chain_position: u32, - - /// Merge hint for the receiving CollapseGate. - /// - /// - [`MergeMode::Bundle`] — associative superposition (Markov-respecting). - /// - [`MergeMode::Xor`] — single-writer delta (faster, breaks Markov; - /// per `I-SUBSTRATE-MARKOV` iron rule, Xor is NOT a Markov transition - /// kernel — only for deltas). - /// - [`MergeMode::Superposition`] — keep ALL deltas without resolution. - /// - [`MergeMode::AlphaFrontToBack`] — volumetric front-to-back composite. - merge_mode: MergeMode, -} - -impl CollapseGateEmission { - /// Create a new emission with no batons yet. - /// - /// # Arguments - /// - /// * `source` — The [`MailboxId`] of the emitting mailbox. - /// * `chain_position` — Position in the source's witness chain (structural time). - /// * `merge_mode` — How the receiving CollapseGate should merge this emission. - /// - /// # Wire cost at construction - /// - /// `wire_cost_bytes()` returns 13 (header only) until batons are pushed. - #[inline] - pub fn new(source: MailboxId, chain_position: u32, merge_mode: MergeMode) -> Self { - Self { - batons: Vec::new(), - source_mailbox: source, - chain_position, - merge_mode, - } - } - - /// Append a baton targeting `target` mailbox with raw edge packing `edge`. - /// - /// `edge` is the raw u64 packing of a `CausalEdge64`; the receiver - /// reconstructs via `CausalEdge64(edge)`. - #[inline] - pub fn push_baton(&mut self, target: u16, edge: u64) { - self.batons.push((target, edge)); - } - - /// Number of batons currently in this emission. - #[inline] - pub fn baton_count(&self) -> usize { - self.batons.len() - } - - /// Serialised wire cost in bytes (plan §8.2). - /// - /// Formula: `13 + 10 × baton_count` - /// - /// - 13-byte header: `source_mailbox` (4 B) + `chain_position` (4 B) - /// + `merge_mode` tag (1 B) + 4 B reserved headroom. - /// - 10 bytes per baton: `u16` target (2 B) + `u64` edge raw (8 B). - /// - /// At 8 batons this is 93 bytes (§8.2 "Total inline emission: ~93 bytes"). - #[inline] - pub fn wire_cost_bytes(&self) -> usize { - 13 + 10 * self.batons.len() - } - - // ── Provenance accessors ───────────────────────────────────────────────── - - /// The [`MailboxId`] of the mailbox that produced this emission. - #[inline] - pub fn source_mailbox(&self) -> MailboxId { - self.source_mailbox - } - - /// Position of this emission in the source mailbox's witness chain. - /// Encodes temporal order structurally (plan §4.4). - #[inline] - pub fn chain_position(&self) -> u32 { - self.chain_position - } - - /// The merge mode hint for the receiving CollapseGate. - #[inline] - pub fn merge_mode(&self) -> MergeMode { - self.merge_mode - } - - /// Read-only view of the baton list. - /// Each element is `(target_mailbox_id: u16, edge_raw: u64)`. - #[inline] - pub fn batons(&self) -> &[(u16, u64)] { - &self.batons - } -} - #[cfg(test)] mod tests { use super::*; - // ── helpers ────────────────────────────────────────────────────────────── - - fn make_emission(mode: MergeMode) -> CollapseGateEmission { - CollapseGateEmission::new(42, 7, mode) - } - - // ── D-CSV-4 test suite (8 tests) ───────────────────────────────────────── - - /// New emission must have 0 batons and wire_cost == 13. - #[test] - fn test_emission_new_empty() { - let e = make_emission(MergeMode::Bundle); - assert_eq!(e.baton_count(), 0); - assert_eq!(e.wire_cost_bytes(), 13); - } - - /// Pushing 3 batons: count == 3, wire_cost == 43. - #[test] - fn test_emission_push_baton() { - let mut e = make_emission(MergeMode::Bundle); - e.push_baton(1, 0xDEAD_BEEF_CAFE_0001_u64); - e.push_baton(2, 0xDEAD_BEEF_CAFE_0002_u64); - e.push_baton(3, 0xDEAD_BEEF_CAFE_0003_u64); - assert_eq!(e.baton_count(), 3); - assert_eq!(e.wire_cost_bytes(), 43); // 13 + 10*3 = 43 - } - - /// 8 batons → wire_cost == 93 (§8.2 inline budget). - #[test] - fn test_emission_wire_cost_8_batons() { - let mut e = make_emission(MergeMode::Bundle); - for i in 0u16..8 { - e.push_baton(i, i as u64); - } - assert_eq!(e.baton_count(), 8); - assert_eq!(e.wire_cost_bytes(), 93); // 13 + 10*8 = 93 - } - - /// Provenance fields survive construction; no public `cycle_id()` method. - /// - /// This test asserts that `source_mailbox` and `chain_position` are - /// accessible and correct. The compile-time absence of `cycle_id()` is - /// enforced structurally — the struct has no such field, so any caller - /// attempting `e.cycle_id()` will receive a compile error. - #[test] - fn test_emission_provenance_no_cycle_id() { - let e = CollapseGateEmission::new(99, 42, MergeMode::Xor); - assert_eq!(e.source_mailbox(), 99u32); - assert_eq!(e.chain_position(), 42u32); - // Verify that `CollapseGateEmission` exposes NO `cycle_id` method. - // This is structural: the field does not exist, so the following line - // would not compile if uncommented: - // let _ = e.cycle_id(); // ← compile error: no method named `cycle_id` - } - - /// Xor merge mode threads through correctly. - #[test] - fn test_emission_merge_mode_xor() { - let e = make_emission(MergeMode::Xor); - assert_eq!(e.merge_mode(), MergeMode::Xor); - } - - /// Bundle merge mode threads through correctly. - #[test] - fn test_emission_merge_mode_bundle() { - let e = make_emission(MergeMode::Bundle); - assert_eq!(e.merge_mode(), MergeMode::Bundle); - } - - /// Superposition merge mode threads through correctly. + /// GateDecision constants carry the (gate, merge) pairs they advertise. #[test] - fn test_emission_merge_mode_superposition() { - let e = make_emission(MergeMode::Superposition); - assert_eq!(e.merge_mode(), MergeMode::Superposition); - } - - /// Full provenance round-trip: encode fields → extract → reconstruct → - /// assert equal. This replaces the separate AlphaFrontToBack mode test - /// (which is covered inside the round-trip) and is the D-CSV-4 "8 - /// round-trip tests" anchor from plan §16 / §15. - /// - /// Also verifies AlphaFrontToBack mode threads through correctly. + fn test_gate_decision_constants() { + assert!(GateDecision::FLOW_XOR.is_flow()); + assert_eq!(GateDecision::FLOW_XOR.merge, MergeMode::Xor); + assert!(GateDecision::FLOW_BUNDLE.is_flow()); + assert_eq!(GateDecision::FLOW_BUNDLE.merge, MergeMode::Bundle); + assert!(GateDecision::FLOW_SUPER.is_flow()); + assert_eq!(GateDecision::FLOW_SUPER.merge, MergeMode::Superposition); + assert!(GateDecision::BLOCK.is_block()); + assert!(GateDecision::HOLD.is_hold()); + } + + /// MergeMode is a 1-byte discriminant (repr(u8)) with stable ordinals. #[test] - fn test_emission_round_trip() { - // Build original emission with AlphaFrontToBack (covers merge-mode test too) - let mut original = CollapseGateEmission::new(123, 456, MergeMode::AlphaFrontToBack); - original.push_baton(10, 0xAABBCCDD_11223344_u64); - original.push_baton(20, 0x55667788_99AABBCC_u64); - - // Extract all fields - let src = original.source_mailbox(); - let pos = original.chain_position(); - let mode = original.merge_mode(); - let batons: Vec<(u16, u64)> = original.batons().to_vec(); - - // Reconstruct from extracted fields - let mut reconstructed = CollapseGateEmission::new(src, pos, mode); - for (target, edge) in batons { - reconstructed.push_baton(target, edge); - } - - // Assert structural equality - assert_eq!(original, reconstructed); - - // Spot-check provenance + mode - assert_eq!(reconstructed.source_mailbox(), 123); - assert_eq!(reconstructed.chain_position(), 456); - assert_eq!(reconstructed.merge_mode(), MergeMode::AlphaFrontToBack); - assert_eq!(reconstructed.baton_count(), 2); - assert_eq!(reconstructed.wire_cost_bytes(), 33); // 13 + 10*2 + fn test_merge_mode_ordinals_stable() { + assert_eq!(MergeMode::Xor as u8, 0); + assert_eq!(MergeMode::Bundle as u8, 1); + assert_eq!(MergeMode::Superposition as u8, 2); + assert_eq!(MergeMode::AlphaFrontToBack as u8, 3); } } diff --git a/crates/lance-graph-contract/src/episodic_edges.rs b/crates/lance-graph-contract/src/episodic_edges.rs index 393d5e40..87a02c0b 100644 --- a/crates/lance-graph-contract/src/episodic_edges.rs +++ b/crates/lance-graph-contract/src/episodic_edges.rs @@ -255,9 +255,11 @@ impl EpisodicEdges64 { } // ── Little-endian byte contract (mirrors `causal-edge::CausalEdge64`) ── - // The frozen LE byte grammar every AriGraph consumer reads: surrealkv WAL, the - // baton wire (`CollapseGateEmission`), Lance columns. Canonical little-endian, - // platform-independent — changing the layout is a WAL/wire migration, never silent. + // The frozen LE byte grammar every AriGraph consumer reads: surrealkv WAL and + // Lance columns (the baton-wire consumer was retired with `CollapseGateEmission` + // per PR #477 — zero-copy in-place store, no inter-mailbox carrier). Canonical + // little-endian, platform-independent — changing the layout is a WAL migration, + // never silent. /// The raw packed `u64` (host value; serialize via [`to_le_bytes`](Self::to_le_bytes)). #[must_use] diff --git a/crates/lance-graph-contract/src/kanban.rs b/crates/lance-graph-contract/src/kanban.rs index 54e6fdfb..38a1b33a 100644 --- a/crates/lance-graph-contract/src/kanban.rs +++ b/crates/lance-graph-contract/src/kanban.rs @@ -16,9 +16,9 @@ //! - **R1 "one SoA never transformed":** a [`KanbanMove`] is a *transition record*, //! not SoA data — it carries only `Copy` scalars + a pointer, never the SoA. //! - **R4 witness-as-pointer:** the witness is a `chain_position` index into the -//! source mailbox's witness arc (mirrors -//! [`crate::collapse_gate::CollapseGateEmission`]'s `chain_position`), never the -//! witnessed data. +//! source mailbox's witness arc — structural time, never the witnessed data. +//! (The retired `CollapseGateEmission` carrier used the same convention; the +//! convention survives the carrier's removal per PR #477.) use crate::collapse_gate::MailboxId; @@ -116,9 +116,9 @@ pub struct KanbanMove { pub from: KanbanColumn, /// Column the mailbox is entering. pub to: KanbanColumn, - /// Witness pointer: position in the source mailbox's witness chain. Mirrors - /// [`crate::collapse_gate::CollapseGateEmission`]'s `chain_position` — - /// structural time, not a wall-clock stamp (R4). + /// Witness pointer: position in the source mailbox's witness chain — + /// structural time, not a wall-clock stamp (R4). (Same convention the + /// retired `CollapseGateEmission` carrier used, kept after its removal.) pub witness_chain_position: u32, /// Libet commit anchor: signed micros relative to the act. `-550_000` on the /// `Planning → CognitiveWork` Σ-commit; `0` otherwise. Structural offset only. diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index f5f5e224..0cd1ffde 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -107,7 +107,7 @@ pub mod world_model; // Re-exports for the most commonly used collapse_gate types. pub use class_view::{ClassId, ClassProjection, ClassView, FieldMask, RenderRow}; -pub use collapse_gate::{CollapseGateEmission, GateDecision, MailboxId, MergeMode}; +pub use collapse_gate::{GateDecision, MailboxId, MergeMode}; pub use episodic_edges::{EdgeRef, EpisodicEdges64}; pub use head2head::{CompetitionOutcome, Head2Head, WinnerCriterion}; pub use identity::{NodeGuid, IDENTITY_LAYOUT_VERSION}; diff --git a/crates/lance-graph/src/graph/witness_tombstone.rs b/crates/lance-graph/src/graph/witness_tombstone.rs index 59e3479f..7e14845d 100644 --- a/crates/lance-graph/src/graph/witness_tombstone.rs +++ b/crates/lance-graph/src/graph/witness_tombstone.rs @@ -47,14 +47,16 @@ /// The ephemeral, in-mailbox episodic working record of a single AriGraph fact /// before it stabilises and calcifies into the cold SPO ontology. /// -/// # NOT a persisted singleton (E-BATON-1) +/// # NOT a persisted singleton (E-BATON-1, scoped by PR #477) /// /// `HotWitness` exists **only inside the owning mailbox** for the duration of /// its sea-star lifecycle (spawn → work → die → merge). It is **never** -/// transmitted across mailbox boundaries as a carrier — the Baton -/// (`CollapseGateEmission`) is the inter-mailbox handoff; `HotWitness` stays -/// local and ephemeral. See `EPIPHANIES.md` E-BATON-1 and the Baton-scoping -/// section of `CLAUDE.md` §"The Click" for the canonical rationale. +/// transmitted across mailbox boundaries as a carrier — per the PR #477 +/// three-tier model there is no inter-mailbox handoff type at all (the former +/// Baton/`CollapseGateEmission` framing is superseded); the SoA is zero-copy +/// from creation to Lance tombstone and `HotWitness` stays local and +/// ephemeral. See `docs/architecture/soa-three-tier-model.md` and +/// `EPIPHANIES.md` E-BATON-1 for the lineage. /// /// When the mailbox dies the [`HotWitness`] is either: /// - **calcified** (fact stabilised) → [`calcify`] produces an SPO cold record