Skip to content

Release notes checker: link rules from data-platform#98 (#70) - #73

Merged
craigmcchesney merged 7 commits into
mainfrom
ci/70-release-notes-link-rules
Oct 1, 2026
Merged

craigmcchesney merged 7 commits into
mainfrom
ci/70-release-notes-link-rules

Conversation

@craigmcchesney

@craigmcchesney craigmcchesney commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #70. Step 1 of osprey-dcs/data-platform#98.

Extends check-release-notes.py with the link rules from data-platform#98, and moves it to .github/scripts/check-release-notes.py, the location now shared by all five repos. The other four repos copy this file verbatim; only the marked configuration block at the top differs.

Rules

  • rel-*.md:
    • R1: no relative cross-file links.
    • R2: osprey-dcs blob/tree/raw links pinned to the file's own tag.
    • R3/R4: paths and anchors into this repo exist in the working tree.
    • R5: no leftover rel-<version> or <previous>.
  • NEXT.md: no relative links, osprey-dcs links on main, and R3/R4.
  • Coverage: reference definitions and bare URLs are checked. Code spans and fenced blocks are stripped for the link rules, but not for R5.
  • Already released: every rel-*.md except the highest version, which gets only R1, R2 and R5.
  • Signing identity: the identity rules for dp-service, dp-desktop-app and dp-grpc sit behind the configuration block, and the self-test covers each one under its own configuration.

Departures from #98's wording

  • R2 also accepts a full 40-character commit SHA. rel-1.16.0.md links README.env, which was added after the tag (700d2ed, Release body is missing the verification and install instructions (rel-1.16.0) #56), so blob/rel-1.16.0/README.env 404s. The link was on main and is now pinned to 700d2ed.
  • R5 does not flag a bare <version>. Three repos use it on purpose in prose that stays after the cut, such as dp-service-<version>.jar.sha256.
  • The repo root is found by walking up to .git, instead of being a fixed depth.

Testing

  • Self-test: good and bad cases for every rule. Switching off any of 18 rules or parsing steps makes the self-test fail.
  • Real notes: the checker passes on rel-1.16.0.md and NEXT.md.
  • Repo checks: ruff, ruff format and mypy are clean. Unit tests: 968 passed.

After merge

The published rel-1.16.0 body is the notes file unchanged, so correcting it takes one command:

gh release edit rel-1.16.0 -R osprey-dcs/dp-python-lib --notes-file doc/release-notes/rel-1.16.0.md

The only diff against the published body is the [readme-env] link.

Merge this before the port PRs (osprey-dcs/dp-grpc#169, osprey-dcs/dp-service#306, osprey-dcs/dp-desktop-app#52, osprey-dcs/data-platform#109): they copy this script, so any change made in review has to be copied to them as well.

🤖 Generated with Claude Code

https://claude.ai/code/session_012nsCzraPATf4LCKVyUStfd

craigmcchesney and others added 4 commits October 1, 2026 11:35
The [readme-env] reference definition pointed at blob/main/README.env, which drifts as main moves.
It cannot point at blob/rel-1.16.0/ as data-platform#98 proposes: README.env was added in 700d2ed
(#56), after the tag, with the backported verification section that links it, so that URL 404s.
Pin it to 700d2ed instead, whose README.env is identical to main's today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012nsCzraPATf4LCKVyUStfd
…98 (#70)

Adds R1-R5 for rel-*.md and N1-N3 for NEXT.md: no relative links; osprey-dcs blob/tree/raw links
pinned to the file's own tag (main in NEXT.md); paths and anchors into this repo checked against the
working tree, with no anchor onto a duplicated heading; no leftover rel-<version>/<version>/<previous>.
Reference definitions and bare URLs are covered, and code spans and fenced blocks are stripped for
the link rules but not for R5.

Every rel-*.md except the highest version counts as already released and gets the form rules only
(R1, R2, R5).  R2 also accepts a full 40-character commit SHA, for a target that did not exist at
the tag, as with rel-1.16.0's README.env link.

The script is now meant to be copied verbatim into the other four repos: everything repo-specific is
in a marked block at the top, and the cosign identity rules for dp-service, dp-desktop-app, and
dp-grpc are implemented behind it and self-tested under their own configurations in every copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012nsCzraPATf4LCKVyUStfd
… checker (#70)

NEXT.md's "Cutting the release" steps 6 and 8 now rely on the checker to list missed links and
leftover placeholders rather than on the reader, and its preamble states the link convention.
CLAUDE.md records the rules and which notes count as already released.  plan/tickets/70/plan.md
records the triage findings and design decisions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012nsCzraPATf4LCKVyUStfd
… docstring (#70)

Decisions made after the four ports reported back (data-platform#98):

- The script lives at .github/scripts/check-release-notes.py in all five repos.  CI, release.yml,
  CLAUDE.md, NEXT.md and the #70 plan point there; release.yml keeps its push-only guard, and no
  `uses:` pin changed.  Plans for earlier tickets keep the old path, as a record of where it was.
  The new path is not gitignored, so ruff now lints and formats it by default.
- The repository root is the first directory at or above the script containing .git (a directory,
  or a file in a worktree), not a fixed parents[2]; with none, it exits with a clear message.
  Self-tested for both .git forms and for its absence.
- R5 catches only rel-<version> and <previous>.  A bare <version> is used deliberately in prose
  that survives the cut (dp-service-<version>.jar.sha256), and now passes.
- Nothing outside the configuration block is specific to dp-python-lib: no fixed usage path, no
  plan/tickets/56 citation, no body_path wording, no rel-1.16.0 history.
- The docstring says releases/tag/... and compare/... links are deliberately left to R2's
  silence, since linking an earlier release is legitimate; a self-test case holds that.
- The self-test's fixture files are written with encoding="utf-8".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012nsCzraPATf4LCKVyUStfd

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Valid Markdown can bypass or be incorrectly accepted by the new link checks, and R4 differs from its linked requirement.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
What changed in this PR

Extends and relocates the release-notes checker to validate links, anchors, placeholders, and signing identities across repositories.

Changes:

  • Adds R1–R5/N1–N3 validation and expanded self-tests.
  • Moves the checker into .github/scripts/ and updates CI/release workflows.
  • Pins the historical README.env link and documents the release process.
File Description
.github/​scripts/​check-release-notes.py Adds the expanded checker and self-tests.
.dev/​tools/​check-release-notes.py Removes the old checker location.
.github/​workflows/​ci.yml Uses the relocated checker.
.github/​workflows/​release.yml Uses the relocated checker at release time.
doc/​release-notes/​rel-1.16.0.md Pins README.env to an immutable commit.
doc/​release-notes/​NEXT.md Documents link rules and cutting steps.
CLAUDE.md Records release-note link conventions.
plan/​tickets/​70/​plan.md Captures design and implementation decisions.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/scripts/check-release-notes.py Outdated
slug = base if n == 0 else f"{base}-{n}"
anchors.add(slug)
by_base.setdefault(base, []).append(slug)
duplicated = {slug for slugs in by_base.values() if len(slugs) > 1 for slug in slugs}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined. This narrowing is an approved departure, recorded as item 7 in the latest comment on osprey-dcs/data-platform#98: R4 rejects a duplicate only when a link's anchor lands on it. Otherwise the two unlinked ### Methods headings in rel-1.16.0.md would fail. The docstring also states the rule.

Comment thread .github/scripts/check-release-notes.py
craigmcchesney and others added 3 commits October 1, 2026 14:05
- A reference definition whose destination is on the next line
  (`[x]:` then `  ../README.md`) is valid CommonMark, and slipped past
  R1/N1.  The pattern now accepts one line break before the target.
- A same-document `#L<n>` anchor passed R4, but a release body has no
  line anchors; they are now accepted only on blob links.
- An `http://` github.com or raw link was not parsed as a repo link, so
  a `main`-pinned one bypassed R2/N2.  Both patterns accept http too.

Each gap has a self-test case that fails if the fix is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012nsCzraPATf4LCKVyUStfd
Found by Copilot on the port PRs (dp-desktop-app#52, dp-grpc#169), and
folded into the reference copy so all five copies stay identical:

- R2/N2 apply only to the five lockstep repos (LOCKSTEP_REPOS, outside
  the per-repo block).  A link into another osprey-dcs repo, such as
  dp-support, has its own release cycle and is no longer checked.
- An `<a name>`/`id` quoted in a fenced block or code span no longer
  counts as an anchor for R4/N3.
- An option value on the line after a backslash continuation
  (`--certificate-identity \` then the value) is read as the value, for
  the cosign and sigstore identity and issuer rules, instead of
  reporting the backslash as a wrong identity.
- The docstring lists blob line anchors (`#L<n>`) as not range-checked.

Each fix has self-test cases that fail if it is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012nsCzraPATf4LCKVyUStfd
…#70)

Found by Copilot on osprey-dcs/dp-service#306.  An unquoted image in
prose ("pull ghcr.io/osprey-dcs/dp-service:rel-2.1.0.") was read as tag
`rel-2.1.0.` and failed correct notes; a trailing comma did the same.
Trailing `.,;:!?` is now stripped from the captured tag, as urls() does,
so a `rel-2.1.0-rc1` tag is still read whole.  Self-tests cover a good
tag followed by a period and by a comma, and an -rc1 tag before a period.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012nsCzraPATf4LCKVyUStfd
@craigmcchesney
craigmcchesney merged commit f04d830 into main Oct 1, 2026
6 checks passed
@craigmcchesney
craigmcchesney deleted the ci/70-release-notes-link-rules branch October 1, 2026 20:16
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.

Release notes checker: add link rules and fix the main-pinned link in rel-1.16.0 (data-platform#98)

2 participants