|
| 1 | +# TB-MVP-01 — Typed Builder Order vertical slice: report and verdict |
| 2 | + |
| 3 | +**VERDICT: `GO_TYPED_BUILDER_MVP`.** |
| 4 | + |
| 5 | +| | commit | |
| 6 | +|---|---| |
| 7 | +| base (`main`, H1 merged, #390) | `877ee69f28ba169c1fd68935b41f4ef26d92f186` | |
| 8 | +| preregistration | `cf7eb01` ([`tb-mvp-01-preregistration.md`](tb-mvp-01-preregistration.md)) | |
| 9 | +| implementation | `95d811b` | |
| 10 | +| gate fix (clean-checkout check only) | `e946d82` | |
| 11 | +| official run (PASS) | `e946d82a46a5` | |
| 12 | + |
| 13 | +Sample: [`samples/OrderBackend`](../../samples/OrderBackend). Gate: |
| 14 | +`scripts/typed_builder_gate.py`. |
| 15 | + |
| 16 | +## Official runs |
| 17 | + |
| 18 | +1. **Run 1 — FAIL**, at `95d811b`: `--clean-checkout --runs 2 --rust`. |
| 19 | + - Every step of both worktree runs passed, with 0 failures each, and the evidence digests matched across the two runs. |
| 20 | + - The run still failed on the gate's own "no build output in a fresh checkout" check. It reported `rust/crates/own-shadow/src/bin`, which is tracked Rust source, not .NET build output. |
| 21 | + - **Defect in the gate check, not in the slice.** `e946d82` narrows it to `bin/`/`obj/` beside a `.csproj`. It changes no registered expectation, and was committed before the rerun. |
| 22 | +2. **Run 2 — PASS**, at `e946d82`: same command. Each of two fresh `git worktree`s of HEAD, with no `bin/`/`obj/`, restores, builds, generates, scans, runs the corpus and runs the acceptance twice. |
| 23 | + |
| 24 | + | evidence | run 1 | run 2 | |
| 25 | + |---|---|---| |
| 26 | + | `acceptance.txt` | `ba447304cdf957ee` | `ba447304cdf957ee` | |
| 27 | + | `corpus.json` | `62d7b100a2ebd186` | `62d7b100a2ebd186` | |
| 28 | + | `orderbackend.facts.json` | `e9a0c81d941d3b50` | `e9a0c81d941d3b50` | |
| 29 | + |
| 30 | + Both runs also equal the committed `samples/OrderBackend/evidence/*`. Full sha256: |
| 31 | + - `acceptance.txt` `ba447304cdf957eef3f04adb6d1d5ee7847be3d21262f221101c5e25d9a12cc3`; |
| 32 | + - `corpus.json` `62d7b100a2ebd18605cc121920cab4085505eaea08594c5e633adbb2db520722`; |
| 33 | + - `orderbackend.facts.json` `e9a0c81d941d3b501b85b2123d2383d2d1edcc6e07c690996825f18a30d4f191`; |
| 34 | + - generated `Order.Protocol.cs` `857f02768f6531c7e6e498af5d0528dbdbba435cb610632e2e0c2d65d5f33484`. |
| 35 | + |
| 36 | +**Before the official run.** Three development-time gate failures were fixed in the gate's |
| 37 | +own matching. No expectation in the registration changed: |
| 38 | +- a comment `Starts a new Order` matched the "no entity created outside `Build()`" regex; |
| 39 | +- the compiler names `'Order.Status'` and `'Order.Order()'`, while the gate looked for `'Status'` and `'Order'`. |
| 40 | + |
| 41 | +## Counts |
| 42 | + |
| 43 | +| | | |
| 44 | +|---|---| |
| 45 | +| generated state types | 4: `DraftOrder`, `SubmittedOrder`, `ApprovedOrder`, `ShippedOrder` | |
| 46 | +| generated transitions | 3: `Submit`, `Approve`, `Ship` | |
| 47 | +| generated region entries (checked refinement) | 3: `WithDraft`, `WithSubmitted`, `WithApproved` (none for the terminal state) | |
| 48 | +| generated builder | `Order.Create().Customer(…).Build()`: 1 required field, 1 step | |
| 49 | +| positive fixtures | 8 (P1–P8), all `[]` on both CLIs | |
| 50 | +| negative fixtures | 20: 10 compiler, 3 extractor, 7 core (C1–C15 with C7b, C10a–c, C11a–b, C12a–b) | |
| 51 | +| stated limits | 2 (K1 `OWN001`, K2 `[]`) | |
| 52 | +| HTTP happy-path requests | 5 (create, submit, approve, ship, get) + 1 list | |
| 53 | +| HTTP rejected transitions | 9, all `409`; plus 4 `404`, 2 `400` create, 16 `500 corrupt_state`, 1 `400` unknown status | |
| 54 | +| EF tracked-identity checks | 3 at run time (`h10-*`) + 1 structural (generated tokens wrap only `order`/`_order`) | |
| 55 | +| database persistence checks (raw SQL oracle) | 5 row-content checks (4 happy-path rows + `h11`) + 13 row-unchanged checks (9 × H9, 4 × H8) + `h12` | |
| 56 | +| acceptance checks | 44 per run, 2 runs per gate, 2 gates | |
| 57 | + |
| 58 | +## The transitions, with their protocol evidence (P26) |
| 59 | + |
| 60 | +These come from the real sample's facts (`evidence/orderbackend.facts.json`). Verdict `[]` on |
| 61 | +Python and Rust, byte-identical CLI output. |
| 62 | + |
| 63 | +| source method | before | transition | after | OwnIR in the region | verdict | |
| 64 | +|---|---|---|---|---|---| |
| 65 | +| `OrderEndpoints.Create` | (none) | `Order.Create().Customer(c).Build()` | Draft | no region: creation | `[]` | |
| 66 | +| `OrderEndpoints.Submit` | Draft | `draft.Submit(now)` | Submitted | `acquire draft`, `call DraftOrder.Submit` | `[]` | |
| 67 | +| `OrderEndpoints.Approve` | Submitted | `submitted.Approve(now)` | Approved | `acquire submitted`, `call SubmittedOrder.Approve` | `[]` | |
| 68 | +| `OrderEndpoints.Ship` | Approved | `approved.Ship(now, Shipping.TrackingNumber(id))` | Shipped | `acquire approved`, **`proven_call OrderBackend.Shipping.TrackingNumber(int)`**, `call ApprovedOrder.Ship` | `[]` | |
| 69 | + |
| 70 | +**H1 used for real.** `heap_effects` holds the record of |
| 71 | +`OrderBackend.Shipping.TrackingNumber(int)` (no writes, no derefs, no calls, inert parameter) |
| 72 | +and the record of its call site `site:samples/OrderBackend/OrderBackend/OrderEndpoints.cs:97:36`. |
| 73 | +The core admits the site through the unchanged H1 predicate. Nothing in the extractor, the |
| 74 | +core or the predicate names the sample. |
| 75 | + |
| 76 | +**Rejected fixtures.** The exact texts are in `evidence/corpus.json`: |
| 77 | + |
| 78 | +| | stage | observed | |
| 79 | +|---|---|---| |
| 80 | +| C1–C6 | compiler | CS1061: `'DraftOrder'`/`'SubmittedOrder'`/`'ApprovedOrder'` does not contain a definition for the illegal transition | |
| 81 | +| C7 | compiler | CS1061: `'ShippedOrder' does not contain a definition for 'Submit'` | |
| 82 | +| C7b | compiler | CS0117: `'OrderProtocol' does not contain a definition for 'WithShipped'` | |
| 83 | +| C8, C9 | core | `OWN002` | |
| 84 | +| C10a | extractor | `'ApplyShip' of 'Order' is not public and belongs to a state protocol` | |
| 85 | +| C10b | core | `OWN013` | |
| 86 | +| C10c | compiler | CS0272: `'Order.Status' cannot be used in this context because the set accessor is inaccessible` | |
| 87 | +| C11a | core | `OWN005` | |
| 88 | +| C11b | core | `OWN013` | |
| 89 | +| C12a | core | refused: `'…C12aHarmfulHelper.Counter.Next()': writes.static is may` | |
| 90 | +| C12b | extractor | `a call to '…LoggerExtensions.LogInformation' inside a protocol region runs code with no stated contract` | |
| 91 | +| C13 | compiler | CS1061: `'Order.DraftBuilder.CustomerStep' does not contain a definition for 'Build'` | |
| 92 | +| C14 | extractor | `a protocol token 'ApprovedOrder' is created outside the protocol's own types` | |
| 93 | +| C15 | compiler | CS0122: `'Order.Order()' is inaccessible due to its protection level` | |
| 94 | + |
| 95 | +## H1–H18 |
| 96 | + |
| 97 | +| H | result | evidence | |
| 98 | +|---|---|---| |
| 99 | +| H1 Draft cannot Approve | PASS | C1 | |
| 100 | +| H2 Draft cannot Ship | PASS | C2 | |
| 101 | +| H3 Submitted cannot Submit | PASS | C3 | |
| 102 | +| H4 Approved cannot Submit | PASS | C5 | |
| 103 | +| H5 Shipped has no transition | PASS | C7, C7b | |
| 104 | +| H6 stale Draft rejected | PASS | C8 `OWN002` | |
| 105 | +| H7 raw Order cannot bypass | PASS | C10a (extractor), C10b `OWN013`, C10c CS0272, C14 | |
| 106 | +| H8 invalid DB state never refines | PASS | `h8-corrupt-*` ×4, `h8-storage-strict` | |
| 107 | +| H9 wrong HTTP transition writes nothing | PASS | `h9-*` ×9, each with the row byte-unchanged | |
| 108 | +| H10 same EF entity tracked | PASS | `h10-same-instance-before/after`, `h10-tracked-change` | |
| 109 | +| H11 SaveChanges persists exactly the new state | PASS | `h11-saved-exactly`: raw columns changed = `Status,SubmittedAt` | |
| 110 | +| H12 reload refines by the persisted state | PASS | `h12-reload-refine` | |
| 111 | +| H13 ordinary LINQ | PASS | P7, `h13-linq-*` (server-side `WHERE "o"."Status" = 'Approved'`) | |
| 112 | +| H14 harmless helper via `proven_call` | PASS | P5, P8, the real sample's Ship region | |
| 113 | +| H15 harmful/Unknown helper rejected | PASS | C12a (core), C12b (extractor) | |
| 114 | +| H16 copy/alias backdoor rejected | PASS | C11a `OWN005`, C11b `OWN013` | |
| 115 | +| H17 generator deterministic | PASS | 2 generations per gate run, byte-identical, equal to the committed file | |
| 116 | +| H18 full runs deterministic | PASS | digests above | |
| 117 | + |
| 118 | +## The 18 GO conditions |
| 119 | + |
| 120 | +1. **One ordinary EF Order underlies all typed states.** One `Orders` table. Every token wraps the region's own `order` (structural check), and the tracked instance is the one transitioned (`h10-*`). |
| 121 | +2. **Valid transitions are typed.** P2–P4, P8. |
| 122 | +3. **Invalid transitions are absent before run time.** C1–C7b. |
| 123 | +4. **Stale reuse is rejected.** C8, C9. |
| 124 | +5. **A runtime-loaded Order refines safely.** P6, `h12`. |
| 125 | +6. **Invalid persisted state cannot fabricate a state.** H8. EF Core 8's own string converter would have mapped `'7'` to an undefined value and `'approved'` to `Approved`; the generated strict converter refuses all four. |
| 126 | +7. **The ChangeTracker tracks the same entity.** H10. |
| 127 | +8. **SaveChanges persists correctly.** H11; `ef-update-emitted`. |
| 128 | +9. **DbSet/LINQ stays usable.** H13. |
| 129 | +10. **The HTTP happy path passes.** `http-*` and `oracle-*`, with a raw row after each step. |
| 130 | +11. **Wrong runtime transitions leave the DB unchanged.** H9. |
| 131 | +12. **H1 `proven_call` is exercised.** H14. |
| 132 | +13. **Harmful and Unknown calls are fail-closed.** H15. |
| 133 | +14. **The independent oracle confirms identity and persisted state.** Raw `Microsoft.Data.Sqlite`, the ChangeTracker, and exact HTTP bodies; none goes through the typed API. |
| 134 | +15. **Generator and full runs are deterministic.** H17, H18. |
| 135 | +16. **The clean-checkout run passes.** Official run 2. |
| 136 | +17. **H1–H18 pass.** |
| 137 | +18. **Foundations are unchanged.** `git diff --stat 877ee69f..HEAD -- ownlang rust spec frontend/roslyn/OwnSharp.Extractor docs/evidence/calibration scripts/perf_baseline.py` is empty. On HEAD, `protocol_gate.py --rust` gives 0 failures with 29 documents byte-identical, `heap_effects_gate.py` PASS. `tests/run_tests.py` gave rc 0 at `95d811b`; `e946d82` changes only the gate script. |
| 138 | + |
| 139 | +## What the slice does not claim |
| 140 | + |
| 141 | +- **Concurrency: option A, out of scope.** No concurrency token. |
| 142 | +- **K1.** A named terminal token is `OWN001`: tokens are linear, not affine (P-010, case G6). The handlers discard the terminal token as an expression statement. Affine tokens are foundation work, not done here. |
| 143 | +- **K2.** `ExecuteUpdate`, metadata writes, raw SQL, reflection and other processes are outside the claim, as in the profile. |
| 144 | +- **The diagnostics' wording** is the core's generic resource wording (`IDisposable local 'draft' is used after it is disposed`). The codes are right. The words are a UX item, not changed here (foundation). |
| 145 | +- **No BCL or logger summaries and no typed write targets were needed.** Logging and the clock stay outside the region: the clock is read before it, and nothing is logged. |
| 146 | + |
| 147 | +**After this verdict: STOP.** No BCL summaries, typed write targets or second aggregate are |
| 148 | +started. |
0 commit comments