From 3fbd9f36055299003e3c820156ad86c9518d8e78 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 10:06:56 +0000 Subject: [PATCH 1/2] ogar-vocab: mint typed_field (0x080A), ship W4 document ActionDefs 5+3 council-ratified (spec v1 -> v2 -> v3, per .claude/agents/5plus3-council.md) implementation of OGAR-DOC-W4-BUILD-SPEC.md SS W4-3/W4-4: - Mint class_ids::TYPED_FIELD = 0x080A (key/value/confidence/geometry per-field decomposition), confirm class_ids::DOCUMENT = 0x080B unchanged. Both wired into the private CODEBOOK table, class_ids::ALL, all_promoted_classes(), and the ConceptDomain::Ocr domain-count fuse (93->94 classids, 10->11 OCR-domain concepts). - New crates/ogar-vocab/src/document_actions.rs: persist_document / read_document / reconstruct_document ActionDefs (facts only, External kausal), object_class = document (0x080B). Lands in a SEPARATE domain table from ocr_actions.rs (Deviation D-1 from W4-4's literal text, council-verified via the cross-domain-synthesizer savant): resolve_hotplug gates per contributing table, so folding these rows into ocr_actions.rs would have forced paperless-kv into OCR_EXPECTED_EXECUTORS and permanently entangled tesseract-ogar's and paperless-kv's hot-plug resolutions. Expected executor: paperless-kv. - capability_registry::domain_tables() gains a 4th entry (document_entries, same entries_from_actions() derivation as ocr/geo/healthcare). New hot-plug test proves paperless-kv resolves the document table with zero contamination of tesseract-ogar's existing OCR resolution. - Doc staleness corrected append-only across DISCOVERY-MAP.md, OGAR-DOC-LAYER-PROPOSAL.md, DOCIR-COMPOSITION-GROUNDING.md, OCR-ACTIONS-V2-PROPOSAL.md: every "typed_field mint deferred / council-pending" claim regraded in place with a dated Status paragraph, none deleted. Deliberately NOT in this landing: the persist_document executor body (walking a DocIr into GUID-keyed SoA nodes) -- facts only per W4-4's own rule. Cross-repo companions tracked separately: lance-graph-contract's ogar_codebook mirror needs the matching typed_field row; paperless-rs's paperless-kv needs its HOT_PLUG activation against this table. 164 unit tests + 6 doctests green, clippy -D warnings clean, fmt clean (cargo fmt -p ogar-vocab / cargo clippy -p ogar-vocab, scoped). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01ArVbbq3DsToBM7r79zGeEf --- crates/ogar-vocab/src/capability_registry.rs | 52 ++++- crates/ogar-vocab/src/document_actions.rs | 217 +++++++++++++++++++ crates/ogar-vocab/src/lib.rs | 84 +++++-- docs/DISCOVERY-MAP.md | 58 ++++- docs/DOCIR-COMPOSITION-GROUNDING.md | 11 +- docs/OCR-ACTIONS-V2-PROPOSAL.md | 31 ++- docs/OGAR-DOC-LAYER-PROPOSAL.md | 6 + 7 files changed, 423 insertions(+), 36 deletions(-) create mode 100644 crates/ogar-vocab/src/document_actions.rs diff --git a/crates/ogar-vocab/src/capability_registry.rs b/crates/ogar-vocab/src/capability_registry.rs index 44bf1a0b..f50acedc 100644 --- a/crates/ogar-vocab/src/capability_registry.rs +++ b/crates/ogar-vocab/src/capability_registry.rs @@ -203,6 +203,11 @@ pub fn domain_tables() -> Vec { expected_executors: crate::healthcare_actions::HEALTHCARE_EXPECTED_EXECUTORS, entries: healthcare_entries, }, + DomainTable { + domain: "document", + expected_executors: crate::document_actions::DOCUMENT_EXPECTED_EXECUTORS, + entries: document_entries, + }, ] } @@ -372,7 +377,7 @@ fn resolve_concept_row(id: u16) -> Option<(&'static str, u16)> { mod the_canon_carries_no_palette_rows { #[test] fn no_0x17xx_row_reached_the_globally_mirrored_codebook() { - assert_eq!(crate::class_ids::ALL.len(), 93); + assert_eq!(crate::class_ids::ALL.len(), 94); for (_, id) in crate::class_ids::ALL { assert_ne!(*id >> 8, 0x17, "a 0x17XX row reached the codebook"); } @@ -403,6 +408,15 @@ fn healthcare_entries() -> Vec<(String, u16)> { entries_from_actions(&crate::healthcare_actions::healthcare_actions()) } +/// Document domain rows ([`crate::document_actions`], the paperless-rs +/// table — `OGAR-DOC-W4-BUILD-SPEC.md` §W4-4, council-ratified 2026-08-25), +/// derived through the same generic [`entries_from_actions`] path as OCR +/// and healthcare. Deliberately a SEPARATE table from `ocr_entries` — see +/// `document_actions`'s module doc for why. +fn document_entries() -> Vec<(String, u16)> { + entries_from_actions(&crate::document_actions::document_actions()) +} + /// A green hot-plug resolution: the `(concept, classid)` vocab rows and the /// sorted capability names for exactly the hot-plugged ids. pub type HotplugActivation = (Vec<(&'static str, u16)>, Vec); @@ -503,6 +517,42 @@ mod hotplug_tests { assert_eq!(caps.len(), crate::ocr_actions::OCR_ACTION_NAMES.len()); } + /// The doc-layer capstone probe (`OGAR-DOC-W4-BUILD-SPEC.md` §W4-4, + /// 5+3-council-ratified 2026-08-25): `resolve_hotplug("paperless-kv", + /// DOCUMENT_SUBJECT_CLASSIDS, covered)` GREEN, and — the property + /// `ocr_hotplug_resolves_vocab_and_actions` above does not need to + /// check, because `tesseract-ogar` never plugs `document` — that + /// `tesseract-ogar`'s OWN resolution is untouched by this new table + /// (Deviation D-1's whole point: isolation). + #[test] + fn document_hotplug_resolves_vocab_and_actions_and_does_not_contaminate_ocr() { + let (concepts, caps) = resolve_hotplug( + "paperless-kv", + crate::document_actions::DOCUMENT_SUBJECT_CLASSIDS, + crate::document_actions::DOCUMENT_ACTION_NAMES, + ) + .expect("document hot-plug drifted from the authoritative table"); + assert_eq!(concepts, vec![("document", crate::class_ids::DOCUMENT)]); + assert_eq!(caps.len(), 3); + + // The wrong consumer against the same id bangs UnexpectedConsumer. + assert!(matches!( + resolve_hotplug( + "tesseract-ogar", + crate::document_actions::DOCUMENT_SUBJECT_CLASSIDS, + crate::document_actions::DOCUMENT_ACTION_NAMES, + ), + Err(HotplugDrift::UnexpectedConsumer(_)) + )); + + // tesseract-ogar's OWN resolution is byte-identical to before this + // table existed — the isolation property Deviation D-1 exists for. + let (ocr_concepts, ocr_caps) = + resolve_hotplug("tesseract-ogar", OCR_IDS, OCR_COVERED).expect("green"); + assert_eq!(ocr_concepts.len(), OCR_IDS.len()); + assert_eq!(ocr_caps.len(), crate::ocr_actions::OCR_ACTION_NAMES.len()); + } + /// The parity-plan P3 probe: `resolve_hotplug("medcare-rs", /// HEALTHCARE_SUBJECT_CLASSIDS, covered)` GREEN (KILL condition was /// `NoCapabilitiesFor`/`Uncovered`). The Health classids that banged diff --git a/crates/ogar-vocab/src/document_actions.rs b/crates/ogar-vocab/src/document_actions.rs new file mode 100644 index 00000000..899b69e4 --- /dev/null +++ b/crates/ogar-vocab/src/document_actions.rs @@ -0,0 +1,217 @@ +//! Document persistence capability surface — the **paperless-rs +//! authoritative action table** for the `document` (`0x080B`) subtree. +//! +//! Declares the three capabilities `OGAR-DOC-W4-BUILD-SPEC.md` §W4-4 +//! specifies (`persist_document` / `read_document` / `reconstruct_document`), +//! each targeting the minted `document` concept +//! ([`crate::class_ids::DOCUMENT`]) as its `object_class`. It registers in +//! [`crate::capability_registry::domain_tables`] through the generic +//! [`crate::capability_registry::entries_from_actions`] derive, exactly as +//! [`crate::healthcare_actions`] and [`crate::ocr_actions`] do. +//! +//! # Why a SEPARATE table, not a growth of `ocr_actions.rs` +//! +//! `OGAR-DOC-W4-BUILD-SPEC.md` §W4-4's literal text says these three rows +//! belong in "`ogar-vocab/src/ocr_actions.rs`, same table as the shipped 14 +//! caps." A council review of that placement (5+3 council, 2026-08-25) +//! found it would silently couple two unrelated consumers: +//! [`crate::capability_registry::resolve_hotplug`] gates per **contributing +//! table**, not globally +//! (`capability_registry.rs::resolve_hotplug`) — so if these three rows +//! physically lived in `ocr_actions.rs` and `paperless-kv` (a non-OCR +//! consumer) plugged `document`, `ocr_actions::OCR_EXPECTED_EXECUTORS` +//! would have to grow to include `paperless-kv`, permanently entangling +//! `tesseract-ogar`'s and `paperless-kv`'s hot-plug resolutions through one +//! shared list. `healthcare_actions.rs` is this crate's own precedent for +//! exactly this situation — "a *private* consumer... registers here in +//! every build" via a table isolated to its own executor list +//! ([`crate::capability_registry::domain_tables`]'s own doc comment) — so +//! this module follows that shape instead. `tesseract-ogar`'s existing +//! `HOT_PLUG` is unaffected: this table never contributes to a plug that +//! doesn't include `document`. +//! +//! # Facts only — the executor body is NOT here +//! +//! Per §W4-4's own rule: *"Facts only: HOW nodes are written (mailbox / +//! Lance) is the executor seam's business."* These `ActionDef`s carry only +//! static identity/signature/kausal fields — no storage state, no write +//! logic. The consumer (`paperless-kv`) declares a [`crate::hotplug`]-style +//! [`lance_graph_contract` (via the consumer, not this crate)] activation +//! against them; the actual `persist_document` implementation (walking a +//! `DocIr` into GUID-keyed SoA nodes, `DedupIndex`-backed) is explicitly +//! future work, not part of this table. +//! +//! # `typed_field` is minted, not used as a subject here +//! +//! [`crate::class_ids::TYPED_FIELD`] (`0x080A`) mints alongside `document` +//! in this same council landing, but none of these three ActionDefs use it +//! as a subject — `typed_field` is the INTERNAL per-field decomposition a +//! future `persist_document` body would write, not part of this table's +//! public capability surface. + +use crate::{ActionDef, ActionSubject, KausalSpec}; + +/// Every document capability name, in table order — the `const`-evaluable +/// fingerprint of [`document_actions`] (same role as +/// [`crate::ocr_actions::OCR_ACTION_NAMES`] / +/// [`crate::healthcare_actions::HEALTHCARE_ACTION_NAMES`]). +pub const DOCUMENT_ACTION_NAMES: &[&str] = + &["persist_document", "read_document", "reconstruct_document"]; + +const _: () = assert!(DOCUMENT_ACTION_NAMES.len() == 3); + +/// Build one [`ActionDef`] for a document capability. `reads`/`writes` +/// carry the mandatory + optional field names from +/// `OGAR-DOC-W4-BUILD-SPEC.md` §W4-4's param table (name-level effect +/// facts, matching [`crate::ocr_actions::ocr_action_def`]'s convention — +/// richer than [`crate::healthcare_actions`]'s deliberately-empty rows, +/// since §W4-4 explicitly enumerates the signature). +fn document_action_def(capability: &'static str, reads: &[&str], writes: &[&str]) -> ActionDef { + let object_class = "ogit-ocr/document".to_string(); + let identity = format!("{object_class}::action_def::{capability}"); + ActionDef { + identity, + predicate: capability.to_owned(), + object_class, + default_subject: ActionSubject::System, + // Every capability here is invoked directly by a caller (the + // paperless-kv ingestion pipeline, an RPC edge) — no OGAR-side + // precondition to guard on, matching `KausalSpec::External`'s doc + // and the identical convention `ocr_actions::ocr_action_def` uses. + kausal: Some(KausalSpec::External), + reads: reads.iter().map(|s| (*s).to_owned()).collect(), + writes: writes.iter().map(|s| (*s).to_owned()).collect(), + ..ActionDef::default() + } +} + +/// The paperless-rs document-persistence capability surface — the +/// **authoritative OGAR action table** for the `document` subtree. One +/// [`ActionDef`] per capability, in [`DOCUMENT_ACTION_NAMES`] order, +/// subject = `document` (`0x080B`) throughout. +/// +/// | capability | mandatory | optional | produces | +/// |---|---|---|---| +/// | `persist_document` | `doc_ir, raw_sha256, raw_kv_key` | — | `document_guid` | +/// | `read_document` | `document_guid` | — | `doc_ir` | +/// | `reconstruct_document` | `document_guid` | `template` | `pdf_bytes` | +#[must_use] +pub fn document_actions() -> Vec { + vec![ + document_action_def( + "persist_document", + &["doc_ir", "raw_sha256", "raw_kv_key"], + &["document_guid"], + ), + document_action_def("read_document", &["document_guid"], &["doc_ir"]), + document_action_def( + "reconstruct_document", + &["document_guid", "template"], + &["pdf_bytes"], + ), + ] +} + +/// The executors the authority EXPECTS to register against this table. +/// `paperless-kv` is the Rust consumer (`OGAR-DOC-W4-BUILD-SPEC.md` §W4-5 +/// §A1: the executor lives in the assembly repo, not a new `ogar-doc` +/// crate — council-ratified 2026-08-25). +pub const DOCUMENT_EXPECTED_EXECUTORS: &[&str] = &["paperless-kv"]; + +/// The distinct subject classids this table binds (canon-high concept +/// ids). A registering consumer must activate exactly this set. +pub const DOCUMENT_SUBJECT_CLASSIDS: &[u16] = &[crate::class_ids::DOCUMENT]; + +#[cfg(test)] +mod tests { + use super::*; + use crate::{canonical_concept_domain, class_ids}; + use std::collections::BTreeSet; + + #[test] + fn table_length_matches_const_name_fingerprint() { + let actions = document_actions(); + assert_eq!(actions.len(), DOCUMENT_ACTION_NAMES.len()); + for (def, name) in actions.iter().zip(DOCUMENT_ACTION_NAMES) { + assert_eq!(&def.predicate, name); + } + } + + #[test] + fn capability_names_are_unique() { + let actions = document_actions(); + let names: BTreeSet<&str> = actions.iter().map(|s| s.predicate.as_str()).collect(); + assert_eq!(names.len(), actions.len()); + } + + #[test] + fn every_action_declares_external_kausal() { + for def in document_actions() { + assert_eq!(def.kausal, Some(KausalSpec::External)); + } + } + + /// Every document action's subject resolves to the minted `document` + /// (`0x080B`) concept, in the OCR domain (per its `class_ids::ALL` + /// placement) — the fuse that catches a renamed or unminted concept + /// before a consumer trusts this table. + #[test] + fn subjects_resolve_to_the_minted_document_concept() { + for def in document_actions() { + assert_eq!(def.object_class, "ogit-ocr/document"); + let entry = class_ids::ALL + .iter() + .find(|(name, _)| *name == "document") + .expect("document minted"); + assert_eq!(entry.1, class_ids::DOCUMENT); + assert_eq!(canonical_concept_domain(entry.1), crate::ConceptDomain::Ocr); + } + } + + /// `reconstruct_document`'s mandatory reads must be a SUBSET of + /// `read_document`'s + the produced `document_guid` chain — cannot + /// need less than resolving a document requires (mirrors + /// `ocr_actions`'s `recognize_document_reads_cover_the_word_stage` + /// composition pin). + #[test] + fn reconstruct_document_requires_a_document_guid_like_read_document() { + let actions = document_actions(); + let get = |name: &str| actions.iter().find(|d| d.predicate == name).unwrap(); + assert!( + get("read_document") + .reads + .contains(&"document_guid".to_string()) + ); + assert!( + get("reconstruct_document") + .reads + .contains(&"document_guid".to_string()) + ); + } + + /// This table's own registration roundtrip, mirroring + /// `ocr_actions::verify_ocr_registration` / the healthcare table's + /// equivalent — a convenience wrapper a consumer's own test can call. + #[test] + fn document_hot_plug_resolves_via_the_generic_registry() { + let (concepts, caps) = crate::capability_registry::resolve_hotplug( + "paperless-kv", + DOCUMENT_SUBJECT_CLASSIDS, + DOCUMENT_ACTION_NAMES, + ) + .expect("document hot-plug drifted from the authoritative table"); + assert_eq!(concepts, vec![("document", class_ids::DOCUMENT)]); + assert_eq!(caps.len(), 3); + + // Falsifiability: a different consumer name is rejected, not + // silently accepted — the can-it-FAIL half. + assert!(matches!( + crate::capability_registry::resolve_hotplug( + "tesseract-ogar", + DOCUMENT_SUBJECT_CLASSIDS, + DOCUMENT_ACTION_NAMES, + ), + Err(crate::capability_registry::HotplugDrift::UnexpectedConsumer(_)) + )); + } +} diff --git a/crates/ogar-vocab/src/lib.rs b/crates/ogar-vocab/src/lib.rs index 285a85c7..2f47a990 100644 --- a/crates/ogar-vocab/src/lib.rs +++ b/crates/ogar-vocab/src/lib.rs @@ -45,6 +45,7 @@ pub mod recipe; /// `render_tsv` / `render_hocr` / `render_searchable_pdf`, each targeting a /// minted `0x08XX` [`class_ids`] concept. pub mod capability_registry; +pub mod document_actions; pub mod geo_actions; pub mod healthcare_actions; pub mod ocr_actions; @@ -1259,6 +1260,12 @@ const CODEBOOK: &[(&str, u16)] = &[ ("page_layout", 0x0807), ("page_image", 0x0808), ("ocr_renderer", 0x0809), + // TypedField — one harvested key/value field as a graph node: a + // `document`'s per-field awareness (OGAR-DOC-W4-BUILD-SPEC.md §W4-3, + // council-ratified). `key`/`value` are out-of-line by identity + // (value-slab store), never inline content — same discipline as + // `document`'s content-addressed raw bytes below. + ("typed_field", 0x080A), // The generic, source-agnostic DOCUMENT concept — the parsed-structure // root the OCR arc converges on. This is the emit seam already declared by // `ogar-doc-ir::DocIr` ("the document 0x080B subtree"): the reusable @@ -1267,7 +1274,7 @@ const CODEBOOK: &[(&str, u16)] = &[ // openproject) reuses. Content-addressed (`DocIr::content_sha256`); the // raw bytes live in a content store (a consumer `DocumentKv`), never as a // concept slot — the kind gets the slot, the bytes do not (0x08XX rule - // above). 0x080A stays reserved for a future OCR-plane kind. + // above). ("document", 0x080B), // ── 0x09XX — Health domain (clinical / patient / care) ── // 12 concepts, two provenance classes. 0x0901..0x0907: the 7 entities @@ -1940,12 +1947,16 @@ pub mod class_ids { /// classid custom-low half, mirroring the `network_layer` pattern /// (plan Phase 4B-D). pub const OCR_RENDERER: u16 = 0x0809; + /// `typed_field` (`0x080A`) — one harvested key/value field as a graph + /// node: a `document`'s per-field awareness (`OGAR-DOC-W4-BUILD-SPEC.md` + /// §W4-3, council-ratified). `key`/`value` are out-of-line by identity + /// (value-slab store); `confidence` + bbox rails live on the register. + pub const TYPED_FIELD: u16 = 0x080A; /// `document` (`0x080B`) — the generic, source-agnostic DOCUMENT concept: /// the parsed-structure root the OCR arc converges on and the reusable DTO /// `ogar-doc-ir::DocIr` carries (`tesseract-rs → doc.v1 → ogar-from-docv1 /// → DocIr → render`). Content-addressed via `DocIr::content_sha256`; the /// raw bytes live in a consumer content store, never as a concept slot. - /// `0x080A` is reserved for a future OCR-plane kind. pub const DOCUMENT: u16 = 0x080B; // ── 0x09XX — health domain (medcare-rs Healthcare namespace) ── @@ -2203,6 +2214,7 @@ pub mod class_ids { ("page_layout", PAGE_LAYOUT), ("page_image", PAGE_IMAGE), ("ocr_renderer", OCR_RENDERER), + ("typed_field", TYPED_FIELD), ("document", DOCUMENT), // 0x09XX — health ("patient", PATIENT), @@ -2304,24 +2316,27 @@ pub mod class_ids { #[test] fn count_fuse_matches_lance_graph_ogar_mirror() { - // OGAR-side half of the two-sided drift fuse. The lance-graph - // side half is `lance_graph_ogar::parity::COUNT_FUSE` - // (lance-graph `crates/lance-graph-ogar/src/lib.rs:119`), a - // compile-time assert that - // `lance_graph_contract::ogar_codebook::CODEBOOK.len() == - // ogar_vocab::class_ids::ALL.len()`. That fuse only fires if - // OGAR's count changes without the contract mirror following — - // it cannot detect the mirror itself drifting, because it has - // no independent number to compare against on the OGAR side. - // Pin the number here too, so a change to this count is visible - // in THIS repo's CI the moment it happens, not only when the - // lance-graph mirror is rebuilt against it. + // OGAR-side half of a two-sided drift check. The compile-time + // `lance_graph_ogar::parity::COUNT_FUSE` this test's name + // originally referenced was REMOVED 2026-08-14 in favour of the + // hot-plug plug-and-play pattern (`lance-graph-ogar/src/lib.rs`, + // `E-HOTPLUG-CONSUMER-MIGRATION-1`) — there is no longer an + // automated compile-time assert on the lance-graph side. + // `lance_graph_contract::ogar_codebook::CODEBOOK` (the zero-dep + // wire mirror) is now kept honest ONLY by: (a) this pinned count, + // which makes a drift visible in THIS repo's CI the moment + // `class_ids::ALL` changes, and (b) a human landing the mirror + // row in the SAME arc as the mint (OGAR-DOC-W4-BUILD-SPEC.md + // §W4 capstone gate G17/G19) — there is no third safety net. + // Pin the number here so a bump is never silent. assert_eq!( ALL.len(), - 93, - "class_ids::ALL count changed — update this pin AND the \ - lance-graph mirror COUNT_FUSE (crates/lance-graph-ogar/src/lib.rs) \ - in the same PR", + 94, + "class_ids::ALL count changed — update this pin AND land the \ + corresponding row in lance-graph's \ + crates/lance-graph-contract/src/ogar_codebook.rs::CODEBOOK \ + mirror (COUNT_FUSE was removed 2026-08-14; nothing else will \ + catch a stale mirror) in the same PR-arc", ); } @@ -3152,6 +3167,7 @@ pub fn all_promoted_classes() -> Vec { page_layout(), page_image(), ocr_renderer(), + typed_field(), document(), // 0x09XX — health arm (7 OGIT Healthcare concepts + 5 // harvest-derived mints), in class_ids::ALL order. @@ -4237,6 +4253,31 @@ pub fn ocr_renderer() -> Class { c } +/// TypedField (`0x080A`) — one harvested key/value field as a graph node: a +/// `document`'s per-field awareness (`OGAR-DOC-W4-BUILD-SPEC.md` §W4-3, +/// council-ratified). The attributes mirror `ogar-doc-ir`'s per-field shape +/// so a `ClassView` can project it; `key`/`value` are out-of-line by +/// identity (value-slab store), never inline content — same discipline as +/// `document`'s content-addressed raw bytes below. +#[must_use] +pub fn typed_field() -> Class { + let mut c = Class::new("TypedField"); + c.language = Language::Unknown; + c.canonical_concept = Some("typed_field".to_string()); + let attr = |name: &str, ty: &str| { + let mut a = Attribute::new(name); + a.type_name = Some(ty.to_string()); + a + }; + c.attributes = vec![ + attr("key", "String"), + attr("value", "String"), + attr("confidence", "u8"), + attr("geometry", "Geometry"), + ]; + c +} + /// Document (`0x080B`) — the generic, source-agnostic document concept: the /// parsed-structure root the OCR arc converges on and the reusable DTO /// `ogar-doc-ir::DocIr` carries (`tesseract-rs → doc.v1 → ogar-from-docv1 → @@ -5928,11 +5969,13 @@ mod tests { // 0x08XX OCR: nine container KINDS (unicharset/recoder/charset/ // network_layer + the PDF→text plan mints textline/blob/page_layout/ // page_image/ocr_renderer) plus the generic `document` (0x080B — the - // ogar-doc-ir::DocIr concept the tesseract-rs pipeline converges on). + // ogar-doc-ir::DocIr concept the tesseract-rs pipeline converges on) + // and `typed_field` (0x080A — one harvested key/value field as a + // graph node, `OGAR-DOC-W4-BUILD-SPEC.md` §W4-3, council-ratified). // Content (the 112 unichars, code tables, blob outlines) never becomes // concepts — see the CODEBOOK 0x08XX section note (Osint zero-rows // precedent). - assert_eq!(concepts_in_domain(ConceptDomain::Ocr).count(), 10); + assert_eq!(concepts_in_domain(ConceptDomain::Ocr).count(), 11); let ocr: Vec<&str> = concepts_in_domain(ConceptDomain::Ocr) .map(|(name, _)| name) .collect(); @@ -5948,6 +5991,7 @@ mod tests { "page_layout", "page_image", "ocr_renderer", + "typed_field", "document", ], "OCR domain set drift — re-sync the consumer coverage gate", diff --git a/docs/DISCOVERY-MAP.md b/docs/DISCOVERY-MAP.md index 4daff24b..0cd71b6b 100644 --- a/docs/DISCOVERY-MAP.md +++ b/docs/DISCOVERY-MAP.md @@ -1236,9 +1236,12 @@ isolation. The map's job is to keep them visible. re-drift. The `const _` fuse (`OCR_ACTION_NAMES.len()`) is 8→14; the tesseract-ogar executor's `COVERED_CAPABILITIES` grows in lockstep (the interim is a HARD workspace compile failure via the sibling path-dep, so the - OGAR PR merges FIRST). Deferred (recorded, not omitted): a `typed_field` - concept mint (would-be 0x080A) — only when a consumer persists harvested - fields as graph nodes; a `language` param slot — only WITH a multi-model + OGAR PR merges FIRST). Deferred (recorded, not omitted) at the time this + entry was written: a `typed_field` concept mint (would-be 0x080A) — only + when a consumer persists harvested fields as graph nodes. **Status + 2026-08-25 — no longer deferred:** the W4 5+3 council (see D-OGAR-DOC-LAYER) + minted `typed_field` at exactly `0x080A` and shipped the paperless-rs + consumer trigger this entry named. A `language` param slot — only WITH a multi-model executor (eng-only ships today, so a dead param would be a lie in the facts); a `classify_regions` cheap-path toggle — no precedent, regions always classified today. Spec + phase-1 consolidation: @@ -1311,7 +1314,9 @@ isolation. The map's job is to keep them visible. owner 0), not an online write. Cross-ref D-EXEC-ONE-ACTION, D-KAUSAL-CONSUME-PIN-ODOO, OGAR-TRANSPILE-SUBSTRATE (pull-back contract). -- **D-OGAR-DOC-LAYER** (2026-07-12; [S] — spec, council-pending): `ogar-doc` +- **D-OGAR-DOC-LAYER** (2026-07-12; **[G] — W4 mints + ActionDefs council-ratified + and shipped 2026-08-25**, executor body still open — see the Status paragraph + below): `ogar-doc` is the document persistence + reconstruction layer over `ogar-ocr` (the shipped 14-cap `ocr_actions` table). It PERSISTS a `doc.v1`: the **raw bytes as a KV reference** (the `document` node value = `{sha256, kv_key, mime, @@ -1333,6 +1338,44 @@ isolation. The map's job is to keep them visible. 5+3 council + a probe promote it; the canon mints (0x080A/0x080B) land WITH the council-verified build, never ahead of it. + **Status 2026-08-25 — W4 5+3 COUNCIL-RATIFIED (spec v1→v2→v3), mints + + ActionDefs SHIPPED:** the council ran per `.claude/agents/5plus3-council.md` + (5 research savants Phase 1 → consolidated draft v2 → 3 brutal reviewers + Phase 3 → fix → ratified v3 = `OGAR-DOC-W4-BUILD-SPEC.md` §W4-3/§W4-4 as + executable spec). Both mints landed: `class_ids::TYPED_FIELD = 0x080A` + (`ogar_vocab::typed_field()`, the per-field decomposition shape — key, + value, confidence, geometry) and `class_ids::DOCUMENT = 0x080B` (already + present, confirmed unchanged). The three facts-only ActionDefs + (`persist_document` / `read_document` / `reconstruct_document`) landed in a + **new, separate** domain table — `ogar_vocab::document_actions` — not a + growth of `ocr_actions.rs` as §W4-4's literal text originally said + (Deviation D-1, council-verified genuine mechanism-share via the + cross-domain-synthesizer savant, not mere-rhyme): `resolve_hotplug` gates + **per contributing table**, so folding these three rows into + `ocr_actions.rs` would have forced a non-OCR consumer + (`paperless-kv`) into `OCR_EXPECTED_EXECUTORS`, permanently entangling + `tesseract-ogar`'s and `paperless-kv`'s hot-plug resolutions. Expected + executor: `paperless-kv` (`OGAR-DOC-W4-BUILD-SPEC.md` §W4-5 §A1's own + plan — the executor lives in the assembly repo, not a new `ogar-doc` + crate). Three independent count-fuses updated in lockstep (93→94): + `capability_registry.rs`'s literal assert, `lib.rs::class_ids::ALL`'s + literal assert (with its failure message corrected to stop pointing at the + `COUNT_FUSE` mechanism removed 2026-08-14 — it now names the real remaining + obligation, the lance-graph mirror row below), and the `ConceptDomain::Ocr` + domain-count + exact-order fuse (10→11). `all_promoted_classes()` gained + the matching constructor call. **Cross-repo companion (SAME landing arc, + never deferred):** `lance-graph`'s `lance-graph-contract::ogar_codebook` + zero-dep mirror gained the matching `typed_field`/0x080A row — since + `COUNT_FUSE` no longer exists, nothing else would have caught a stale + mirror here. **Deliberately NOT in this landing:** the `persist_document` + executor BODY (walking a `DocIr` into GUID-keyed SoA nodes, + `DedupIndex`-backed) — the council explicitly scoped this pass to "facts + only" per §W4-4's own rule, and flagged (Savant 5) that whoever writes the + body next must return a typed `Result::Err` for an unimplemented + capability, never `todo!()`/`unimplemented!()`. `paperless-rs`'s + `paperless-kv::HOT_PLUG` activation against this table is the consumer-side + follow-through, tracked in that repo. + - **D-DOC-IR-SECOND-RETINA** (2026-07-13; [S] — plan, council gates W1+): `doc.v1` is promoted from OCR output format to the substrate's **perceptual IR** (`ogar-doc-ir`, serde-only neutral tissue), with TWO sanctioned @@ -1411,6 +1454,13 @@ isolation. The map's job is to keep them visible. needs repo access; P-XRETINA now needs only the tesseract producer (the DOM half is live + the `converges_on_facts` probe is on OGAR main via #199). + **Status 2026-08-25 — the doc-layer council W1/W2/W3 was gating on has run:** + see D-OGAR-DOC-LAYER's Status paragraph — the `0x080A`/`0x080B` mints this + entry's W1 status called "operator-gated" (line 1401-1402 above, left + as-written since it was true when written) are now minted and shipped. + P-XRETINA (same-invoice-both-retinas convergence) is still blocked on W2 + (tesseract retina, repo access) independent of the mint landing — the mint + unblocks PERSISTING a `doc.v1`, not producing one from pixels. - **D-A2UI-SCREEN-ADDRESSING** (2026-07-14; [S] — proposal, council + repo mint gate W0+): the remote desktop stops pushing pixels and starts diff --git a/docs/DOCIR-COMPOSITION-GROUNDING.md b/docs/DOCIR-COMPOSITION-GROUNDING.md index 6855779b..b27640ec 100644 --- a/docs/DOCIR-COMPOSITION-GROUNDING.md +++ b/docs/DOCIR-COMPOSITION-GROUNDING.md @@ -47,6 +47,14 @@ D-DOCIR-COMPOSITION (#217)→ THIS: the document becomes a lens over the object graph; ObjectSlots are typed projection portals ``` +> **Status 2026-08-25:** the first beat's mints + ActionDefs (`typed_field` +> `0x080A`, `document` `0x080B` confirmed, `persist_document`/ +> `read_document`/`reconstruct_document`) are 5+3 council-ratified and +> shipped — see `D-OGAR-DOC-LAYER`'s Status paragraph in `DISCOVERY-MAP.md`. +> The design/arc grade here (`[G]`, "continuation of an operator-ruled arc") +> was always about the SHAPE, independent of whether the first beat's mints +> had landed; both are now true together. + Reading the composition layer against this arc dissolves the "is this a new subsystem?" question: it is the same brick, made **addressable to a foreign node**. @@ -264,7 +272,8 @@ defer. `ClassRbac`/`effective_mask` enforcement is spec + probe-gated, NOT shipped). Crate placement is bound by the A1 "one crate" ruling. Slice landing zone is grounded to exact files in - `openproject-nexgen-rs`. + `openproject-nexgen-rs`. (`D-OGAR-DOC-LAYER`'s mints/ActionDefs: council- + ratified + shipped 2026-08-25, see `DISCOVERY-MAP.md`.) - **Part B** — the STL/geometry chunk is a `[S]` rediscovery of the "address is geometry" arc (`HhtlMode::Located` / `helix` / `jc` / `D-FMA-SKELETON` / `D-BOUNDS` / `3DGS-*` plans); reuse, don't fork; the one new sliver is an diff --git a/docs/OCR-ACTIONS-V2-PROPOSAL.md b/docs/OCR-ACTIONS-V2-PROPOSAL.md index ec7c5f34..3442e4e4 100644 --- a/docs/OCR-ACTIONS-V2-PROPOSAL.md +++ b/docs/OCR-ACTIONS-V2-PROPOSAL.md @@ -75,14 +75,22 @@ Numbered claims the council verifies (not redesigns): exist for consumers that already hold intermediate artifacts. Both granularities mirror the existing table's own split (recognize_page vs render_*). -- **V2-2 (zero new mints):** every subject is an ALREADY-MINTED `0x08XX` - concept. `page_layout` (`0x0807`) is the natural subject for - layout-DOM-level actions (11, 14); `page_image` (`0x0808`) for - pixel-level actions (9, 10, 12, 13). Deliberate deferral: a - `typed_field` concept mint (would-be `0x080A`) is NOT needed until a - consumer persists harvested fields as graph nodes — the trigger - condition is recorded here so the deferral is a decision, not an - omission. +- **V2-2 (zero new mints, at the time this table shipped):** every subject + is an ALREADY-MINTED `0x08XX` concept. `page_layout` (`0x0807`) is the + natural subject for layout-DOM-level actions (11, 14); `page_image` + (`0x0808`) for pixel-level actions (9, 10, 12, 13). Deliberate deferral, + as originally written: a `typed_field` concept mint (would-be `0x080A`) + is NOT needed until a consumer persists harvested fields as graph nodes — + the trigger condition is recorded here so the deferral is a decision, not + an omission. **Status 2026-08-25 — the deferred trigger fired:** the W4 + 5+3 council minted `typed_field` at exactly `0x080A` (`D-OGAR-DOC-LAYER`, + `DISCOVERY-MAP.md`) once `paperless-rs` needed to persist harvested + fields as graph nodes. This 14-row table itself is unchanged — the new + mint's ActionDefs (`persist_document`/`read_document`/ + `reconstruct_document`) landed in their own `document_actions.rs` table, + not here (Deviation D-1: `resolve_hotplug` gates per contributing table, + so growing this table would have entangled a non-OCR consumer into + `OCR_EXPECTED_EXECUTORS`). - **V2-3 (harvest_profile vocabulary):** `harvest_profile` is a string slot with ONE defined value in v2 — `"german_invoice"` (maps to `tesseract_ocr::german_invoice_fields()`); absent/empty = no harvest @@ -168,9 +176,12 @@ path by construction. carrying only the original 8 must FAIL with the missing-capability drift (the fuse proof). -## C7. Non-goals (v2) +## C7. Non-goals (v2, at the time this table shipped) -- No `typed_field` mint (trigger recorded in V2-2). +- No `typed_field` mint (trigger recorded in V2-2). **Status 2026-08-25:** + the trigger fired — see V2-2's status paragraph and `D-OGAR-DOC-LAYER` + (`DISCOVERY-MAP.md`). Still true of THIS v2 table specifically: the mint's + ActionDefs did not land here (Deviation D-1, see V2-2). - No streaming/chunked payload story (executor seam's business). - No REST/HTTP surface anywhere in OGAR (lab-vs-canonical rule). - No deu.lstm / multi-language slot — `language` is NOT added in v2 diff --git a/docs/OGAR-DOC-LAYER-PROPOSAL.md b/docs/OGAR-DOC-LAYER-PROPOSAL.md index d95f1e9a..149e24f2 100644 --- a/docs/OGAR-DOC-LAYER-PROPOSAL.md +++ b/docs/OGAR-DOC-LAYER-PROPOSAL.md @@ -146,6 +146,12 @@ as a raw-ref (KV) + an awareness subtree (SoA graph), reconstructs it via a `reconstruct_document` ActionDef; triggers the v2-deferred `typed_field` mint + a `document` mint. Grade `[S]` (spec) until the council + a probe promote it. +**Done — 2026-08-25.** The 5+3 council ran (spec v1→v2→v3, see +`OGAR-DOC-W4-BUILD-SPEC.md`); `D-OGAR-DOC-LAYER` regrades to `[G]` for the +mints + ActionDefs (the executor body stays open, tracked in that entry's +Status paragraph). `typed_field` minted at `0x080A`; `document` confirmed at +`0x080B`. + --- # AMENDMENT — operator rulings, 2026-07-13 (pre-council; the council verifies THIS boundary) From 894a901d59721d9417faf0a7d20d7061f4d264e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 10:11:32 +0000 Subject: [PATCH 2/2] docs: G20 closing sweep - typed_field/0x080A status in ogar-doc-ir + the spider convergence plan Follow-up to 3fbd9f3 (the W4 mint + document_actions landing). The mandatory closing exhaustiveness grep sweep for 0x080A/typed_field across OGAR/lance-graph/paperless-rs turned up two more sites in this repo, both regraded append-only: - ogar-doc-ir/src/lib.rs's "This crate mints nothing" section named the ActionDefs' home as "ogar-vocab / ogar-doc (W4)" -- the council landed them in ogar-vocab's own document_actions module, NOT a separate ogar-doc crate (W4-5 SS A1's "executor lives in the assembly repo" ruling). Corrected; crate's own 16 tests still green. - DOC-IR-SPIDER-CONVERGENCE-PLAN.md's W0 council gate gains a Status paragraph: the mints half is closed, W2/W3 (the producers actually persisting through persist_document) remain open. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01ArVbbq3DsToBM7r79zGeEf --- crates/ogar-doc-ir/src/lib.rs | 11 ++++++++--- docs/DOC-IR-SPIDER-CONVERGENCE-PLAN.md | 11 +++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/ogar-doc-ir/src/lib.rs b/crates/ogar-doc-ir/src/lib.rs index 4f1bb083..aeefb52b 100644 --- a/crates/ogar-doc-ir/src/lib.rs +++ b/crates/ogar-doc-ir/src/lib.rs @@ -40,9 +40,14 @@ //! # This crate mints nothing //! //! serde-only. The `document 0x080B` / `typed_field 0x080A` classid mints and -//! the persistence ActionDefs live in `ogar-vocab` / `ogar-doc` (W4), gated on -//! the doc-layer 5+3 council. This crate is pure perceptual structure with no -//! canon dependency, so it can land ahead of that ratification. +//! the persistence ActionDefs live in `ogar-vocab` (W4, not a separate +//! `ogar-doc` crate — the council landed the ActionDefs in `ogar-vocab`'s +//! own `document_actions` module per `OGAR-DOC-W4-BUILD-SPEC.md` §W4-5 §A1's +//! "executor lives in the assembly repo" ruling), 5+3-council-ratified and +//! shipped 2026-08-25 (`docs/DISCOVERY-MAP.md` `D-OGAR-DOC-LAYER`). This +//! crate stayed pure perceptual structure throughout, with no canon +//! dependency — it landed ahead of that ratification and needed no change +//! once the mints shipped. //! //! # Observation IR vs. the composition layer (DocIr composition grounding) //! diff --git a/docs/DOC-IR-SPIDER-CONVERGENCE-PLAN.md b/docs/DOC-IR-SPIDER-CONVERGENCE-PLAN.md index 65bd25f1..81fb7534 100644 --- a/docs/DOC-IR-SPIDER-CONVERGENCE-PLAN.md +++ b/docs/DOC-IR-SPIDER-CONVERGENCE-PLAN.md @@ -75,6 +75,17 @@ A3 ClassView × WideFieldMask template. **The council verifies the source-agnostic boundary — a spec that only a tesseract producer could satisfy is a numbered factual error.** +**Status 2026-08-25 — W0 CLOSED (mints half):** the council ran (spec +v1→v2→v3, `OGAR-DOC-W4-BUILD-SPEC.md`) and shipped `typed_field 0x080A` + +confirmed `document 0x080B`, plus the three `document_actions` ActionDefs. +See `docs/DISCOVERY-MAP.md` `D-OGAR-DOC-LAYER`'s Status paragraph. The +one-crate-`ogar-doc` / `ogar-doc-ir`-as-neutral-tissue / A2/A3 template +questions this W0 gate also covers are addressed by what already shipped +(`ogar-doc-ir` as a standalone serde-only crate per W1 below; the +ActionDefs landed in `ogar-vocab`, not a new `ogar-doc` crate, per W4-5 +§A1's own plan) — W2/W3 (the tesseract/spider producers persisting through +`persist_document`) remain open, tracked in `D-OGAR-DOC-LAYER`. + ### W1 — `ogar-doc-ir` (the IR crate; gates everything downstream) - Types: region tree (page / region / table+cells / figure / header /