Skip to content

fix(release-readiness): close three coherence defects found on dev - #1810

Closed
lidge-jun wants to merge 2 commits into
devfrom
codex/release-readiness
Closed

fix(release-readiness): close three coherence defects found on dev#1810
lidge-jun wants to merge 2 commits into
devfrom
codex/release-readiness

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Summary

Coherence review of everything that landed on dev this session found three defects that the per-PR audits could not see, because each only appears once the changes are combined or exercised against the real repository. All three are fixed here with activating regressions.

1. Log Guard mutations were weaker than the compatibility report they honor (High)

inspect.ts classifies a database as compatible only with exact table SQL, exact column metadata, and every canonical index. Both mutation paths rechecked column names only inside their write transaction.

The Log Guard lock serializes opencodex against itself; it does not stop Codex or another SQLite writer. So a migration landing between the outer inspection and the locked write is a real window. Reproduced on dev HEAD by dropping idx_logs_ts in that gap:

  • Protect installed a row-dropping trigger and returned ok: true
  • Reclaim vacuumed pages

both on a database the inspector reports as unsupported / monitor-only.

hasCurrentLogsSchema is now exported from inspect.ts and both paths delegate to it, so the pre-write gate and the compatibility report cannot disagree.

2. Preview releases were unreleasable (High)

selectReleaseBaseline picked the newest tag of either channel without regard to reachability. A preview therefore selected the newest stable tag — which lives on main and is not reachable from preview — and the ancestry guard added in #1740 then threw. Verified against this repository's real tags and refs:

preview:          without-filter=v2.21.0 (ancestor=false) -> with-filter=v2.19.0
preview-from-dev: without-filter=v2.21.0 (ancestor=false) -> with-filter=v2.17.0

The moment the stable lineage moved ahead, no preview could be released at all. Selection now filters to tags actually reachable from the target; the ancestry guard stays as a fail-closed assertion for any future caller that skips the filter.

3. Hidden text could forge a duplicate closure (Medium)

issue-triage-autoclose.cjs stripped only closed <!-- ... --> comments, while the quality parser treats an unclosed comment as hiding everything through EOF. Two issues whose visible bodies differ could therefore match on text GitHub never renders, and issue-triage.yml closes on that match. Now uses the same through-EOF semantics.

Verification

Ground truth first, at dev HEAD 8a0de6c44, full suite on an isolated Linux checkout:

12491 tests across 813 files — 13 skip, 7 fail

All 7 failures are Cannot find package 'react' / react/jsx-dev-runtime from gui/ sources, because the scratch clone installs no GUI dependencies. None touch routing, catalog, or log-guard.

After these fixes, on the same host at this branch head:

bun x tsc --noEmit                                        exit 0
bun run test (release-changelog, ci-workflows, log-guard) 169 pass / 0 fail
node --test (issue-triage-autoclose, issue-quality,
             pr-quality)                                  209 pass / 0 fail

Each new regression was reproved to fail without its source change.

Three independent gpt-5.6-sol explorers audited dev in parallel across catalog/discovery, the log-guard stack, and CI-script governance. Their "checked and sound" findings are worth recording too: inspection cache invalidation works correctly (every mutation changes the DB/WAL/SHM stamp), the lock correctly serializes Protect against Reclaim, and the changelog builder renders the real 12-commit and 23-commit ranges with zero coverage errors.

Two lower-severity findings from those lanes are not fixed here and are recorded rather than silently dropped: a late slug collision can misattribute capabilities to an unroutable custom model, and prefix stripping runs before sibling enrichment so a Cloudflare-style @cf/... id loses its sibling capabilities. Both are pre-existing interactions rather than regressions from this session, and each deserves its own audited change.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • Bug Fixes

    • Improved duplicate-signature detection by ignoring hidden text after unterminated HTML comments.
    • Release changelog generation now avoids selecting unrelated release tags.
    • Strengthened log protection and maintenance checks to detect missing canonical indexes before changes are applied.
  • Tests

    • Added coverage for release ancestry filtering and schema-protection regressions.

…ctor

Coherence review of the merged stack found the mutation paths weaker than the
compatibility report they claim to honor. inspect.ts requires exact table SQL,
exact column metadata and every canonical index; protection.ts and
maintenance.ts each rechecked column NAMES only.

The lock serializes OpenCodex against itself but not against Codex or another
SQLite writer, so a migration landing between the outer inspection and the
locked write was a real window. Reproduced on dev HEAD by dropping idx_logs_ts
in that gap: Protect installed a row-dropping trigger and returned ok:true, and
Reclaim vacuumed pages, both on a database the inspector reported as
unsupported/monitor-only.

hasCurrentLogsSchema is now exported from inspect.ts and both mutation paths
delegate to it, so the pre-write gate and the compatibility report can no longer
disagree. Regression drives the exact window.
…uplicates

Two findings from the dev coherence review.

1. Preview releases were unreleasable. selectReleaseBaseline picked the newest
   tag of either channel without regard to reachability, so a preview selected
   the newest STABLE tag - which lives on main and is not reachable from
   preview - and the ancestry guard then threw. Verified against the real
   repository:

     preview:          without-filter=v2.21.0 (ancestor=false) -> with-filter=v2.19.0
     preview-from-dev: without-filter=v2.21.0 (ancestor=false) -> with-filter=v2.17.0

   Selection now filters to tags actually reachable from the target. The
   ancestry guard remains as a fail-closed assertion for any future caller that
   skips the filter.

2. The duplicate-closure gate stripped only closed HTML comments, while the
   quality parser treats an unclosed comment as hiding everything through EOF.
   Two issues whose VISIBLE bodies differ could therefore match on text GitHub
   never renders, and the workflow closes on that match. Now uses the same
   through-EOF semantics; verified that hidden text no longer matches while
   ordinary visible signatures still do.
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 16, 2026
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8138694f-4f84-4aa0-9569-163f4be63a59

📥 Commits

Reviewing files that changed from the base of the PR and between e6354c2 and ff1b2e5.

📒 Files selected for processing (7)
  • .github/scripts/issue-triage-autoclose.cjs
  • scripts/build-release-changelog.ts
  • src/codex/log-guard/inspect.ts
  • src/codex/log-guard/maintenance.ts
  • src/codex/log-guard/protection.ts
  • tests/build-release-changelog.test.ts
  • tests/codex-log-guard-protection.test.ts

📝 Walkthrough

Walkthrough

The changes update unterminated HTML-comment normalization, ancestry-aware release baseline selection, and shared Codex Log Guard schema validation. Tests cover unreachable release tags and protection behavior when the canonical log index is missing.

Changes

Signature normalization

Layer / File(s) Summary
Remove unterminated HTML comments
.github/scripts/issue-triage-autoclose.cjs:69-73
normalizeSignatureLine removes HTML comments through end-of-input when no closing delimiter exists.

Release baseline selection

Layer / File(s) Summary
Filter baselines by ancestry
scripts/build-release-changelog.ts:121-139
selectReleaseBaseline accepts an optional ancestry predicate and skips release tags that are not ancestors of the target version.
Cache ancestry checks and validate selection
scripts/build-release-changelog.ts:544-560, tests/build-release-changelog.test.ts:41-63
The release builder caches ancestry results for all tags. Tests cover filtered prerelease and stable selection, plus the existing unfiltered behavior.

Codex Log Guard schema validation

Layer / File(s) Summary
Add the shared schema predicate
src/codex/log-guard/inspect.ts:264-280
hasCurrentLogsSchema reads logs column metadata and checks the canonical table and index definitions.
Apply the predicate to maintenance and protection
src/codex/log-guard/maintenance.ts:6,148-151, src/codex/log-guard/protection.ts:6,196-200, tests/codex-log-guard-protection.test.ts:330-347
Maintenance and locked protection checks use the shared predicate. The regression test verifies that a missing idx_logs_ts index returns unsupported_schema without installing a trigger.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: wibias, ingwannu

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/release-readiness

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Landed directly on dev as 3c633d7 and 65eda6c per maintainer instruction; closing this PR rather than merging it twice.

@lidge-jun lidge-jun closed this Aug 16, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff1b2e5418

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// quality parser treats it. Matching only `<!-- ... -->` let hidden text
// become the "exact shared signature" that closes an issue: two reports
// whose visible bodies differ could match on text GitHub never renders.
.replace(/<!--[\s\S]*?(?:-->|$)/g, " ")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Strip comments before splitting the issue body

When an unclosed comment spans multiple lines, this replacement sees only one line at a time because extractStrongFailureSignatures splits the text afterward and calls normalizeSignatureLine separately for each line. For example, a body containing <!-- followed on the next line by a strong failure signature still returns that hidden signature, allowing two visibly different issues to be auto-closed as duplicates. Strip comments from the complete body through EOF before splitting it into candidate lines.

Useful? React with 👍 / 👎.

const probe = await runCommand(["git", "merge-base", "--is-ancestor", tag, target]);
ancestryCache.set(tag, probe.exitCode === 0);
}
const baseline = selectReleaseBaseline(version, tags, tag => ancestryCache.get(tag) === true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pass the reachable baseline into the renderer

When a preview branch cannot reach the newest stable tag, this correctly selects an older reachable baseline for GitHub-generated notes and releaseCommits, but buildReleaseNotes later calls selectReleaseBaseline(input.version, input.tags) without the reachability predicate. The published Full Changelog URL and reported baseline therefore still name the newer unreachable stable tag even though the entries were built from another range. Pass this selected baseline into buildReleaseNotes, or pass only the reachable tag set, so every stage uses the same range.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant