From bfb747bd7e8cc0a23cc0d025f1f48bc2b5ab35a1 Mon Sep 17 00:00:00 2001 From: jared Date: Fri, 10 Jul 2026 17:13:57 -0400 Subject: [PATCH] os-backlog: restore full snapshot card key set + record/verify 0.2.0 migration (refs #18, #19, #20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01BS59Nkb7fvJMFB9dZiwZ7H --- docs/implementation-status.md | 37 +++++++++++++++++++++++-- plugins/os-backlog/lib/backlog/cards.rb | 22 ++++++++++++--- plugins/os-backlog/tests/cards_test.rb | 17 ++++++++++++ 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/docs/implementation-status.md b/docs/implementation-status.md index 7ada993..d76721d 100644 --- a/docs/implementation-status.md +++ b/docs/implementation-status.md @@ -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 diff --git a/plugins/os-backlog/lib/backlog/cards.rb b/plugins/os-backlog/lib/backlog/cards.rb index 57c7d27..a80e4c4 100644 --- a/plugins/os-backlog/lib/backlog/cards.rb +++ b/plugins/os-backlog/lib/backlog/cards.rb @@ -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 diff --git a/plugins/os-backlog/tests/cards_test.rb b/plugins/os-backlog/tests/cards_test.rb index ccb7c65..8655f1f 100644 --- a/plugins/os-backlog/tests/cards_test.rb +++ b/plugins/os-backlog/tests/cards_test.rb @@ -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" }