diff --git a/.console/backlog.md b/.console/backlog.md index b8bd5e5d1..7e637a099 100644 --- a/.console/backlog.md +++ b/.console/backlog.md @@ -71,6 +71,29 @@ far all came from real CI runs, not from the hunt. There is no `provider` field on `GitSettings` (`config/settings.py:32`), so the key is silently ignored — the example promises configuration that does nothing. Either add the field or delete the line. +### Retire the Plane wire names — they outlived the adapter + +The 2026-08-18 cutover deleted the Plane adapter but left its vocabulary in +live code. The cosmetic half is done (see Done, 2026-08-19); what remains are +names that are **not** cosmetic because something outside the process reads +them, so each needs a compatibility shim rather than a rename: + + - `plane_task_id` — `pr_review_watcher/main.py` reads it out of the + **on-disk review state** (`state.get("plane_task_id")`, ~8 sites). A bare + rename orphans every in-flight review at the moment it deploys. + - `plane_issue_id` — written into **proposer artifacts** and read back by + `decision/service.py` (`item.get("plane_issue_id")`). Same problem across + artifacts already on disk. + - `"plane"` as an **alert channel name** — `observer/alert_channels.py` + (`self.name = "plane"`), and `alert_config.py` both validates against + `{"operator_log", "plane", "slack", "pagerduty"}` and ships four default + routes using it. Operator alert config names this channel, so renaming it + is a config break. + - `entry["plane"]` — an emitted key in the `custodian_sweep` report. + +Shape of the fix: write the new key, read both, drop the old read a release +later. Worth doing — `plane_task_id` in a Forgejo-only system actively +misleads — but it is a migration, not a cleanup. ### Split extraction_health_history.py, or the C29 exclusion becomes permanent - The module was at exactly 500 lines; #478's `edge_cases` field pushed it to 506 and @@ -233,6 +256,54 @@ registered against the instance. Everything upstream of it is done — +### 2026-08-19: Plane leftovers swept, and two Up Next items closed as already-done (✅ COMPLETE) + +Follow-up to the cutover. Three separate things, all found by asking why the +codebase still says "Plane" 2,000 times. + +**Cosmetic leftovers removed** (no behavior change — every rename below had +exactly one importer or none): + + - `propagation/plane_adapter.py` → `board_adapter.py`, `PlaneTaskCreator` → + `BoardTaskCreator`; sole importer is `entrypoints/propagate/main.py`. + Docstrings in `propagator.py` referenced `PlaneClient.create_issue` — a + class deleted in #521. + - `tests/test_plane_parsing.py` → `test_task_parsing.py`. It never tested + Plane; it tests `TaskParser`. + - `docs/design/plane_kodo_wrapper.md` → `docs/history/plane-kodo-wrapper.md`, + `status: implemented` → `superseded`. It sat in `design/` (live docs) + describing both a retired board and a retired engine. + +**A real bug behind one of them.** `config/plane_task_template.example.md` was +a stale copy of what `render_task_template()` generates (it still told the +operator to describe the change "you want Kodo to make"). Nothing read it — +`oc setup` writes `config/task_template.local.md`. But `.gitignore`, +`docs/operator/setup.md`, and **both secrets scripts** still pointed at the +dead `plane_task_template.local.md` path, so `backup-secrets.sh` was backing +up a file that never exists and the template the operator actually has was in +no backup at all. All four repointed at the real path; the stale example +deleted. Also dropped three `.gitignore` entries for `deployment/plane/**` and +`tools/report/kodo_plane/` — none of those directories exist. + +**Both Up Next items were already done, just unrecorded:** + + - *CI runs ~1,830 fewer tests* — closed by #525, and the coverage survived + the #527 port: `.forgejo/workflows/ci.yml` job `test-rest` + ("Test (suites outside tests/unit)") runs `pytest -q tests/ --ignore=tests/unit`. + - *`audit` on Forgejo Actions* — the runner is installed and registered + (`forgejo-runner` container up, Actions jobs executing), the workflow is + ported as `.forgejo/workflows/custodian-audit.yml`, and live branch + protection on `main` requires + `['custodian-audit / audit (pull_request)', 'reviewer-verdict']` with + `apply_to_admins: true`. Note the spec's "identical context name" + requirement was met the other way round — the required-context list was + updated to the Forgejo name rather than the name being preserved. The + cutover is live: `pr_backend: forgejo` in `operations_center.local.yaml`. + +What was deliberately **not** touched: the Plane names that are wire formats +(`plane_task_id`, `plane_issue_id`, the `"plane"` alert channel). Those read +persisted state and operator config, so they need a shim, not a rename — see +Up Next. ### 2026-08-18/19: Plane → Forgejo board migration (✅ COMPLETE) diff --git a/.console/log.md b/.console/log.md index 9cc0e1835..961cf0893 100644 --- a/.console/log.md +++ b/.console/log.md @@ -341,6 +341,41 @@ while keeping 2026-06-18 ones. Result: 271 entries / 417,089 bytes → 169 entries / 237,189 bytes, 46% of budget. +## 2026-08-19 — Plane leftovers: a dead name is only cosmetic until something points at it + +Swept the Plane vocabulary the cutover left behind. Most of it was what it looked +like — a module, a class, a test file and a design doc named for a system that no +longer exists, each with one importer or none. Details in `.console/backlog.md`. + +One wasn't cosmetic. `config/plane_task_template.example.md` was dead in the sense +that no code read it — `oc setup` writes `config/task_template.local.md` from +`render_task_template()`. But `.gitignore`, `docs/operator/setup.md`, +`backup-secrets.sh` and `setup-secrets.sh` all still named the old +`plane_task_template.local.md` path. So `backup-secrets.sh` has been faithfully +backing up a file that cannot exist, and the template the operator actually has +was in no backup at all. The lesson generalises past this file: **grep for the +name before calling it unused — "nothing reads it" and "nothing references it" +are different claims,** and the gap between them is where silent data loss lives. + +Deliberately did not rename the Plane names that are wire formats: +`plane_task_id` (read from on-disk review state), `plane_issue_id` (read from +proposer artifacts), and `"plane"` as an alert-channel name (validated against +operator config). Those are a write-both/read-both migration, not a cleanup; +filed in Up Next. Renaming them in place would have orphaned every in-flight +review at deploy time. + +Process note, and it is a trap worth remembering: **the fleet executes out of the +live working tree.** Every supervisor runs +`/home/diane/GitHub/OperationsCenter/.venv/bin/python -m operations_center...` +with `cd` into that checkout and re-execs its child every 30s, so editing `src/` +there — or switching its branch — changes what the running fleet does on the next +restart. Did the work in `git worktree` at `~/GitHub/oc-plane-cleanup` instead. +That collides with the already-recorded worktree trap (a worktree has no venv and +no editable install, so bare `python` there measures the *main* checkout); +defeated it by running the main venv's interpreter with +`PYTHONPATH=/src`, and **proved** it rather than assuming — +`operations_center.__file__` resolved to the worktree before running anything. +67 targeted tests pass; ruff clean. ## 2026-08-19 — correcting myself: the status context is the JOB name diff --git a/.gitignore b/.gitignore index 5ad6217ff..21ed6fdc8 100644 --- a/.gitignore +++ b/.gitignore @@ -23,11 +23,8 @@ __pycache__/ .ruff_cache/ .env.operations-center.local config/operations_center.local.yaml -config/plane_task_template.local.md +config/task_template.local.md logs/ -deployment/plane/runtime/ -deployment/plane/logs/ -tools/report/kodo_plane/ tools/report/operations_center/ state/ .review/ diff --git a/README.md b/README.md index fb54450e1..a2f86d2d1 100644 --- a/README.md +++ b/README.md @@ -1556,7 +1556,7 @@ The repo-aware autonomy loop is behaving well when: - [Repo-Aware Autonomy Layer](docs/design/autonomy/repo_aware_autonomy.md) - [Self-Tuning Regulator](docs/design/autonomy/autonomy_self_tuning_regulator.md) - [Execution Budget And Safety Controls](docs/design/execution_budget_and_safety_controls.md) -- [Execution Plane Design](docs/design/plane_kodo_wrapper.md) — Historical: original Plane/kodo integration design +- [Plane + Kodo Wrapper Design](docs/history/plane-kodo-wrapper.md) — Superseded: the original Plane/kodo integration design - [Roadmap](docs/design/roadmap.md) ### Operator Guides diff --git a/config/plane_task_template.example.md b/config/plane_task_template.example.md deleted file mode 100644 index 53fb73c17..000000000 --- a/config/plane_task_template.example.md +++ /dev/null @@ -1,18 +0,0 @@ -# Plane task template — copy to config/plane_task_template.local.md and edit. -# Generated by `oc setup` and used by `oc task create` as the default body. -# This file is safe to commit. Your local copy is gitignored. - -## Execution -repo: OperationsCenter -base_branch: main -mode: goal -allowed_paths: - - src/ - -## Goal -Describe the code change you want Kodo to make. - -## Constraints -- Keep the selected base branch within: main -- Limit edits to the listed allowed paths. -- Leave deployment and infrastructure files alone unless explicitly requested. diff --git a/docs/history/README.md b/docs/history/README.md index 828a2b2c7..1437666ef 100644 --- a/docs/history/README.md +++ b/docs/history/README.md @@ -23,3 +23,6 @@ and [PlatformDeployment/docs/architecture/](https://github.com/ProtocolWarden/Pl logs from the autonomous-spec-driven-chain era. - **runtime-truth-remediation-summary.md** — Summary of the runtime-truth remediation pass. +- **[plane-kodo-wrapper.md](plane-kodo-wrapper.md)** — The original Plane board + + kodo engine integration design. Superseded on both halves: the board is + Forgejo, and the engine is selected per task. diff --git a/docs/design/plane_kodo_wrapper.md b/docs/history/plane-kodo-wrapper.md similarity index 92% rename from docs/design/plane_kodo_wrapper.md rename to docs/history/plane-kodo-wrapper.md index b392714eb..2f9d7a063 100644 --- a/docs/design/plane_kodo_wrapper.md +++ b/docs/history/plane-kodo-wrapper.md @@ -1,8 +1,15 @@ --- -status: implemented +status: superseded --- # Plane + Kodo Wrapper Design +> **Superseded.** Both halves of this design are retired: the board moved from +> Plane to a self-hosted Forgejo instance in the 2026-08-18 cutover (the Plane +> adapter was deleted in OperationsCenter#521), and kodo is no longer the coding +> engine — executors are selected per task via `worker_backend`. Kept as a record +> of the original integration design. Not maintained; see `docs/architecture/` +> for current behavior. + ## Purpose Build a self-hosted AI execution wrapper that uses Plane as the Jira-like board and [Kodo](https://github.com/ikamensh/kodo) as the coding engine, with explicit repo and base-branch selection per task. diff --git a/docs/operator/setup.md b/docs/operator/setup.md index 8da322fce..d0be04447 100644 --- a/docs/operator/setup.md +++ b/docs/operator/setup.md @@ -16,6 +16,7 @@ Setup writes (all gitignored): - `config/operations_center.local.yaml` - `.env.operations-center.local` +- `config/task_template.local.md` - `config/managed_repos/local/*.yaml` — per-repo managed repo entries ## Backup and Restore (SS) diff --git a/scripts/backup-secrets.sh b/scripts/backup-secrets.sh index 0da6e8a8a..7b6bb8948 100755 --- a/scripts/backup-secrets.sh +++ b/scripts/backup-secrets.sh @@ -17,7 +17,7 @@ mkdir -p "$DEST" # Flat files: repo path → flat backup filename declare -A flat_files=( ["config/operations_center.local.yaml"]="oc__config__operations_center.local.yaml" - ["config/plane_task_template.local.md"]="oc__config__plane_task_template.local.md" + ["config/task_template.local.md"]="oc__config__task_template.local.md" [".env.operations-center.local"]="oc__.env.operations-center.local" ) diff --git a/scripts/setup-secrets.sh b/scripts/setup-secrets.sh index a12fc064d..1ec7fdb6d 100755 --- a/scripts/setup-secrets.sh +++ b/scripts/setup-secrets.sh @@ -22,7 +22,7 @@ fi # Flat files: flat backup filename → repo target path declare -A flat_files=( ["oc__config__operations_center.local.yaml"]="config/operations_center.local.yaml" - ["oc__config__plane_task_template.local.md"]="config/plane_task_template.local.md" + ["oc__config__task_template.local.md"]="config/task_template.local.md" ["oc__.env.operations-center.local"]=".env.operations-center.local" ) diff --git a/src/operations_center/entrypoints/propagate/main.py b/src/operations_center/entrypoints/propagate/main.py index 69c9010cd..5b753579c 100644 --- a/src/operations_center/entrypoints/propagate/main.py +++ b/src/operations_center/entrypoints/propagate/main.py @@ -11,7 +11,7 @@ Composes the EffectiveRepoGraph from the configured platform_manifest block, walks the contract-impact set for the target, and (per the -contract_change_propagation policy) creates Plane tasks for downstream +contract_change_propagation policy) creates board tasks for downstream consumers. Always writes a structured PropagationRecord to ``/.json``. @@ -44,7 +44,7 @@ PropagationRegistry, PropagationSettings, ) -from operations_center.propagation.plane_adapter import PlaneTaskCreator +from operations_center.propagation.board_adapter import BoardTaskCreator from operations_center.propagation.policy import _Action, _PairOverride from operations_center.repo_graph_factory import ( build_effective_repo_graph_from_settings, @@ -82,7 +82,7 @@ def _build_parser() -> argparse.ArgumentParser: p.add_argument( "--dry-run", action="store_true", - help="Skip Plane API calls; record what would be created. Forces a " + help="Skip board API calls; record what would be created. Forces a " "fake task creator that returns synthetic IDs.", ) p.add_argument( @@ -135,8 +135,8 @@ def main(argv: list[str] | None = None) -> int: if args.dry_run: task_creator = _DryRunTaskCreator() else: - plane_client = make_board_client(settings) - task_creator = PlaneTaskCreator(client=plane_client) + board_client = make_board_client(settings) + task_creator = BoardTaskCreator(client=board_client) propagator = ContractChangePropagator( policy=policy, @@ -189,7 +189,7 @@ def _build_policy(pcfg: ContractChangePropagationSettings) -> PropagationPolicy: class _DryRunTaskCreator: - """Stand-in task creator that doesn't hit Plane. Used for --dry-run.""" + """Stand-in task creator that doesn't hit the board. Used for --dry-run.""" def __init__(self) -> None: self._next = 0 diff --git a/src/operations_center/propagation/plane_adapter.py b/src/operations_center/propagation/board_adapter.py similarity index 87% rename from src/operations_center/propagation/plane_adapter.py rename to src/operations_center/propagation/board_adapter.py index 2c35b5c8d..45456bf49 100644 --- a/src/operations_center/propagation/plane_adapter.py +++ b/src/operations_center/propagation/board_adapter.py @@ -3,7 +3,7 @@ """Board client → propagation `_TaskCreator` adapter. Wraps a `operations_center.adapters.board.BoardClient` so -the propagator stays decoupled from Plane's API shape. The adapter +the propagator stays decoupled from the board's API shape. The adapter honors `promote_to_ready` by calling `transition_issue` to "Ready for AI" after creation; otherwise the task stays in the default "Backlog" state. @@ -17,7 +17,7 @@ @dataclass -class PlaneTaskCreator: +class BoardTaskCreator: """Adapter implementing the propagator's `_TaskCreator` protocol.""" client: BoardClient @@ -32,7 +32,7 @@ def create_issue( labels: tuple[str, ...], promote_to_ready: bool, ) -> str: - """Create the Plane issue. Promote to Ready if the policy says so.""" + """Create the board issue. Promote to Ready if the policy says so.""" result = self.client.create_issue( name=title, description=body, @@ -48,4 +48,4 @@ def create_issue( return issue_id -__all__ = ["PlaneTaskCreator"] +__all__ = ["BoardTaskCreator"] diff --git a/src/operations_center/propagation/links.py b/src/operations_center/propagation/links.py index c8e9200f1..b0fae25cb 100644 --- a/src/operations_center/propagation/links.py +++ b/src/operations_center/propagation/links.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (C) 2026 ProtocolWarden -"""Parent-child link metadata embedded in every propagated Plane task. +"""Parent-child link metadata embedded in every propagated board task. When the propagator creates a downstream task, it appends a structured HTML-comment block to the task body. Operators (and a future diff --git a/src/operations_center/propagation/propagator.py b/src/operations_center/propagation/propagator.py index 1f8b96f77..f02fb647a 100644 --- a/src/operations_center/propagation/propagator.py +++ b/src/operations_center/propagation/propagator.py @@ -9,12 +9,12 @@ - PropagationPolicy (decides per-pair: skip / backlog / ready) - PropagationRegistry (task title/body/labels per pair) - PropagationDedupStore (idempotency) - - TaskCreator (Plane client adapter — Protocol, so tests inject a fake) + - TaskCreator (board client adapter — Protocol, so tests inject a fake) Output: PropagationRecord — structured artifact written to state/propagation/. Operators read these to answer "why did/didn't propagation fire?" - even when no Plane tasks were created. + even when no board tasks were created. """ from __future__ import annotations @@ -39,9 +39,9 @@ @runtime_checkable class _TaskCreator(Protocol): - """Minimal Plane-client interface the propagator needs. + """Minimal board-client interface the propagator needs. - Real adapter wraps `PlaneClient.create_issue` plus an optional + Real adapter wraps `BoardClient.create_issue` plus an optional transition to "Ready for AI" state when the policy says so. """ @@ -53,7 +53,7 @@ def create_issue( labels: tuple[str, ...], promote_to_ready: bool, ) -> str: - """Create the issue. Returns Plane's issue ID.""" + """Create the issue. Returns the board's issue ID.""" ... @@ -65,7 +65,7 @@ class PropagationOutcome: consumer_canonical: str decision_action: str # "skip" | "backlog" | "ready_for_ai" decision_reason: str - issue_id: str | None = None # Plane issue when fired; None when skipped + issue_id: str | None = None # board issue when fired; None when skipped error: str | None = None # populated only if create_issue raised diff --git a/src/operations_center/propagation/registry.py b/src/operations_center/propagation/registry.py index 05e359a20..cb55d466a 100644 --- a/src/operations_center/propagation/registry.py +++ b/src/operations_center/propagation/registry.py @@ -2,7 +2,7 @@ # Copyright (C) 2026 ProtocolWarden """Task templates per (target_repo_id, consumer_repo_id). -Each consumer that fires gets a Plane task with a templated title + +Each consumer that fires gets a board task with a templated title + body + label set. The registry holds the templates; lookup is by (target, consumer) with a sensible default fallback. @@ -69,7 +69,7 @@ def render( @dataclass(frozen=True) class RenderedTask: - """Concrete title/body/labels — what a Plane client receives. + """Concrete title/body/labels — what a board client receives. The propagator builds the *full* body by combining ``body_prelude`` with the parent-link block (see ``links.py``). The registry only diff --git a/tests/test_plane_parsing.py b/tests/test_task_parsing.py similarity index 100% rename from tests/test_plane_parsing.py rename to tests/test_task_parsing.py