You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1f47ce6
Browse filesBrowse the repository at this point in the historyBrowse files
test(api-contract): gate writeOnce over HTTP in all five ports, both lanes
FR-037 R1's acceptance half. The shared Author entity gains an
`@mutability: "writeOnce"` column (issuedCurrency) and a `writeonce-patch-stripped`
scenario: POST sets it, a later PATCH carrying a NEW value returns 200 with the
STORED value unchanged, and a present-null does not clear it. Every port runs it
in BOTH lanes — the hand-rolled reference server and the generated artifact
booted over HTTP.
IT IMMEDIATELY FOUND TWO REAL DEFECTS, both the same shape, and both invisible to
the unit tests that already passed. This is ADR-0045 exactly: the OUTERMOST
generated write artifact enforces the mode, and in two ports that artifact is not
the one I had fixed.
PYTHON — excluding writeOnce from <Entity>Patch was NOT enough. The generated
FastAPI handler binds `dto: dict[str, Any]` (the RAW body), validates it against
the patch model, then hands the raw dict to the repository — so a field merely
absent from the model still reached the row. @autoset survives this only because
the router OVERWRITES its key server-side; writeOnce has no server value to
clobber. Fixed by emitting a _FROZEN_FIELDS strip in the router, vanilla and TPH.
C# — SetAfterSaveBehavior(Ignore) governs EF's SAVE, but the merge loop assigns
entry.CurrentValues[target] and the handler returns the in-memory `existing`
entity, so a caller-supplied value was echoed in the RESPONSE even where EF
omitted the column from the UPDATE. Fixed by skipping the key in the merge loop
itself, exactly as @autoset is skipped — vanilla and the TPH per-subtype route.
Both were caught by the generated lane and would have shipped otherwise.
The reference servers needed no strip in Python, C#, Java or Kotlin: each builds
its UPDATE from an explicit allowlist, so an unlisted column is excluded by
construction. Each now says so at the omission site, because the failure mode is a
future reader "helpfully" adding the missing branch. TS is the exception — its
reference destructures the body — so it drops the key the same way it already
drops @autoset:onCreate.
r3 (the present-null arm) deliberately carries a readWrite field alongside the
null. Sending the null ALONE would also probe the empty-PATCH contract, which this
corpus has never settled cross-port — TS no-ops and returns the row, the C# and
Java references 400 on an empty SET — and settling that is not this scenario's
business. The scenario says so inline so the next reader does not "simplify" it.
Verified per port, both lanes, 27 scenarios each: TS 27+27 · C# 27+27 ·
Java 27 + BUILD SUCCESS · Kotlin 27+27 · Python 72 (both lanes together).
Python unit 1723 · C# four Passed! lines 0 error CS · TS typecheck clean ·
ci-local.sh --only gates PASSED.
Also lands the batch close-out:
- CHANGELOG [0.24.0] — the ⚠️ callout is EXTENDED to name all four retirements
(A template direction, B requirement vocabulary, C @mutability, D
origin.collection) rather than gaining a second callout, plus a section each
for U1/U2/U3 carrying the reasoning, not just the rewrite.
- docs/features/migrations/readonly-to-mutability.md — the U3 guide, leading
with the measured zero adopter cost and the writeOnce-on-an-assigned-key
asymmetry that justifies the enum.
- spec/roadmap.md — the breaking window is marked SHIPPED with a per-unit table,
and the "open tension" paragraph is RESOLVED in place (batch first, then 1.0)
rather than deleted, so the decision is legible.
- docs/1.0-readiness.md — §G3 quiet-period clock RESET to 0.24.0, stated as an
adjudicated cost with the reasoning, because the failure mode is a later reader
finding the reset and treating it as an accident.
Remaining for FR-037 R1: a TPH-flavoured writeOnce scenario. Every port's TPH code
path is already patched and stated (C# AppendTphSubtypeRoutes, Java's
settableFields TPH overload, Python's TPH router branch, Kotlin's TPH controller) —
but patched is not gated, and an ungated TPH path is the precise 0.19.4 failure
mode. Tracked as the last acceptance item.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhpswkF1NvwxhFWMmdAT15
_Last updated 2026-08-21 (post the `0.24.0` coordinated pre-1.0 breaking batch — ADR-0052 + FR-038 + FR-037 R1/R2 — which reset the quiet-period clock again (G3) and moved `metamodelVersion``0.9` → `0.10`. Earlier: `0.15.1`/`7.7.1` + `0.15.2`; the `0.15.x` passthrough type-preservation move (#185) had reset it before — C3/G3; A1 ratified — decouple).
156
170
Owner: maintainer. Update the status marks as items land; ratify the remaining §A
0 commit comments