* feat(#4668): add StateWriteIntent type surface and opaque-transform guard recognition (ADR-4629 C1)
Child C1 of epic #4629 — migration-order step (1) of ADR-4629: the guard/type
scaffolding, with NO behavior change and no caller migrated.
1. StateWriteIntent (src/state-transition.cts) extends StateTransaction with the
ADR-4629 section 8.1 concepts: field/section assertions marked required vs
best-effort, plus a declared mutation scope (narrow | broad). Frozen like its
base. createStateWriteIntent builds one from an existing transaction. Nothing
in production constructs it yet — section 8.1's caller-side rule is Required in
Phase 2; C2 (the verifying executor) and C3+ (caller migration) consume it.
2. findOpaqueStateTransforms (scripts/lint-state-write-path-drift.cjs) recognizes
a residual readModifyWriteStateMd(path, (content) => ...) write whose transform
is an inline anonymous arrow/function — the opaque shape section 8.1 replaces
with a declared StateWriteIntent. readModifyWriteStateMd goes THROUGH the seam
(it is not a raw-write bypass, Axis 2's concern), but its opaque body transform
is neither verified (section 8.2) nor bounded (section 8.3).
This ships recognition as a CAPABILITY: exported and unit-tested (positive
control on a seeded fixture) but DELIBERATELY NOT wired into collect()'s
failing scan. Wiring it now would turn the ~16 residual callers red at once,
and ADR-3473 section 8.6 retired the ratchet that would otherwise absorb them.
C2 wires it terminal as the verifying executor lands and callers migrate under
ADR-3408 section 6 phasing.
No behavior change: the guard is green on the tree (detection not wired), every
state verb's output is unchanged, and the relevant suites (1763 tests) plus
lint:ci pass. Regression tests are failing-first: positive/negative controls for
the guard capability and a shape test for the type, plus a pin that collect() has
no opaque-transform findings (C1 must not enforce; that is C2).
Closes#4668
* chore(#4668): backfill changeset pr field to the real PR number (#4676)
pr: 0 is rejected by parseFragment as invalid_pr (it is not a valid placeholder);
the fragment must carry the real PR number, which fixes both changeset-lint and
docs-lint (fail_invalid_fragment / fail_malformed_fragment).
---------
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>