|
| 1 | +# HARVEST — WoA sink-in substrate v1 |
| 2 | + |
| 3 | +`WoA` (Flask-SQLAlchemy, `/home/user/WoA/models.py`, READ-ONLY corpus) transcoded |
| 4 | +through the ruff → OGAR pipeline into the V3 transpile substrate. Produced for |
| 5 | +MISSION "OGAR V3: Criticals testen + Python-Arm real machen (WoA 1:1)" as the |
| 6 | +concrete SPEC-5 Part B/C/D reality-check artifact set. |
| 7 | + |
| 8 | +## Pipeline (real code path, nothing hand-written) |
| 9 | + |
| 10 | +``` |
| 11 | +/home/user/WoA/models.py |
| 12 | + │ ruff_sqlalchemy_spo::extract_file (frontend, /tmp/wt-gr crates/ruff_sqlalchemy_spo, |
| 13 | + │ branch claude/spo-python-main) |
| 14 | + ▼ |
| 15 | +ruff_spo_triplet::ModelGraph |
| 16 | + │ ogar-from-ruff::sqlalchemy::lift_model_graph_sqlalchemy |
| 17 | + ▼ |
| 18 | +Vec<ogar_vocab::Class> (schema stratum: attributes / associations) |
| 19 | + │ ogar-from-ruff::mint::compile_graph_sqlalchemy::<WoaPort> |
| 20 | + ▼ |
| 21 | +Vec<CompiledClass> (Class + 16-byte rail Facet, classid via WoaPort) |
| 22 | + │ ogar-from-ruff::emit::emit_python -> models.py |
| 23 | + │ (association walk, classid-annotated) -> woa_graph.spo |
| 24 | + │ ogar-adapter-postgres-ddl::emit_facet_table_ddl + emit_postgres_ddl -> woa_facet.sql |
| 25 | + ▼ |
| 26 | +this directory |
| 27 | +``` |
| 28 | + |
| 29 | +Driver: a scratch Cargo binary at `/tmp/harvest-woa` (NOT committed anywhere — |
| 30 | +path-deps + a local `[patch]` block only, per the mission's leitplanken). See |
| 31 | +"Reproduction" below for the exact commands to regenerate. |
| 32 | + |
| 33 | +## Metrics |
| 34 | + |
| 35 | +| Metric | Value | |
| 36 | +|---|---| |
| 37 | +| `db.Model` classes harvested | **139** (`ruff_sqlalchemy_spo` corpus-gate test asserts `>= 139`; WoA's own `grep -c 'class.*db.Model'` on `models.py` also gives 139 — exact match, no discrepancy) | |
| 38 | +| Attributes (`Attribute` after FK-dedup) | **1961** | |
| 39 | +| Associations (`db.relationship`, `BelongsTo`/`HasMany`) | **107** | |
| 40 | +| Aliased (resolve through `WOA_ALIASES` convergence pins, concept ≠ 0) | **6** | |
| 41 | +| Bootstrap (unmapped model name → classid `0x0000_0003`, concept = 0) | **133** | |
| 42 | + |
| 43 | +### The 6 aliased classes (WOA_ALIASES convergence pins, `ogar-vocab/src/ports.rs`) |
| 44 | + |
| 45 | +| Class | classid | concept | Same concept converges with | |
| 46 | +|---|---|---|---| |
| 47 | +| `TimesheetActivity` | `0x0103_0003` | `0x0103` (`BILLABLE_WORK_ENTRY`) | `OpenProjectPort::TimeEntry`, `RedminePort::TimeEntry` | |
| 48 | +| `Customer` | `0x0204_0003` | `0x0204` | (CRM-concept alias) | |
| 49 | +| `WorkOrder` | `0x0202_0003` | `0x0202` | (matches Odoo's `account.move`-adjacent concept family — same concept id Odoo's `sale.order`/`account.move` land in) | |
| 50 | +| `Position` | `0x0201_0003` | `0x0201` | | |
| 51 | +| `RecurringInvoice` | `0x0202_0003` | `0x0202` | same concept as `WorkOrder` — both are "billing document" shaped | |
| 52 | +| `TaxRate` | `0x0203_0003` | `0x0203` | | |
| 53 | + |
| 54 | +The remaining 133 models mint the **bootstrap** classid `0x0000_0003` |
| 55 | +(concept `0x0000`, WoA app prefix `0x0003`) — `WOA_ALIASES` is a small, |
| 56 | +deliberately curated table (per `ports.rs` docs, a handful of named |
| 57 | +convergence pins), not a name-for-name mirror of all 139 WoA models. This is |
| 58 | +the honest, measured state (matches `sqlalchemy.rs`'s own doc comment on |
| 59 | +`compile_graph_sqlalchemy`, which names this exact caveat for |
| 60 | +`TimesheetActivity`) — no attempt was made to mint new aliases (mint |
| 61 | +governance: no new Codebook concepts without an Operator ruling). |
| 62 | + |
| 63 | +### Convergence spot-check (mission-mandated) |
| 64 | + |
| 65 | +``` |
| 66 | +TimesheetActivity -> classid 0x01030003 (concept 0x0103, app 0x0003) |
| 67 | +``` |
| 68 | + |
| 69 | +Matches the fixture in `ogar-from-ruff/src/mint.rs`'s own doc comment on |
| 70 | +`compile_graph_sqlalchemy` and its test |
| 71 | +`compile_graph_sqlalchemy_timesheet_activity_converges_via_alias_not_bootstrap` |
| 72 | +byte-for-byte. `TimesheetActivity`'s harvested shape also matches WoA source |
| 73 | +`models.py:1746-1753` exactly: `id` (required), `beschreibung` (required), |
| 74 | +`created_at` (nullable — no `nullable=False` in source), `timesheet_id` |
| 75 | +FK-shadowed by the `timesheet: ToOne["TimeSheet"]` association (not |
| 76 | +double-projected). |
| 77 | + |
| 78 | +## Gates run |
| 79 | + |
| 80 | +- `python3 -m py_compile models.py` → **PASS** (module parses; see "Known |
| 81 | + gaps" below for what this gate does NOT prove). |
| 82 | +- `cargo test -p ruff_sqlalchemy_spo` corpus gate (`WOA_SRC=/home/user/WoA/models.py`) |
| 83 | + already asserts `>= 139` models upstream in the frontend crate itself (not |
| 84 | + re-run here — this harvest's own driver run independently reproduces the |
| 85 | + same 139). |
| 86 | + |
| 87 | +## Reproduction |
| 88 | + |
| 89 | +```sh |
| 90 | +# 1. Scratch driver (path deps + local-only [patch], never committed): |
| 91 | +mkdir -p /tmp/harvest-woa/src |
| 92 | +cat > /tmp/harvest-woa/Cargo.toml <<'TOML' |
| 93 | +[package] |
| 94 | +name = "harvest-woa" |
| 95 | +version = "0.1.0" |
| 96 | +edition = "2021" |
| 97 | +publish = false |
| 98 | +[[bin]] |
| 99 | +name = "harvest-woa" |
| 100 | +path = "src/main.rs" |
| 101 | +[dependencies] |
| 102 | +ruff_sqlalchemy_spo = { path = "/tmp/wt-gr/crates/ruff_sqlalchemy_spo" } |
| 103 | +ogar-from-ruff = { path = "/workspace/ogar/crates/ogar-from-ruff" } |
| 104 | +ogar-vocab = { path = "/workspace/ogar/crates/ogar-vocab" } |
| 105 | +ogar-adapter-postgres-ddl = { path = "/workspace/ogar/crates/ogar-adapter-postgres-ddl" } |
| 106 | +[patch."https://github.com/AdaWorldAPI/ruff"] |
| 107 | +ruff_spo_triplet = { path = "/tmp/wt-gr/crates/ruff_spo_triplet" } |
| 108 | +ruff_spo_address = { path = "/tmp/wt-gr/crates/ruff_spo_address" } |
| 109 | +TOML |
| 110 | +cp /workspace/ogar/rust-toolchain.toml /tmp/harvest-woa/rust-toolchain.toml # pins 1.95.0 |
| 111 | +# (src/main.rs: extract_file -> compile_graph_sqlalchemy::<WoaPort> -> emit_python |
| 112 | +# + a classid-keyed association walk for woa_graph.spo + the postgres-ddl adapter |
| 113 | +# calls for woa_facet.sql — see this harvest's provenance pins below for the |
| 114 | +# exact commit this was run against.) |
| 115 | + |
| 116 | +cd /tmp/harvest-woa |
| 117 | +cargo run --quiet -- /home/user/WoA/models.py /tmp/harvest-woa/out |
| 118 | +python3 -m py_compile /tmp/harvest-woa/out/models.py |
| 119 | +``` |
| 120 | + |
| 121 | +## Provenance |
| 122 | + |
| 123 | +| Input | Pin | |
| 124 | +|---|---| |
| 125 | +| WoA (`AdaWorldAPI/WoA`, `models.py`, READ-ONLY) | `4427b3d715d841290ea9108e3bf94b22d0cb72c2` (last commit touching `models.py`) | |
| 126 | +| ruff (`AdaWorldAPI/ruff`, `ruff_sqlalchemy_spo`) | `/tmp/wt-gr`, branch `claude/spo-python-main`, HEAD `66db5c417eddf6017e924706031a23b019c17e81` | |
| 127 | +| OGAR (`ogar-from-ruff`, `ogar-vocab`, `ogar-adapter-postgres-ddl`) | `/workspace/ogar`, branch `claude/v3-criticals-woa-parity`, HEAD `d5479ce382136b3aedad3c122e13d3aab6f9695e` | |
| 128 | + |
| 129 | +## Known gaps (honest, not papered over) |
| 130 | + |
| 131 | +1. **Behavior = names only.** `Model::functions` (methods) are harvested |
| 132 | + name-only by `ruff_sqlalchemy_spo::functions` (v0, per its module doc) — |
| 133 | + no params, no `reads`/`writes`, no `body_source`. `emit_python` therefore |
| 134 | + emits **zero** methods; the generated dataclasses are schema-only mirrors |
| 135 | + of WoA's `db.Model` classes, not behavior-complete ports. This is the same |
| 136 | + gap the mission's Critical #1 (`behavior`) names for `lift_actions` |
| 137 | + generally (`ogar-from-ruff/src/lib.rs:495`) — this SQLAlchemy harvest |
| 138 | + doesn't lift `ActionDef`/`KausalSpec` at all (v0 doesn't wire it), so the |
| 139 | + gap is total rather than partial here. |
| 140 | +2. **`woa/models_shop.py` (12 more `db.Model` classes) is out of scope for |
| 141 | + this v1.** Only the monolithic `/home/user/WoA/models.py` (139 classes) |
| 142 | + was harvested, per the mission's explicit instruction. Follow-up: run |
| 143 | + `ruff_sqlalchemy_spo::extract_file` a second time against |
| 144 | + `woa/models_shop.py` (or `extract_with` over the `woa/` tree) and merge |
| 145 | + the two `ModelGraph`s before minting, so shop-domain FKs into the core 139 |
| 146 | + resolve rather than dangling. |
| 147 | +3. **`emit_python`'s import list is incomplete — `models.py` does not |
| 148 | + `import` (nor is `ogar_sdk.py` shipped alongside).** The header only |
| 149 | + imports `OgScalar, ToOne, ToMany` from `ogar_sdk` (mirroring the PR #154 |
| 150 | + Python-emit precedent's own header), but `emit_python`'s |
| 151 | + `og_scalar_type()` mapping emits bare names — `OgInt`, `OgStr`, `OgBool`, |
| 152 | + `OgDateTime`, `OgFloat`, `OgMoney`, `OgDate`, `OgBytes`, `OgSelection`, |
| 153 | + `OgJson` — that are **never imported**. `python3 -m py_compile` still |
| 154 | + passes because compiling to bytecode does not evaluate the class-body |
| 155 | + annotation expressions at import time in a way that raises — it is a |
| 156 | + syntax-level gate only, not proof the module is importable/runnable. |
| 157 | + Actually running `import models` would raise `NameError` on the first |
| 158 | + annotated attribute. This is `ogar-from-ruff::emit_python`'s existing gap |
| 159 | + (not introduced by this harvest), reported per the mission's instruction |
| 160 | + to name the "emit_python required-Lücke" honestly rather than |
| 161 | + hand-patching the generated output. |
| 162 | +4. **`woa_graph.spo` target-classid resolution: measured, not assumed.** An |
| 163 | + association's target (`Association::class_name`, e.g. `TimeSheet` for |
| 164 | + `TimesheetActivity.timesheet`) is looked up by name against the 139 |
| 165 | + harvested+minted classes; a miss (renamed target, target lives in |
| 166 | + `models_shop.py`, or a `class_name` string that doesn't match any |
| 167 | + harvested class) is written as `[--------]` rather than silently guessed. |
| 168 | + Measured over the actual 107-edge output: **0 of 107 edges are |
| 169 | + `[--------]`** — every association target in `models.py` resolves within |
| 170 | + the same 139-class corpus (the file is self-contained; nothing points |
| 171 | + into the excluded `models_shop.py`). This is a stronger result than |
| 172 | + expected going in and is called out explicitly so it isn't mistaken for |
| 173 | + an unverified assumption. |
| 174 | +5. **`PostgreSQL` DDL is the relational (`emit_postgres_ddl`) shape, not yet |
| 175 | + wired to the facet table.** `woa_facet.sql` emits the two pieces |
| 176 | + side-by-side (facet table via `emit_facet_table_ddl("facet")`, then one |
| 177 | + `CREATE TABLE` per class via `emit_postgres_ddl`) but does **not** attempt |
| 178 | + the dual-write / parity-checker wiring the Operator's ruling (b, refined) |
| 179 | + describes — that is explicitly a "follow-up, NOT built here" per |
| 180 | + `ogar-adapter-postgres-ddl`'s own module doc. |
| 181 | +6. **Falsifizierer #1 (WP-Parity)-style field-by-field diff against the WoA |
| 182 | + source was not run for all 139 classes** — only the `TimesheetActivity` |
| 183 | + fixture (§ "Convergence spot-check" above) was checked field-for-field |
| 184 | + against source. A full N/N-columns-typed parity metric (mission's |
| 185 | + D-PARITY-PROBE-style measure) across all 139 classes is a reasonable |
| 186 | + next step but out of scope for this harvest pass. |
0 commit comments