Proposal (rationale)
WI-068’s packed existing-repository journey installed the tracked profile, committed the untouched
result, and immediately ran a same-version upgrade --apply. Preview reported
0 to update · 0 diverged, but apply changed .ai/gates.toml: separators between managed module
blocks disappeared and the terminal newline was removed. A no-op lifecycle command that dirties a
consumer makes upgrade unsuitable as Arena Lab’s repeatable health check and contradicts the
managed-file contract.
Decision
accepted — 2026-09-05. Fix the byte instability before WI-068 or the Arena Lab bootstrap can
complete. Keep the change limited to managed-block composition and its regression evidence.
Plan
Requirements
- Re-registering unchanged gate blocks preserves
.ai/gates.tomlbyte-for-byte for LF and CRLF files, including blank separators between blocks and the file’s terminal newline. - Managed-block replacement still updates the selected block when its version or body changes and never changes consumer-owned bytes before or after that block.
- A same-version upgrade preview and repeated
--applyon a freshly committed tracked scaffold leave its tracked tree clean and byte-identical.
Impacts
src/substitute.ts, wheremergeBlockdefines replacement and append boundaries for every shared managed file.- Focused unit coverage in
test/core.test.js; WI-068’s packed journey remains the end-to-end consumer regression and must pass unchanged after this fix. - No module template or module-version bump: this corrects lifecycle composition rather than changing emitted module content.
Approach
Reproduce the registry layout with adjacent managed blocks and a final block at end-of-file. Make replacement preserve all bytes outside the matched marker range; return the existing bytes when line-ending-normalised managed content is equal, and make a changed fragment adopt the existing block’s line-ending convention. Cover identical and real replacements under LF and CRLF before relying on the packed consumer journey.
Acceptance criteria / tests
- A focused
mergeBlocktest replaces unchanged middle and final gate blocks under LF and CRLF with no byte difference. - The same test changes one block’s marker/body while preserving its surrounding separators, consumer prose and final newline exactly.
- WI-068’s packed journey reaches both same-version applies with an unchanged tracked digest and clean Git state.
npm test,git diff --checkandnpm run rungs -- checkpass.
Out of scope
- The inert release-fragment gate recorded as F-039; it receives its own work item.
- General CRLF normalisation recorded as F-034, changes to module content, or new upgrade behavior.
Execution
Implemented on feature/WI-069-idempotent-gate-registration from 0ed80b5.
The initial LF fix was incomplete. Both marker expressions used \s* inside the marker line;
JavaScript’s \s includes newlines, so the end-marker match greedily consumed the blank lines and
terminal newline that followed it. Restricting that whitespace to one line preserved LF, but an
explicit \r? still consumed the CR in CRLF files and a generated LF fragment would rewrite their
managed block. The final expressions stop before either line-ending byte. Replacement returns the
original string when normalised content is equal and otherwise adopts the existing block’s EOL.
Added unit coverage for an unchanged middle block, an unchanged final block and a real version/body
replacement. WI-068 merged this branch as dependency commit 10ac4be without changing its packed
test and replayed the complete consumer journey successfully.
Review
Verified 2026-09-05.
- Met. Unchanged hash-comment blocks return the exact LF or CRLF input string, including the blank separator and terminal newline; the CRLF cases also pass an LF fragment as registration does in production.
- Met. A
first@1.0.0→first@1.1.0body replacement matches the exact expected LF and CRLF registries; the runner prefix, adjacent block, separator and final newline are unchanged. - Met. The unchanged WI-068 packed journey passed 1/1 from integration commit
10ac4bein 14.48 seconds. Both apply digests match the committed tracked digest, all Git status checks are clean and rollback restores the original ref/file state. - Met. The two focused
mergeBlocktests pass;npm testpasses 44/44,git diff --checkpasses andnpm run rungs -- checkpasses 29/29. The existing warning for 45 fixtures without builders remains explicit and out of scope.