|
| 1 | +# Evaluation: Foundry Workshop low-code on Elixir abstraction, compiled to Rust |
| 2 | + |
| 3 | +> **READ BY:** agents working the Elixir frontend, Foundry-shape codegen, |
| 4 | +> Workshop/A2UI surface, or the SPO ontology consumer path. |
| 5 | +> |
| 6 | +> **Status:** EVALUATION (2026-05-28, opus-4-8 as SavantPattern). Grounded in |
| 7 | +> read files (cited file:line); honest about gaps. Companion to |
| 8 | +> `.claude/knowledge/semantic-operational-handbook-v0.1.md` (the primitive |
| 9 | +> catalogue) and the now-verified SPO ontology loader |
| 10 | +> (`crates/lance-graph/src/graph/spo/odoo_ontology.rs`, 4 tests green). |
| 11 | +> |
| 12 | +> **Deterministic only.** No LLM, no similarity, no inference. The Workshop |
| 13 | +> low-code layer resolves names against a typed ontology + dispatches |
| 14 | +> compile-time recipes; the runtime is Rust + ractor, not a model. |
| 15 | +
|
| 16 | +## The question |
| 17 | + |
| 18 | +Can Foundry Workshop's "apps over ontology" low-code model sit on top of an |
| 19 | +**Elixir-syntax abstraction layer** whose **holy grail is compile-to-Rust |
| 20 | +underneath**? Where does the mapping hold, where does it break? |
| 21 | + |
| 22 | +## The three layers, concretely |
| 23 | + |
| 24 | +``` |
| 25 | + Workshop low-code surface (what a domain expert authors) |
| 26 | + │ view/action/widget bound to ontology |
| 27 | + ▼ |
| 28 | + Elixir-syntax abstraction (readable DSL — NOT BEAM at runtime) |
| 29 | + │ tree-sitter-elixir parse → typed resolve against ontology |
| 30 | + ▼ |
| 31 | + Rust compiled underneath (the holy grail — executable semantics) |
| 32 | + │ recipe → KernelHandle → ractor mailbox → SPO store |
| 33 | + ▼ |
| 34 | + SPO ontology substrate (what names resolve against) |
| 35 | + odoo_ontology.spo.ndjson → SpoStore (22 245 triples, VERIFIED) |
| 36 | +``` |
| 37 | + |
| 38 | +The bottom layer is **built and tested as of this session**. The top two are |
| 39 | +partially specced (`lance-graph-elixir-frontend-v1.md`) and partially gapped |
| 40 | +(below). |
| 41 | + |
| 42 | +## What HOLDS (grounded in read files) |
| 43 | + |
| 44 | +### 1. The ontology substrate exists and is queryable — deterministically |
| 45 | + |
| 46 | +`crates/lance-graph/src/graph/spo/odoo_ontology.rs` (this session, 4 tests |
| 47 | +green) loads 22 245 triples into the existing `SpoStore`. The Foundry |
| 48 | +primitives map 1:1 onto triple classes: |
| 49 | + |
| 50 | +| Foundry (Workshop) | SPO triple class | count | |
| 51 | +| --- | --- | ---: | |
| 52 | +| Object Type | `(odoo:<family>, rdf:type, ogit:ObjectType)` | 388 | |
| 53 | +| Property | `(odoo:<fam>.<field>, rdf:type, ogit:Property)` | 3 107 | |
| 54 | +| Function | `(odoo:<fam>.<fn>, rdf:type, ogit:Function)` | 3 328 | |
| 55 | +| Function dependency | `(field, depends_on, dep)` | 6 309 | |
| 56 | +| Function output | `(field, emitted_by, fn)` | 3 228 | |
| 57 | +| Link / traversal | `(fn, traverses_relation, rel)` | 11 | |
| 58 | +| Guard signal | `(fn, raises, exc:<Type>)` | 451 | |
| 59 | + |
| 60 | +The "**a + b → c through d?**" query — Foundry's core Function-resolution |
| 61 | +semantics — is a deterministic graph deduction over `depends_on` + |
| 62 | +`emitted_by`, NOT a similarity search. Verified working: |
| 63 | +`account_move.amount_total emitted_by _compute_amount, depends_on |
| 64 | +{line_ids.balance, line_ids.amount_residual, currency_id, …}`. |
| 65 | + |
| 66 | +### 2. The Elixir-as-surface decision is already doctrine, not invention |
| 67 | + |
| 68 | +`.claude/plans/lance-graph-elixir-frontend-v1.md` (read this session) states |
| 69 | +**"The BEAM is not a runtime target — Elixir is the SURFACE only; the |
| 70 | +substrate stays typed Rust + SIMD + JIT."** This is exactly the |
| 71 | +"Elixir abstraction, compile to Rust" question — and the workspace already |
| 72 | +committed to that shape. tree-sitter-elixir parses; HM-style inference over |
| 73 | +`|>` pipelines resolves types; the target is `lance-graph-contract::cognition` |
| 74 | +calls. 9 stages, 5 verbs, ~50 OpKind discriminants per domain. |
| 75 | + |
| 76 | +### 3. The recipe hot-load protocol matches Workshop's add-without-redeploy |
| 77 | + |
| 78 | +`crates/lance-graph-contract/src/recipe.rs:28-35` (read this session) defines |
| 79 | +the Elixir open/closed split: |
| 80 | +- `add-atom` = **data** change (new row in the LOCKED 33-dim basis, no recompile) |
| 81 | +- `add-style`/`add-persona` = **template** change (register a `RecipeTemplate` |
| 82 | + → JIT `KernelHandle` at next activation) |
| 83 | + |
| 84 | +This is precisely Workshop's promise: a power-user adds an Action or Function |
| 85 | +without a platform redeploy. The recipe layer is the compile-time analogue of |
| 86 | +Workshop's runtime action registry. |
| 87 | + |
| 88 | +### 4. The mailbox IS Foundry Automate, actor-native |
| 89 | + |
| 90 | +The 400 ms ractor mailbox + SurrealQL kanban ("request-lose Goalstate |
| 91 | +Maschine", `EPIPHANIES.md E-FOUNDRY-LAYER-1`) is the actor-native form of |
| 92 | +Foundry Automate: object-change → goal-card → mailbox resolves → action |
| 93 | +staged/applied → audit. Foundry does this as a runtime service; this stack |
| 94 | +does it as supervised actors. Equivalent operational loop, different |
| 95 | +substrate. |
| 96 | + |
| 97 | +## What BREAKS / is GAPPED (honest) |
| 98 | + |
| 99 | +### Gap 1 — Workshop's widget/view layer has NO ontology-native primitive |
| 100 | + |
| 101 | +The existing `ruff_python_dto_check::codegen` (read this session: |
| 102 | +`codegen/jinja.rs:1-12`, `codegen/columns.rs:1-20`) is **Flask→askama HTML |
| 103 | +translation** — `<table>{% for %}` extraction, `_translate_cell_expr`, |
| 104 | +`elif→else-if`. That is route-handler HTML view generation, NOT |
| 105 | +ontology-bound widgets. Workshop widgets (`object table`, `object view`, |
| 106 | +`action button`, `kanban`, `scenario`) bind to Object Types + Actions, not to |
| 107 | +jinja templates. |
| 108 | + |
| 109 | +**Consequence:** the `view` primitive in the handbook (§5) has no existing |
| 110 | +emitter. Building it is NOT a reuse of the jinja codegen — it is a new |
| 111 | +ontology→widget emitter. Do not conflate them (this was a category error |
| 112 | +earlier in the session). |
| 113 | + |
| 114 | +### Gap 2 — Actions exist as openings, but `requires{}` / `effects{}` are unsplit |
| 115 | + |
| 116 | +The 16 openings (`.claude/odoo/openings_hops.py`, this session) classify |
| 117 | +3 555 methods into named verbs (`iter_records_compute_from_related`, |
| 118 | +`iter_records_raise_on_violation`, …). But a Foundry Action needs the |
| 119 | +guard/effect split: `requires { precondition }` + `effects { mutation }`. |
| 120 | +The openings give the *shape* (e.g. `iter_records_raise_on_violation` = a |
| 121 | +guard that raises); they do NOT yet emit the structured |
| 122 | +`requires{}`/`effects{}` blocks. The `raises` triples (451) are the guard |
| 123 | +signal; the `emitted_by`/`writes` are the effect signal — both are in the SPO |
| 124 | +graph now, but the Action-DSL emitter that composes them is unbuilt. |
| 125 | + |
| 126 | +### Gap 3 — Submission criteria / governance has no Odoo-extracted primitive |
| 127 | + |
| 128 | +Foundry Actions carry submission criteria (user/role/object/relation |
| 129 | +permissions). The Odoo extraction has `@api.constrains` validators (data |
| 130 | +integrity) but NOT the user-permission layer — Odoo `ir.model.access` / |
| 131 | +record rules live in XML/CSV, not in the method bodies the harvest read. |
| 132 | +**The governance spine is absent from the current extraction.** It would need |
| 133 | +a second extraction pass over Odoo's security CSVs. |
| 134 | + |
| 135 | +### Gap 4 — depends_on hop chains are stored as flat strings, not resolved links |
| 136 | + |
| 137 | +`account_move.amount_total depends_on |
| 138 | +line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual` |
| 139 | +— that 5-hop dotted path is stored as ONE object string. It is a real link |
| 140 | +chain (Foundry would resolve each segment to a Link traversal across Object |
| 141 | +Types), but the current emitter does not split it into per-hop |
| 142 | +`(ObjectType, link, ObjectType)` triples. The chain is queryable as a literal |
| 143 | +but not yet as a traversable link path. Splitting it requires the |
| 144 | +relation-target table (which model `line_ids` points at) — partially in the |
| 145 | +`OdooEntity` consts (`odoo_blueprint/extracted/`), not yet joined. |
| 146 | + |
| 147 | +## Verdict |
| 148 | + |
| 149 | +**The Elixir-on-Rust-with-Foundry-Workshop shape is sound and the bottom two |
| 150 | +layers are real**, not vapor: |
| 151 | + |
| 152 | +- The SPO ontology substrate is built + tested (this session). |
| 153 | +- The Elixir-surface/compile-to-Rust decision is committed doctrine. |
| 154 | +- The recipe hot-load + ractor mailbox are the Workshop-action + Automate |
| 155 | + analogues, actor-native and compile-first. |
| 156 | + |
| 157 | +**The top layer (Workshop widgets) and the Action guard/effect split are the |
| 158 | +real unbuilt work** — and crucially, the existing jinja codegen is NOT a |
| 159 | +shortcut to them (it is HTML-route translation, a different domain). |
| 160 | + |
| 161 | +The honest one-line assessment: |
| 162 | + |
| 163 | +> Foundry operationalizes ontology at **runtime** via a governed platform. |
| 164 | +> This stack compiles ontology into **executable Rust semantics** at build |
| 165 | +> time, dispatched by actors. The ontology spine + dependency graph are |
| 166 | +> done; the widget surface + the governance/permission spine are the gaps. |
| 167 | +
|
| 168 | +## Concrete next deliverables (deterministic, no model) |
| 169 | + |
| 170 | +1. **Action guard/effect emitter** — compose `raises` (guard) + `emitted_by` |
| 171 | + (effect) per opening into `requires{}`/`effects{}` blocks. Input: the SPO |
| 172 | + graph (built). Output: one Action spec per (family, opening). |
| 173 | +2. **Link-chain splitter** — join `depends_on` dotted paths against the |
| 174 | + `OdooEntity` relation-target table (`odoo_blueprint/extracted/`) to emit |
| 175 | + per-hop `(ObjectType, link, ObjectType)` triples. Makes the dependency |
| 176 | + graph traversable as Foundry Links. |
| 177 | +3. **Governance extraction (second pass)** — read Odoo `ir.model.access.csv` + |
| 178 | + record rules → `(role, may_execute, action)` triples. Closes Gap 3. |
| 179 | +4. **Workshop widget emitter** — ontology Object Type → object-table / |
| 180 | + object-view / action-button widget spec. New emitter, NOT the jinja path. |
| 181 | + This is the largest unbuilt piece. |
| 182 | + |
| 183 | +Each consumes the SPO ontology that is now in the store, and each is |
| 184 | +deterministic graph→spec emission. |
0 commit comments