Skip to content

Commit 1804762

Browse files
authored
Merge pull request #619 from AdaWorldAPI/claude/facet-schema-modes
feat(contract): facet_schema — classid-selected 6x2 | 4x3 | 2x48 facet readings
2 parents c86563c + 5dc384a commit 1804762

2 files changed

Lines changed: 170 additions & 0 deletions

File tree

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//! `facet_schema` — the classid-selected **format** of a [`FacetCascade`]'s 12
2+
//! payload bytes.
3+
//!
4+
//! A [`FacetCascade`] is `facet_classid(4) | 12 payload bytes`. The substrate is
5+
//! content-blind (see [`crate::facet`]); the `{domain}{schema}` `facet_classid`
6+
//! selects how a consumer reads those 12 bytes. `96 bits` tiles three ways, all
7+
//! exact:
8+
//!
9+
//! | schema | tiling | meaning | precedent |
10+
//! |---|---|---|---|
11+
//! | [`FacetSchema::TierCascade`] | `6 × (8:8)` | `(part_of:is_a)` cascade | the existing [`FacetCascade::tiers`] / `hi_chain` / `lo_chain` |
12+
//! | [`FacetSchema::SpoTriplet`] | `4 × (8:8:8)` | `(subject:predicate:object)` SPO edges | the `ruff_spo_*` triple corpus |
13+
//! | [`FacetSchema::Pair48`] | `2 × 48-bit` | two 6-byte codes | `helix` `Signed360` / `cam_pq` `[u8; 6]` (both already 48-bit) |
14+
//!
15+
//! These are **readings of the same bytes** — re-tiling, not re-encoding — so
16+
//! switching schema never moves a byte and never touches the operator-LOCKED
17+
//! 480-byte value slab.
18+
19+
use crate::facet::FacetCascade;
20+
21+
/// The classid-selected reading of a facet's 12 payload bytes.
22+
///
23+
/// `TierCascade` is the default (`== 0`), so every existing facet keeps its
24+
/// current `6 × (8:8)` meaning unchanged.
25+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
26+
#[repr(u8)]
27+
pub enum FacetSchema {
28+
/// `6 × (8:8)` — the `(part_of:is_a)` tier cascade. Default.
29+
#[default]
30+
TierCascade = 0,
31+
/// `4 × (8:8:8)` — `(subject:predicate:object)` SPO triplets.
32+
SpoTriplet = 1,
33+
/// `2 × 48-bit` — two contiguous 6-byte codes (`helix` / `cam_pq` shape).
34+
Pair48 = 2,
35+
}
36+
37+
impl FacetSchema {
38+
/// Read the schema field from a `{domain}{schema}` `facet_classid`.
39+
///
40+
/// **Provisional field position.** The exact bits of the `schema` sub-field
41+
/// within `facet_classid` are pending operator/panel ratification; until
42+
/// then this reads the low two bits of the high byte and **defaults to
43+
/// [`TierCascade`](Self::TierCascade)** for every other value, so no
44+
/// existing facet changes meaning. Callers that know their schema should
45+
/// prefer the explicit `as_*` / `from_*` accessors on [`FacetCascade`].
46+
#[inline]
47+
#[must_use]
48+
pub const fn of_classid(facet_classid: u32) -> Self {
49+
match (facet_classid >> 24) & 0b11 {
50+
1 => Self::SpoTriplet,
51+
2 => Self::Pair48,
52+
_ => Self::TierCascade,
53+
}
54+
}
55+
}
56+
57+
impl FacetCascade {
58+
/// The 12 payload bytes (everything after the 4-byte `facet_classid`), LE.
59+
#[inline]
60+
#[must_use]
61+
pub const fn payload(self) -> [u8; 12] {
62+
let b = self.to_bytes();
63+
[
64+
b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15],
65+
]
66+
}
67+
68+
/// The classid-selected [`FacetSchema`] of this facet (provisional resolver,
69+
/// see [`FacetSchema::of_classid`]).
70+
#[inline]
71+
#[must_use]
72+
pub const fn schema(self) -> FacetSchema {
73+
FacetSchema::of_classid(self.facet_classid)
74+
}
75+
76+
/// The `4 × 3` SPO-triplet reading: `[[subject, predicate, object]; 4]`.
77+
#[inline]
78+
#[must_use]
79+
pub const fn as_triplets(self) -> [[u8; 3]; 4] {
80+
let p = self.payload();
81+
[
82+
[p[0], p[1], p[2]],
83+
[p[3], p[4], p[5]],
84+
[p[6], p[7], p[8]],
85+
[p[9], p[10], p[11]],
86+
]
87+
}
88+
89+
/// The `2 × 48-bit` reading: two contiguous 6-byte codes (`helix` / `cam_pq`).
90+
#[inline]
91+
#[must_use]
92+
pub const fn as_pair48(self) -> [[u8; 6]; 2] {
93+
let p = self.payload();
94+
[
95+
[p[0], p[1], p[2], p[3], p[4], p[5]],
96+
[p[6], p[7], p[8], p[9], p[10], p[11]],
97+
]
98+
}
99+
100+
/// Build a facet from a `facet_classid` + `4 × 3` SPO triplets.
101+
#[inline]
102+
#[must_use]
103+
pub const fn from_triplets(facet_classid: u32, t: [[u8; 3]; 4]) -> Self {
104+
let cid = facet_classid.to_le_bytes();
105+
let b = [
106+
cid[0], cid[1], cid[2], cid[3], t[0][0], t[0][1], t[0][2], t[1][0], t[1][1], t[1][2],
107+
t[2][0], t[2][1], t[2][2], t[3][0], t[3][1], t[3][2],
108+
];
109+
FacetCascade::from_bytes(&b)
110+
}
111+
112+
/// Build a facet from a `facet_classid` + `2 × 48-bit` codes.
113+
#[inline]
114+
#[must_use]
115+
pub const fn from_pair48(facet_classid: u32, pair: [[u8; 6]; 2]) -> Self {
116+
let cid = facet_classid.to_le_bytes();
117+
let b = [
118+
cid[0], cid[1], cid[2], cid[3], pair[0][0], pair[0][1], pair[0][2], pair[0][3],
119+
pair[0][4], pair[0][5], pair[1][0], pair[1][1], pair[1][2], pair[1][3], pair[1][4],
120+
pair[1][5],
121+
];
122+
FacetCascade::from_bytes(&b)
123+
}
124+
}
125+
126+
#[cfg(test)]
127+
mod tests {
128+
use super::*;
129+
130+
#[test]
131+
fn triplet_roundtrip_preserves_classid_and_bytes() {
132+
let t = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]];
133+
let f = FacetCascade::from_triplets(0x00AB_CDEF, t);
134+
assert_eq!(f.facet_classid, 0x00AB_CDEF);
135+
assert_eq!(f.as_triplets(), t);
136+
}
137+
138+
#[test]
139+
fn pair48_roundtrip_preserves_classid_and_bytes() {
140+
let pair = [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]];
141+
let f = FacetCascade::from_pair48(0x0000_1234, pair);
142+
assert_eq!(f.facet_classid, 0x0000_1234);
143+
assert_eq!(f.as_pair48(), pair);
144+
}
145+
146+
#[test]
147+
fn payload_is_the_twelve_bytes_after_classid() {
148+
let f = FacetCascade::from_bytes(&[
149+
0xEF, 0xCD, 0xAB, 0x00, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
150+
]);
151+
assert_eq!(f.payload(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
152+
}
153+
154+
#[test]
155+
fn schema_defaults_to_tier_cascade() {
156+
// High byte low-2-bits == 0 → TierCascade (every existing facet).
157+
assert_eq!(FacetSchema::of_classid(0x0012_3456), FacetSchema::TierCascade);
158+
assert_eq!(FacetSchema::default(), FacetSchema::TierCascade);
159+
}
160+
161+
#[test]
162+
fn re_tilings_are_the_same_twelve_bytes() {
163+
let f = FacetCascade::from_bytes(&[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
164+
let from_trip: Vec<u8> = f.as_triplets().concat();
165+
let from_pair: Vec<u8> = f.as_pair48().concat();
166+
assert_eq!(from_trip, from_pair);
167+
assert_eq!(from_trip, f.payload().to_vec());
168+
}
169+
}

crates/lance-graph-contract/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub mod escalation;
7272
pub mod exploration;
7373
pub mod external_membrane;
7474
pub mod facet;
75+
pub mod facet_schema;
7576
pub mod faculty;
7677
pub mod grammar;
7778
pub mod graph_render;

0 commit comments

Comments
 (0)