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
9 changes: 9 additions & 0 deletions .console/log.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 2026-06-13 — fix(spec-hygiene): active.json projects only active campaigns (campaign GC)

_rebuild_active_projection wrote every campaign — incl. complete/cancelled — to state/campaigns/
active.json, which OperatorConsole's campaign pane reads. Terminal campaigns are history (their
record lives in Plane) but accumulated forever in the projection (observed: 11 records, 10 terminal,
0 truly active — cluttering the pane). Fix: skip non-active campaigns when rebuilding the projection.
+1 test. (A one-time prune of active.json doesn't stick — it's rebuilt each spec-hygiene cycle — so
the projection filter IS the durable prune.)

## 2026-06-13 — fix(reviewer): gate merge on the full required-check set (guard D)

#272 + Guard C close "merge on red/incomplete/no-checks CI", but a hole remained: a required check
Expand Down
8 changes: 8 additions & 0 deletions src/operations_center/entrypoints/spec_hygiene/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ def _rebuild_active_projection(
else:
status = "active"

# active.json is the ACTIVE projection (OperatorConsole's campaign pane
# reads it). Terminal campaigns (complete/cancelled) are history — their
# record lives in Plane — so they are not projected here. Without this the
# projection accumulates every finished campaign indefinitely, cluttering
# the status pane (observed: 11 records, 10 terminal, only 0 truly active).
if status != "active":
continue

# Pull slug + spec_file + created_at from the parent issue when we can.
parent = next(
(i for i in issues if str(i.get("name", "")).startswith("[Campaign]")),
Expand Down
28 changes: 28 additions & 0 deletions tests/maintenance/test_spec_hygiene_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,31 @@ def _boom(settings, client): # noqa: ARG001
result = task.run_once(MaintenanceContext(cycle_id="c", now=datetime.now(timezone.utc)))
assert result.status == "failed"
assert result.error == "plane down"


def test_rebuild_active_projection_excludes_terminal(tmp_path):
"""active.json projects only active campaigns; terminal ones (complete/cancelled)
are history (in Plane) and must not accumulate in the pane projection."""
from operations_center.spec_author.state import CampaignStateManager

mgr = CampaignStateManager(state_path=tmp_path / "active.json")

def issue(cid: str, state: str) -> dict:
return {
"name": f"[Campaign] {cid}",
"labels": [{"name": "source: spec-campaign"}, {"name": f"campaign-id: {cid}"}],
"state": {"name": state},
}

spec_hygiene_main._rebuild_active_projection(
mgr,
[
issue("active1", "in progress"), # → active
issue("cancelled1", "cancelled"), # → cancelled (excluded)
issue("done1", "done"), # → complete (excluded)
],
)

saved = mgr.load()
assert [c.slug for c in saved.campaigns] == ["active1"]
assert all(c.status == "active" for c in saved.campaigns)
Loading