diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 1c289577..b4d3e8ff 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,5 +1,11 @@ # LATEST_STATE — What Just Shipped (read this FIRST) +## 2026-07-14 — branch `claude/review-medcare-rust-dt7MS` — `class_view::execute_defaults` + `ClassView::default_targets` — the Default-recipe half of the ActionDef value executor (lane-3a inc 2) + +### Current Contract Inventory — new entry + +- **`class_view::execute_defaults(targets, present, store, apply_default)`** (NEW free fn next to `execute_compute_dag`) + **`ClassView::default_targets(class) -> &[u8]`** (NEW default trait method, zero-fallback `&[]`, next to `compute_dag`). The **Default-recipe** (write-if-blank, `RecipeCentroid::Default` — the C# `if (field == null) field = new …` lazy-init idiom, 56 methods in the MedCare corpus) execution primitive: fires `apply_default(store, t)` for each target whose presence bit is CLEAR, in slice order; skips present AND already-fired positions (duplicate-safe — after a default fires the field IS populated; pins the C# `GetOrCreateChartPanel` init-only-on-create quirk, devcomponent_chart.cs:161-180); abort-at-target reusing `ExecuteComputeError::Compute` (`Cyclic` unreachable — defaults have no dependency order; a default reading a computed field is a Compute recipe). **Presence gate = `WideFieldMask`** (not `FieldMask`): every u8 position addressable, no 64-field ceiling — the wide-mask lesson the a2ui screen-addressing #205 correction paid for, applied at birth. Phase rule documented (not interleaved): defaults run BEFORE `execute_compute_dag`; the caller folds the fired list into its presence mask. One more brick, not a parallel path (the `screens_reachable_from` precedent): existing mask, existing error, same abort semantics as the Compute half. +7 unit tests (slice-order fire, present-skip, abort-keeps-earlier, empty-noop, wide-fire-past-64, duplicate-fires-once, hook-default-empty); 898 contract tests green; fmt + clippy clean (also carries the pending rustfmt reflow on `grammar/thinking_styles.rs` + `style_family.rs` — main was fmt-dirty on those two test files). Consumer witness lands in MedCare-rs (`chart_default_parity`, same arc). + ## 2026-07-12 — branch `claude/review-claude-board-files-nhqgx1` — `contract::temporal_pov::{VersionRange, TemporalPov}` — zero-dep temporal POV range filter (operator-directed) ### Current Contract Inventory — new entry diff --git a/.claude/board/PR_ARC_INVENTORY.md b/.claude/board/PR_ARC_INVENTORY.md index 98b68f45..f38695c1 100644 --- a/.claude/board/PR_ARC_INVENTORY.md +++ b/.claude/board/PR_ARC_INVENTORY.md @@ -33,6 +33,14 @@ > - **Docs** — knowledge files produced (immutable) > - **Confidence (YYYY-MM-DD):** — the ONLY mutable field +## PR (pending) — `claude/review-medcare-rust-dt7MS` — lane-3a inc 2: `execute_defaults` (Default-recipe half) + +- **Added:** `class_view::execute_defaults` (fire-if-absent runner over `WideFieldMask` presence, ~40 LOC + docs); `ClassView::default_targets` (zero-fallback default trait method); 7 unit tests. +- **Locked:** (1) Default-recipe presence gate is the WIDE mask from birth — no 64-field ceiling anywhere in the Default path (a2ui #205 lesson applied preemptively). (2) `ExecuteComputeError` is the ONE error type for the value-executor phase family (defaults + recompute thread one error; `Cyclic` documented-unreachable for defaults). (3) Duplicate manifest entries fire at most once — post-fire, a position IS present (the C# GetOrCreateChartPanel init-only-on-create quirk, pinned by test). (4) Phase rule: defaults BEFORE recompute DAG; documented, not interleaved (no combined runner in inc 2). (5) The consumer trait seam (`ActionDefExecutor`, serde-shaped) STAYS in medcare-rs — the zero-dep contract carries recipe-kind execution primitives only (the inc-1 precedent, ratified reading of medcare commitment #5's "upstream executor"). +- **Deferred:** Guard recipe (needs pre-write validation phase + `ClassView::constraints`, explicitly deferred since probe-excel-compute-dag-v1 Inc 0); Cascade recipe (cross-row addressing, dirty-set, reverse closure); a combined defaults-then-recompute runner; Normalize (1 corpus method — coverage worthless until Guard lands). +- **Docs:** this entry; medcare-rs side documents the corpus enumeration (56 Defaults, `DEFAULT-RECIPE-TARGETS.md`). +- **Confidence (2026-07-14):** shipped-in-PR; 898 contract tests green. + --- ## #676 lance-graph: Post-#674 doc arc — E-NOBODY-WAITS-1 + VISION.md (graded AGI canon) + ancestry census + D-MTS-1 design inputs diff --git a/crates/lance-graph-contract/src/class_view.rs b/crates/lance-graph-contract/src/class_view.rs index 3e0e7200..4529456d 100644 --- a/crates/lance-graph-contract/src/class_view.rs +++ b/crates/lance-graph-contract/src/class_view.rs @@ -823,6 +823,70 @@ pub fn execute_compute_dag( Ok(order) } +/// Execute a class's **default targets** over a consumer-owned store — the +/// Default-recipe half of the ActionDef value executor (the medcare transpile +/// arc's lane-3a increment 2, following the Compute half above). +/// +/// A *Default* recipe is write-if-blank (`this.Name ??= "unknown"` — the +/// `RecipeCentroid::Default` centroid): the value fires ONLY where the field +/// is not already populated. Same split of responsibilities as +/// [`execute_compute_dag`]: this function owns the *fire/skip decision* +/// (presence-gated, slice order), while `apply_default` owns *value +/// semantics* — it writes field position `t`'s default onto `store` (a +/// hand-ported default expression; never a formula invented here). +/// +/// One more brick, not a parallel path (the [`screens_reachable_from`] +/// precedent): the presence gate is the existing [`WideFieldMask`] — every +/// `u8` position is addressable, so there is no 64-field ceiling to skip +/// around (the wide-mask lesson the screen-addressing charter's #205 +/// correction paid for). The error is the existing [`ExecuteComputeError`] +/// so a defaults-then-recompute pipeline threads ONE error type; +/// [`ExecuteComputeError::Cyclic`] is unreachable from this function +/// (defaults have no dependency order — a default that reads another +/// computed field is a Compute recipe, not a Default). +/// +/// Fire rules, in order, per target in **slice order**: +/// +/// - `present.has(t)` → **skip** (the field is populated; a default never +/// overwrites); +/// - `t` already fired earlier in this call (duplicate manifest entry) → +/// **skip** (after firing, the field IS populated — a second entry sees +/// it present); +/// - otherwise → `apply_default(store, t)`; on error, abort-at-target +/// (earlier fired defaults remain written, matching +/// [`execute_compute_dag`]'s abort semantics). +/// +/// Returns the fired positions, in fire order (unique). The caller folds +/// them into its presence mask (`fired.iter().fold(present, |m, &p| +/// m.with(p))`) before running [`execute_compute_dag`] — **defaults run +/// BEFORE the recompute DAG** (a recompute reads post-default inputs), the +/// phase rule this increment documents but does not interleave. +/// +/// # Errors +/// +/// [`ExecuteComputeError::Compute`] wrapping the consumer's error at the +/// failing target (later targets are not attempted). Never +/// [`ExecuteComputeError::Cyclic`]. +pub fn execute_defaults( + targets: &[u8], + present: &WideFieldMask, + store: &mut S, + mut apply_default: impl FnMut(&mut S, u8) -> Result<(), E>, +) -> Result, ExecuteComputeError> { + let mut fired_mask = WideFieldMask::EMPTY; + let mut fired = Vec::new(); + for &t in targets { + if present.has(t) || fired_mask.has(t) { + continue; + } + apply_default(store, t) + .map_err(|source| ExecuteComputeError::Compute { target: t, source })?; + fired_mask = fired_mask.with(t); + fired.push(t); + } + Ok(fired) +} + /// The class as a **meta lookup that flies above the SoA** — the resolver trait. /// /// An implementor (in `lance-graph-ontology`, over the OGIT cache) is the @@ -1089,6 +1153,23 @@ pub trait ClassView { fn compute_dag(&self, _class: ClassId) -> &[ComputeEdge] { &[] } + + /// The class's **default targets** — the field positions carrying a + /// write-if-blank default (the `RecipeCentroid::Default` harvest: + /// `this.Name ??= …`, the C# lazy-init `if (field == null) field = new …` + /// idiom), in declaration order. The manifest [`execute_defaults`] + /// consumes — same layering as [`compute_dag`](ClassView::compute_dag): + /// resolution metadata above the SoA, stores nothing on the row. + /// + /// Default `&[]` — the zero-fallback: an unconfigured class has no + /// defaulted fields. An implementor returns a generated `const &[u8]`; + /// no build-time validation is needed (defaults are order-free and + /// duplicate-safe — [`execute_defaults`] fires each position at most + /// once). Phase rule: defaults run BEFORE the recompute DAG. + #[inline] + fn default_targets(&self, _class: ClassId) -> &[u8] { + &[] + } } /// One populated field to render — the late-resolved `label` + its `predicate` key. @@ -1450,6 +1531,133 @@ mod tests { assert_eq!(row, [1]); } + // ── execute_defaults (lane-3a increment 2: the Default-recipe half) ────── + + /// An empty presence mask fires every target, in SLICE order (declaration + /// order — defaults have no dependency order to reorder by). + #[test] + fn execute_defaults_fires_absent_targets_in_slice_order() { + let mut row = [0i32; 8]; + let fired = execute_defaults(&[3, 1, 7], &WideFieldMask::EMPTY, &mut row, |r, t| { + r[t as usize] = 100 + i32::from(t); + Ok::<(), core::convert::Infallible>(()) + }) + .expect("defaults fire"); + assert_eq!(fired, vec![3, 1, 7], "slice order, not sorted"); + assert_eq!(row[3], 103); + assert_eq!(row[1], 101); + assert_eq!(row[7], 107); + } + + /// A present field is skipped — a default never overwrites (the + /// write-if-blank contract; the C# `if (field == null)` guard). + #[test] + fn execute_defaults_skips_present_targets() { + let mut row = [0i32; 8]; + row[1] = 999; // already populated + let present = WideFieldMask::from_positions(&[1]); + let fired = execute_defaults(&[3, 1, 7], &present, &mut row, |r, t| { + r[t as usize] = 100 + i32::from(t); + Ok::<(), core::convert::Infallible>(()) + }) + .unwrap(); + assert_eq!(fired, vec![3, 7], "present target 1 not fired"); + assert_eq!(row[1], 999, "populated value untouched"); + } + + /// A failing default aborts at its target: earlier fired defaults stay + /// written, later targets never run (matching execute_compute_dag). + #[test] + fn execute_defaults_abort_at_target_keeps_earlier_writes() { + let mut row = [0i32; 8]; + let err = execute_defaults( + &[3, 1, 7], + &WideFieldMask::EMPTY, + &mut row, + |r, t| match t { + 3 => { + r[3] = 103; + Ok(()) + } + _ => Err("boom"), + }, + ) + .unwrap_err(); + assert_eq!( + err, + ExecuteComputeError::Compute { + target: 1, + source: "boom" + } + ); + assert_eq!(row[3], 103, "earlier default stays written"); + assert_eq!(row[7], 0, "later target never ran"); + } + + /// Empty target manifest = nothing fired (zero-fallback, mirrors + /// `compute_dag`'s empty default). + #[test] + fn execute_defaults_empty_targets_is_noop() { + let mut row = [5i32]; + let fired = execute_defaults(&[], &WideFieldMask::EMPTY, &mut row, |_, _| { + Ok::<(), core::convert::Infallible>(()) + }) + .unwrap(); + assert!(fired.is_empty()); + assert_eq!(row, [5]); + } + + /// Positions past 64 fire natively — the presence gate is the WIDE mask, + /// so there is no 64-field ceiling (the wide-mask lesson the + /// screen-addressing charter's #205 correction paid for; every u8 + /// position is addressable). + #[test] + fn execute_defaults_fires_wide_positions_past_64() { + let mut hits: Vec = Vec::new(); + let fired = execute_defaults(&[133, 200], &WideFieldMask::EMPTY, &mut hits, |h, t| { + h.push(t); + Ok::<(), core::convert::Infallible>(()) + }) + .unwrap(); + assert_eq!(fired, vec![133, 200], "wide positions fire"); + // ...and a wide presence bit skips exactly its position. + let present = WideFieldMask::from_positions(&[133]); + let fired = execute_defaults(&[133, 200], &present, &mut hits, |h, t| { + h.push(t); + Ok::<(), core::convert::Infallible>(()) + }) + .unwrap(); + assert_eq!(fired, vec![200], "wide present bit 133 skips"); + } + + /// A duplicate manifest entry fires at most once: after a default fires, + /// the field IS populated — the second entry sees it present. (The C# + /// GetOrCreateChartPanel quirk: create+init run only in the absent + /// branch; a present panel is returned untouched.) + #[test] + fn execute_defaults_duplicate_target_fires_once() { + let mut count = 0u32; + let fired = execute_defaults(&[5, 5, 5], &WideFieldMask::EMPTY, &mut count, |c, _| { + *c += 1; + Ok::<(), core::convert::Infallible>(()) + }) + .unwrap(); + assert_eq!(fired, vec![5], "unique fire list"); + assert_eq!(count, 1, "closure ran exactly once"); + } + + /// Default `default_targets` is the zero-fallback empty manifest (no + /// defaulted fields for an unconfigured class). + #[test] + fn default_targets_default_is_empty() { + let c = FakeClasses { + invoice: vec![], + ..Default::default() + }; + assert!(c.default_targets(7).is_empty()); + assert!(c.default_targets(0).is_empty()); + } + #[test] fn nav_reachability_is_a_forward_closure() { // root(0) -> orders(1) -> lines(2); root -> settings(3) diff --git a/crates/lance-graph-contract/src/grammar/thinking_styles.rs b/crates/lance-graph-contract/src/grammar/thinking_styles.rs index 70d14f69..257ba586 100644 --- a/crates/lance-graph-contract/src/grammar/thinking_styles.rs +++ b/crates/lance-graph-contract/src/grammar/thinking_styles.rs @@ -1141,7 +1141,11 @@ coverage: ("reflective", ThinkingStyle::Reflective), ("sovereign", ThinkingStyle::Sovereign), ] { - assert_eq!(parse_style_name(name).unwrap(), expected, "passthrough: {name}"); + assert_eq!( + parse_style_name(name).unwrap(), + expected, + "passthrough: {name}" + ); } assert!(parse_style_name("nonsensestyle").is_err()); } diff --git a/crates/lance-graph-contract/src/style_family.rs b/crates/lance-graph-contract/src/style_family.rs index bd7c5f8c..df5c5bf3 100644 --- a/crates/lance-graph-contract/src/style_family.rs +++ b/crates/lance-graph-contract/src/style_family.rs @@ -186,7 +186,11 @@ mod tests { #[test] fn default_runbook_round_trips_to_its_family() { for f in StyleFamily::ALL { - assert_eq!(f.default_runbook().family(), f, "round-trip broke for {f:?}"); + assert_eq!( + f.default_runbook().family(), + f, + "round-trip broke for {f:?}" + ); } } @@ -271,7 +275,10 @@ mod tests { for (i, f) in StyleFamily::ALL.into_iter().enumerate() { assert_eq!(StyleFamily::from_ordinal(i as u8), Some(f)); assert_eq!(StyleFamily::from_name(f.name()), Some(f)); - assert_eq!(StyleFamily::from_name(&f.name().to_ascii_uppercase()), Some(f)); + assert_eq!( + StyleFamily::from_name(&f.name().to_ascii_uppercase()), + Some(f) + ); } assert_eq!(StyleFamily::from_ordinal(12), None); assert_eq!(StyleFamily::from_name("empathetic"), None); // a runbook name, not a family