Skip to content

Add the knowledge merge flow, snapshot activation, release sweep and scheduler - #208

Merged
tzhouam merged 1 commit into
mainfrom
kb/merge-activate-sweep
Sep 28, 2026
Merged

tzhouam merged 1 commit into
mainfrom
kb/merge-activate-sweep

Conversation

@tzhouam

@tzhouam tzhouam commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

PR 5b of the knowledge-service redesign (after #203–#207): the lifecycle after the gate.

  • merge: signed publisher acks advance each change set (pr_requested → pr_open → verdict_posted → queued → merged/closed); the kb-gate verdict is signed for the PR's exact head, and enqueue is issued only after the PR-stage kb-gate succeeds, with at most one queued PR per repo. Supports pause/resume and a global pause.
  • activate: immutable per-main snapshots, verified (every file re-hashed), atomic switch, rollback pin, and retention.
  • sweep: a release-triggered T1/T2/T3 sweep with per-page progress and retries, a held aggregate circuit breaker, and a purge of rules retired a release ago.
  • scheduler (kb serve): holds the lease; each tick signs control, applies acks, runs per-repo advance, overturn breaker, recovery publish, intake and sweep, then activation.
  • Playbooks kb-merge / kb-sweep / kb-activate; ledger v1→v2 migration; test/test_kb_flow.py.

Commit hook: approved at iteration 3. Full suite and spec freshness pass locally.

🤖 Generated with Claude Code

…scheduler

Completes the knowledge service's lifecycle after the gate.

- merge: the publisher's signed acks advance a change set through
  pr_requested, pr_open, verdict_posted, queued and merged. The service signs
  the kb-gate verdict for the PR's exact head (patch manifest, per-block and
  consistency judgements) and posts it through an outbox post_verdict item.
  It issues `enqueue` only after the PR-stage kb-gate status succeeded, with at
  most one queued knowledge PR per repository. A head change after signing
  goes to people; a PR closed unmerged is recorded; merged retirements become
  purge candidates for the next release. pause_open_prs issues dequeue+draft
  items.
- activate: every main SHA becomes an immutable snapshot (knowledge tree plus
  MANIFEST). It is verified (loads as a KnowledgeView, no tree issues, every
  _routes.yaml loads) and then activated by an atomic symlink swap; rollback
  and retention (last 10 plus the active one) are included. A snapshot that
  does not load is an ActivationError and `active` is left untouched.
- sweep: triggered by a release (the first one seen only records the
  baseline) or the fallback interval. T1 reports structural issues to people,
  and its only automatic fix, missing index links, is now accepted by L1 for
  existing unlisted pages. T2/T3 asks the generator per rule page, with the
  release diff of the paths that page owns, to keep, edit, replace or retire
  each active rule. Rules retired a release ago are purged. Every page is its
  own gated change set, and a sweep-wide circuit breaker sends all of them to
  people.
- scheduler (`kb serve`): holds the lease for its lifetime. Each tick it
  re-signs the control record and hold list, applies acks, advances merges and
  runs intake and sweeps per repository (isolated from each other), and
  activates a new main. Two knowledge PRs closed unmerged by people within 24
  hours pause the repository and dequeue its open PRs.
- robustness: each change set records the outbox item it waits on; nothing
  is re-issued while that item is unexpired, and an ack applies only to that
  item from its matching state. A rollback pins `active` until main moves on.
  Snapshot activation re-hashes every manifest entry. Sweep progress is
  tracked per page: a failed evaluation is retried (people get it after 3
  attempts) and the baseline advances only once every page is settled. Sweep
  change sets are held until the sweep settles, so the sweep-wide breaker sees
  every attempt. Activation, rollback and the scheduler's pin check share an
  interprocess lock. `kb resume` returns paused PRs to re-signing, and each
  tick re-publishes anything still gated. The ledger migrates v1 to v2 in
  place.
- steps and playbooks kb-merge, kb-sweep and kb-activate (candidate); CLI
  `kb serve [--once]`, `kb activate`, `kb rollback --to SHA`; `kb pause` also
  issues pause items for open PRs.

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-28T12:25:30.660560Z 7d263ee 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 9c73cc9 into main Sep 28, 2026
2 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: 7d263ee8c9

ℹ️ 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 +339 to +345
progress.setdefault("held", []).append(changeset_id)
if key == "#t1":
progress["t1_done"] = True
elif key == "#purge":
progress["purge_done"] = True
else:
progress["done"].append(key)

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 Checkpoint each staged sweep change set

If the process exits or a later gate_and_stage call raises after this append, the already-created sweep_held ledger row is durable but sweep_progress is not written until the end of run_sweep. The next run therefore stages the same page again while the original change set remains permanently held and untracked. Persist the updated progress after each successful staging operation, ideally in the same fenced transaction as the change set.

Useful? React with 👍 / 👎.

Comment on lines +97 to +102
if release.trigger == "github_release":
latest = rt.github.latest_release(lifecycle.full_name)
tag = latest["tag"] if latest else ""
elif release.trigger == "tag_pattern":
tags = rt.github.tags(lifecycle.full_name, release.tag_pattern)
tag = tags[0] if tags else ""

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 Implement or reject branch-cut release triggers

For a lifecycle configured with the accepted branch_cut trigger, neither branch here assigns tag, so no baseline is ever initialized and neither release nor fallback sweeps can ever run. This configuration is silently accepted by parse_lifecycle but leaves the repository permanently unswept; handle the trigger here or reject it during configuration parsing.

Useful? React with 👍 / 👎.

Comment on lines +111 to +112
if lifecycle.full_name and self._due(f"release:{lifecycle.repo}", self.release_every):
upstream = UpstreamRepo(rt.state_dir / "upstream" / f"{lifecycle.repo}.git", lifecycle.full_name)

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 Schedule maintenance sweeps for the general knowledge slice

When KB_GENERAL_ENABLED=1, the registered general lifecycle has an empty full_name, so this condition prevents its sweep path from ever running. Consequently its configured periodic maintenance cannot perform even the upstream-independent T1 checks or purge work; the scheduler needs a separate fallback path for enabled lifecycles without an upstream repository.

Useful? React with 👍 / 👎.

Comment on lines +86 to +88
except ActivationError as exc:
rt.ledger.enqueue_human("*", f"snapshot activation refused: {exc}")
self._record("*", "activation_refused", error=str(exc))

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 Deduplicate activation-refusal queue entries

While main remains at a snapshot that fails verification, every scheduler tick retries activation and inserts another unresolved human-queue row for the same SHA and error. A single malformed knowledge commit can therefore grow the queue indefinitely at one entry per minute; record that this SHA has already been escalated or make the queue insertion idempotent.

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