Repository navigation
Closed
Labels
Description
Purpose
The iOS runner writes snapshot quality state as a free Swift String, while the host accepts only healthy | recovered | sparse. A typo or one-sided addition can make the host drop the verdict and its disclosure. The contracts annotation reader is looser still: it casts any string state to the verdict type.
Implementation plan
- Next to
SnapshotQualityinRunnerTests+SnapshotCapturePlan.swift, defineSnapshotQualityState: String, Codable, CaseIterablewith exactly.healthy,.recovered,.sparse. Use it forSnapshotQuality.state,stampedSnapshotPayload(state:), every producer in that file andRunnerTests+Snapshot.swift, and the sparse/truncation comparison. Raw-valueCodablekeeps the JSON values unchanged. KeepreasonCodeopen. - In
packages/kernel/src/snapshot.ts, replace the repeated handwritten state union with one exported readonly state tuple and deriveSnapshotQualityVerdict['state']from it. Export a small state predicate from that owner. Have bothpackages/capture-kit/src/snapshot-quality-verdict.tsandpackages/contracts/src/snapshot-capture-annotations.tsuse it; do not import capture-kit into contracts or retain a second accepted-state set. The annotation reader must reject an unknown state rather than type-cast it. Keep its other projection behavior unchanged. - Add
contracts/fixtures/ios-snapshot-quality-states.jsoncontaining only the three wire strings. Add a Swift runner unit test comparing fixture order toSnapshotQualityState.allCases.map(\.rawValue)and a TS test comparing the fixture to the kernel tuple. Reuse the existing fixture-path pattern inRunnerTests+SnapshotBackendCapabilitiesTests.swift; do not add a generator or a larger schema for a three-value vocabulary.
Proof and completion
- Before changing production code, demonstrate red on a one-sided extra/renamed fixture state, and red on a malformed annotation state. Keep focused tests for all three raw JSON values and for unknown-state rejection in both TS readers.
- The runner compiles with no verdict
state: Stringor state string literals in producers, and existing encoded verdict tests retain the same wire JSON. Run the focused Swift runner unit tests andpnpm check:affected --run; report exact commands/results. - Limit the change to the verdict vocabulary, the shared fixture, and their existing tests. No new compatibility parser, warning channel, or reason-code registry.
Activity
refactor(snapshot): answer the review round on sharing, parity, and e…
thymikee commentedon Sep 24, 2026
Eager-closure gate blocks step 2's shared kernel predicate — accepted deviation from #2888.
Step 2 asks the kernel to ship
isSnapshotQualityState(plus anisSnapshotCaptureBackend, theisSnapshotBackendidiom) inkernel/snapshot.tsand both readers to call it. That shape was implemented first and CI'sCoveragejob rejected it: run 35993787971/job 107613817783.Why, precisely — there is no budget table to edit, which is what made this hard to find:
scripts/__tests__/eager-closure-budgets.tsmeasures each entry surface's eager closure, and for any entry the merge-base already carries the budget is the merge-base measurement —eager-closure-budgets.test.ts:42says "ratcheted against the committed merge-base rather than against a table of numbers" (tree at:518-520, per-entry assertion at:594).discoverFacadeEntryFiles/facadeEntryFiles(eager-closure-budgets.ts:93), not listed inHUB_ENTRY_FILES(:103). Their budgets are therefore 9 modules forpackages/contracts/src/facades/capture.tsand 2 forpackages/capture-kit/src/snapshot-quality-verdict.ts, and no file in the repo states those numbers.Measured again on
7b8b4636c's tree just now: adding the two predicates tokernel/snapshot.tsand calling them from both readers fails six carried entries —contracts/src/facades/capture.ts10 vs 9,capture-kit/src/snapshot-quality-verdict.ts3 vs 2,capture-kit/src/png-resize.ts19 vs 18,capture-kit/src/recording/contact-sheet.ts58 vs 57,capture-kit/src/snapshot/snapshot-freshness/index.ts17 vs 16,replay-test/src/index.ts21 vs 20. The only module both frozen closures already evaluate iskernel/snapshot.ts— the one edge the gate refuses — so no shared runtime home exists.A full field-by-field projection in
contracts(the other way to close the cast) is refused too: fallow reports a 4-group / 104-line clone family against capture-kit's normalizer, plus a complexity finding on the projected reader.What #2888 ships instead: the kernel keeps the single declaration (
SNAPSHOT_QUALITY_STATES, from whichSnapshotQualityVerdict['state']derives) and thecontracts/fixtures/ios-snapshot-quality-states.jsonpin; each gate-frozen reader holds an exhaustiveRecord<_, true>keyed over the kernel union withObject.hasOwnmembership, and the two readings are pinned payload-by-payload inpackages/capture-kit/src/snapshot-quality-verdict.test.ts. A one-sided rename, add, or delete is then a compile error at each reader instead of a silently dropped verdict. The cost is the ~30 lines step 2 hoped to delete — which the two gates price on purpose.refactor(snapshot): answer the review round on sharing, parity, and e…
refactor(snapshot): answer the review round on sharing, parity, and e…
refactor(snapshot): answer the review round on sharing, parity, and e…
refactor(ios-runner): make the snapshot quality verdict state a close…