Skip to content

Commit 2e346ea

Browse files
authored
Merge pull request #171 from AdaWorldAPI/claude/medcare-rs-transcode-ruff-3y2olh
docs: proposal — ActionDef value-dispatch (body lowering + interpreter)
2 parents d305d2e + c2af494 commit 2e346ea

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# ActionDef value-dispatch — body lowering + a value interpreter
2+
3+
> **Status:** PROPOSAL (2026-07-07). Producer/executor capability gap; no
4+
> consumer-specific detail. Sibling of `OGAR-AS-IR.md` (the compiler framing)
5+
> and `OGAR-TRANSPILE-SUBSTRATE.md` (the bidirectional per-class transpiler).
6+
7+
## Problem
8+
9+
`compile_graph_*` (the `ruby` / `python` / `sqlalchemy` / `csharp` frontends)
10+
emit `CompiledClass { class, facet, actions }`. Each `ActionDef` carries
11+
`identity` / `predicate` / `object_class` plus the effect sets `writes` /
12+
`reads` / `calls` / `raises` — but **not what the method computes**.
13+
14+
Measured over a real compiled corpus (2748 `ActionDef`s), every emitted action has:
15+
16+
- `kausal = None`, `body_source = None`, `on_enter = None`, `decorators = []`
17+
- `default_modal` / `default_temporal` / `default_subject` = the constants
18+
`Sync` / `Immediate` / `System` (no per-method information)
19+
20+
Only `identity` / `predicate` / `object_class` + the name-level effect sets are
21+
populated. `crates/ogar-vocab/src/lib.rs` documents this directly:
22+
23+
- `writes`*"the value written is not captured by today's frontend, so this is
24+
name-level only and does NOT auto-build an `on_enter` `EnterEffect`"*
25+
(lib.rs:402-403).
26+
- `calls`*"Effect annotation for call-graph analysis; no body is captured."*
27+
(lib.rs:406).
28+
29+
Crucially, the **typed carriers already exist**`on_enter: Option<EnterEffect>`
30+
(lib.rs:423), `EnterEffect` (lib.rs:447), and `ActionDef::kausal` /
31+
`ActionDef::body_source` (consumed by `ogar-emitter` at lib.rs:667-674). The gap
32+
is not the schema — it is that **the frontend never populates them**.
33+
34+
## Consequence
35+
36+
An `ActionDef` says *which fields a method touches*, not *what it computes*. A
37+
consumer can therefore verify **structural effect-parity** — does the running
38+
code's observed write-set match the declared `writes`? — but it **cannot execute
39+
an `ActionDef` to reproduce a recoverable method's return value**. There is no
40+
expression, formula, or lowered body in the artifact.
41+
42+
The two executors that exist today are not value interpreters:
43+
44+
- `ogar-action-handler::NativeCommandExecutor` / `CapabilityExecutor::execute`
45+
runs a capability by shelling a bound `command` parameter to an external
46+
handler (SSH/REST/shell). The logic lives in the external handler plus the
47+
invocation params, not in the `ActionDef`.
48+
- `lance-graph-ogar::OgarActionProvider` + `contract::action::ActionInvocation::commit`
49+
— the authorization/gate path (RBAC → StateGuard → MUL `GateDecision`). A
50+
capability dispatcher / gate, not a value interpreter.
51+
52+
So a consumer that wants a recoverable `Compute` / `Normalize` method to run
53+
*through* its `ActionDef` (instead of a hand-written reimplementation) is blocked:
54+
reproducing the value would force the consumer to re-derive the formula itself —
55+
exactly the "logic in the consumer" that the consumer-side boundary forbids
56+
(`OGAR-CONSUMER-BEST-PRACTICES.md`: the classid is address; the magic resolves at
57+
the target — behaviour is a property of the Core node, never re-implemented
58+
downstream).
59+
60+
## Proposal
61+
62+
Two coordinated pieces, so recoverable methods can dispatch value-for-value.
63+
64+
### 1. Frontend lowering — populate the carriers that already exist
65+
66+
During `compile_graph_*`, capture method bodies into an executable form and
67+
populate the **existing** typed fields rather than name-level effect sets only:
68+
69+
- `ActionDef::on_enter: EnterEffect` — the value-producing transition for a
70+
`Compute` / `Normalize` recipe (the recipe-codebook's recoverable band).
71+
- `ActionDef::kausal: KausalSpec` — the guard/lifecycle/dependency structure for
72+
`Guard` / `Cascade`.
73+
74+
This is the frontend half of the recipe-codebook's promise: a recoverable body is
75+
supposed to lower to a *declarative recipe*, but today the recipe classification
76+
lands while the recipe *content* does not. The `fuzzy-recipe-codebook` method
77+
(ruff `.claude/knowledge/`) already correlates a body to a `(verb, criteria)`
78+
recipe; the missing step is emitting that recipe's computable form into
79+
`EnterEffect` / `KausalSpec`.
80+
81+
### 2. A value-reproducing interpreter
82+
83+
An interpreter that takes an `ActionDef` (+ typed inputs) and yields its effect
84+
values. Per the "thinking lives in lance-graph" boundary, this belongs on the
85+
lance-graph side, consistent with `lance-graph-ogar` already owning the
86+
authorization/commit path. Consumers consume it through a thin trait seam:
87+
88+
```rust
89+
trait ActionDefExecutor {
90+
fn apply(&self, def: &ActionDef, inputs: &EffectInputs)
91+
-> Result<EffectValues, Unsupported>;
92+
}
93+
```
94+
95+
A consumer wires this seam with a default `Unsupported` implementation today and
96+
flips it to the real impl the moment the interpreter lands — never implementing
97+
the formula itself.
98+
99+
## Scope / non-goal
100+
101+
This is the gap between *"the offline transpile emits effect descriptors"*
102+
(works today) and *"a runtime dispatches recoverable methods value-for-value"*
103+
(blocked). It is purely a producer/executor capability gap with no
104+
consumer-specific detail.
105+
106+
**Staging.** Until both pieces land, a consumer's honest ceiling for any
107+
recoverable route is **structural-effect-parity green, value-dispatch pending**:
108+
it can diff a running handler's observed write-set against the declared `writes`
109+
(a mechanical, non-thinking check) and shadow it as observability, but it cannot
110+
promote a route to `ActionDef`-primary. The `ActionDefExecutor`-defaults-to-
111+
`Unsupported` seam is exactly the forward-compatible shape for that staging: no
112+
behaviour changes downstream until the interpreter exists, and no formula is ever
113+
re-implemented in a consumer.
114+
115+
## Falsifier
116+
117+
The proposal is validated when, for one recoverable `Compute` method, an
118+
`EnterEffect`-lowered `ActionDef` fed through the interpreter reproduces the
119+
hand-written oracle's return value **bit-for-bit** on a golden fixture set — the
120+
same shape as the existing byte-parity discipline elsewhere in the stack.

0 commit comments

Comments
 (0)