Repository navigation
Add the knowledge merge flow, snapshot activation, release sweep and scheduler - #208
Conversation
…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>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| 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 "" |
There was a problem hiding this comment.
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 👍 / 👎.
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| except ActivationError as exc: | ||
| rt.ledger.enqueue_human("*", f"snapshot activation refused: {exc}") | ||
| self._record("*", "activation_refused", error=str(exc)) |
There was a problem hiding this comment.
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 👍 / 👎.
PR 5b of the knowledge-service redesign (after #203–#207): the lifecycle after the gate.
enqueueis issued only after the PR-stage kb-gate succeeds, with at most one queued PR per repo. Supports pause/resume and a global pause.kb serve): holds the lease; each tick signs control, applies acks, runs per-repo advance, overturn breaker, recovery publish, intake and sweep, then activation.test/test_kb_flow.py.Commit hook: approved at iteration 3. Full suite and spec freshness pass locally.
🤖 Generated with Claude Code