Skip to content

Commit 1fb82cc

Browse files
authored
Merge pull request #115 from AdaWorldAPI/claude/medcare-bridge-lance-graph-wmx76z
docs: OGAR der Türsteher — ActionHandler as classes, RBAC hardcoded, Rung as Flughöhe
2 parents 991450a + 8be2c07 commit 1fb82cc

1 file changed

Lines changed: 206 additions & 0 deletions

File tree

docs/ACTIONHANDLER-TURSTEHER.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# OGAR der Türsteher mit Köpfchen — ActionHandler as classes, RBAC hardcoded, Rung as Flughöhe
2+
3+
> **Status: DOCTRINE v0 (2026-06-23).** Pairs with `HIRO-IN-CLASSES.md`
4+
> (what bardioc gains), `HIRO-DO-ARM-LIFT.md` (the DO-arm mapping +
5+
> lossless-DO rule), `CLASSID-RBAC-KEYSTONE-SPEC.md` (the four-axis
6+
> ReBAC keystone), and ada-docs `architecture/COLD_PATH_MUL_ACTIONHANDLER.md`
7+
> (the cold-path / MUL / kgV-ActionHandler treatment from the consumer side).
8+
> Most of this doc is `[G]` — it is grounded in shipped code, cited inline.
9+
10+
The operator's pin, verbatim: *"wie wäre es wenn wir den actionhandler mit
11+
Klassen im OGAR mit einbauen — dann hat Hiro/Bardioc das Gefühl, daß sie da
12+
gleich andocken können, und die Flughöhe darüber machen wir mit den Rung-Levels
13+
(1-9); die Flughöhe sind dann im Hot-Path und RBAC ist hardcoded in OGAR — das
14+
macht sich bei Compliance-Leuten, die von Semantik keine Ahnung haben, gut. OGAR
15+
der Türsteher mit Köpfchen."* And the containment pin: *"ob der da oben AGI hat,
16+
braucht sie nicht zu beunruhigen — OGAR kriegt sie alle."*
17+
18+
This doc carves that into the canon in three moves: **(1)** the ActionHandler
19+
lives as OGAR classes; **(2)** RBAC is a compile-time `const` on those classes,
20+
not a runtime policy; **(3)** the cognition above is bounded by the Rung
21+
Flughöhe in the hot path, and bounded by *structure* — not by trust.
22+
23+
---
24+
25+
## 1. ActionHandler IS OGAR classes — so HIRO/Bardioc dock native `[G]`
26+
27+
The DO arm of a `Class` is a `const` table of `ActionDef`s — the action-axis
28+
sibling of the THINK-arm `ClassView`. This is now shipped as the
29+
**`OgarActionProvider`** (`lance-graph/crates/lance-graph-ogar/src/actions.rs`):
30+
31+
```
32+
classid ──► ClassActions { classid, actions: &'static [ActionDef] }
33+
actions_for(classid) — own surface (zero-fallback &[])
34+
effective_actions(classid) — own ∪ parent, overrides applied
35+
```
36+
37+
The worked seed is the auth family (keystone §7's `0x0B` core domain):
38+
39+
| class | classid | DO surface |
40+
|---|---|---|
41+
| `auth_store` | `0x0B01` | `issue_token` · `revoke_token` · `rotate_secret` |
42+
| `auth_zitadel` | `0x0B02` | *(is-a `auth_store`)* + `sync_org_roles`, `issue_token` overridden |
43+
44+
`auth_zitadel` **is-a** `auth_store`, so its *effective* surface is the base's
45+
plus its net-new, with `issue_token` overridden onto the Zitadel
46+
org-role-aware path — the exact `classid → ClassView` inheritance the field-set
47+
already uses (`contract::action::effective_actions`). HIRO's `gen_statem`
48+
lifecycles and Bardioc's handlers dock here by becoming `ActionDef` rows on the
49+
same `Class` they already are (`HIRO-IN-CLASSES.md` §1, §3.1) — they do not
50+
wrap OGAR, they *are* OGAR. **No adapter carries its own action table** (the
51+
core-first invariant): the harvest IS the manifest.
52+
53+
This is the "andocken" the operator wants: a HIRO engineer adds a behaviour by
54+
adding a `const ActionDef`, not by writing a new dispatcher.
55+
56+
---
57+
58+
## 2. RBAC is hardcoded — `required_role` is a `const` on the class `[G]`
59+
60+
Every mutating `ActionDef` carries its `required_role` as a **compile-time
61+
literal**, not a runtime policy row:
62+
63+
```rust
64+
// lance-graph-ogar/src/actions.rs (the auth_store DO surface)
65+
ActionDef { predicate: "rotate_secret", object_class: 0x0B01,
66+
required_role: Some("auth_admin"),
67+
guard: Some(StateGuard { field: "status", value: "active" }),
68+
exec: ExecTarget::Native, overrides: None }
69+
```
70+
71+
Why a `const`, and why this is the Türsteher:
72+
73+
- **A compliance reviewer reads the grant surface off the source.** No database,
74+
no admin console, no "effective permissions" query — the class's authorization
75+
is `grep required_role`. For Compliance-Leute who do not read semantics, this
76+
is the legible artifact: *who may rotate the secret? `auth_admin`, full stop,
77+
pinned in the class.*
78+
- **A roleless mutating action is structurally visible.** `required_role: None`
79+
is an explicit, audited declaration ("unguarded"), never an accidental open
80+
door that a missing policy row would create. The test asserts the invariant
81+
directly: *every mutating auth action has `Some(role)`*
82+
(`auth_store_surface_has_hardcoded_rbac_on_every_mutating_action`).
83+
- **An override cannot silently widen access.** `auth_zitadel::issue_token`
84+
restates `required_role: Some("auth_user")` — the override is `[G]`-tested to
85+
keep the grant, not elevate it (`zitadel_inherits_auth_store_and_overrides_issue_token`).
86+
87+
The hardcoded role is the **§3 axis-1 verb gate** of the keystone, frozen at the
88+
class. The richer runtime grant *map* (which actor holds `auth_admin`) is still
89+
data — resolved by an `auth_store` OIDC profile from token claims (keystone §7,
90+
I-K7). The class fixes *what role is required*; the token fixes *who has it*. The
91+
typed `granted` value-tenant that carries the runtime half is now shipped too:
92+
`contract::rbac::{OpMask, ClassGrant, grants_permit}` (keystone §6) — the
93+
first-class replacement for `project_role.permissions: text`.
94+
95+
---
96+
97+
## 3. The cold path the class enforces — { RBAC · Libet · Rubikon@MUL } `[G]`
98+
99+
An `ActionDef`'s `required_role` and `guard` are the **def half** of the
100+
cold-path gate. The whole hard lifecycle is already in
101+
`contract::action::ActionInvocation::commit`, which adjudicates in order:
102+
103+
```
104+
def-match → RBAC (required_role vs ActorContext)
105+
→ state guard (Libet do/don't — fire only if field==value)
106+
→ MUL impact (the Rubicon crossing: Flow→Committed, Hold→Pending, Block→Cancelled)
107+
```
108+
109+
The kgV executor that runs a *committed* action is `graph-flow-action`'s
110+
`ActionHandler` (rs-graph-llm); the outer planning-cycle envelope is
111+
`graph-flow-kanban`'s `KanbanPlanEnvelope` over the 4-phase Rubicon board
112+
(`Planning → CognitiveWork → Evaluation → {Commit|Plan|Prune}`). OGAR supplies
113+
the *authorized DO surface* both sit over. Three layers, one rule
114+
(ada-docs `COLD_PATH_MUL_ACTIONHANDLER.md`):
115+
116+
| layer | what | where |
117+
|---|---|---|
118+
| **cold path** (hard floor) | RBAC · Libet do/don't · Rubikon last phase · SLA | OGAR class `const` + `commit` |
119+
| **MUL** (one soft window) | `GateDecision::{Flow,Hold,Block}` | planner / cognition, *before* the Libet veto |
120+
| **hot path** (execution) | `ActionHandler::handle` | the executor, reached only on `Committed` |
121+
122+
The kgV invariant (`I-ACTIONHANDLER-IS-KGV-NOT-CHOKEPOINT`): thinking stays
123+
**wide and upstream**; it never crawls under the executor waist. The class fixes
124+
the floor; cognition decides above it; neither can lower the floor.
125+
126+
---
127+
128+
## 4. Rung 1-9 is the Flughöhe — cognition altitude in the hot path `[G]`
129+
130+
The operator's "Flughöhe darüber" is the **Rung** (`contract::cognitive_shader::RungLevel`,
131+
`Surface`=0 … `Transcendent`=9) — *how deep the thinking goes*. It is a hot-path
132+
quantity (it rides `ShaderDispatch.rung`), distinct from the **execution cost
133+
ladder** `ElevationLevel` (`Point`=0 … `Async`=5) — *how much machinery the
134+
search spends*. The two are now calibrated:
135+
136+
```rust
137+
// lance-graph-planner/src/elevation/mod.rs
138+
ElevationLevel::from_rung(rung) -> ElevationLevel // monotone non-decreasing
139+
// Surface/Shallow→Point · Contextual/Analogical→Scan · Abstract/Structural→Cascade
140+
// Counterfactual→Batch · Meta→IvfBatch · Recursive/Transcendent→Async
141+
```
142+
143+
Deeper cognition implies a higher cost **floor of ambition** — but only a floor.
144+
The **Csíkszentmihályi Flow channel**, already in code as
145+
`mul::FlowState{Flow,Anxiety,Boredom,Transition}` + `flow_state_from(challenge,
146+
skill)` driving `elevation::homeostasis`, tunes the actual spend around that
147+
floor: `Boredom` (skill ≫ challenge) raises the patience budget → go *deeper*
148+
than the rung floor; `Anxiety` (challenge ≫ skill) cuts it → cap *below*. So the
149+
Rung sets where the search starts; the flow-modulated budget decides where it
150+
ends. The Flughöhe is real, measured, and self-regulating — not a knob.
151+
152+
This is why the Rung lives *above* the cold path: it modulates *how hard the
153+
system thinks before it decides*, never *whether the class lets it act*. A
154+
Transcendent-rung deliberation and a Surface-rung reflex hit the **same** `commit`
155+
gate with the **same** `required_role`. Altitude does not buy authority.
156+
157+
---
158+
159+
## 5. Containment by structure — "OGAR kriegt sie alle" `[H]`
160+
161+
The operator: *"ob der da oben AGI hat, braucht sie nicht zu beunruhigen."* The
162+
claim is that OGAR contains whatever sits above it **without needing to trust
163+
it** — and the mechanism is structural, not behavioural:
164+
165+
- **Capability-bounding, not alignment-bounding.** The cognition above can be
166+
arbitrarily capable; it still acts only through `ActionDef`s that exist, with
167+
`required_role`s it cannot edit, through a `commit` gate it cannot bypass. The
168+
bound is on *what doors exist*, not on *how clever the agent is*. An AGI that
169+
wants to `rotate_secret` still needs `auth_admin` — the class does not care how
170+
it reasoned its way to the request.
171+
- **The DO surface is closed at the Core.** There is no "register a new action
172+
at runtime" path; the manifest is `const`, harvested, reviewed. Cognition
173+
cannot mint itself a capability. (Contrast: a tool-use agent that can author
174+
arbitrary shell commands has an *open* DO surface — the failure mode this
175+
closes.)
176+
- **Move/ownership semantics make the gate unforgeable.** `commit` consumes the
177+
invocation's state transition; there is no aliased path to `Committed` that
178+
skips the RBAC/Libet/MUL checks (the Firewall: no serialization in the hot
179+
path, ADR-022/023). The compiler is the bouncer's muscle.
180+
181+
So the Türsteher has *Köpfchen* precisely because the intelligence is in the
182+
**doorway geometry**, not in vetting each guest's intentions. You do not have to
183+
solve alignment of the thing above to bound what it can do — you only have to fix
184+
the doors. That is a structural containment claim (`[H]`: argued from the shipped
185+
`const`-surface + `commit` gate; the falsifier is any path that reaches an
186+
external mutation without transiting `commit`).
187+
188+
---
189+
190+
## 6. Cross-references
191+
192+
- `HIRO-IN-CLASSES.md` — the four efficiency wins bardioc gains (dispatch,
193+
validation, dep-graph, similarity) by living as OGAR classes.
194+
- `HIRO-DO-ARM-LIFT.md` — HIRO Automation → DO arm mapping; the lossless-DO rule
195+
(`ActionDef` points-to-body, never flattens into DDL).
196+
- `CLASSID-RBAC-KEYSTONE-SPEC.md` — §3 four axes, §5 `authorize`, §6 typed
197+
`granted`, §7 the `0x0B` AuthStore family, §10 `PROBE-OGAR-RBAC-AUTHORIZE`.
198+
- `SURREAL-AST-AS-ADAPTER.md` — why the behavioural arm cannot live in DDL (the
199+
reason RBAC is a class `const`, not a `DEFINE EVENT … WHEN … THEN`).
200+
- ada-docs `architecture/COLD_PATH_MUL_ACTIONHANDLER.md` — the cold-path / MUL /
201+
kgV-ActionHandler / containment treatment (consumer-side companion).
202+
- Shipped code: `lance-graph/crates/lance-graph-ogar/src/actions.rs`
203+
(`OgarActionProvider`), `…/lance-graph-contract/src/{action,rbac,kanban}.rs`,
204+
`…/lance-graph-planner/src/elevation/mod.rs` (`from_rung`),
205+
`rs-graph-llm/graph-flow-action` (kgV `ActionHandler`),
206+
`rs-graph-llm/graph-flow-kanban` (`KanbanPlanEnvelope`).

0 commit comments

Comments
 (0)