Skip to content

Commit 762fafa

Browse files
committed
capability_registry: generic consumer-registration roundtrip for authoritative tables
OGAR is the authoritative store; this closes the confirmation loop the operator asked for: a domain table (ocr_actions today, the planned thinking-styles best-practice table next) declares capabilities + subject classids + EXPECTED executors; the consumer registers itself via a CapabilityRegistration const and asserts verify_registration in its own tests. Checks: consumer expected, coverage both directions, activated classid set == declared subject set, every id minted (unique mint already guarded by the codebook duplicate test). Drift bangs once at test time in the one binary -- no serde, no ontology payload outside OGAR; lance-graph carries nothing of this. ocr_actions gains OCR_EXPECTED_EXECUTORS (["tesseract-ogar"]), OCR_SUBJECT_CLASSIDS (textline/page_image/ocr_renderer) and the verify_ocr_registration convenience roundtrip. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
1 parent ac7fdd7 commit 762fafa

3 files changed

Lines changed: 176 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//! Generic consumer-registration roundtrip for AUTHORITATIVE capability
2+
//! tables (operator, 2026-07-07).
3+
//!
4+
//! OGAR is the authoritative store: a domain table (e.g.
5+
//! [`crate::ocr_actions`], the planned thinking-styles best-practice table)
6+
//! declares WHAT exists and WHO is expected to execute it. The consumer
7+
//! "trägt sich ein" by exporting a [`CapabilityRegistration`] const and
8+
//! asserting [`verify_registration`] in its own tests — so the whole loop
9+
//! (table declared → consumer registered → classids activated) is checked
10+
//! in the one binary, at test time, with zero serialization and zero
11+
//! ontology payload outside OGAR. lance-graph carries NOTHING of this: its
12+
//! armed contract fuse only guards the wire mirror; the authority checks
13+
//! (unique mint, correct classid activation, executor coverage) live HERE.
14+
//!
15+
//! Drift bangs once: a capability added to the table without a consumer arm,
16+
//! a consumer covering a capability the table dropped, a wrong/missing
17+
//! classid activation, or an unregistered executor name — each is a failed
18+
//! [`verify_registration`] in the consumer's test suite.
19+
20+
/// A consumer's self-registration against one authoritative table.
21+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22+
pub struct CapabilityRegistration {
23+
/// Consumer crate name — must be one of the table's expected executors.
24+
pub consumer: &'static str,
25+
/// Capability names the consumer's executor covers (its dispatch arms).
26+
pub covered: &'static [&'static str],
27+
/// The subject classids the consumer activated (canon-high concept ids
28+
/// from [`crate::class_ids`]) — must equal the table's subject set.
29+
pub subject_classids: &'static [u16],
30+
}
31+
32+
/// Why a registration failed the roundtrip.
33+
#[derive(Debug, Clone, PartialEq, Eq)]
34+
pub enum RegistrationDrift {
35+
/// Consumer name is not in the table's expected-executor list.
36+
UnexpectedConsumer(String),
37+
/// Declared capability with no consumer arm.
38+
Uncovered(String),
39+
/// Consumer arm for a capability the table does not declare.
40+
Undeclared(String),
41+
/// Consumer's activated classid set differs from the table's subjects.
42+
ClassidMismatch {
43+
/// Subject ids the table declares.
44+
declared: Vec<u16>,
45+
/// Ids the consumer activated.
46+
activated: Vec<u16>,
47+
},
48+
/// A subject classid is not minted in [`crate::class_ids::ALL`].
49+
Unminted(u16),
50+
}
51+
52+
/// Verify one consumer registration against an authoritative table's
53+
/// capability names, subject classids and expected-executor list.
54+
///
55+
/// Checks, in order: consumer expected; coverage both directions
56+
/// (declared ⊆ covered AND covered ⊆ declared); activated classid set ==
57+
/// declared subject set; every id minted (unique mint is guarded by the
58+
/// codebook's own `no_duplicate_ids` test — a concept is assigned once,
59+
/// here we only require it exists).
60+
pub fn verify_registration(
61+
reg: &CapabilityRegistration,
62+
declared_capabilities: &[&str],
63+
declared_subjects: &[u16],
64+
expected_executors: &[&str],
65+
) -> Result<(), RegistrationDrift> {
66+
if !expected_executors.contains(&reg.consumer) {
67+
return Err(RegistrationDrift::UnexpectedConsumer(reg.consumer.into()));
68+
}
69+
for cap in declared_capabilities {
70+
if !reg.covered.contains(cap) {
71+
return Err(RegistrationDrift::Uncovered((*cap).into()));
72+
}
73+
}
74+
for cap in reg.covered {
75+
if !declared_capabilities.contains(cap) {
76+
return Err(RegistrationDrift::Undeclared((*cap).into()));
77+
}
78+
}
79+
let mut declared: Vec<u16> = declared_subjects.to_vec();
80+
declared.sort_unstable();
81+
declared.dedup();
82+
let mut activated: Vec<u16> = reg.subject_classids.to_vec();
83+
activated.sort_unstable();
84+
activated.dedup();
85+
if declared != activated {
86+
return Err(RegistrationDrift::ClassidMismatch {
87+
declared,
88+
activated,
89+
});
90+
}
91+
for &id in &activated {
92+
if !crate::class_ids::ALL.iter().any(|&(_, cid)| cid == id) {
93+
return Err(RegistrationDrift::Unminted(id));
94+
}
95+
}
96+
Ok(())
97+
}
98+
99+
#[cfg(test)]
100+
mod tests {
101+
use super::*;
102+
103+
const EXPECTED: &[&str] = &["demo-executor"];
104+
const CAPS: &[&str] = &["a", "b"];
105+
106+
fn reg(covered: &'static [&'static str], ids: &'static [u16]) -> CapabilityRegistration {
107+
CapabilityRegistration {
108+
consumer: "demo-executor",
109+
covered,
110+
subject_classids: ids,
111+
}
112+
}
113+
114+
#[test]
115+
fn green_roundtrip() {
116+
// 0x0805 textline is a real mint.
117+
assert_eq!(
118+
verify_registration(&reg(&["a", "b"], &[0x0805]), CAPS, &[0x0805], EXPECTED),
119+
Ok(())
120+
);
121+
}
122+
123+
#[test]
124+
fn each_drift_arm_bangs() {
125+
let mut r = reg(&["a", "b"], &[0x0805]);
126+
r.consumer = "stranger";
127+
assert!(matches!(
128+
verify_registration(&r, CAPS, &[0x0805], EXPECTED),
129+
Err(RegistrationDrift::UnexpectedConsumer(_))
130+
));
131+
assert!(matches!(
132+
verify_registration(&reg(&["a"], &[0x0805]), CAPS, &[0x0805], EXPECTED),
133+
Err(RegistrationDrift::Uncovered(_))
134+
));
135+
assert!(matches!(
136+
verify_registration(&reg(&["a", "b", "c"], &[0x0805]), CAPS, &[0x0805], EXPECTED),
137+
Err(RegistrationDrift::Undeclared(_))
138+
));
139+
assert!(matches!(
140+
verify_registration(&reg(&["a", "b"], &[0x0806]), CAPS, &[0x0805], EXPECTED),
141+
Err(RegistrationDrift::ClassidMismatch { .. })
142+
));
143+
assert!(matches!(
144+
verify_registration(&reg(&["a", "b"], &[0xFFFE]), CAPS, &[0xFFFE], EXPECTED),
145+
Err(RegistrationDrift::Unminted(0xFFFE))
146+
));
147+
}
148+
}

crates/ogar-vocab/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub mod recipe;
4141
/// `extract_text_layer` / `extract_page_image` / `render_text` /
4242
/// `render_tsv` / `render_hocr` / `render_searchable_pdf`, each targeting a
4343
/// minted `0x08XX` [`class_ids`] concept.
44+
pub mod capability_registry;
4445
pub mod ocr_actions;
4546

4647
/// Source language hint — discriminates the producer for traceability

crates/ogar-vocab/src/ocr_actions.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,33 @@ pub fn ocr_actions() -> Vec<OcrActionSpec> {
316316
]
317317
}
318318

319+
/// The executors the authority EXPECTS to register against this table —
320+
/// "die Ontologie wurde nicht vergessen" has a name attached: an empty or
321+
/// stale list here is itself the drift signal a downstream fuse catches.
322+
pub const OCR_EXPECTED_EXECUTORS: &[&str] = &["tesseract-ogar"];
323+
324+
/// The distinct subject classids this table binds (canon-high concept ids).
325+
/// A registering consumer must activate exactly this set — verified via
326+
/// [`crate::capability_registry::verify_registration`].
327+
pub const OCR_SUBJECT_CLASSIDS: &[u16] = &[
328+
crate::class_ids::TEXTLINE,
329+
crate::class_ids::PAGE_IMAGE,
330+
crate::class_ids::OCR_RENDERER,
331+
];
332+
333+
/// Convenience roundtrip for THIS table: verify a consumer registration
334+
/// against the OCR capability names, subjects and expected executors.
335+
pub fn verify_ocr_registration(
336+
reg: &crate::capability_registry::CapabilityRegistration,
337+
) -> Result<(), crate::capability_registry::RegistrationDrift> {
338+
crate::capability_registry::verify_registration(
339+
reg,
340+
OCR_ACTION_NAMES,
341+
OCR_SUBJECT_CLASSIDS,
342+
OCR_EXPECTED_EXECUTORS,
343+
)
344+
}
345+
319346
#[cfg(test)]
320347
mod tests {
321348
use super::*;

0 commit comments

Comments
 (0)