Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .claude/board/EPIPHANIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,58 @@

## Entries (newest first)

## 2026-06-23 — OGIT's Configuration entity ⊨ the keystone's auth_store; the 0x0B AuthStore family is minted (autoattended resolution)
**Status:** FINDING
**Scope:** OGAR keystone §7 ↔ canonical OGIT shape convergence × the 0x0B mint × autoattended decision-making

The operator's insight — *"having our vision and already the canonical
OGIT shape it's easy"* — is correct and now has a receipt in code. The
OGAR keystone (`CLASSID-RBAC-KEYSTONE-SPEC.md` §7) and the canonical
OGIT Auth shape (the 2026-06-23 entry below) converge **1:1**, which
collapses the "which auth harness" question from a fraught decision into
plain sequencing.

The convergence, term-for-term:

| OGIT Auth (canonical shape, upstream) | keystone §7 | Zitadel |
|---|---|---|
| `Account` (the `sub`) | actor `0x0104` | User |
| `Application` | class scope | Project/App |
| `Role` | role `0x0117` | Project-Role |
| `RoleAssignment` | membership tuple `0x0108/0x0118` | Grant |
| `Organization`/`OrgDomain` | row-scope (axis 3) | Org |
| `DataScope`/`scopeId` | row-scope predicate | scope |
| **`Configuration`** (keyed org/app/account/scope IDs + `configurationData`) | **`auth_store 0x0B01`** | the IdP config record |

The punchline: **arago's January-2026 `Configuration`-bridge entity IS
the keystone's `auth_store`** — same four external-ID keys, same config
blob, built upstream independently. Keystone §7 had already written
"Zitadel maps 1:1"; the OGIT shape is the receipt that it isn't
speculative.

**Autoattended resolution (this session):** because the vision and the
canonical shape agree, the tractable part shipped without a steer
round-trip — the `0x0B` Auth domain is **minted** in `ogar-vocab`:
`auth_store 0x0B01` + `auth_zitadel 0x0B02` / `auth_zanzibar 0x0B03` /
`auth_ory_keto 0x0B04` (CODEBOOK + `class_ids` consts + `ALL` +
`ConceptDomain::Auth` + `all_promoted_classes()` builders +
`ogar-class-view` registration + tests). 298/0 workspace tests.

What stayed gated (the keystone's OWN gates, not caution): the
`authorize()` **enforcement** waits on `PROBE-OGAR-RBAC-AUTHORIZE`
(§10); the woa `WoaMembraneGate` mirror and the `project_role.permissions`
→ typed-grant Core change land per keystone §11 build order. Minting the
profiles is "reserving costs nothing"; enforcing them is the gated,
security-review-class step. Full decision record:
`.claude/board/ISSUES.md` ISS-RBAC-AUTHORIZE-BY-CLASSID.

Method note (autoattended decision-making): autonomy means honoring the
PROJECT'S ratified gates (the probe, the 5+3-hardened keystone), not
bulldozing them. The mint is spec-ratified (keystone §7 is hardened,
zero BLOCK) and confirmed by the OGIT shape, so it ships; the
enforcement has an explicit probe gate, so it waits. That distinction
is what makes "auto-resolve" responsible rather than reckless.

## 2026-06-23 — Live 2026 receipt for the semantic-compiler thesis: bardioc is actively extending OGIT's Auth symbol table with a linker-phase external-IAM bridge (probably Zitadel)
**Status:** FINDING (shape-grounded; external system not named in-file → [H], not [G])
**Scope:** addendum to the 2026-06-22 "OGIT was already a semantic compiler's symbol table" entry below × Auth-domain dating × the AuthStore-mapping pattern × the queued 0x0BXX cross-walk
Expand Down
96 changes: 96 additions & 0 deletions .claude/board/ISSUES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# ISSUES.md — open issues / blockers / tracked decisions for OGAR

> **APPEND-ONLY.** Newest at top. Each entry: an id, a `**Status:**`
> line (OPEN / RESOLVED / SUPERSEDED — the only mutable line), the
> question, the evidence, the decision/resolution, and what (if
> anything) remains gated. Corrections append as new dated lines citing
> the original.

## Entries (newest first)

## ISS-RBAC-AUTHORIZE-BY-CLASSID — reconcile the shipped MembraneGate path with the keystone; mint the AuthStore family
**Status:** RESOLVED (decision made autoattended; the `0x0B` mint is shipped this session; the `authorize()` *enforcement* remains gated on `PROBE-OGAR-RBAC-AUTHORIZE`, the keystone's own §10 gate)
**Filed/Resolved:** 2026-06-23

### What this corrects
An earlier framing called classid-keyed authorize a "hard blocker." It
is not. The auth surface is largely SHIPPED, just not unified, and
OGAR's own design for it is already hardened:

- `lance-graph-rbac` exists: `Policy` / `Role` / `Operation::{Read{depth},
Write{predicate}}` / `AccessDecision::{Allow,Deny,Escalate}` /
`smb_policy()`.
- smb-office-rs PR #29 shipped `SmbMembraneGate` (~30 LOC, newtype over
`Arc<lance_graph_rbac::Policy>`, keyed `(role × entity_type)`, impl
`MembraneGate`). The spec's enforcement, instantiated.
- woa-rs is on the OLD path (`UnifiedBridge<WoaBridge>`); migration =
mirror smb #29. NOT blocked on a missing primitive.
- medcare is the bigger lift (lacks `medcare-rbac` + `medcare-realtime`,
~800 LOC, deferred behind DM-7/DM-8 per `MEDCARE_POLICY_GAP.md`).
- **OGAR's own `docs/CLASSID-RBAC-KEYSTONE-SPEC.md` v2 is hardened
(zero remaining BLOCK)** and already decided classid-keyed authorize
is canonical (I-K1), with the AuthStore class family preminted (§7).

### It's not two harnesses — it's interim (shipped) vs canonical (gated), one sequence
1. SHIPPED INTERIM: `Policy` (keyed `entity_type: &str`) + `MembraneGate`
/ `SmbMembraneGate` (smb #29). Proven.
2. lance-graph `super-domain-rbac-tenancy-v1.md` §3.9: 4-stage
`authorize`; §13.1 composes onto `callcenter::policy`.
3. OGAR CANONICAL: `CLASSID-RBAC-KEYSTONE-SPEC` v2 — `authorize(actor,
classid, op)` via the `ClassRbac` trait, ReBAC-compiled-at-the-key,
typed grants replacing `project_role.permissions: text`, consumer
bridges evaporate after collapse (§11.5). Gated on
`PROBE-OGAR-RBAC-AUTHORIZE` (§10).

The keystone §1 names the string-`entity_type` `Policy` as the *defect*
it removes — so "MembraneGate canonical" and "keystone canonical" differ
on END-STATE but NOT on sequencing: the keystone is gated on an un-green
probe, so MembraneGate is the right thing to ship NOW.

### The OGIT convergence that makes this easy (operator insight 2026-06-23)
OGAR's keystone vision and the canonical OGIT shape CONVERGE 1:1.
arago's January-2026 `NTO/Auth/Configuration` entity (keyed by
`organizationId`/`accountId`/`applicationId`/`scopeId` +
`configurationData`, "registered in hiro knowledge core") IS the
keystone's `auth_store` — the IdP→classid mapping class — built
upstream independently. Zitadel maps 1:1
(Project→class-scope, Project-Role→role, Grant→membership tuple,
Org→scope, User→`sub`). The decision dissolves: we are matching an
existing shape, not inventing one. (Receipt: `EPIPHANIES.md` 2026-06-23
entries; `OGAR-AS-IR.md` linker phase.)

### Decision (autoattended, RESOLVED)
- **Canonical end-state:** the keystone v2 — classid-keyed `authorize`
via `ClassRbac`, AuthStore profiles. Already hardened; confirmed by
the OGIT shape.
- **Interim (ship now, unblocks woa):** woa-rs mirrors smb #29 —
`WoaMembraneGate` over `Arc<lance_graph_rbac::Policy>`, classid from
`WoaPort::class_id`. This is the SHIPPED `MembraneGate` pattern, NOT a
`*Bridge` stopgap — so it does not violate the README guidance.
- **Sequencing:** the probe orders them; they do not compete.

### Shipped this session (the tractable, ungated part)
The `0x0B` Auth domain is **minted** in `ogar-vocab` per keystone §7 —
`auth_store 0x0B01` + `auth_zitadel 0x0B02` / `auth_zanzibar 0x0B03` /
`auth_ory_keto 0x0B04`: CODEBOOK entries, `class_ids` consts, `ALL`,
`ConceptDomain::Auth` (`0x0B` → Auth), `all_promoted_classes()`
builders, `ogar-class-view` `all_canonical_classes()` registration,
tests (`auth_domain_concepts_resolve_and_route`). Reservations only —
"reserving costs nothing." 298/0 workspace tests; fmt-clean; no new
clippy.

### Held (gated, NOT this session — the keystone's own gates)
- The `authorize()` **enforcement** (ClassRbac trait impl + the
bit-for-bit decision) — gated on `PROBE-OGAR-RBAC-AUTHORIZE` (§10)
running green against a reference (Odoo `ir.model.access ∧ ir.rule`,
Redmine `User#allowed_to?`, or an OpenFGA model). Security-review-class.
- The woa-rs `WoaMembraneGate` mirror — a different repo; mirrors smb #29
when woa work is picked up. Unblocked, not gated.
- `project_role.permissions: text` → typed-grant Core change (§6) —
lands with the keystone build order §11, after the probe.

### Refs
`docs/CLASSID-RBAC-KEYSTONE-SPEC.md` (§7 AuthStore, §10 probe, §11
order); lance-graph `super-domain-rbac-tenancy-v1.md` §3.9/§13.1;
smb-office-rs PR #29; `MEDCARE_POLICY_GAP.md`; `EPIPHANIES.md`
2026-06-23 (the OGIT convergence + the mint).
48 changes: 31 additions & 17 deletions crates/ogar-class-view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@ use lance_graph_contract::{
ontology::{DisplayTemplate, FieldRef, ObjectView},
};
use ogar_vocab::{
billable_work_entry, billing_party, canonical_concept_id, commercial_document,
commercial_line_item, currency_policy, diagnosis, lab_value, medication, patient,
payment_record, priority, project, project_actor, project_attachment, project_changeset,
project_comment, project_custom_field, project_custom_value, project_enabled_module,
project_forum, project_journal, project_member_role, project_membership, project_message,
project_news, project_query, project_relation, project_repository, project_role,
project_status, project_type, project_version, project_watcher, project_wiki_page,
project_work_item, tax_policy, treatment, visit, vital_sign, Class,
Class, auth_ory_keto, auth_store, auth_zanzibar, auth_zitadel, billable_work_entry,
billing_party, canonical_concept_id, commercial_document, commercial_line_item,
currency_policy, diagnosis, lab_value, medication, patient, payment_record, priority, project,
project_actor, project_attachment, project_changeset, project_comment, project_custom_field,
project_custom_value, project_enabled_module, project_forum, project_journal,
project_member_role, project_membership, project_message, project_news, project_query,
project_relation, project_repository, project_role, project_status, project_type,
project_version, project_watcher, project_wiki_page, project_work_item, tax_policy, treatment,
visit, vital_sign,
};

/// All 32 promoted canonical concepts: `(canonical_concept_name, Class)`.
Expand Down Expand Up @@ -124,6 +125,11 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> {
("treatment", treatment()),
("visit", visit()),
("vital_sign", vital_sign()),
// ── 0x0BXX — auth (the AuthStore class family, keystone §7) ──
("auth_store", auth_store()),
("auth_zitadel", auth_zitadel()),
("auth_zanzibar", auth_zanzibar()),
("auth_ory_keto", auth_ory_keto()),
]
}

Expand All @@ -140,7 +146,8 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> {
/// [`DisplayTemplate::Detail`] — per-class template selection is the
/// renderer's job, not this adapter's.
fn lift_object_view(class: &Class) -> ObjectView {
let mut fields: Vec<FieldRef> = Vec::with_capacity(class.attributes.len() + class.associations.len());
let mut fields: Vec<FieldRef> =
Vec::with_capacity(class.attributes.len() + class.associations.len());
for attr in &class.attributes {
fields.push(FieldRef::new(attr.name.clone(), attr.name.clone()));
}
Expand Down Expand Up @@ -402,13 +409,13 @@ mod tests {
let v = OgarClassView::new();
let id = canonical_concept_id("billable_work_entry").unwrap();
let class = billable_work_entry();
let assoc_names: std::collections::HashSet<&str> = class
.associations
let assoc_names: std::collections::HashSet<&str> =
class.associations.iter().map(|a| a.name.as_str()).collect();
let field_names: std::collections::HashSet<&str> = v
.fields(id)
.iter()
.map(|a| a.name.as_str())
.map(|f| f.predicate_iri.as_str())
.collect();
let field_names: std::collections::HashSet<&str> =
v.fields(id).iter().map(|f| f.predicate_iri.as_str()).collect();
for assoc in &assoc_names {
assert!(
field_names.contains(assoc),
Expand Down Expand Up @@ -437,10 +444,18 @@ mod tests {
// green for the 0x09XX block.
let v = OgarClassView::new();
for concept in [
"patient", "diagnosis", "lab_value", "medication", "treatment", "visit", "vital_sign",
"patient",
"diagnosis",
"lab_value",
"medication",
"treatment",
"visit",
"vital_sign",
] {
let id = canonical_concept_id(concept).unwrap();
let view = v.object_view(id).unwrap_or_else(|| panic!("{concept} not registered"));
let view = v
.object_view(id)
.unwrap_or_else(|| panic!("{concept} not registered"));
assert!(!view.fields.is_empty(), "{concept} has no field basis");
}
}
Expand All @@ -457,5 +472,4 @@ mod tests {
assert_eq!(fields[10].predicate_iri, "patient");
assert_eq!(fields[11].predicate_iri, "encounter");
}

}
Loading
Loading