Task — engineering-spec@1
"Store build-number monotonicity — release-time floor decouples re-tags from BUILD_NUMBER bumps (Play versionCode 10706 collision)"
doneTASK-IMP-078
§1
stamp-release-version.mjsMUST accept--store-monotonic: effective build number =max(BUILD_NUMBER, floor(now/60s)). Without the flag, behavior is byte-identical to today (committed baseline,--checkdrift detection, version.yml bump commits all unchanged).release.yml's android and iOS stamp steps MUST pass the flag; no other call site does. The high-water-mark guard keeps validating the FILE value (the committed floor), and the effective value can only be >= it.BUILD_NUMBERkeeps its existing role and is NOT bumped here: one mechanism owns re-tag safety (the wall-clock floor), one owns the committed baseline (version bumps). A second incrementer was considered and rejected - it re-introduces the operator-memory dependency this task removes.- Wall-clock minutes, not commit timestamps: a re-tag of the SAME commit must still get a fresh number (commit-time would collide; run-time cannot). ~29.7M in 2026 vs Play's 2100000000 cap - headroom measured in millennia.
Lean profile: one flag + two step args; defect, fix, and both-mode behavior machine-verified in-session; store acceptance proven by the next tag run.
§5 (run 2026-07-13)
node --checkPASS; plain--checkrun: "all release artifacts already match VERSION" (baseline untouched). PASS--apply --store-monotonic: stamped versionCode/CURRENT_PROJECT_VERSION 10706 -> 29731417 (> 10706, < 2.1e9), both pbxproj occurrences; files then restored to committed baseline. PASS- release.yml YAML parses; both stamp steps carry the flag (
grep -c 'store-monotonic' .github/workflows/release.yml== 2 run-lines + comments). PASS - Testing pass 2026-07-13 (post gate-1 "approve all"): full battery re-run green; fresh floor stamped 29731447 (> the earlier test's 29731417 - the clock floor visibly advances between runs), baseline restored.
§9
- Two jobs in one run stamp slightly different numbers (independent clocks). Cosmetic only - Play and ASC never compare; recorded, not fixed.
- Should
cyberos-version.mjsfast-forward BUILD_NUMBER to the last shipped effective number at the next real version bump? Not required for correctness (the floor always wins at release time); revisit only if a human-readable committed number regains value.
§10
| Failure | Detection | Recovery |
|---|---|---|
| two release runs inside the same minute | store rejects the second upload, loud | re-run: next minute, next number |
| runner clock skew backwards | floor still >= committed BUILD_NUMBER; worst case equals a consumed number -> loud store rejection | re-run after NTP settles |
| flag typo'd/dropped in a future workflow edit | next re-tag reproduces the 10706-class failure loudly at upload | re-add flag; this spec's §5 grep pins the 2 call sites |
| someone passes the flag in version.yml | committed files would carry a wall-clock number - visible in the bump commit diff, and --check flags drift on the next run | revert; flag is release.yml-only by §1 clause 2 |
End of TASK-IMP-078.
Audit
- ISS-001: draft fixed only the android lane (the reported failure) - resolved: the same 10706 is already consumed at ASC for iOS 1.0.0, so the iOS stamp step gets the flag in the same change; fixing one lane would have moved the failure, not removed it (spec risk_if_skipped).
- ISS-002: draft derived the floor from the tagged commit's timestamp (deterministic, same value across jobs) - resolved: rejected because re-tagging the SAME commit would reuse the number and collide; wall-clock minutes are collision-free across runs (spec §1 clause 4 records the trade). Score = 10/10.