Repository navigation
Release notes: one changelog.d file per change, moved into the dated section by the changelog step [PROTECTED: owner merges] - #870
Conversation
…ves them into the dated section Every PR that added a note to the one `## v0.5.3 (unreleased)` section re-conflicted every other open PR on the same lines (a full re-merge round on 7 PRs). Now each change's notes live in their own file, changelog.d/<issue or PR number>-<short-slug>.md, holding `### <Heading>` blocks under a fixed set of headings (Upgrading, After install, Guardrail changes: what each one does, Release tooling, Removals). Two PRs never edit the same file, so the conflict can't happen. `release.mjs changelog X.Y.Z` (changelog.mjs withFragments) builds the dated section: the fragments' lines, verbatim, under the fixed headings in their fixed order, fragments by number, then the generated lists; it deletes the fragments it used, for the release PR to commit with the section. It writes and deletes nothing when a fragment line could be dropped or moved: an unknown heading, any other line starting with `#` (a `##` line would start a section), an empty fragment, text before the first heading, a heading with nothing under it or given twice, a CR, the end-of-notes line, a misnamed file or anything else in the folder, two spellings of Upgrading, or a CHANGELOG.md that already has a section for the version. The result is checked two ways after it is built (every raw fragment line counted, and the notes read back as blocks), and the denylist scan now runs before anything is written. With no fragments it is withSection, byte for byte. The tag step is unchanged. The v0.5.3 section moves into one changelog.d fragment, verbatim: 151 non-blank lines, and the release step rebuilds the old section's body from it byte for byte. Not moved: the `## v0.5.3 (unreleased)` heading and the section's empty trailing `### Guardrail changes`, which the generated lists write. test/changelog.test.ts gains the backstop: it fails on any (unreleased) or undated `##` section in CHANGELOG.md, or a fragment the release step would refuse, and tells the author to add changelog.d/<n>-<slug>.md. The owner-terminal test reads v0.5.3's notes from changelog.d until the release moves them. Test (fails without): test/changelog-fragments.test.ts (10 tests) and the backstop in test/changelog.test.ts; 11 of 37 fail on the base (TypeError: c.withFragments is not a function; the step exits 0 and leaves the fragments; the backstop names "## v0.5.3 (unreleased)"). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…3 notes move into the fragment CHANGELOG.md conflicted only inside the `## v0.5.3 (unreleased)` section this branch removes. It is resolved to main's CHANGELOG.md with that section cut out, byte for byte. Everything main's section gained since ddcb32a is in changelog.d/870-notes-before-changelog-d.md, verbatim, under the same headings and in the same order: #738, #736 (which also rewrote the #749 bullet, taken as main has it), #832, #708, deploy/**, #783, #707, #821, #705, and the new `### Removals` (#743). Move check against origin/main: 186 non-blank lines removed from CHANGELOG.md, 184 in the fragment. The two not moved are the `## v0.5.3 (unreleased)` heading and the empty trailing `### Guardrail changes`. The notes the release step builds from the fragment equal main's section body byte for byte (66207 chars). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict: CHANGES NEEDEDReviewed head d2e549b (the merge of main efc1fd1 after #831 into the branch). I also checked dfa298d before the merge, so this one comment covers both. The code files are byte-identical between the two heads. Only CHANGELOG.md, the moved-notes fragment and docs/releasing.md (main's own edits, auto-merged) differ (measured: The design holds. There are two small required changes: one input the step should refuse but builds, and one claim to correct. Acceptance criteria
Merge commit (measured)
Release path end to end (measured, d2e549b)I ran this in a scratch worktree,
Fail-without (measured, method (b))I made a fresh worktree at ddcb32a. I wrote
On the head, Drop attacks beyond the builder's list (measured: in memory,
|
…he built notes apart from the builder's order code Two gaps the #870 review measured. 1. A fragment line that was the end-of-notes comment with trailing spaces or a tab was built: CHANGELOG.md kept it, and the tag's notes dropped it, after the step had deleted the fragment. The fragment reader now refuses every line NOTES_END_LINE matches, through isNotesEndLine, made from that same pattern (what notesOf and tagNotes match). There is no second copy of the rule, and tagNotes is unchanged. 2. The check on the built notes read them back through the builder's own grouping and sorting (grouped, byNumber), so a mistake there (headings reversed, fragments reversed under a heading, the number sort reversed) was built, not refused. The check now compares the notes, byte for byte, with expectedNotes. It works out the order on its own: FRAGMENT_HEADINGS in order, the files by the number in their name and then the name (its own comparison), each file's raw lines (its own walk and trim). The raw-line count stays. Test (fails without): test/changelog-fragments-selfcheck.test.ts, 2 tests, both fail on d2e549b. The end-of-notes comment with two trailing spaces is not refused; the reversed-heading mutant (imported from memory) is built. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
|
Round 2 is pushed as a879a00 (one commit on top of d2e549b, signed off).
Fail-without (measured, method (b)). I wrote
After (measured, a879a00, temp HOME):
|
Evaluator verdict (round 2): PASSDelta check of head a879a00, one commit on d2e549b. Both round-1 required changes are done, and nothing else changed. The delta (measured)
isNotesEndLine is what the tag step strips (inferred from code, measured by attack)
Round-1 attacks, rerun on a879a00 (measured, in memory from
|
…in-required-checks.md Main's CHANGELOG.md holds released sections only (#870). The two bullets this PR added are the fragment's, byte for byte, under the same headings; CHANGELOG.md is main's exactly. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
This PR's only note was its edit of #788's Upgrading bullet (the paste-ready block). With notes in fragments (#870), that edit is made in changelog.d/788-pin-required-checks.md, byte for byte as before; CHANGELOG.md is #788's exactly, and this PR adds no fragment of its own (it adds no note line of its own). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…854-server-first-hour.md CHANGELOG.md is main's, byte for byte (#870); the fragment holds this PR's one bullet, unchanged, under Upgrading (from v0.5.2). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…ins.md Main's CHANGELOG.md holds released sections only (#870). The two bullets this PR added are the fragment's, byte for byte, under the same headings; CHANGELOG.md is main's exactly. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…loads 57 modules Since #870, release notes live in changelog.d/, one file per PR, and CHANGELOG.md holds only released sections. This PR's one note, under "Guardrail changes: what each one does", moves to changelog.d/839-guard-loads-only-its-modules.md word for word, with one change the stack-delta review asked for: the guard loads 57 modules, not 56. Stacked on #788, whose src/check-source.ts is in the guard's tree through src/backlog/types.ts, the pinned tree is 57 modules. Test (fails without): test/changelog-fragments.test.ts and test/changelog.test.ts read every fragment; the note itself changes no behaviour. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…helper #870 moved release notes to changelog.d/; CHANGELOG.md is now #769's, byte for byte. #770's notes, as before the conversion: - its two own Upgrading bullets: changelog.d/770-dashboard-only-tightens.md; - its two rewrites of #769's bullets (the existing-install step gains the machine-helper rerun sentence; the guardrail note gains the dashboard and tighten-only part): in #769's fragment, changelog.d/769-policy-yaml-root.md; - its rewrite of the old "No sudoers rule is added, so no machine-helper rerun" After install line, which is false once set-policy ships: that one line in the shared changelog.d/870-notes-before-changelog-d.md, replaced with the set-policy sudoers text. docs/releasing.md step 9 conflicted: #770's "0.5.3: yes (set-policy, #726)" line is kept, ending with main's new pointer text ("Its other root steps are in v0.5.3's "After install" notes (changelog.d until the changelog step moves them into CHANGELOG.md)"). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…rc/ack-reason.ts Main's CHANGELOG.md holds released sections only (#870): this PR's notes are changelog.d/834-resume-waits-for-green-main.md, rewritten for the redesign (the coordinator answers only a human owner's request; a stop file with no merge.stopped is answered too; the measurement shown is the stop in force's). src/ack-reason.ts is reached by the guard through src/events/types.ts, so it is listed and locked as the guard (test/review.test.ts). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…part 2 of 2) The release note for this PR's change, as its own fragment (#870): Upgrading (from v0.5.2). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
… guard's locked tree (58) Since #870, release notes are fragments in changelog.d/ and CHANGELOG.md holds released sections only. The #786 bullet this PR had in CHANGELOG.md moves, byte for byte, into changelog.d/844-env-reads-ignore-prototype.md under the same heading, with one change the stack-delta review required: it says that src/os/own-env.ts joins the agents' tool guard's locked tree, which takes it to 58 modules. Test: test/changelog.test.ts and test/changelog-fragments*.test.ts pass with the fragment (measured). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
The owner's option (a) for #746. An updater from v0.5.2 or older has no retired-check rule: it requires a successful "desktop" run on every commit it installs, and the release channel reads the checks on a release's base commit the same way. Removing the job with the app would leave every v0.5.2 install waiting forever ("required check "desktop" hasn't run on this commit"). So the job stays, same id and check name, with one `true` step and nothing that can skip, fail or rename it, and the desktop entry stays in .worklane/config.yaml and UPDATE_DEFAULT_CHECKS. #877 removes the job and the check together a release later. This also puts back what the branch had changed only because the job was gone: ci-slow's --add-check desktop step, update-checks' list of PR checks, and the desktop entries in review.yaml's engine globs, package-files' forbidden prefixes and chrome-keychain's walked folders (each a narrowing; the owner's call, with #877). Release notes: a changelog.d fragment (Upgrading: the check stays one more release, and v0.5.2 installs rerun updates.sh once; Removals: the window). #870's fragment said the release that removes the window also removes the job; it now says the job stays one more release. Test (fails without): test/desktop-removed.test.ts. On origin/main ec3fd42: 3 of 7 fail (the desktop job isn't a no-op, the app is still there, no note). On this branch before this commit (285434a, the job removed): 4 of 7 fail, among them "UPDATE_DEFAULT_CHECKS ... desktop" with no job and "no such job". Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Intent: every PR that added a release note to the one shared
## v0.5.3 (unreleased)section re-conflicted every other open PR on the same lines. Today that cost a full re-merge round on 7 PRs. This PR moves the next release's hand-written notes into per-change fragment files,changelog.d/<issue or PR number>-<short-slug>.md. Two PRs never edit the same file, so the conflict can't happen.release.mjs changelog X.Y.Zbuilds the dated section from the fragments and deletes them in the release PR. It writes nothing if a fragment line would be dropped or moved.Risk class: protected. It changes
scripts/lib/changelog.mjs,scripts/release.mjs(release tooling, in the owner's critical set) anddocs/releasing.md(engine-guards).No issue: the owner asked for fragments directly (orchestrator brief, 2026-10-20). It was stacked on #831, which has merged. This PR now targets main and has origin/main (efc1fd1) merged in. #831's
tagNotesand revert reading are not touched.What changes
The design that makes the conflict impossible:
changelog.d/, one file per change. A fragment holds### <Heading>blocks, and the bullet text under them is free markdown. Only the fixed headings the unreleased section uses are allowed, in this order:Upgrading(orUpgrading (from vX.Y.Z), spelled the same in every fragment);After install;Guardrail changes: what each one does;Release tooling;Removals(on main's section since Remove voice and Siri: spoken approvals never count, no gateway, its ports and tokens removed; doctor names leftovers (#743) [PROTECTED: owner merges] #836).The generated headings (Features, Fixes, …,
Guardrail changes) stay the tool's own.release.mjs changelog X.Y.Z(withFragmentsin changelog.mjs) writes## vX.Y.Z (date), then each fixed heading in order, then each fragment's lines under it, verbatim. Fragments go in number order, then by name. The generated lists come after, as Release: an (unreleased) CHANGELOG section survives the changelog step whole, by design [PROTECTED: owner merges] #829 writes them. It then deletes the fragments it used, so the release PR commits the section and the deletions together.Release: an (unreleased) CHANGELOG section survives the changelog step whole, by design [PROTECTED: owner merges] #829's invariant, extended to fragments. The step throws and writes nothing when:
#(up to 3 spaces in), such as##or#;isNotesEndLine, built from the tag step's ownNOTES_END_LINEpattern; there is no second copy of the rule, andtagNotesis unchanged;The output is then checked two ways (
fragmentProblems), and the step throws if either fails:expectedNotesworks out on its own. It shares no code with the builder's grouping (grouped), sorting (byNumber) or block reader (blocksOf): it takesFRAGMENT_HEADINGSin order, the files by the number in their name and then the name (its own comparison), and each file's raw lines (its own walk and trim). Round 2 made this independent; before, the check reusedgrouped/byNumber, so a mistake in them was caught only by the tests (the Release notes: one changelog.d file per change, moved into the dated section by the changelog step [PROTECTED: owner merges] #870 review measured three such mutants built).The denylist scan now runs before anything is written. With no fragments the step is
withSection, byte for byte.The tag step (Release: a GitHub-button revert is read from its body, and the tag step refuses an (unreleased) section [PROTECTED: owner merges] #831) is unchanged. It still reads only the dated section from
HEAD:CHANGELOG.md.The move.
## v0.5.3 (unreleased)moves intochangelog.d/<this PR>-notes-before-changelog-d.md, verbatim, as one fragment. One fragment keeps the order within each heading exactly. CHANGELOG.md now holds only dated sections.Backstop test (named as such):
backstop (changelog.d)in test/changelog.test.ts. It fails when:(unreleased)/Unreleasedheading, or any##heading that isn't## vX.Y.Z (YYYY-MM-DD);Its message says: "add your release notes as changelog.d/-.md instead (the format: changelog.d/README.md)".
Docs:
changelog.d/README.md: the format, the headings, what is refused, and one example.docs/releasing.mdstep 1, and its v0.5.3 pointer.scripts/release.mjs's header.git grep -i changelog).The line move (measured)
node check-move.mjs <worktree> origin/main(scratch script: multiset of non-blank lines removed from main's CHANGELOG.md against the lines in the new fragment, plus two byte-for-byte checks). It was run after merging main at efc1fd1:deploy/**lock, Merge: a PR that removes or weakens test assertions, lint rules or guards waits for the owner #783, Public adversarial suite in CI: the merge gate refuses every hostile PR case (prompt and delimiter injection, rename and copy tricks, poisoned .claude settings and hooks, credential smuggling), with the existing guard tests folded in #707, Dashboard: /api/prs gives viewers the merge decision's hold reasons (found by #741's builder) #821, Docs and proof: a "how the guardrails work" page, a security write-up of this week's guard gaps (merged fixes only, each linked to its PR and test), and a quickstart for the merge-gate use #705 and a new### Removals(Remove voice and Siri: spoken approvals, the Siri gateway, its ports and device tokens (deprecate, then remove) #743). Retire the subscription-account machinery: profiles, failover, multi-account switching and the relayed sign-in (deprecate, then remove) #736 also rewrote the claude credentials: the dashboard never takes one; the four paths that did are gone (#711 part 2, server) [PROTECTED: owner merges] #749 bullet, and the fragment takes it as main has it. All of this went into the fragment in the merge commit, verbatim and in main's order.### Guardrail changesheading. It had no lines under it. It was a leftover from before Release: an (unreleased) CHANGELOG section survives release.mjs whole, by design (no heading-layout rule) #811, when notes had to end with an empty generated heading (Release: an (unreleased) CHANGELOG section survives release.mjs whole, by design (no heading-layout rule) #811's own bullet says they no longer need to). The generated lists write### Guardrail changesanyway. A fragment can't hold an empty heading, so it can't move verbatim.How it was verified
cmpmatched test/changelog.test.ts), then built and run: 11 of 37 fail, 26 pass. Causes:TypeError: c.withFragments is not a function(and parseFragment, fragmentNotes, readFragments);release.mjs changelogrun exits 0 and leaves the fragments in place;CHANGELOG.md has "## v0.5.3 (unreleased)": add your release notes as changelog.d/<issue or PR number>-<short-slug>.md instead ….changelog-fragments,changelog,releaseandcomment-policy-cli: 54/54 pass.owner-terminal-removed, the one test changed there (--test-name-pattern=placeholder): 1/1. The file's other tests start bash with/usr/binon PATH, so CI runs them.npm run typecheck: clean.npm run check:denylist: 1322 files clean.check-dco: all signed off.check-comments --base HEAD^1: off in config.push-gate-hook --range origin/issue-712..HEAD: clean.test/changelog-fragments-selfcheck.test.tswith Write into a fresh worktree at d2e549b (sha256 7e3d1f72… matches the head copy), then built and ran it: 2 of 2 fail.Missing expected exception: parseFragment " ": the comment with two trailing spaces is not refused.Missing expected exception: the headings in reverse order: refused: the mutant, imported from memory, is built.changelog-fragments-selfcheck,changelog-fragments,changelog(with the backstop),releaseandcomment-policy-cli: 56/56 pass.npm run typecheck: clean.check:denylist: 1320 files clean.changelog-fragments,changelog(with the backstop),releaseandcomment-policy-cli: 54/54 pass.npm run typecheck: clean.check:denylist: 1319 files clean.fullsuite.sh, 425 files, mcp- and plugin-fetch excluded):* 3881 pass, 2 fail, 57 skipped. The two failures are "event cache: every view read incrementally…" and "the GPU lock: a flock another process holds…". Neither file touches the changelog code, and both pass in CI on this head (measured), so I take them as local load flakes (inferred).Existing tests changed
## v0.5.3section. Now it reads CHANGELOG.md's v0.5.3 section if there is one, else the notes the release step builds from changelog.d. That is the same text, byte for byte (measured above). The assertions are unchanged.(unreleased)sections, and there are now none, so those loops run zero times on repo data. Their fixture halves still run. What they protected (the repo's unreleased notes survive a release) is now covered by the backstop's "a release cut now would carry every fragment line" (inferred: equivalent for fragments).Package size (measured)
Against main after the merge: 0 B (inferred). The diff against main touches 9 paths (CHANGELOG.md, changelog.d/*, docs/releasing.md, scripts/lib/changelog.mjs, scripts/release.mjs and 3 test files), and none is in the package's
fileslist.Measured on the first head,
npm pack --dry-run --json --ignore-scripts, base worktree (issue-712) against head dfa298d: 477 files, unpacked 4,712,762 B, tarball 1,403,800 B in both. Delta: 0 B. Neitherchangelog.d/norCHANGELOG.mdis in the package (filesin package.json is an allowlist, and there is no .npmignore). No budget change.New dependencies
None.
What this can never do
withSection's, byte for byte, for every dated section in CHANGELOG.md (measured).Compromised component
changelog.mjsto drop lines. This PR is protected (owner merges), and the release PR shows the whole dated section and the deletions in one diff (inferred).expectedNotescomparison. A mistake inexpectedNotesitself fails closed: the step refuses correct notes rather than write wrong ones (inferred).scripts/release.mjsand tests, and nothing ships in the package (measured: 0 B delta).Adversarial pass (measured,
adversarial.mjsin the scratchpad)Mutants were made by editing the module's source in memory and importing it from a
data:URL. No repo file was edited."- two" is missinggroupedwrites the headings in reverse orderexpectedNotescomparison. Permanent test inchangelog-fragments-selfcheck.test.tsgroupedreverses the fragments under a headingbyNumberreversed" - nested" is missing). A permanent test was added ("an indent trimmed").## v9.9.7 (…)line inside a fragment##+ no-break space +Unreleased#+ space/tab. Such a line would have passed, then failed the CHANGELOG backstop after release. Any#-led line (up to 3 spaces in) that isn't a fixed heading is now refused. Tests added for NBSP,##v9, 3-space indent,#unreleased,###<tab>### Guardrail changes(the generated heading) to hide notes among the lists- none in this release,- no merged PRs …,- x (#77))(unreleased)section, a dated one, or for an older versionIn-flight PRs: the one-time conversion
Each open PR that touches CHANGELOG.md does this once, after this PR merges (or after it merges main with this in it):
Measured on 2026-10-20 from each PR's diff at the head shown, against that head's CHANGELOG.md (
pr-convert.mjs). "Lines" counts non-blank lines.changelog.d/748-bench-pinned-downloads.md### Guardrail changes: what each one does: its 5 lines ("The benchmark (scripts/bench/, not installed) downloads only what … pins" and 4 sub-bullets). Its 6th line is #851's bullet, which goes in #851's fragment, not this onechangelog.d/748-bench-preflight.md### Guardrail changes: what each one does: 1 ("The benchmark's own scripts … never run as root")changelog.d/739-first-hour.md### Upgrading (from v0.5.2): 1 ("A server's first hour (#739, part 1 of 2)")changelog.d/786-env-own-variables.md### Guardrail changes: what each one does: 10 (the bullet, its 8 sub-bullets and its closing paragraph)changelog.d/785-guard-modules.md### Guardrail changes: what each one does: 1 ("The agents' tool guard loads 56 modules")changelog.d/776-resume-waits-green.md### Upgrading (from v0.5.2): 5 ("Resuming auto-merge … waits for green main" and 4 sub-bullets). The rebuild on #788 doesn't change this: #788's lines stay in #788's own fragmentchangelog.d/727-required-check-pins.md### Upgrading (from v0.5.2): 1;### Guardrail changes: what each one does: 1727-required-check-pins.mdchangelog.d/727-updater-check-pins.md### After install: 1;### Guardrail changes: what each one does: 1changelog.d/726-policy-root.md### Upgrading (from v0.5.2): 1;### Guardrail changes: what each one does: 1726-policy-root.md, and adds its 2 new Upgrading lines and 1 After install line (in that file or its own726-policy-helper.md). Its After install line replaces an existing note ("No sudoers rule is added, so no machine-helper rerun …"). That line now lives inchangelog.d/870-notes-before-changelog-d.md, so #770 edits that one line there. It is the only PR that edits the shared fragment (owner: fine)changelog.d/744-remove-desktop.md### Upgrading (from v0.5.2): 2. Its third line sits under the generated### Guardrail changesin its head, which a fragment can't use, so it goes under### Guardrail changes: what each one does. Its removal of- none in this releasehas no counterpart (no such line now)Owner decisions (given 2026-10-20)
Removals): yes.Still open: the empty trailing
### Guardrail changesis the one non-blank line of the section not moved (above). Say if you want it kept another way.Refusals: none.