Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .console/backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -233,6 +256,54 @@ registered against the instance. Everything upstream of it is done —

</details>

### 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)

Expand Down
35 changes: 35 additions & 0 deletions .console/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<worktree>/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

Expand Down
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 0 additions & 18 deletions config/plane_task_template.example.md

This file was deleted.

3 changes: 3 additions & 0 deletions docs/history/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
1 change: 1 addition & 0 deletions docs/operator/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion scripts/backup-secrets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down
2 changes: 1 addition & 1 deletion scripts/setup-secrets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down
12 changes: 6 additions & 6 deletions src/operations_center/entrypoints/propagate/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
``<settings.contract_change_propagation.record_dir>/<run_id>.json``.

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -17,7 +17,7 @@


@dataclass
class PlaneTaskCreator:
class BoardTaskCreator:
"""Adapter implementing the propagator's `_TaskCreator` protocol."""

client: BoardClient
Expand All @@ -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,
Expand All @@ -48,4 +48,4 @@ def create_issue(
return issue_id


__all__ = ["PlaneTaskCreator"]
__all__ = ["BoardTaskCreator"]
2 changes: 1 addition & 1 deletion src/operations_center/propagation/links.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/operations_center/propagation/propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
"""

Expand All @@ -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."""
...


Expand All @@ -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


Expand Down
4 changes: 2 additions & 2 deletions src/operations_center/propagation/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
File renamed without changes.