docs: contribution policy — CONTRIBUTING, PR template, changelog CI guards - #14
Merged
Conversation
Ports the policy set deployed to connectome-host (PR #49) to this repo, codifying norms already practiced here rather than introducing new ones: merge-commit-only history, comment-based evidence review, declared AI authorship, companion-PR merge-order declarations. Conventional-commit titles are written down as recommended-not-required, which is what this repo's history actually shows. Adds the changelog discipline this repo has never had — 7 releases, no changelog — with enforcement layered cheapest-first: an entry-with-the-change rule binding direct pushes as well as PRs, a soft PR check (src/ touched => CHANGELOG.md touched, `no-changelog` label escape), and a tag-time publish guard that refuses to release a tag with no matching section. `npm version` now cuts the Unreleased section via scripts/release-changelog.mjs, which refuses to run on more than one `## Unreleased` heading (only the first is ever cut, so later ones strand entries) and splices by match index rather than `String.replace(substring)`. Both hardenings are over the connectome-host original, whose CHANGELOG.md accumulated six such headings with one stranding ~60 lines of entries that reached no release. Two things are specific to this repo: - The `publish` job gains `if: startsWith(github.ref, 'refs/tags/v')`, which its siblings already had and it lacked. Without it a workflow_dispatch against a branch would publish whatever version package.json carried, bypassing the changelog guard entirely. Gating on the ref rather than the event keeps manual re-dispatch against an existing tag working. - `github-release` deliberately takes no `needs:`. Unlike the sibling repos, the prerequisite here is a five-target cross-compile matrix, and gating the release notes on the flakiest part of the pipeline would defeat the reason the job is independent in the first place — github-clone consumers need notes even when the cross-build or the publish fails. Being a persistence layer, the PR template adds a Compatibility section: does this touch the record log, blob layout, snapshot encoding or wire format, and do existing stores still open. The audience-scoped breaking-entry format calls out that same question as the one every reader asks first. Verified: release script refuses the empty Unreleased (exit 1); full ritual exercised on a copy — entry -> cut -> tag guard pass/refuse -> notes extraction; all three workflows parse. No Rust touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The policy this branch introduces binds direct pushes as much as PRs, so the file it adds should not land already behind main. 0.3.0 was tagged 2026-08-01 — after this branch was cut — and is the first release the changelog is expected to cover. Entries reconstructed from the ten commits in v0.2.7..v0.3.0, with the MessagePack state_update encoding filed as a Breaking (on-disk format) entry: it is forward-only, which is the one thing every reader of this package's changelog wants stated plainly. Verified against the tooling this branch ships: - publish.yml tag guard greps '## 0.3.0' — matches. - github-release awk extracts a 107-line, 6,646-byte notes body that starts at '### Breaking' and ends at the section's last line. - scripts/release-changelog.mjs on a copy: refuses the empty Unreleased, cuts cleanly to '## 0.3.1 — 2026-08-07' once an entry exists, and refuses the duplicate on re-run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…edentials Greptile flagged the new github-release job (contents: write) for running actions/checkout at a mutable major-version ref: retargeting that tag would change the code running with permission to create and edit releases. Valid, and it undersells the exposure — that job is the LEAST privileged of the write-capable ones. Fixed across every workflow rather than just the flagged line, because a half-pinned repo invites the same finding next time: - 14 actions pinned across changelog.yml, ci.yml and publish.yml, including three third-party ones: dtolnay/rust-toolchain, Swatinem/rust-cache and the artifact actions that carry the native binaries into the publish job. - dtolnay/rust-toolchain@stable was a BRANCH ref — the most mutable of the lot, and it feeds the .node binaries that the OIDC publish job ships. The channel is now named explicitly in `with:`, since pinning to a SHA would otherwise take it from whichever branch that SHA was reached from. Two things the review did not name: - The npm publish job holds `id-token: write` for OIDC trusted publishing. A swapped action there can reach a live publish credential, which is a worse outcome than editing release notes. - checkout defaults to persist-credentials: true, writing the job token into .git/config where every later step in the job can read it. Nothing here pushes over git — publish uses OIDC, the release job uses gh with GH_TOKEN — so all 4 checkouts now set it false. Version tags are kept as trailing comments so the pins stay readable and Dependabot can still bump them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 7, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
chronicle has no
CONTRIBUTING.md, no PR template, and no changelog — and nomechanism that would notice. Consumers of
@animalabs/chroniclecurrentlylearn what a release contains by reading
git log, and the only GitHub"release" this repo has ever had is a stale v0.2.0 draft.
That matters more here than elsewhere in the ecosystem: this is the
persistence layer, so the changes people most need warned about are on-disk
and wire-format ones. 0.3.0 shipped a forward-only
state_updateencodingchange on 2026-08-01 — correct, deliberate, well-tested, and announced
nowhere a consumer would look.
The mechanism has to bind direct pushes, not just PRs. Across the ecosystem
52–85% of commits on
mainarrive by direct push, and version bumps arerelease-time maintainer actions by construction — so a PR-side check alone
would miss exactly the commits that cut releases. This is the third rollout
of the same policy, after connectome-host #49 and context-manager #51.
Changes
Ported from those two, adapted to this repo (Rust + napi,
cargo testcaveats, format-compatibility framing):
CONTRIBUTING.md— codifies existing practice rather than aspiration:merge commits only, comment-based review with run-the-branch transcripts,
evidence-over-assertion test reporting, and AI attribution as the norm.
Explicitly binds direct pushes and PRs, human and AI authors, identically.
.github/PULL_REQUEST_TEMPLATE.md— Problem / Changes / Tests /Compatibility / Not verified, plus a changelog checkbox.
.github/workflows/changelog.yml— soft PR check: touchingsrc/without
CHANGELOG.mdfails, with ano-changeloglabel opt-out (labelcreated).
publish.yml— two additions. The publish job is now gated on the refrather than the event, so a manual dispatch against a branch can no
longer publish whatever version
package.jsonhappens to carry; and a tagwith no matching
## X.Y.Zchangelog section fails the release. A newgithub-releasejob mirrors the tag's section into GitHub release notes,deliberately independent of the npm publish job and its cross-build matrix
— some consumers run github-clone checkouts, so notes must exist even when
the native build or publish fails.
scripts/release-changelog.mjs+ aversionhook inpackage.json—npm version <level>cutsUnreleasedinto## X.Y.Z — YYYY-MM-DDpre-tag, so the tag and tarball carry it. Refuses an empty Unreleased, a
duplicate section, and more than one
Unreleasedheading (a second onesilently strands entries).
CHANGELOG.md, watermarked at 0.2.7, with 0.3.0 written up in full.That last point is the deliberate one. The earliest policy PRs held that the
enforcement PR should itself conform, and this branch was cut on 2026-07-27 —
before 0.3.0 tagged. Landing an empty changelog would mean shipping a file
that is already behind
main, with the one release it exists to describemissing. The 0.3.0 entries are reconstructed from the ten commits in
v0.2.7..v0.3.0; the MessagePack encoding change is filed as### Breaking (on-disk format)with the who-acts / migration / unchangedshape the policy asks for, because "existing stores still open, but a store
written by 0.3.0 cannot be read by older chronicle" is the first thing any
reader of this package's changelog wants stated.
Tests
No
src/changes, so the Rust suite is untouched and was not run — this isdocs, CI config, and one npm lifecycle hook. What was exercised is the
tooling this branch adds, against the real
CHANGELOG.md:v0.3.0: section found, exit 0.6,646 bytes, starting at
### Breaking (on-disk format)and ending on thesection's last line — no bleed into neighbouring sections.
scripts/release-changelog.mjsagainst a copy,package.jsonat0.3.1:
Unreleased→'## Unreleased' has no entries — nothing to release as 0.3.1., exit 1cut Unreleased into '## 0.3.1 — 2026-08-07'.,exit 0, with a fresh empty
Unreleasedleft above ita '## 0.3.1' section already exists., exit 1Unreleasedheading count: exactly 1.Compatibility
No format change — no Rust source is touched. The only runtime-adjacent edit
is the
versionscript inpackage.json, which runs duringnpm versionand not during install, build or publish.
Not verified
changelog.ymlcheck has not run yet — this PR touches nosrc/files, so it will pass trivially rather than exercising the failure path.
The equivalent workflow was verified live on connectome-host #49.
github-releasejob cannot run until the next release;they stay unexercised on real infrastructure until then, as they did on
the previous two rollouts. The next
npm version <level> && git push --follow-tagswill be this repo's first non-draft GitHub release.measurements quoted in them, not re-measured. The performance numbers are
as their authors reported them.
CHANGELOG.mdupdated under## Unreleased— or this change isinternal-only / test-only / docs-only (apply the
no-changeloglabel).Docs-only by the letter of the check, but the file ships with 0.3.0 written
up rather than empty, per the self-conformance point above.
🤖 Generated with Claude Code