Skip to content

Audit main daily for knowledge changes that landed without a record - #216

Merged
tzhouam merged 1 commit into
mainfrom
kb/daily-merge-audit
Sep 28, 2026
Merged

tzhouam merged 1 commit into
mainfrom
kb/daily-merge-audit

Conversation

@tzhouam

@tzhouam tzhouam commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Knowledge-service redesign follow-up: the daily post-merge audit from the design. Every knowledge change that landed on main must have a ledger record; if one doesn't, auto-merge is paused and people are alerted.

  • What it checks: audit.audit_main walks main's first-parent history from a ledger cursor (the first run records a baseline). Every commit that touches knowledge/ must be a recorded merge.
  • Proof is structural, never a commit message. Either:
    • the commit is a change set's merge SHA; or
    • it is a two-parent merge of a merged change set's exact head, changing knowledge only as that head did (same paths, content and file modes).
  • On a finding:
    • repositories in auto_merge are paused (everything, for knowledge outside a repository scope), their open knowledge PRs dequeued, and people alerted;
    • other repositories only get an unrecorded_merge trace.
  • Timing: commits younger than 2 hours wait for the next run, so the audit never races the scheduler recording a merge. The scheduler runs it daily.
  • Refactor: the pause logic is now Scheduler._pause, shared with the overturn breaker.
  • Tests: a direct push, a message citing a recorded PR, a merge of a recorded head smuggling edits or an executable bit, recorded merges, code changes, grace period, shadow repos, and out-of-scope knowledge.

Commit hook approved; full suite and SPEC freshness pass locally.

🤖 Generated with Claude Code

The gate constrains every merge path the service knows about. The design
also requires a daily audit for the paths it does not (an admin bypass, a
direct push, a merge the ledger missed): any unrecorded knowledge merge
pauses auto-merge and alerts.

- audit.audit_main walks main's first-parent history from the last audited
  commit (a ledger cursor; the first run only records the baseline). Every
  commit whose change against its first parent touches knowledge/ must be a
  recorded merge. The proof is structural, never a commit message: the
  commit's SHA is a change set's merge SHA, or it is a two-parent merge whose
  second parent is the exact head a merged change set recorded (what the
  merge queue lands) and that changed knowledge only as that head did (every
  knowledge path it changed was changed by the PR and holds exactly the
  head's content and file mode). A direct push titled "(#42)", or a merge of a recorded
  head that smuggles extra edits, is still caught. Anything else pauses the repositories it touched
  (everything, for knowledge outside a repository scope), dequeues their open
  knowledge PRs and goes to people. Repositories not in auto_merge only get an
  unrecorded_merge trace record.
- Commits younger than a 2-hour grace period wait for the next run, so the
  audit never races the scheduler recording a merge.
- The scheduler runs it every 24 hours. The pause logic is now
  Scheduler._pause, shared with the overturn breaker.
- KnowledgeRepo gains first_parent_commits and changed_names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T16:07:45.027643Z e94b2a5 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@tzhouam
tzhouam merged commit aed7c89 into main Sep 28, 2026
5 checks passed

@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: e94b2a5d33

ℹ️ 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".

Comment on lines +72 to +73
if rt.clock() - committed_at < GRACE:
break # too recent to judge: the next run starts here

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Base the grace period on an observed arrival time

A direct-pushed commit can carry an arbitrarily future committer timestamp, so rt.clock() - committed_at < GRACE remains true and this break leaves the cursor before that commit. Because every later audit encounters the same commit first, it also prevents all subsequent first-parent commits from being audited until that timestamp ages past the grace period, defeating the control this code is intended to provide. Persist a first-observed time or use a trusted remote timestamp rather than %ct.

Useful? React with 👍 / 👎.

Comment on lines +80 to +81
if sha in merge_shas or (len(parents) == 2 and parents[1] in merged_heads
and _merge_carries_only_the_head(rt, parents, sha, paths)):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bind each recorded head to its actual merge occurrence

For a squash-merged change set, the recorded head_sha is not an ancestor of main and remains in merged_heads indefinitely. A later bypass commit can therefore merge that stale head as its second parent and restore any subset of its knowledge files to the stale head's blobs; the subset and blob checks pass, so this new unrecorded revert is treated as authorized even though the ledger record belongs to the earlier squash merge. The fallback proof must be tied to the specific merge occurrence rather than accepting any future two-parent commit carrying a previously recorded head.

Useful? React with 👍 / 👎.

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