feat(ogar-class-view): bridge canonical Class onto lance-graph ClassView#77
Conversation
Wires the calcified inner shape (ogar_vocab::Class, codebook-keyed) onto the classview-parameterized outer materialization surface (lance_graph_contract::ClassView). The seam that unblocks the askama templating layer the integration plan documents below. The contract crate (lance-graph-contract::class_view) already supplies ClassId / FieldMask(u64) / ObjectView / FieldRef / DisplayTemplate + the ClassView trait (with project / render_rows iterators). What was missing was the adapter that lifts every promoted ogar_vocab canonical concept into an ObjectView keyed by canonical_concept_id. ogar-class-view::OgarClassView is that adapter: - Walks all 32 promoted class fns (26 project-mgmt + 6 commerce). - N3 stable field order: typed attributes first (declaration order), then family-edge associations (declaration order). Append-only contract. - ClassId == canonical_concept_id (same u16). Unknown ids project to empty field set (no panic). - DisplayTemplate::Detail default; primary_label set to "name" when the class carries a typed `name` attribute. - dolce_category_id returns 0 (the OGIT cache supplies live DOLCE; this adapter sits below it). Tests (8 / 8 green): - registry_carries_every_codebook_concept (forward gate). - every_codebook_id_appears_in_class_ids_all (reverse gate against the class_ids::ALL pin). - field_basis_fits_in_one_u64_mask (FieldMask::MAX_FIELDS=64; widest promoted class is well under). - field_order_is_attributes_then_associations (N3 pin). - render_rows_skips_off_bits_under_a_real_mask (the askama input path). - unknown_class_id_projects_to_no_rows (no-panic on out-of-registry). - billable_work_entry_carries_all_12_family_edges (the 12-edge contract). - primary_label_is_set_for_concepts_with_a_name_attribute. Workspace check + workspace test green. No new runtime deps beyond lance-graph-contract (zero-dep at runtime; serde_yaml is build-only). Also adds docs/integration/CLASSVIEW-MATERIALIZATION-PLAN.md documenting the +5+3 follow-on roadmap (5 askama templates + 3 consumer wirings) that this bridge unblocks.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 37989e7542
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// downstream consumers that want to iterate every class (e.g. emit | ||
| /// one Rust struct per class). | ||
| pub fn known_class_ids(&self) -> impl Iterator<Item = ClassId> + '_ { | ||
| self.by_id.keys().copied() |
There was a problem hiding this comment.
Return class ids in a stable order
When downstream renderers use known_class_ids() to emit all class materializations, this iterator inherits HashMap's randomized iteration order, so the generated structs/interfaces or drift-guard snapshots can reorder across process runs without any schema change. Since the public API is documented for bulk emission, return ids from a stable source such as class_ids::ALL, a sorted Vec, or a BTreeMap instead.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in follow-up #79 — known_class_ids() now iterates ogar_vocab::class_ids::ALL in codebook order with a regression test pinning both stability and second-call-equals-first.
Adds the build-time askama codegen harness over the calcified canonical
layer. Structurally a mirror of AdaWorldAPI/woa-rs crates/codegen
(RFC-v02-006): the same shape WoA-rs uses for its route codegen, but the
typed input is ogar_vocab::Class instead of a JSON-loaded RouteSpec.
The kit:
- enum ArtifactKind { RustStruct, TsInterface, SurrealqlTable,
OpenapiSchema, NodeGuidRoutingArm } — append-only.
- trait ArtifactEmitter — one fn `emit(spec) -> Result<String, askama::Error>`.
- artifact_kinds::for_kind(kind) -> Box<dyn ArtifactEmitter> — dispatcher.
- templates/dispatch/<kind>.askama — one template per kind, no class name
ever hardcoded.
Phase-0 (this PR):
- ArtifactKind::RustStruct: REAL emitter + template. Renders a Rust
struct + `pub const CLASS_ID: u16` + family-edge fields from the
canonical class, mapping Rails-side type names to coarse Rust types
(downstream consumers specialise per their precision needs).
- The other 4 kinds: Stub emitter — compilable, emits a marker comment,
exercises the full pipeline (lookup + dispatch + return) before the
concrete templates land.
The 800 -> 7-70 collapse: adding a new canonical concept costs ZERO new
templates (flows through the existing kit). Adding a new target is one
ArtifactKind variant + one askama template; every promoted concept emits
through it automatically.
Decoupled from ogar-class-view (PR #77):
- ogar-render-askama is BUILD-TIME (consumes Class, emits source files).
- ogar-class-view is RUN-TIME (resolves SoA rows through ClassView trait
to render rows for live projection).
Both pipelines are askama-templated; both share the N3 field order
convention; they consume different shapes — they don't compete.
A2UI integration (AdaWorldAPI/A2UI v0.8) carried as T6 on the roadmap
only — same northstar from the output side, deferred. DUSK_Solution /
MUIBridge noted as design lineage, no active deps.
Tests: 6/6 unit + 1 doctest (ignored — askama derive). Workspace check
and workspace test green.
What
Wires the calcified inner shape (
ogar_vocab::Class, codebook-keyed) onto the classview-parameterized outer materialization surface (lance_graph_contract::ClassView). The seam between the canonical layer and the askama templating layer.lance-graph-contract::class_viewalready supplies the contract —ClassId/FieldMask(u64)/ObjectView/FieldRef/DisplayTemplate+ theClassViewtrait withproject/render_rowsiterators. What was missing was the adapter that lifts every promotedogar_vocabcanonical concept into anObjectViewkeyed bycanonical_concept_id.This PR is that adapter.
The bridge
ogar_class_view::OgarClassView— oneimpl ClassView, built once at startup by walking all 32 promoted canonical class fns (26 project-mgmt + 6 commerce).N3 stable field order
Each
Class→ObjectViewwith fields in append-only stable order:A test (
field_order_is_attributes_then_associations) pins it. Any reorder trips the test before existingFieldMaskproducers silently misalign.ClassId == canonical_concept_id
Both are
u16, ratified byOD-CLASSID-WIDTH. The bridge uses them as one thing — no translation table.Tests (8 / 8 green)
registry_carries_every_codebook_concept— forward gate.every_codebook_id_appears_in_class_ids_all— reverse gate againstclass_ids::ALL.field_basis_fits_in_one_u64_mask—FieldMask::MAX_FIELDS = 64; widest class (billable_work_entry) well under.field_order_is_attributes_then_associations— the N3 pin.render_rows_skips_off_bits_under_a_real_mask— the askama input path.unknown_class_id_projects_to_no_rows— no-panic on out-of-registry.billable_work_entry_carries_all_12_family_edges— the 12-edge contract.primary_label_is_set_for_concepts_with_a_name_attribute.Workspace check + workspace test green.
The +5+3 roadmap
docs/integration/CLASSVIEW-MATERIALIZATION-PLAN.mddocuments the follow-on PR sequence this bridge unblocks:+5 askama templates (one per artifact kind):
rust_struct.askamastruct+pub const CLASS_IDts_interface.askamainterface+class_ids.tssurrealql_table.askamaDEFINE TABLEopenapi_schema.askamanode_guid_routing.askamamatcharm onClassIdEach template is small, takes the same
(OgarClassView, ClassView_bits, ClassId, FieldMask)context, and no template hardcodes a class name.+3 consumer wirings:
op-codegen-projectionadoptsOgarClassView+ askama on the templated paths currently usingformat!openproject-nexgen-rsredmine-canonre-exportsOgarClassView(no logic; symmetric with theclass_idsre-export)redmine-rsop-surreal-ast::from_class_viewadapter — feeds the canonical shape into the byte-identical-pinned typed ASTopenproject-nexgen-rsEach lands with three calibration gates: round-trip parse, target-toolchain compile (
cargo check/tsc --noEmit/ SurrealQL parse), and ClassView drift guard.Scope note
Bridge crate has no
serdeand no I/O. It's a pure in-process adapter. The downstream renderers (T1-T5) handle output emission.