os-backlog slice 8: Operations board + tracker-routing rubric + ADR-0033 (issue #17)
- ADR-0033 canonizes Planka=state / git-issues=specs, tracker key grammar, card-as-pointer and promotion rules (cites route/to-issues/session-start). - os-context audit rubric: new category 8 tracker-routing (compliance only). - BoardEnsurer label-color fallback also rescues Planka::BadRequestError (Planka rejects unknown colors as HTTP 400, found live) + regression test. - os-status /os-status:fix step d: invoke /os-backlog:route directly instead of pre-asking forgejo-vs-planka (boundary rule uses both; cf. issue #26). - Records updated per index+leaf convention. Closes #17. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CLeMz48rvG3s9XpAsDxeho
This commit is contained in:
parent
46b0584643
commit
49c8f5eace
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
id: "0033"
|
||||||
|
date: 2026-07-13
|
||||||
|
status: Accepted
|
||||||
|
supersedes:
|
||||||
|
superseded-by:
|
||||||
|
affected-paths: [plugins/os-backlog/skills/route/SKILL.md, plugins/os-backlog/hooks/session_start.py, plugins/os-backlog/bin/os-backlog, .cc-os/config]
|
||||||
|
affected-components: [os-backlog, to-issues]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 0033 — Tracker routing: Planka is state, git issues are specs
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The backlog pilot plan (vault note `vault-backlog-pilot-plan.md`) originally routed everything to Forgejo issues, later amended informally to "Forgejo = code work in a repo; Planka = everything else." The os-backlog slices then shipped a sharper boundary in practice — encoded in `plugins/os-backlog/skills/route/SKILL.md`, the `/to-issues` destination-tracker step (global skill `~/.agents/skills/to-issues/SKILL.md`, step 5), and the `plugins/os-backlog/hooks/session_start.py` injection note — but no ADR canonizes it, so the rule lives only in skill prose and could drift or be silently edited away. This ADR records already-implemented practice; it does not introduce new behavior.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Canonize the tracker-routing boundary, amending the pilot plan's "Forgejo for everything" rule. (1) Boundary rule: Planka = task STATE (backlog/next/doing/waiting/review/done — what's happening now); git issues = durable SPECS (text that must survive and be referenced: features, migrations, architecture-level work, tracer-bullet slices from /to-issues, PRDs); `repo:<path>` is the escape hatch for projects that already track issues in-repo and want to stay there. (2) Each project declares exactly one destination via the `tracker` key in `.cc-os/config`, grammar: `planka:<board>` | `forgejo:<owner>/<repo>` | `github:<owner>/<repo>` | `repo:<path>` (validated by `os-backlog config-write`). (3) Card-as-pointer + issue-chain-as-spec: when a code effort needs both state tracking and a durable spec, the Planka card is a pointer (title + link) into the git issue chain holding the spec — never duplicate spec text into a card description. (4) Promotion rule: when a Planka-tracked effort accretes significant code/design decisions in card comments, that is the signal to promote — open a git issue (or chain) for the durable spec and turn the card into a pointer; the AI flags promotion to the user, never performs it silently. Operational detail lives in the cited sources (route SKILL.md, to-issues step 5, session_start.py note) and is not restated here; column-ownership rules are ADR-0029's.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Easier: one recorded boundary the route skill, /to-issues, and the SessionStart note can all be audited against; changing the boundary now requires a superseding ADR rather than a quiet skill edit; the pilot plan's informal amendment has a durable home. Harder: two tracking surfaces per code project (board + issue tracker) instead of one, mitigated by the pointer pattern and back-links; promotion is a judgment call the AI can only flag, so under-flagging is a live risk (measured via the session-audit rubric).
|
||||||
|
|
||||||
|
## Alternatives rejected
|
||||||
|
|
||||||
|
1) Keep "Forgejo for everything" (original pilot plan) — rejected: process/coordination/household-style tasks have no repo home and card state (columns, WIP) is exactly what issue trackers do badly. 2) Planka for everything, specs in card descriptions — rejected: card descriptions are not durable, linkable, or commit-referenceable; specs need git-issue permanence. 3) Leave the rule in skill prose only, no ADR — rejected: prose rules drifting out of sync with enforcement is precisely what ADR-0029 fixed for column moves; the routing boundary deserves the same durability.
|
||||||
|
|
@ -39,4 +39,5 @@ One file per decision, `NNNN-kebab-title.md`, created via `/os-adr:create`.
|
||||||
| 0030 | [Read-only work uses Claude Code plan mode; no /readonly shortcut command](0030-read-only-work-uses-claude-code-plan-mode-no-readonly-shortcut-command.md) | Accepted | 2026-07-13 |
|
| 0030 | [Read-only work uses Claude Code plan mode; no /readonly shortcut command](0030-read-only-work-uses-claude-code-plan-mode-no-readonly-shortcut-command.md) | Accepted | 2026-07-13 |
|
||||||
| 0031 | [os-context prompt-composer plugin supersedes single-file os-orchestration](0031-os-context-prompt-composer-plugin-supersedes-single-file-os-orchestration.md) | Accepted | 2026-07-13 |
|
| 0031 | [os-context prompt-composer plugin supersedes single-file os-orchestration](0031-os-context-prompt-composer-plugin-supersedes-single-file-os-orchestration.md) | Accepted | 2026-07-13 |
|
||||||
| 0032 | [AI-maintained knowledge files are indexes with progressive disclosure](0032-ai-maintained-knowledge-files-are-indexes-with-progressive-disclosure.md) | Accepted | 2026-07-13 |
|
| 0032 | [AI-maintained knowledge files are indexes with progressive disclosure](0032-ai-maintained-knowledge-files-are-indexes-with-progressive-disclosure.md) | Accepted | 2026-07-13 |
|
||||||
|
| 0033 | [Tracker routing: Planka is state, git issues are specs](0033-tracker-routing-planka-is-state-git-issues-are-specs.md) | Proposed | 2026-07-13 |
|
||||||
<!-- adr-index:end -->
|
<!-- adr-index:end -->
|
||||||
|
|
|
||||||
|
|
@ -62,12 +62,13 @@ entry is in the leaf file named.
|
||||||
skill (ADR-0030); os-doc-hygiene `file_length` scanner signal shipped (issue #25 part 1);
|
skill (ADR-0030); os-doc-hygiene `file_length` scanner signal shipped (issue #25 part 1);
|
||||||
this file distilled to index+PD (issue #25 parts 2–3, ADR-0032). os-backlog slice 7
|
this file distilled to index+PD (issue #25 parts 2–3, ADR-0032). os-backlog slice 7
|
||||||
shipped: SessionStart injection note + /to-issues tracker routing (issue #16); board-id
|
shipped: SessionStart injection note + /to-issues tracker routing (issue #16); board-id
|
||||||
cache via BoardResolver (issue #22). Detail: os-context / os-doc-hygiene / os-backlog
|
cache via BoardResolver (issue #22); slice 8 — Operations board + tracker-routing rubric
|
||||||
leaves.
|
category + ADR-0033 canonizing Planka-state/git-issues-spec (issue #17). Detail:
|
||||||
|
os-context / os-doc-hygiene / os-backlog leaves.
|
||||||
|
|
||||||
**Remaining optional items:** additional project onboarding (one at a time, per ADR-0013);
|
**Remaining optional items:** additional project onboarding (one at a time, per ADR-0013);
|
||||||
bulk vault migration; os-adr rollout to pilot projects; os-backlog slice #17 and routing
|
bulk vault migration; os-adr rollout to pilot projects; os-backlog routing rollout (#14);
|
||||||
rollout.
|
cross-project filing convention (#27) and wakeup design spike (#28).
|
||||||
|
|
||||||
## Implemented components (one-liners)
|
## Implemented components (one-liners)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,19 @@ column ownership).
|
||||||
returned board name; 404/mismatch/any cache failure degrades to name lookup and rewrites
|
returned board name; 404/mismatch/any cache failure degrades to name lookup and rewrites
|
||||||
the cache; write failures never fail the command). Comment-preserving `Config.set`
|
the cache; write failures never fail the command). Comment-preserving `Config.set`
|
||||||
mirrors os-status's writer. 14 new tests; suite 90 runs / 198 assertions / 0 failures.
|
mirrors os-status's writer. 14 new tests; suite 90 runs / 198 assertions / 0 failures.
|
||||||
- **Outstanding:** slice #17 (ops board + rubric category + routing ADR; blocked-by #16 now
|
- **Slice 8 (2026-07-13, issue #17):** Operations board created in the Dev project (board
|
||||||
cleared) awaits its human decision gates; #14 routing-skill rollout (onboard cc-os
|
`1818308120145822783`) with uniform lists/labels; recurring card "Biweekly orchestration
|
||||||
itself, then one more project) pending.
|
IRL audit (recurring)" (card `1818310715438531668`, P2 + semi, first progress comment
|
||||||
|
`1818310889929966683`; recurrence contract: comment + move back to Backlog, never Done).
|
||||||
|
Tracker-routing convention canonized as **ADR-0033** (Planka = state / git issues = specs,
|
||||||
|
tracker key grammar, pointer + promotion rules; cites route/to-issues/session-start
|
||||||
|
rather than restating). os-context audit rubric gained category 8 `tracker-routing`
|
||||||
|
(capture/route/promote compliance only). Bug found live and fixed: label color fallback
|
||||||
|
only rescued `Planka::ValidationError`, but Planka rejects unknown colors as HTTP 400
|
||||||
|
`Planka::BadRequestError` — fallback now rescues both (regression test; suite 91 runs /
|
||||||
|
0 failures). os-status `/os-status:fix` step d reworded: invoke `/os-backlog:route`
|
||||||
|
directly, never pre-ask "forgejo or planka?" (boundary rule uses both — surfaced by the
|
||||||
|
servers-repo onboarding, cf. issue #26).
|
||||||
|
- **Outstanding:** #14 routing-skill rollout (onboard cc-os itself, then one more project);
|
||||||
|
#26 route-inspect issue-shape classification; #27 cross-project filing index; #28 wakeup
|
||||||
|
design spike.
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ module Backlog
|
||||||
last_error = nil
|
last_error = nil
|
||||||
candidates.each do |color|
|
candidates.each do |color|
|
||||||
return @client.labels.create(board.id, name: label_name, color: color, position: 65_536)
|
return @client.labels.create(board.id, name: label_name, color: color, position: 65_536)
|
||||||
rescue Planka::ValidationError => e
|
rescue Planka::ValidationError, Planka::BadRequestError => e
|
||||||
last_error = e
|
last_error = e
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,17 @@ class BoardEnsurerTest < Minitest::Test
|
||||||
refute_equal first_choice, p0.color
|
refute_equal first_choice, p0.color
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_label_color_falls_back_when_server_rejects_with_400
|
||||||
|
first_choice = Backlog::BoardSpec::LABEL_COLOR_CANDIDATES.fetch("P3").first
|
||||||
|
client = FakePlankaClient.new(reject_colors_with_400: [first_choice])
|
||||||
|
ensurer = Backlog::BoardEnsurer.new(client: client, human_user_id: "human-1")
|
||||||
|
|
||||||
|
ensurer.ensure("llf-schema", project_name: "Dev")
|
||||||
|
|
||||||
|
p3 = client.labels_store.find { |l| l.name == "P3" }
|
||||||
|
refute_equal first_choice, p3.color
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def test_activate_renames_archived_board_back
|
def test_activate_renames_archived_board_back
|
||||||
client = FakePlankaClient.new
|
client = FakePlankaClient.new
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ module BacklogTestHelpers
|
||||||
class FakePlankaClient
|
class FakePlankaClient
|
||||||
attr_reader :projects, :boards, :lists, :labels, :cards, :comments
|
attr_reader :projects, :boards, :lists, :labels, :cards, :comments
|
||||||
|
|
||||||
def initialize(reject_colors: [])
|
def initialize(reject_colors: [], reject_colors_with_400: [])
|
||||||
@next_id = 0
|
@next_id = 0
|
||||||
@projects_store = []
|
@projects_store = []
|
||||||
@boards_store = []
|
@boards_store = []
|
||||||
|
|
@ -24,7 +24,8 @@ module BacklogTestHelpers
|
||||||
@projects = ProjectsResource.new(self)
|
@projects = ProjectsResource.new(self)
|
||||||
@boards = BoardsResource.new(self)
|
@boards = BoardsResource.new(self)
|
||||||
@lists = ListsResource.new(self)
|
@lists = ListsResource.new(self)
|
||||||
@labels = LabelsResource.new(self, reject_colors: reject_colors)
|
@labels = LabelsResource.new(self, reject_colors: reject_colors,
|
||||||
|
reject_colors_with_400: reject_colors_with_400)
|
||||||
@cards = CardsResource.new(self)
|
@cards = CardsResource.new(self)
|
||||||
@comments = CommentsResource.new(self)
|
@comments = CommentsResource.new(self)
|
||||||
end
|
end
|
||||||
|
|
@ -179,9 +180,10 @@ module BacklogTestHelpers
|
||||||
end
|
end
|
||||||
|
|
||||||
class LabelsResource
|
class LabelsResource
|
||||||
def initialize(client, reject_colors: [])
|
def initialize(client, reject_colors: [], reject_colors_with_400: [])
|
||||||
@client = client
|
@client = client
|
||||||
@reject_colors = reject_colors
|
@reject_colors = reject_colors
|
||||||
|
@reject_colors_with_400 = reject_colors_with_400
|
||||||
end
|
end
|
||||||
|
|
||||||
def list(board_id)
|
def list(board_id)
|
||||||
|
|
@ -189,6 +191,11 @@ module BacklogTestHelpers
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(board_id, attrs)
|
def create(board_id, attrs)
|
||||||
|
if @reject_colors_with_400.include?(attrs[:color])
|
||||||
|
# Planka 2.x rejects unknown color names as E_MISSING_OR_INVALID_PARAMS
|
||||||
|
raise Planka::BadRequestError.new(status: 400,
|
||||||
|
body: { "message" => "color #{attrs[:color]} rejected" })
|
||||||
|
end
|
||||||
if @reject_colors.include?(attrs[:color])
|
if @reject_colors.include?(attrs[:color])
|
||||||
raise Planka::ValidationError.new(status: 422,
|
raise Planka::ValidationError.new(status: 422,
|
||||||
body: { "message" => "color #{attrs[:color]} rejected" })
|
body: { "message" => "color #{attrs[:color]} rejected" })
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,13 @@ severity, and category.
|
||||||
one scripted Bash loop.
|
one scripted Bash loop.
|
||||||
7. **drift** — session starts disciplined then drifts into long direct runs mid-session,
|
7. **drift** — session starts disciplined then drifts into long direct runs mid-session,
|
||||||
especially after a design/decision settles.
|
especially after a design/decision settles.
|
||||||
|
8. **tracker-routing** — mid-session follow-up work (a concrete task, bug, or deferred
|
||||||
|
item not done this session) left as a TODO comment or chat mention instead of captured
|
||||||
|
to the tracked board; durable spec text routed to a Planka card description (or task
|
||||||
|
state routed to git issues) against the Planka-state/git-issues-spec boundary
|
||||||
|
(ADR-0033, `.cc-os/config` tracker key); or a Planka card visibly accreting spec-like
|
||||||
|
decisions in comments with no promotion flag raised to the user — silent promotion
|
||||||
|
counts too. Convention compliance only, not whether the routing choice was wise.
|
||||||
|
|
||||||
## Per-session report format (return exactly this)
|
## Per-session report format (return exactly this)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,14 @@ like a `.gitignore` addition or a config stamp.
|
||||||
|
|
||||||
c. **`project-graph-present`** → invoke `/os-vault:onboard-project`.
|
c. **`project-graph-present`** → invoke `/os-vault:onboard-project`.
|
||||||
|
|
||||||
d. **`tracker-configured`** → **human gate.** Do not guess a tracker. Ask the user
|
d. **`tracker-configured`** → invoke `/os-backlog:route` directly and let it run
|
||||||
which tracker this project uses, then invoke `/os-backlog:route` (the
|
its own inspect → propose → confirm flow. Do NOT pre-ask the user "forgejo or
|
||||||
os-backlog routing skill) with that answer. If `/os-backlog:route` does not
|
planka?" — that framing is wrong: the boundary rule uses BOTH (Planka = state,
|
||||||
exist yet in this installation (issue #14 not yet landed), tell the user and
|
git issues = specs; a project routinely keeps its git-issue spec chains AND a
|
||||||
skip — do not fabricate a `.cc-os/config` tracker value yourself.
|
`planka:` tracker key for new ad hoc state). The route skill owns that
|
||||||
|
decision conversation and its gates. If `/os-backlog:route` is not installed,
|
||||||
|
tell the user and skip — do not fabricate a `.cc-os/config` tracker value
|
||||||
|
yourself.
|
||||||
|
|
||||||
e. **`subagent-model-env-override`** → **human gate, and typically out of scope
|
e. **`subagent-model-env-override`** → **human gate, and typically out of scope
|
||||||
for a project-level fix.** This is an environment/settings.json condition, not
|
for a project-level fix.** This is an environment/settings.json condition, not
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue