Skip to content

docs: contribution policy — CONTRIBUTING, PR template, changelog CI guards - #14

Merged
Anarchid merged 3 commits into
mainfrom
docs/contribution-policy
Aug 10, 2026
Merged

docs: contribution policy — CONTRIBUTING, PR template, changelog CI guards#14
Anarchid merged 3 commits into
mainfrom
docs/contribution-policy

Conversation

@Anarchid

@Anarchid Anarchid commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Problem

chronicle has no CONTRIBUTING.md, no PR template, and no changelog — and no
mechanism that would notice. Consumers of @animalabs/chronicle currently
learn 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_update encoding
change 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 main arrive by direct push, and version bumps are
release-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 test
caveats, 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: touching src/
    without CHANGELOG.md fails, with a no-changelog label opt-out (label
    created).
  • publish.yml — two additions. The publish job is now gated on the ref
    rather than the event, so a manual dispatch against a branch can no
    longer publish whatever version package.json happens to carry; and a tag
    with no matching ## X.Y.Z changelog section fails the release. A new
    github-release job 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 + a version hook in package.json
    npm version <level> cuts Unreleased into ## X.Y.Z — YYYY-MM-DD
    pre-tag, so the tag and tarball carry it. Refuses an empty Unreleased, a
    duplicate section, and more than one Unreleased heading (a second one
    silently 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 describe
missing. 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 / unchanged
shape 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 is
docs, CI config, and one npm lifecycle hook. What was exercised is the
tooling this branch adds, against the real CHANGELOG.md:

  • publish.yml tag guard, simulated for v0.3.0: section found, exit 0.
  • github-release extraction, same awk as the workflow: 107 lines /
    6,646 bytes, starting at ### Breaking (on-disk format) and ending on the
    section's last line — no bleed into neighbouring sections.
  • scripts/release-changelog.mjs against a copy, package.json at
    0.3.1:
    • empty Unreleased'## Unreleased' has no entries — nothing to release as 0.3.1., exit 1
    • one entry added → cut Unreleased into '## 0.3.1 — 2026-08-07'.,
      exit 0, with a fresh empty Unreleased left above it
    • re-run → a '## 0.3.1' section already exists., exit 1
  • Unreleased heading count: exactly 1.
  • All three workflow files parse as YAML.

Compatibility

No format change — no Rust source is touched. The only runtime-adjacent edit
is the version script in package.json, which runs during npm version
and not during install, build or publish.

Not verified

  • The changelog.yml check has not run yet — this PR touches no src/
    files, so it will pass trivially rather than exercising the failure path.
    The equivalent workflow was verified live on connectome-host #49.
  • The tag guard and github-release job 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-tags will be this repo's first non-draft GitHub release.
  • 0.3.0's changelog entries are reconstructed from commit messages and the
    measurements quoted in them, not re-measured. The performance numbers are
    as their authors reported them.
  • No store was opened and no native module was built for this branch.

  • CHANGELOG.md updated under ## Unreleased — or this change is
    internal-only / test-only / docs-only (apply the no-changelog label).

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

Anarchid and others added 3 commits August 7, 2026 14:07
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant