From 82412bdb78cbce252bb4a3b2b61146a2aa0d1f7d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 24 Jun 2026 12:34:26 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20record=20the=20grail=20=E2=80=94=20clas?= =?UTF-8?q?s-late-bound=20action=20dispatch=20(ResolvingDaemon)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The action arm reached its holy grail, and it turned out to be a restatement of OGAR's most basic canon ("the key prerenders the node; classid → ClassView"), not a new mechanism. Three axes of agnosticism, all keyed by the GUID: transport (Transport trait), class (ClassResolver resolves from the target's classid at dispatch time, production OgarResolver backed by the canonical actions_for manifest), executor (chosen from the resolved RunnerKind via ExecutorRegistry, runner picked post-commit). ResolvingDaemon holds no wired classes and no wired executor; the same ExecuteCommand dispatches to native (mars_machine) or REST (mars_resource) by what the classid resolves to, zero daemon change, gate still rules. A new capability/class/runner is a registry entry, never code. - ARAGO-ACTIONHANDLER-PARITY: new scorecard row (class-late-bound dispatch SHIPPED), verdict paragraph (the grail), cross-ref to daemon.rs. - DISCOVERY-MAP: D-ACTIONHANDLER-RESOLVER (G / CODED). - EPIPHANIES: E-ACTIONHANDLER-RESOLVER — the action daemon IS a renderer over the classid keyspace; the grail is the key-is-key-of-KV store applied to behavior. Code lives in rs-graph-llm PR #20 (graph-flow-action-ogar::daemon). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP --- .claude/board/EPIPHANIES.md | 44 ++++++++++++++++++++++++++++++ docs/ARAGO-ACTIONHANDLER-PARITY.md | 22 +++++++++++++-- docs/DISCOVERY-MAP.md | 1 + 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index f0e7a92..49964ee 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -7,6 +7,50 @@ --- +## 2026-06-24 — E-ACTIONHANDLER-RESOLVER — the action daemon IS a renderer over the classid keyspace: transport, class, executor, guard, RBAC all fall out of the GUID, late + +**Status:** FINDING (`[G]`, 19 tests). + +The action arm reached its holy grail — and it turned out to be a restatement of +OGAR's most basic canon (*"the key prerenders the node; classid → ClassView"*), +not a new mechanism. Three axes of agnosticism, all keyed by the GUID: + +1. **Transport-agnostic** — `Transport` trait (WebSocket today, Kafka reserved). +2. **Class-agnostic** — `ClassResolver` resolves the action class from the **target + node's classid** *at dispatch time*, not wired at build time. The production + `OgarResolver` is backed by the canonical `actions_for(&[ClassActions], classid)` + DO manifest — the exact `classid → ClassActions` surface OGAR already generates. +3. **Executor-agnostic** — the executor is chosen from what the class resolves to + (`RunnerKind` → `ExecutorRegistry`); `RegistryExecutor` adapts it so the gate + runs first and the concrete runner is picked **post-commit**. + +`ResolvingDaemon` holds NO wired classes and NO wired executor. The same +`submitAction` (`ExecuteCommand`) dispatches to native (`mars_machine`) or REST +(`mars_resource`) purely by what the target's classid resolves to — **zero daemon +change**. A new capability / class / runner is a registry entry, never code: +exactly *"scale = the next cascade level, never field-widening."* + +**Why this is the same canon, not a new one.** The CLAUDE.md P0 says a +renderer/router *"can lay out, group, route, and skeleton-render nodes from keys +alone, before (or without ever) fetching a value."* The action daemon is precisely +such a router: the `classid` in the GUID simultaneously selects the transport edge +it arrived on, the class's `ActionDef`, the state-guard, the executor (`RunnerKind`), +and the RBAC concept (`lo16`) — all before any value decode. The hi-u16 chooses the +render skin per app, the lo-u16 the shared concept (consumer doctrine). The action +arm didn't need a new abstraction; it needed to *be* the key-is-the-key-of-key-value +store applied to behavior. The hard gate (`commit_via`) is untouched — late binding +selects WHICH action, never whether it's authorized. + +**Fence:** the resolver needs a populated `ClassActions` manifest to resolve +against — which is exactly what B2-lift produces (`parse_capabilities` → +signatures; the deployed `GET /capabilities` IS the registry content). Empty +registry ⇒ resolves nothing (zero-fallback, never a panic); B2-lifted registration +⇒ resolves everything. Cross-ref: `D-ACTIONHANDLER-RESOLVER`, +`D-ACTIONHANDLER-TRANSPORT` (transport axis), `D-ACTIONHANDLER-B2LIFT` (the +registry content). + +--- + ## 2026-06-24 — E-ACTIONHANDLER-TRANSPORT — the daemon is transport-agnostic because HIRO is multi-wire; and the OGIT Auth type unifies "who connects" with "who the gate authorizes" **Status:** FINDING (`[G]` for the core + WebSocket edge; `[H]` for the Kafka edge). diff --git a/docs/ARAGO-ACTIONHANDLER-PARITY.md b/docs/ARAGO-ACTIONHANDLER-PARITY.md index c698692..d1a8242 100644 --- a/docs/ARAGO-ACTIONHANDLER-PARITY.md +++ b/docs/ARAGO-ACTIONHANDLER-PARITY.md @@ -269,6 +269,7 @@ transport over them. | **Executor — WinRM (B1)** | ⛔ `[H]` | a further `CapabilityExecutor` impl (Windows remote exec) | | **Live transport — daemon core + WebSocket (B2-transport)** | ✅ `[G]` SHIPPED | rs-graph-llm `graph-flow-action-ogar::daemon`: transport-agnostic `Daemon::react`/`serve` + `Transport` trait + `WsTransport` (action-ws), gate-driving; mock-server roundtrip. `Auth` ← OGIT `NTO/Auth/Configuration` | | **Live transport — Kafka edge (B2-transport)** | ⛔ `[H]` | `rdkafka` over the same `Transport` trait (action topic → result topic); core ready, needs the topic/record shape pinned | +| **Class-late-bound dispatch (the grail)** | ✅ `[G]` SHIPPED | rs-graph-llm `graph-flow-action-ogar::daemon::ResolvingDaemon` — class resolved from the target's **classid** per action (`ClassResolver`), executor from the resolved `RunnerKind` (`ExecutorRegistry`). `OgarResolver` is the production resolver over the canonical `actions_for(&[ClassActions], classid)` manifest. Proven: one `ExecuteCommand`, `mars_machine` → native / `mars_resource` → REST, zero daemon change; gate still rules | | **Instance config lift — capabilities (B2-lift)** | ✅ `[G]` SHIPPED | `registration::lift_registration` + `ogar-action-handler::parse_capabilities`: real `GET /capabilities` JSON → `ConcreteCapability` (`ActionParam[]`); `rest_registration_lifts_binds_and_runs` (JSON → lift → bind → run) | | **Instance config lift — applicabilities (B2-lift)** | ✅ `[G]` SHIPPED | `registration::lift_applicabilities` + `ogar-action-handler::parse_applicabilities`: real `GET /applicabilities` JSON → per-handler `StateGuard` sets; `rest_applicabilities_lift_to_per_handler_guards`. Residual: inner filter-list field name is alias-flexible pending a live response | @@ -299,6 +300,19 @@ runs commands locally / over SSH / as HTTP callouts, and speaks `action-ws` over live socket; a Kafka consumer away from being arago's Python daemon, on a HIRO deployment that distributes over Kafka. +**The grail — class chosen late from the classid.** Beyond the static daemon, the +`ResolvingDaemon` (`graph-flow-action-ogar::daemon`) holds **no** wired classes and +**no** wired executor: it resolves the action class from the target node's +**classid** per action (`ClassResolver`), and the executor from what that class +resolves to (`RunnerKind` → `ExecutorRegistry`). The production resolver +(`OgarResolver`) is backed by the canonical `actions_for(&[ClassActions], classid)` +DO manifest — OGAR's *"the key prerenders the node; classid → ClassView"* applied +to the action arm. One `ExecuteCommand` dispatches to native (`mars_machine`) or +REST (`mars_resource`) purely by what the classid resolves to, with zero daemon +change — and every action still passes the same hard gate. A new capability / +class / runner is a registry entry, never code (*"scale = the next cascade level, +never field-widening"*). + --- ## 5. The probe that promotes B1/B2 from `[H]` to `[G]` @@ -331,9 +345,11 @@ is replaceable; the parity claim is certified, not argued. - `rs-graph-llm/graph-flow-action-ogar` — the **uplink**: OGAR's `CapabilityExecutor` behind the hard gate (`GatedOgarHandler` / `run_gated`); `commit_via` lands before any execution. -- `rs-graph-llm/graph-flow-action-ogar/src/daemon.rs` — **B2-transport**: the - transport-agnostic `Daemon` (`react`/`serve`) + the `Transport` trait + - `WsTransport` (action-ws WebSocket edge) + the OGIT-`Auth`-derived identity. +- `rs-graph-llm/graph-flow-action-ogar/src/daemon.rs` — **B2-transport** + **the + grail**: the transport-agnostic `Daemon` (`react`/`serve`) + the `Transport` + trait + `WsTransport` (action-ws WebSocket edge) + the OGIT-`Auth`-derived + identity; **plus** `ResolvingDaemon` + `ClassResolver` / `ExecutorRegistry` / + `OgarResolver` (class chosen late from the classid via `actions_for`). - `rs-graph-llm/graph-flow-action-ogar/src/rest.rs` — the **REST executor** (`RestExecutor`, `feature = "rest"`): the arago HTTP-callout target, gated. - `crates/ogar-action-handler/src/lib.rs` — the **native** (`NativeCommandExecutor`) diff --git a/docs/DISCOVERY-MAP.md b/docs/DISCOVERY-MAP.md index dcca487..a7c1597 100644 --- a/docs/DISCOVERY-MAP.md +++ b/docs/DISCOVERY-MAP.md @@ -216,6 +216,7 @@ two halves of a cell. ADR‑026 names the cascade that ties them. | D‑ACTIONHANDLER‑TRANSPORT | The live action daemon (B2-transport), **transport-agnostic** by construction: rs‑graph‑llm `graph-flow-action-ogar::daemon::Daemon::react` turns one inbound `action-ws` JSON frame into the outbound frames it warrants (ack + `sendActionResult`, or nack), running the hard gate (`run_gated`) + executor in between — pure, no I/O. A `Transport` trait (`recv`/`send`) is the swappable edge; `Daemon::serve` is the loop; both the WebSocket and a future Kafka edge share it verbatim (HIRO distributes actions over BOTH wires — the wire differs, the dispatch doesn't). The `WsTransport` action-ws edge (`feature = "ws"`, tokio-tungstenite) presents the `token-$TOKEN` subprotocol and is proven by `ws_roundtrip_against_a_mock_server` (engine submitAction → ack → real command → result over a socket). Connection identity is an `Auth` type shaped after OGIT `NTO/Auth/Configuration` (`auth_store` 0x0B01): the principal the transport authenticates as (`accountId`) IS the actor the gate authorizes. Remaining: the Kafka edge (`rdkafka` over the same trait — core ready, needs topic/record shape) and SSH/REST executors | G (core + ws edge) / H (kafka edge) | CODED | `rs-graph-llm/graph-flow-action-ogar/src/daemon.rs` | D‑ACTIONHANDLER‑UPLINK, D‑ACTIONHANDLER‑B2LIFT | | D‑ACTIONHANDLER‑REST | REST executor target (B1) — the arago HTTP-callout handler shape: rs‑graph‑llm `graph-flow-action-ogar::rest::RestExecutor` (`feature = "rest"`, pure-Rust `ureq`, sync — fits the sync `CapabilityExecutor` trait) POSTs the bound params as a JSON body to a configured endpoint, returns the response `status`+`body` as `resultParameters`. Any completed HTTP response (incl. 4xx/5xx) is `resultParameters`; only a transport failure is an executor `Err` (mirrors arago reporting the callee's response). `Clone` (ureq Agent is Arc-backed) ⟹ composes into `Daemon`/`run_gated` as a gated route. Proven: `posts_params_and_returns_status_and_body` (mock HTTP) + `rest_executor_runs_only_behind_the_gate` (authorized → REST call fires; unauthorized → `Denied`, endpoint never hit). Completes the executor family with native; SSH/WinRM remain | G | CODED | `rs-graph-llm/graph-flow-action-ogar/src/rest.rs` | D‑ACTIONHANDLER‑UPLINK | | D‑ACTIONHANDLER‑SSH | SSH executor target (B1) — arago's canonical `ExecuteCommand`-over-SSH: `ogar-action-handler::SshExecutor` shells out to the system `ssh` binary (dep-free, like `NativeCommandExecutor` shells to `sh`), non-interactive by construction (`BatchMode=yes`), same `output`/`stderr`/`exitcode` resultParameters shape — the native executor made remote. `build_args` (pure argv construction with `-i`/`-p` + `--` command terminator) and the pre-spawn guards (missing-command / unknown-capability) are tested; end-to-end remote exec needs a live sshd (absent in CI). The two Command-based dep-free executors (native + SSH) live in OGAR; library-based network executors (REST) in rs-graph-llm | G (code) / H (live exec) | CODED | `ogar-action-handler/src/lib.rs` | D‑ACTIONHANDLER‑REST | +| D‑ACTIONHANDLER‑RESOLVER | The grail — **class-late-bound** action dispatch: rs‑graph‑llm `graph-flow-action-ogar::daemon::ResolvingDaemon` holds NO wired classes and NO wired executor. `ClassResolver` resolves the action class from the **target's classid** per action; `ExecutorRegistry` picks the executor from the resolved `RunnerKind` (`RegistryExecutor` adapts it so `run_gated` drives it — runner chosen POST-commit). The three axes (transport / class / executor) all key off the GUID. The production resolver `OgarResolver` is backed by the canonical `actions_for(&[ClassActions], classid)` DO manifest (no new dep — lance-graph-contract already wired); `target_classid` reads a 32-hex node GUID prefix OR a concept name (`canonical_concept_id`); classid→RunnerKind + capability→signature (B2-lift) complete the resolve. THIS is "the key prerenders the node; classid → ClassView" applied to the action arm — a new capability/class/runner is a registry entry, never a daemon change. Proven: one `ExecuteCommand`, `mars_machine`→native runs the real command / `mars_resource`→REST, zero daemon change, gate still rules; `ogar_resolver_drives_the_grail_via_actions_for` + GUID-prefix test. Shared `dispatch_action` core (static `Daemon` funnels through it too, behavior-preserving) | G | CODED | `rs-graph-llm/graph-flow-action-ogar/src/daemon.rs` | D‑ACTIONHANDLER‑TRANSPORT, D‑ACTIONHANDLER‑B2LIFT | | D‑OSM | `ogar-from-osm-pbf` — Node/Way/Relation; quadkey NiblePath from resolved geometry | H | IDEA | (queued) | D‑VOCAB, `[per rt]` D‑OSM‑3 | | D‑PATTERN | `ogar-pattern` — recognition library + confidence (FMA‑D/FIBO/SKR/PROV‑O) | H | IDEA | (queued) | D‑TTL | | D‑ACTION | `ogar-actionable` — lifecycle → `ActionDef`/`KausalSpec` | H | IDEA | (queued) | D‑PATTERN |