Skip to content

Commit 328ea31

Browse files
authored
Merge pull request #140 from AdaWorldAPI/claude/odoo-rs-transcode-lf8ya5
docs(classview-bitmask + §1.5): class-conditioned shape, simple rules
2 parents 2af8f48 + f7ace0a commit 328ea31

2 files changed

Lines changed: 126 additions & 38 deletions

File tree

docs/CLASSVIEW-FIELDVIEW-ASKAMA-BITMASK.md

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,52 @@ The bitmask **is** the selected / unselected partition:
6666
Versions, roles, and projections are simply **different masks over the same
6767
template**. There is no template per version — one compiled artifact, any subset.
6868

69+
## Wide classes — class-conditioned shape, not a locked width
70+
71+
The `u64` mask above is **one bucket** of 64 field positions. A wide class — an
72+
Odoo `account.move` carries ~100+ fields — overflows one bucket, but **not the
73+
pattern**: the mask widens with the class, and the `selected()` loop is
74+
bucket-agnostic (it filters `FieldDesc[]` by `idx`; the bucket is `idx / 64`, the
75+
bit `idx % 64`). So a ~109-field model is clean.
76+
77+
**Crucially, the width is not a locked constant — it is class-conditioned**
78+
(operator veto 2026-06-29). The mask shape is **mapped from the class's inherited
79+
format and selected by `classid`** (the filter): the cascade is one of the
80+
per-class [`CascadeShape`](../../lance-graph/crates/lance-graph-contract/src/facet.rs)s
81+
**Rails → `6×2`, other frameworks → `4×3`, the canonical GUID → `3×4`** (all
82+
`G·D = 12`, 8-bit tiers; the depth `D ∈ {2,3,4}` is the per-class knob, via
83+
`CascadeShape::from_levels(d)`). Do **not** restate or lock a `[u64; 4]`
84+
"quadruplet" — that was a misread of the `3×4` GUID shape; the real knob is the
85+
inherited, classid-selected `D`.
86+
87+
The only fixed bound is the **god-object cardinality**: `< 256` (the byte
88+
cardinality / the per-tier sibling rank) is maskable by one ClassView; `≥ 256`
89+
is the SoC split signal — split into sub-ClassViews, never widen/lock a mask.
90+
Pinned + tested in `ruff_spo_address::soc`: `FIELD_MASK_CAP = MAX_SIBLINGS_PER_TIER`
91+
(one cap, not a second lock), the `Duplication` verdict collapses to
92+
`≤ FIELD_MASK_CAP` distinct `field_type`s (a 109-field class is `Duplication`/
93+
maskable, not a `Counterexample`). The matching
94+
`lance_graph_contract::class_view::FieldMask` (today `u64` / `MAX_FIELDS = 64`)
95+
is the *eventual* expansion — to the class-conditioned shape, not a locked width,
96+
validated by the ruff test.
97+
98+
## Simple rules (operator 2026-06-29)
99+
100+
- **If it's a template, it's probably a ClassView.** A Redmine ERB partial, an
101+
Odoo view, an Askama field partial — each is a render over a class's field set,
102+
i.e. a `ClassView` + a mask. Don't reach for a per-template type; reach for a
103+
mask over the generated `FieldDesc[]`.
104+
- **Deduplicate routes.** N routes that are "the same record, different visible
105+
fields" (a card, a full view, an RBAC view, a tenant projection) are **one**
106+
templated ClassView render with N masks — not N handlers. Route proliferation
107+
is usually an un-applied mask.
108+
- **`< 256` is clean; `≥ 256` is the god-object signal.** A field/sibling set
109+
under the byte cardinality is maskable by one ClassView (in whatever
110+
class-conditioned shape its `classid` selects). At/over 256 the design (not the
111+
storage) is the problem — split concerns into sub-ClassViews, the same SoC the
112+
`ruff_spo_address::soc` lint flags. Never widen/lock a mask to dodge the split;
113+
and never restate the shape — it is inherited and `classid`-selected.
114+
69115
## Why this is right (not just convenient)
70116

71117
1. **§1.5 alignment — render-mask = read-mask.** "One compiled reader subsumes
@@ -119,10 +165,13 @@ user-authored, per-tenant templates — never as the default.
119165

120166
## Summary
121167

122-
One generic Askama field partial + a generated `FieldDesc[]` table + a `u64`
123-
mask = the whole dynamic ClassView field view: Redmine-shaped, JSON-free, no
124-
conditionals in the template, type-checked structure, dynamic across
125-
versions/roles/projections. **The mask carves; the loop renders.** Askama's
168+
One generic Askama field partial + a generated `FieldDesc[]` table + a mask
169+
whose width follows the class's **class-conditioned shape** (`6×2`/`4×3`/`3×4`,
170+
selected by `classid` from the inherited format — never a locked width) = the
171+
whole dynamic ClassView field view: Redmine-shaped, JSON-free, no conditionals
172+
in the template, type-checked structure, dynamic across versions/roles/
173+
projections, and wide-class-clean (a god object at `≥ 256` is a split signal, not
174+
a wider/locked mask). **The mask carves; the loop renders.** Askama's
126175
compile-time nature is not a cage — the mask is the runtime knob, and it is the
127176
same selector the data layer already uses to prune columns.
128177

@@ -135,3 +184,13 @@ same selector the data layer already uses to prune columns.
135184
pattern slots into.
136185
- `I-LEGACY-API-FEATURE-GATED` — why the mask bits must be generated, never
137186
hand-numbered alongside a layout.
187+
- `ruff_spo_address::soc``FIELD_MASK_CAP = MAX_SIBLINGS_PER_TIER` (the
188+
byte-cardinality cap, one bound not a second lock) and the `≥ 256` god-object
189+
SoC lint (where the "wide classes / split, don't widen, shape is inherited"
190+
rule is tested).
191+
- `lance_graph_contract::facet::CascadeShape` — the class-conditioned shape
192+
(`6×2`/`4×3`/`3×4`, `from_levels(d)`) the mask width follows; selected by
193+
`classid`, never locked.
194+
- `lance_graph_contract::canonical_node::GUIDS_PER_NODE` (= 32) — the node-level
195+
twin: clean/SoC over packed, Tetris concerns across the 32 GUID slots; the
196+
field-level mask here is the same SoC doctrine one level down.

docs/OGAR-TRANSPILE-SUBSTRATE.md

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -87,38 +87,49 @@ facet, baked into the binary. Three properties:
8787

8888
### a. One ClassView *rotates* the facet layout — no "versions"
8989

90-
The 16-byte facet's tier payload is not locked to a single carving. A ClassView
91-
can **always rotate** — read the SAME 12 cascade bytes under a different
92-
grouping — to fit the class. The carvings (pinned as
93-
`lance_graph_contract::facet::CascadeShape`, `CascadeShape::ROTATIONS`):
90+
The 16-byte facet's tier payload is not locked to a single carving. The shape is
91+
**class-conditioned***mapped from the class's inherited format and selected by
92+
`classid`* (the filter), never restated or locked (operator veto 2026-06-29). A
93+
ClassView can also **rotate** — read the SAME 12 cascade bytes under a different
94+
grouping. The shapes (pinned as `lance_graph_contract::facet::CascadeShape`,
95+
`CascadeShape::from_levels(d)`):
9496

9597
```
96-
6× (1:2) ALIGNED default — 6 tiers, each a 1:2 hierarchy (group_of = i >> 1, a shift)
97-
3× (1:2:3:4) ALIGNED default — 3 tier-pairs, each 1:2:3:4 (group_of = i >> 2, a shift)
98-
4× (1:2:3) WORST CASE — straddles tier boundaries (group_of = i / 3, a DIVIDE)
98+
6× (1:2) Rails — 6 tiers, each a 1:2 hierarchy (group_of = i >> 1, a shift)
99+
4× (1:2:3) other frameworks — 4 tier-groups, each 1:2:3 (group_of = i / 3, a divide)
100+
3× (1:2:3:4) canonical GUID — 3 tier-pairs, each 1:2:3:4 (group_of = i >> 2, a shift)
99101
```
100102

101-
**Only the byte-aligned carvings are defaults.** `6×(1:2)` and `3×(1:2:3:4)`
102-
keep `group_of` a pure shift (the canon's "tier-of-level is a shift, never a
103-
branch"). **`4×(1:2:3)` is the worst case, not a co-equal carving** — it
104-
straddles tier boundaries so `group_of` must DIVIDE (`CascadeShape::is_byte_aligned()`
105-
is `false`, `shift()` is `None`). It is *prevented on the common path* and kept
106-
only as the **rare rotation / escape hatch**: a ClassView may rotate to it
107-
deliberately when a rare class (some Odoo models) needs to relieve
108-
**classid-stacking entropy** — rotate the reading rather than mint another
109-
classid. So there is **no need for hardcoded facet "versions" (V1/V2/V3)** — one
110-
compiled ClassView subsumes the rotation set; the straddle stays legal only as a
111-
deliberate, rare rotation. Hardcoding a format per version is the thing to
112-
*delete*.
113-
114-
> **Carvings address the VIEW, never the functions.** A rotation re-reads the
115-
> data layout; it does NOT reach behaviour. Functions are encoded by the
103+
**The shape follows the class, not a global lock** (operator: "Rails might need
104+
6x2x8bit, others 4x3x8bit"). The depth `D ∈ {2,3,4}` is a per-class constant the
105+
`classid` resolves (`CascadeShape::from_levels`). `6×2`/`3×4` carve on tier
106+
boundaries so `group_of` is a pure shift (the canon's "tier-of-level is a shift,
107+
never a branch"); **`4×3` is legitimate for the frameworks that need it** — its
108+
`group_of` divides (`is_byte_aligned()` is `false`), the per-class *cost* a class
109+
opts into, **not a prohibition**. So there is **no need for hardcoded facet
110+
"versions" (V1/V2/V3)** — one compiled ClassView reads whichever shape the
111+
classid selects. Hardcoding a format per version (or locking a single shape) is
112+
the thing to *delete*.
113+
114+
> **Carvings address the VIEW, never the functions.** A shape/rotation re-reads
115+
> the data layout; it does NOT reach behaviour. Functions are encoded by the
116116
> **classid acting as an additional switch**`lance_graph_contract::facet::ClassArm`
117117
> `{ View, Functions }`, the OGAR THINK/DO split (`OGAR-AST-CONTRACT.md`).
118118
> Reaching a function = switch the classid to the `Functions` arm (the
119119
> `ActionDef`/`KausalSpec` on the resolved Core node), *never* slice the
120-
> tier-bytes. A straddling carve to "get to" a function is exactly the worst
121-
> case the `4×(1:2:3)` example warns against.
120+
> tier-bytes — that is the genuine mistake (a straddle used to "get to" a
121+
> function), distinct from a class whose *data layout* legitimately needs `4×3`.
122+
123+
**Out of transpile scope — the `G2×48bit` encoding lane.** The byte-shaped
124+
shapes above (`6×2`/`4×3`/`3×4`, 8-bit tiers) are the *transpile / ClassView
125+
field-grouping* lane. A **separate** lane reads the same facet bytes at
126+
`G2×48bit` granularity — the two 48-bit chains (`FacetCascade::hi_chain` /
127+
`lo_chain`, cf. the CAM-PQ `6×256` path code) — for **helix** (location encoding;
128+
q2 / helix) and **CAM-PQ** (centroid encoding; lance-graph / DeepNSM). These are
129+
**not required by transpile** and must not be dragged into ClassView shape
130+
selection (operator 2026-06-29). The DeepNSM **COCA** 4096-word English-vocabulary
131+
CAM index codebook is a likely future `G2×48bit` consumer — it may *earn its
132+
keep* there, but it is not a transpile dependency.
122133

123134
### b. Sub-range mapping + nested ClassViews stacked into constructors
124135

@@ -172,16 +183,19 @@ case.)
172183
> `lance_graph_contract::facet::CascadeShape` (`G6D2` / `G4D3` / `G3D4`,
173184
> `G·D = CASCADE_UNITS = 12`) over `FacetCascade::tier_bytes()` — `index(g,l) =
174185
> g·D + l`, `group_of`/`level_of` inverses, `cascade_byte`, per-group LCP
175-
> `cascade_group_shared`. `CascadeShape::ALIGNED = [G3D4, G6D2]` are the
176-
> shift-`group_of` **defaults** (`shift()` is `Some`); `CascadeShape::ROTATIONS`
177-
> is the full rotation set a ClassView may rotate through. **`G4D3` is the worst
178-
> case**`is_byte_aligned()` is `false`, `shift()` is `None`, `group_of`
179-
> divides — excluded from `ALIGNED`, kept in `ROTATIONS` only as the rare
180-
> escape-hatch rotation (classid-stacking-entropy relief). **Functions are NOT a
181-
> carving**`facet::ClassArm { View, Functions }` is the classid's additional
182-
> THINK/DO switch; carvings address `View` only. Zero-dep, `const fn`,
183-
> probe-verified (lance-graph #621). One algebra for both the facet bytes and a
184-
> 12-field class — the shared substrate the three language SDKs (§1.6) all read.
186+
> `cascade_group_shared`. The shape is **class-conditioned**, selected by the
187+
> `classid` from the inherited format via `CascadeShape::from_levels(d)`
188+
> `2 → G6D2` (Rails), `3 → G4D3` (other frameworks), `4 → G3D4` (the GUID
189+
> default); `CascadeShape::ALIGNED = [G3D4, G6D2]` are the shift-`group_of` shapes
190+
> and `ROTATIONS` the full set. **`G4D3` (`4×3`) is legitimate per-class, not a
191+
> "worst case to prevent"** (operator veto 2026-06-29): its `group_of` divides
192+
> (`is_byte_aligned()` is `false`) — a per-class *cost*, not a prohibition;
193+
> `is_byte_aligned`/`shift`/`ALIGNED` distinguish the shift fast-path from the
194+
> divide shape, never a reject gate. **Functions are NOT a carving**
195+
> `facet::ClassArm { View, Functions }` is the classid's additional THINK/DO
196+
> switch; carvings address `View` only. Zero-dep, `const fn`, probe-verified
197+
> (lance-graph #621). One algebra for both the facet bytes and a 12-field class —
198+
> the shared substrate the three language SDKs (§1.6) all read.
185199
186200
---
187201

@@ -310,6 +324,21 @@ A node is `key(128/GUID) + value`. Lance may compress the value arbitrarily
310324
(columnar, dictionary, PQ); the key is never compressed and never needs the
311325
value decoded to route. (Canon: "THE GUID IS THE KEY OF KEY-VALUE.")
312326

327+
**Two doctrines for the consumer model (operator 2026-06-29), neither a blocker:**
328+
329+
- **Clean ⇒ expansion is `classid`-inherited.** A clean class can grow its field
330+
set / capacity; the `classid` selects the expanded shape — no global change.
331+
Expansion is never a transpile blocker (cf. the class-conditioned `CascadeShape`
332+
/ "don't lock the shape").
333+
- **Bulk raw data → a *separate* table, not the SoA value.** The 480-byte value
334+
is for structured/compressible columns; a raw payload that won't fit even
335+
compressed (a ~3.2 Gbp genome; the FMA / BodyParts3D anatomy mesh at **4M
336+
vertices / 6M triangles**) lives in its own Lance table, referenced by
337+
`key`/`classid` — out-of-line, addressed not inlined, and still not a blocker
338+
(the anatomy mesh **baked cleanly as a SoA release**). Transpile mints the
339+
*structured* class; the bulk payload is a table reference, not a transpile
340+
dependency.
341+
313342
---
314343

315344
## 3. The 85 / 15 split (the consumer model)

0 commit comments

Comments
 (0)