Skip to content

✨ fix(release): reject literal-emoji commit subjects and close the ### Other blind spot (D-168) - #116

Closed
konih wants to merge 9 commits into
mainfrom
lane/emoji-subject-r2
Closed

konih wants to merge 9 commits into
mainfrom
lane/emoji-subject-r2

Conversation

@konih

@konih konih commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

What this closes

main was RED for four days on a changelog-integrity defect with two halves, filed by the
REDMAIN-F01 reviewer as REDMAIN-N1 and REDMAIN-N2. The INBOX states they are two halves of one
defect and must land together, so they do.

  • REDMAIN-N1 — a literal emoji in a commit subject defeats cliff.toml's classifier (every
    parser keys on the ASCII shortcode), and nothing rejected such a subject at commit time. New
    hack/release/commit_subject_gate.sh scans every subject and rejects a non-ASCII-leading one.
    It is reachable on pull_request as a verify: step and locally through the existing
    release-changelog-gate-test stage — no new task check stage, so CHECK_STAGES in
    hack/audit/exitgate_test.sh stays at 21.
  • REDMAIN-N2 — changelog_gate_test.sh §8's ### Other detector required an ASCII shortcode,
    so it was fail-open for exactly the literal-emoji shape that causes the mis-filing it exists to
    catch. The detector now matches a fileable type behind a shortcode, behind a literal emoji, or
    behind no prefix; §8b pins the regression by showing the pre-fix pattern miss the same probe.

The two exemption lists are decoupled (:bug: fix(release)) so that REDMAIN-N3 — the remaining
cliff.toml parser entry that re-files dfdae69 out of ### Other — has a green state to land
into. When it lands, §8's SHA exemption stops matching and the gate reds telling you to delete it;
that red is the hand-off, by design (REQ-REDMAIN-N2-02).

Decision row: D-168. Spec: openspec/specs/p5-redmain-changelog-integrity/spec.md.

Local pre-merge evidence

Both AUD gates are SKIPPED on pull_request, so a green PR alone proves nothing about changelog
drift. Run locally on the rebased head:

  • task check — EXIT=0, 21 stages, matching CHECK_STAGES exactly.
  • task coverage — 91.1% against the 91% floor.
  • hack/release/commit_subject_gate.sh — 911 subjects scanned, all ASCII-leading; the single
    published-history exemption verified to still be a real detection. This includes every commit on
    this branch, so the lane's own new gate passes on its own rebase.
  • CHANGELOG.md was regenerated once with task changelog-write after the rebase and never
    hand-edited. All eight lane commits classify correctly; the 👷 ci(docs) entry remains under
    ### Other because that is REDMAIN-N3, still open by design.

Rebase notes

Rebased from 20c80cb onto 8ec54b8. Four conflicts, all the keep-both adjacency shape:
openspec/specs/backlog.md once (main's P5-DOCSNAV/D-170 section vs the lane's REDMAIN-N section,
appended at the same tail position) and docs/decisions/decisions.md three times (the lane adds
D-168; main now carries D-170 and D-171 — no ID collision). Every one resolved by keeping both
sides' rows; no decision ID was renumbered. The three pure mechanical CHANGELOG.md regeneration
commits were dropped and the file regenerated once at the end, so no known-wrong CHANGELOG.md
exists anywhere in the history.

Unblocks

Taskfile.yml and .github/workflows/verify.yaml are free again once this lands: DOCTRUTH,
DOCSNAV-R02 and COUNT1-F01 all need them.

Carried follow-ups (P3)

Both filed to the INBOX rather than fixed here — neither is a defect in the shipped gate:

  1. The REDMAIN-N4 backlog row says the three §8 mechanisms "have no standing control … so running
    changelog_gate_test.sh exercises none of them". The subset invariant does execute live on
    every run (fail-closed); what is missing is a negative control proving it can red. That row
    should say "no negative control". The spec file itself is correctly worded.
  2. The lane's handoff cited REDMAIN-N4 at backlog.md:822; it is at :825 (a 3-line section-intro
    insertion shifted it). The row is correct — only the citation was stale.

…s (REDMAIN-N1/N2)

The INBOX filed N1 (nothing rejects a literal-emoji subject at commit time)
and N2 (changelog_gate_test.sh §8's `### Other` detector requires an ASCII
shortcode, so it is fail-open for exactly that shape) as two halves of ONE
defect that one lane must own. This is that spec: five REQs, the narrow rule
and why it is narrow, and the two things deliberately out of scope — re-filing
the rendered entry (needs cliff.toml, filed as REDMAIN-N3) and a 22nd
`task check` stage (CHECK_STAGES asserts equality with the Taskfile list).
… them (D-168)

Half B: hack/release/commit_subject_gate.sh rejects a commit subject whose
first character is not ASCII — the narrow rule, so Dependabot's `build(deps):`
and GitHub's `Merge pull request …` stay legal. Published history is exempted
by full SHA and each exemption must itself still BE a detection, so it cannot
rot into a blanket escape. Reachable as an UNGUARDED verify: step (a PR is the
last moment a subject can be reworded) and, locally, through the existing
release-changelog-gate-test stage — no new `task check` stage.

Half A: §8's `### Other` detector now takes an OPTIONAL gitmoji prefix in two
spellings, so a fileable type is seen behind a shortcode, behind a literal
emoji, or behind nothing. §8b pins the regression by running the pre-fix
pattern over the same probe and showing it miss the literal-emoji line. The
`### Other` exemption is read from the gate's own SHA list, so the two halves
cannot drift, and it reds as dead scaffolding once cliff.toml files the entry.
…s and its hand-off

Why the rule is "first character must be ASCII" and not the whole convention;
why there is no 22nd `task check` stage; why the verify: step is deliberately
NOT guarded off pull_request while the drift gate beside it still is; how the
SHA exemption is kept honest and made self-retiring into REDMAIN-N3; and the
incidental finding that AGENTS.md/CLAUDE.md are untracked, so GUIDELINES.md is
the authority a gate can actually cite.
…green state (D-168)

Review simulated REDMAIN-N3 landing and found the self-retire hand-off named a
remedy nobody could execute. Deriving §8's `### Other` exemption from
`LEGACY_ALLOW_SHAS` coupled two facts that decouple at exactly that moment:
the commit SUBJECT stays a literal emoji forever, while the RENDERED ENTRY
stops being mis-filed. Coupled, there was no green allowlist state after N3 —
keep it and §8 reds as stale, drop it and both gates red.

§8 now keeps its own OTHER_EXEMPT_SHAS, which may be EMPTY (the post-N3 steady
state, where every Other line is checked — strictly stronger), linked to the
gate only by the one-way invariant OTHER_EXEMPT_SHAS subset-of
LEGACY_ALLOW_SHAS, which the empty set satisfies. The retire message now names
the one line to delete, and says which list not to touch.

Also: pin the allowlist's exact content from §9d, because all three in-gate
self-checks pass for a SHA appended in the same PR as the commit it excuses;
widen OTHER_MAPPABLE_RE, which accepted exactly one non-ASCII token and exactly
one space and so missed three more mis-filing spellings; and point the
shallow-clone messages at the cause they actually have.
…or the allowlist pin

REQ-REDMAIN-N2-02 said the exemption was derived from one authority and would
"red the moment it stops being needed" — true, but the state it redirected to
was unreachable. It now states the two predicates, why they diverge at N3, the
one-way subset invariant, and the Given/When/Then that ends green. REQ-N1-04 is
new and records the allowlist-growth hole review found. The backlog N3 row
carries the exact one-line hand-off, and neither doc quotes a commit count any
more — a self-dating denominator rots.
…ng instinct here

The row claimed the two halves share one authority "so they cannot drift
apart". They were never meant to stay equal: one fact is permanent and the
other retires at REDMAIN-N3, and coupling them left no green state. Recorded as
a correction with the mechanism, not silently rewritten. (e) now carries the
executable remedy and the end-to-end proof; (c) carries the second-file size
pin; the "893 commits" figure is gone from both this row and the spec.
…ave; file REDMAIN-N4

The Test:/Verify: line named the subset invariant, the empty-list path and the
retire message as if running changelog_gate_test.sh exercised them. It does
not: on the real tree the subset grep always passes, the empty-list branch is
never taken and the retire message is never emitted. They were proved by
one-off scratch-clone simulation — evidence produced once, not a probe that
re-runs — which is weaker than the standing controls §8a/§8b/§9b/§9d carry.

The line now says what running the script actually covers and names the gap in
those words. REDMAIN-N4 is the tracked OPEN residual for the missing §8c, on
its own row, carrying the three fail-closed P3s the reviewer found.
… at REDMAIN-N4

The row implied (d) and (e) were held by the gate. They are held by a
simulation this lane ran once; the gate has no standing control for any of the
three. Recorded as a bounded claim with the follow-up named, not narrowed into
vagueness.
@konih

konih commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #117: main moved to 00c429e (the golang.org/x/mod v0.40.0 bump in #115) while this PR was awaiting checks, and strict status checks require the branch to be up to date. Rebasing rewrote already-pushed commits, so per the no-force-push rule the rebased series was pushed to lane/emoji-subject-r3 and reopened as #117 rather than force-pushed here. Same content, plus a CHANGELOG regeneration that also absorbs the x/mod entry main was missing.

@konih konih closed this Sep 5, 2026
@konih
konih deleted the lane/emoji-subject-r2 branch September 5, 2026 16:15
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