os-backlog: restore full snapshot card key set + record/verify 0.2.0 migration (refs #18, #19, #20)

Cards#snapshot again emits all 19 Types::Card fields (camelCase) plus
labels — 8df0cb7 had narrowed it to 8 keys, dropping listChangedAt and
silently breaking board-audit's stale-Doing check. A regression test
now pins the complete key set (35 runs green). implementation-status
records the 0.2.0 typed-returns migration and the live end-to-end
verification against the real Planka board (run on ovh-vps 2026-07-10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BS59Nkb7fvJMFB9dZiwZ7H
This commit is contained in:
jared 2026-07-10 17:13:57 -04:00
parent 8df0cb7e1e
commit bfb747bd7e
3 changed files with 70 additions and 6 deletions

View File

@ -137,6 +137,36 @@ behind gem issue 13) and live verification completed against production Planka
Backlog card: get planka_bot into credvault (credvault has no import path — rotate vs.
add import capability is a human decision).
**os-backlog migrated to planka-api 0.2.0 typed returns (2026-07-10):** commit `8df0cb7`
moved the gem from 0.1.0 hash-return to 0.2.0, which returns `Planka::Types` value objects
instead of parsed-JSON hashes. `board_ensurer.rb` and `cards.rb` were rewritten on typed
accessors (`card.id`, `card.list_id`, …); `Cards#snapshot` now builds explicit string-keyed
hashes from `BoardDetail#lists`/`#cards`/`#labels`/`#card_labels` (`BoardDetail` replaces the
old `included`-hash shape). Test fakes in `tests/test_helper.rb` were rebuilt to return real
`Planka::Types` records instead of hashes. JSON output stays byte-compatible, so skill/agent
contracts are unchanged. A follow-up fix (issue #18, same day) found the migration had
silently dropped one field from `Cards#snapshot`'s per-card hash; restored the full 19-key
camelCase card key set (`id`, `createdAt`, `updatedAt`, `type`, `position`, `name`,
`description`, `dueDate`, `isDueCompleted`, `stopwatch`, `commentsTotal`, `isClosed`,
`listChangedAt`, `boardId`, `listId`, `creatorUserId`, `prevListId`, `coverAttachmentId`,
`isSubscribed`, plus `labels`) with a regression test pinning the key set including
`listChangedAt`. Suite green at 35 runs (was 34 pre-#18).
**os-backlog live verification (0.2.0) (2026-07-10):** end-to-end re-verification of
`bin/os-backlog` against the real Planka instance with the installed 0.2.0 gem (issue #20).
Local machine had no `PLANKA_*` env/credvault entry, so the plugin tree was rsynced to
`ovh-vps:/tmp/os-backlog-verify` (where `planka-api` 0.2.0 is also installed) and exercised
there with bot credentials sourced from `~/services/planka/bot-credentials.env` in a
subshell, per the established pattern — credential contents were never read or printed.
Results: `tests/all.rb` 35 runs/0 failures on ovh-vps; `board-ensure cc-os --project Dev` run
twice both reported `(existing)` with no lists/labels created (idempotent); `card-add`
created a card on Backlog; `card-label` attached `afk-ready`; `card-comment` posted a
comment; `snapshot --board cc-os` showed the card with the full 20-key set (19 card fields +
`labels`) including `"listChangedAt": "2026-07-10T21:04:37.013Z"` and `"labels":
["afk-ready"]`. The test card was deleted via `Planka::Client#cards.delete` (the CLI has no
delete subcommand) and a follow-up snapshot confirmed it was gone; the rsynced verification
tree on ovh-vps was removed afterward. No regressions vs. the pre-0.2.0 behavior.
**Remaining optional items:** additional project onboarding (one at a time, per ADR-013); bulk
vault migration. All required build steps complete as of 2026-06-15.
@ -499,7 +529,8 @@ symlinked into `~/.claude/plugins/os-backlog`; PRD Forgejo issue #9, slices #10
(Planka = task state by default; git issues = implementation specs only), mid-session
capture, pull-only listing, board lifecycle, and the three-value autonomy convention
(`hitl`/`semi`/`afk-ready`).
- Lib (Ruby, os-adr `lib/`+`bin/` pattern, installed planka-api gem only — no source
- Lib (Ruby, os-adr `lib/`+`bin/` pattern, installed planka-api gem only (0.2.0, typed
`Planka::Types` returns as of 2026-07-10) — no source
coupling): `board_spec.rb` (uniform lists/labels contract), `board_ensurer.rb`
(idempotent create/repair; Planka 2.1.1 `type` fields, shared-project + owner-manager
visibility fix, label color candidates with fallback; `archived--` rename convention),
@ -520,7 +551,9 @@ symlinked into `~/.claude/plugins/os-backlog`; PRD Forgejo issue #9, slices #10
- Outstanding: Semi slices #14/#16/#17 (routing/migration skill, injection note +
/to-issues routing, ops board + rubric category + routing ADR). Live verification
completed 2026-07-10 (snapshot / board-ensure / card-add / card-label against production;
gem 0.1.0 locally installed via `rake install`).
gem 0.1.0 locally installed via `rake install`); re-verified 2026-07-10 (later) against the
installed 0.2.0 gem post-migration — see "os-backlog migrated to planka-api 0.2.0" and
"os-backlog live verification (0.2.0)" timeline entries above.
## Operational procedures

View File

@ -76,16 +76,30 @@ module Backlog
# Build the plain-hash card representation the CLI/JSON output emits,
# keeping the output shape byte-compatible with the 0.1.x snapshot
# (string keys, "labels" merged in) even though Types::Card is an
# immutable Data object we can no longer +merge+.
# immutable Data object we can no longer +merge+. Emits every
# Types::Card field — dropping fields here silently breaks consumers
# like board-audit, which reads "listChangedAt".
def card_hash(card, label_names)
{
"id" => card.id,
"createdAt" => card.created_at,
"updatedAt" => card.updated_at,
"type" => card.type,
"position" => card.position,
"name" => card.name,
"description" => card.description,
"position" => card.position,
"listId" => card.list_id,
"boardId" => card.board_id,
"dueDate" => card.due_date,
"isDueCompleted" => card.is_due_completed,
"stopwatch" => card.stopwatch,
"commentsTotal" => card.comments_total,
"isClosed" => card.is_closed,
"listChangedAt" => card.list_changed_at,
"boardId" => card.board_id,
"listId" => card.list_id,
"creatorUserId" => card.creator_user_id,
"prevListId" => card.prev_list_id,
"coverAttachmentId" => card.cover_attachment_id,
"isSubscribed" => card.is_subscribed,
"labels" => label_names
}
end

View File

@ -49,6 +49,23 @@ class CardsTest < Minitest::Test
assert_equal Backlog::BoardSpec::LABEL_NAMES.sort, snapshot["labels"].sort
end
def test_snapshot_card_emits_complete_key_set
@cards.add(board_name: "llf-schema", title: "captured task")
snapshot = @cards.snapshot(board_name: "llf-schema")
backlog = snapshot["lists"].find { |l| l["name"] == "Backlog" }
card = backlog["cards"].first
expected_keys = %w[
id createdAt updatedAt type position name description dueDate
isDueCompleted stopwatch commentsTotal isClosed listChangedAt
boardId listId creatorUserId prevListId coverAttachmentId
isSubscribed labels
]
assert_equal expected_keys.sort, card.keys.sort
assert card.key?("listChangedAt"), "snapshot card must carry listChangedAt for board-audit"
end
def test_snapshot_includes_card_label_names
card = @cards.add(board_name: "llf-schema", title: "labeled task")
p1 = @client.labels_store.find { |l| l.name == "P1" }