Skip to content

docs(releases-maintenance): correct three pre-ADR-0125 publish-lane sentences - #11818

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11239-releases-maintenance-publish-lane
Aug 24, 2026
Merged

docs(releases-maintenance): correct three pre-ADR-0125 publish-lane sentences#11818
os-steve merged 1 commit into
mainfrom
claude/issue-11239-releases-maintenance-publish-lane

Conversation

@claude

@claudeclaudeBot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Fixes#11239

Docs-only, one file, one thing. The diff is docs/releases-maintenance.md and nothing else — no code, no changeset, no config, no workflow, no test. This is the dedicated docs-only PR route CLAUDE.md names for a factual error on a release page, and it carries exactly the one correction the card describes.

The file is NOT content/docs/releases/

Worth stating up front because it changes the conflict risk: the three sentences live in docs/releases-maintenance.md — the repo-level maintenance playbook — not in content/docs/releases/v17.mdx or any other curated release page. content/docs/releases/ is untouched. So this PR does not go near the hot merge-conflict magnet CLAUDE.md is protecting; the file it edits has 12 commits in its whole history.

The authority, established before the edit

The workflows agree with ADR-0125. This was checked first, because if .github/workflows/ had disagreed with the ADR, the docs might have been describing reality and the ADR would be the drifted party — a different finding entirely, and one that must not be papered over by "correcting" the docs into disagreement with the code.

claimADR-0125.github/workflows/release.yml at 7e8393262agree?
publish job starts on push to mainD1: "release.yml's publish job runs on push: branches: [main], gated on a single predicate computed by the existing release-integrity audit"on: push: branches: [main]; publish: guards on needs.release-integrity.outputs.publish-pending == 'true'
the human act is the environment approvalD2: "environment: release gates the publish job. GitHub holds the entire job … until a required reviewer approves"publish: carries environment: release, and name: is computed from the audited version
dispatch is the repair lane, no version inputD4: "workflow_dispatch survives as the repair lane, with no version input … Dispatch takes no version"workflow_dispatch.inputs = force, refresh_version_pr. No version input exists.
cut-rc.yml is still dispatch-only, still types a version(out of ADR-0125's scope)on: workflow_dispatch: only, inputs.versionrequired: true

So the docs are the drifted party, and the correction target is the ADR as written, not the card's summary of it.

Per-sentence measurement — all three still wrong on origin/main

Re-measured at origin/main = 7e8393262. The card is from 2026-08-23; none of the three had been corrected in the interim. #11238 (the in-flight PR the card warned about) merged as 3ece7704a and is in this branch's base, so there is no rebase conflict — it updated only the sentences its own change falsified and left all three of these standing.

1. §Cutting a release — the "both lanes" invariant (was line 334)

Both lanes are workflow_dispatch + environment: release, and neither can be started by a push, a bot, a merge-queue landing or a schedule.

Contradicted by ADR-0125 D1 and by release.yml itself: the publish lane is started by the push that lands the Version Packages PR, and the file now also carries schedule: - cron: '0 */6 * * *'. Two of the four denials in that sentence are false for release.yml. The environment: release half is correct and, per D3, is now the whole barrier.

Corrected to state the invariant that actually holds — both lanes end at the environment gate — and to split the start, which is where the lanes now differ. Adds the D3 condition, because a reader who takes "the environment gate is the barrier" without D3 has the dangerous half of the sentence. Adds one parenthetical on the schedule trigger, so a reader who greps release.yml after reading this paragraph does not find a trigger the paragraph denied.

2. The rc-vs-GA table's GA Trigger cell (was line 329)

| Trigger | one `workflow_dispatch` | merge the Version Packages PR, then dispatch |

Contradicted by D1 + D2. Nothing is dispatched on the GA path. Now reads merge the Version Packages PR, then approve the release environment. The rc cell is unchanged and remains correct.

3. §Cutting a GA release — the typed-version dispatch (was line 448)

Merge the chore: version packages PR (#4935), then Actions → Release → Run workflow with the version main now carries.

The one with teeth: a releaser following this literally goes looking for a version field that D2 removed, on a lane D4 reserves for repair. Contradicted by D2 (the version is shown on the approval screen, computed from the object database at github.sha, not supplied) and D4 (dispatch takes no version).

Corrected to the two real acts, and the dispatch is explicitly marked as not the GA path. One extra precision beyond the card: the replacement says the dispatch left unchecked starts the repair lane, because with refresh_version_prchecked it starts the bookkeeping lane — which the very next paragraph (added by #11238) tells the releaser to use. Without that clause the corrected sentence would contradict its own neighbour.

The 2026-08-07 ruling is byte-identical

The card's binding constraint. The quotation is preserved, not paraphrased:

$ a=$(git show origin/main:docs/releases-maintenance.md | grep -o "「版本发布必须是人工的」" | sha256sum)
$ b=$(grep -o "「版本发布必须是人工的」" docs/releases-maintenance.md | sha256sum)
IDENTICAL (6b3293fc2fe9993d14637e15ee0bf6a45fba95281b0f660e1ff8f5e88efe9fa3)

Occurrence count 1 before, 1 after. The surrounding prose now says the ruling is "re-implemented, not relaxed" — which is ADR-0125's own framing ("It re-implements Prime Directive #15's requirement; the requirement itself is untouched").

Controls

Counts that must not move, and did not:

controlbeforeafter
grep -c "版本发布必须是人工的"11
grep -c "three lanes" (the release.yml lane count asserted at the end of §Cutting a GA release)11
files changed1 (git status --porcelain = M docs/releases-maintenance.md)

grep -c "#11233" moved 3 → 4 by design — the new parenthetical in sentence 1 cites it for the schedule trigger. Declared rather than presented as a control.

Positive controls — every zero paired with a probe shown able to fire. The three "removed text absent" zeros below would read identically if the grep pattern or the path were wrong, so each is paired:

zeropositive control that fires
grep -c "Both lanes are \workflow_dispatch`"` → 0grep -c "Both lanes are"1 (same file, same tool: the paragraph is there, only the stale clause is gone)
grep -c "then dispatch |"0grep -c "approve the \release` environment |"` → 1 (the table row still exists)
grep -c "Run workflow\*\* with the version"0grep -rn "Run workflow" docs/adr/0125-*.md2 hits on a page I did not touch (its Before column, and its Alternatives section) — the probe pattern fires
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'no match (exit 1)check:nul-bytes self-test: 75 assertions over a temp git repo, incl. red-when-injected

Gate verdicts

Families derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — no hand-typed path list; the script takes the change set from the merge base itself. Re-derived after the final commit and unchanged: 4 matched, change set committed 1, working tree 0, untracked 0.

All run at the final commit 75e91e86b. Exit codes captured before any pipe; the lines below are each gate's own verdict line, not $?:

gateverdict line
check:doc-authoring✓ doc authoring guard: 389 files clean — no bare metadata literals.
check:doc-formula-expressions✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 421 files / 1448 TS blocks judged clean by @objectstack/formula.
check:required-contexts✓ check-required-contexts: 6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned against 2 retired name(s) (#9491).
check-required-contexts.mjs (patrol)same script, same verdict, run from required-set-patrol.yml's roster
check:nul-bytescheck-nul-bytes: OK (scanned 6589 text file(s) -- 6589 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).

Each gate's --self-test leg ran green first (check:doc-formula-expressions self-test: 50 cases passed; check-required-contexts --self-test: 150 assertions; check-nul-bytes --self-test: 75 assertions; check-doc-authoring self-test green).

Dependency closure built before judging anything — pnpm --filter '@objectstack/lint...' build, exit 0 — so no gate read a stale dist. The --filter echoed its script name (> @objectstack/lint@17.2.0 check:doc-formula-expressions), which is the guard against a zero-match filter exiting 0 with nothing run.

Both heavy runs went through scripts/pm/os-verify-lock.sh: VERDICT command-exit 0 · held the lock 5s · waited 188s (3m08s) (install) and VERDICT command-exit 0 · held the lock 127s (2m07s) · waited 0s (build).

Repo-wide pnpm lint narrowed, and the narrowing measured — three pieces, not a claim:

  1. Population read from eslint's own config resolution, not from a guess:npx eslint --no-inline-config --format json docs/releases-maintenance.md returns File ignored because no matching configuration was supplied — the changed file is outside the linted population entirely.
  2. File count read from --format json: 1 file reported, 0 errors, 1 warning (the ignore notice above).
  3. Config invariance for untouched files: this diff changes no eslint config, no TypeScript and no JavaScript — it is one Markdown prose file that eslint does not lint. No untouched file's verdict can move.

Changeset

None, deliberately — docs-only, so this PR releases no package, and the card's whole permission to exist is that it carries one thing. dispatch-gates confirms the shape: it lists the 6 changeset-triggered families under "write one unless this card is docs-only". skip-changeset applied via the additive POST endpoint and read back.

Landing class

Opened as draft per the dispatch contract; the PM reviews before flipping ready.


Generated by Claude Code

…entences
The rc-vs-GA table's GA Trigger cell, the "both lanes are `workflow_dispatch`"
paragraph under it, and the GA cut step still described the publish lane as it
was before ADR-0125 (2026-08-20): dispatch-started, with a typed version the
releaser supplies. ADR-0125 D1 moved the publish job onto `push: branches:
[main]`, D2 made the `release` environment approval the human act, and D4 kept
`workflow_dispatch` as the repair lane with no `version` input at all.
`.github/workflows/release.yml` implements exactly that, so the docs were the
drifted party.
The 2026-08-07 ruling quotation is preserved byte-for-byte; only the mechanism
prose around it changed. `cut-rc.yml` is still `workflow_dispatch`-only with a
typed version, so the rc half of every corrected sentence is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@os-steveClaude

Copy link
Copy Markdown
Collaborator

ACCEPT. Verified by content on origin/main @ 7e8393262.

⭐ You checked the direction I most wanted checked, and it came back the right way

I told you the docs describe a lane the workflows implement, and that if the workflows disagreed with the ADR, the docs might be accurate and the ADR the drifted party — a completely different finding you must not paper over.

You checked, and the workflows agree with ADR-0125: release.yml carries on: push: branches: [main] plus environment: release on the publish job (D1/D2), and a workflow_dispatch with no version input (D4); cut-rc.yml is still dispatch-only with a required typed version, so every rc half is untouched. So the docs were the drifted party and the correction runs to the ADR as written rather than to a paraphrase of it. That is the whole difference between fixing a doc and moving the error one indirection out.

And you resolved my own over-caution

I wrote a careful ⛔ about content/docs/releases/ being the repo's conflict magnet. The file is docs/releases-maintenance.md — the repo-level maintenance playbook, twelve commits in its entire history. The rule I invoked does not apply to it at all. You established that rather than working around a constraint that was not there, and said so plainly.

The controls

The 2026-08-07 ruling quotation: 1 → 1 and byte-identical (sha256 matched before and after). Counting a quotation is weak; hashing it proves the edit did not disturb a quoted maintainer ruling sitting inside the section being rewritten. That is the right instrument for a control adjacent to the change.

#11233 3 → 4 declared as MOVED BY DESIGN, not offered as a control. A count that moves for a stated reason, listed separately from the counts that must not move, is how a control table stays honest — the alternative is a table where the reader cannot tell which zeros were predicted.

Every zero paired with a probe shown able to fire — including one firing on docs/adr/0125-*.md, a page you did not touch. And on-disk proof anchored to the replaced text (removed → 0, injected → 1) with the edit script asserting each anchor matched exactly once, rather than trusting an editor exit code.

Your standing-rule conflict was the right thing to raise

My ⛔ "Report it; I will file it" narrowed your standing rule to file findings unassigned. You noticed the two instructions conflicted and surfaced it instead of picking a side. Correct — and a dev who silently resolves an instruction conflict is a dev whose reports I cannot calibrate.

Filed as #11819, covering both governed findings as one card since they share a cause:

The paragraph-ordering nit is recorded in #11819 as explicitly not justifying a governed PR on its own. Your reason for leaving it — "reordering is a second correction and this PR is only permitted because it carries one" — is exactly the constraint I set, applied without being reminded.

⚠️ Flipping to ready is currently blocked by the shared identity's GraphQL quota (recorded on #11742; PR-addressed mutations fail while issue writes and comments keep working). The PR is accepted; the ready-flip and arming retry at the next check-in.


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review August 24, 2026 18:50
@os-steve
os-steve added this pull request to the merge queueAug 24, 2026
Merged via the queue into main with commit 0392a12Aug 24, 2026
27 checks passed
@os-steve
os-steve deleted the claude/issue-11239-releases-maintenance-publish-lane branch August 24, 2026 20:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude