feat: list and warn about Runs whose spend Ploeg cannot settle #201

Closed
ryangr0 wants to merge 3 commits from ryangr0/ploeg-unsettled-spend into development AGit
Owner

Summary

Ploeg, VIK-1634 (https://vikunja.webgrip.dev/tasks/1634): new read-only GET /api/v1/operator/unsettled-accounts. It lists finished Runs whose account is still minting, issued or unknown. These are the Runs the block sweep retries and logs as managed key block retry unresolved.

  • Each row has runId, workItemId, team, accountState, heldUsd (from run_budget_holds.reserved) and since (run_llm_accounts.updated_at). Rows are oldest first, at most 200.
  • Totals {count, heldUsd} come from a window sum, so they also count matching Runs beyond the 200 on the page.
  • Rows are limited to the consumer's Teams. Any query parameter gets 400 invalid_request. No run token, alias or key is returned.
  • PendingLLMBlocks and the new query share one selection constant, so the two cannot drift.
  • The operator-api.v1 schema gains unsettledAccountsResponse, and step 6 of investigate-a-runs-spend.md names the endpoint.
  • No migration, sweep or settlement change, so this stays clear of VIK-1755.

Vloer, VIK-1635 (https://vikunja.webgrip.dev/tasks/1635): PloegClient.summary reads unsettled-accounts in its own try. It filters rows by the user's Teams, sums the count and total from those rows, and adds unsettled and unsettledError to the summary.

  • Now shows a severe callout, "3 Runs hold budget Ploeg cannot release", with the format.money total, a "Show the 3 Runs" disclosure linking #work/<id>, and the runbook apps/ploeg/docs/ops/managed-workers.md, "Reconcile uncertainty".
  • Insights gets a "Cannot release" tile.
  • A failed read shows "Could not be loaded". A 404 from an older Ploeg shows "Not reported by this Ploeg".
  • The demo reports 0 without calling Ploeg. There is no settle, release or retry action.
  • The site replay is re-recorded in a separate chore(site) commit, because the demo summary gained the two fields.

Billing-adjacent: a human should review this.

What was reused from the 2026-10-01 branches

Nothing was carried over. I read both branches:

  • fix/ploeg-unsettled-holds-park: the floor sweep waits for unsettled holds, a new close reason, the Vikunja link and ADR-0048.
  • fix/vloer-work-item-clarity: the "Budget held, not spent" wording and Run counts.

Both change settlement behaviour, close reasons or Work Item page wording. That work is outside these tickets and overlaps the VIK-1755 settlement logic. Only their diagnosis was used: finished Runs keep holding budget. No ADR or ADR status change is included.

Tests

  • New pkg/store/llm_unsettled_test.go: the selection equals PendingLLMBlocks, holds equal run_budget_holds, rows are oldest first, totals include Runs beyond the page, plus scope, empty and limit cases.
  • New pkg/httpapi/operator_unsettled_test.go: schema validation, silver scope, no token, alias or key in the body, empty 200, 400 for a query, 405 for POST.
  • New Vloer cases in test/ploeg.test.ts, test/now-view.test.mjs and test/ploeg-activity.test.mjs.
  • cd apps/ploeg && go test -count=1 ./... && go vet ./...: all ok.
  • apps/vloer: npm test 704/704 pass, npm run check ok.
  • mise run verify: all gates passed (vloer, demo-replay, vloer-extension, ploeg, brand, site, site-demo, helm, release, integration, docs).

Checks left to CI

None. Live-provider tests stay opt-in.

🤖 Generated with Claude Code

## Summary **Ploeg, VIK-1634** (https://vikunja.webgrip.dev/tasks/1634): new read-only `GET /api/v1/operator/unsettled-accounts`. It lists finished Runs whose account is still `minting`, `issued` or `unknown`. These are the Runs the block sweep retries and logs as `managed key block retry unresolved`. - Each row has `runId, workItemId, team, accountState, heldUsd` (from `run_budget_holds.reserved`) and `since` (`run_llm_accounts.updated_at`). Rows are oldest first, at most 200. - Totals `{count, heldUsd}` come from a window sum, so they also count matching Runs beyond the 200 on the page. - Rows are limited to the consumer's Teams. Any query parameter gets 400 `invalid_request`. No run token, alias or key is returned. - `PendingLLMBlocks` and the new query share one selection constant, so the two cannot drift. - The `operator-api.v1` schema gains `unsettledAccountsResponse`, and step 6 of `investigate-a-runs-spend.md` names the endpoint. - No migration, sweep or settlement change, so this stays clear of VIK-1755. **Vloer, VIK-1635** (https://vikunja.webgrip.dev/tasks/1635): `PloegClient.summary` reads `unsettled-accounts` in its own try. It filters rows by the user's Teams, sums the count and total from those rows, and adds `unsettled` and `unsettledError` to the summary. - Now shows a severe callout, "3 Runs hold budget Ploeg cannot release", with the `format.money` total, a "Show the 3 Runs" disclosure linking `#work/<id>`, and the runbook `apps/ploeg/docs/ops/managed-workers.md`, "Reconcile uncertainty". - Insights gets a "Cannot release" tile. - A failed read shows "Could not be loaded". A 404 from an older Ploeg shows "Not reported by this Ploeg". - The demo reports 0 without calling Ploeg. There is no settle, release or retry action. - The site replay is re-recorded in a separate `chore(site)` commit, because the demo summary gained the two fields. Billing-adjacent: a human should review this. ## What was reused from the 2026-10-01 branches Nothing was carried over. I read both branches: - `fix/ploeg-unsettled-holds-park`: the floor sweep waits for unsettled holds, a new close reason, the Vikunja link and ADR-0048. - `fix/vloer-work-item-clarity`: the "Budget held, not spent" wording and Run counts. Both change settlement behaviour, close reasons or Work Item page wording. That work is outside these tickets and overlaps the VIK-1755 settlement logic. Only their diagnosis was used: finished Runs keep holding budget. No ADR or ADR status change is included. ## Tests - New `pkg/store/llm_unsettled_test.go`: the selection equals `PendingLLMBlocks`, holds equal `run_budget_holds`, rows are oldest first, totals include Runs beyond the page, plus scope, empty and limit cases. - New `pkg/httpapi/operator_unsettled_test.go`: schema validation, silver scope, no token, alias or key in the body, empty 200, 400 for a query, 405 for POST. - New Vloer cases in `test/ploeg.test.ts`, `test/now-view.test.mjs` and `test/ploeg-activity.test.mjs`. - `cd apps/ploeg && go test -count=1 ./... && go vet ./...`: all ok. - `apps/vloer`: `npm test` 704/704 pass, `npm run check` ok. - `mise run verify`: all gates passed (vloer, demo-replay, vloer-extension, ploeg, brand, site, site-demo, helm, release, integration, docs). ## Checks left to CI None. Live-provider tests stay opt-in. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
GET /api/v1/operator/unsettled-accounts lists, oldest first and at most
200, the finished Runs whose account is still minting, issued or unknown:
the Runs the block sweep retries and logs as "managed key block retry
unresolved". Each row has its Run, Work Item, Team, account state, the
hold from run_budget_holds and since when. Totals cover every matching
Run in the consumer's Teams, also past the page. It takes no query
parameters and never returns a run token, alias or key.

The block queue and the new list share one selection, so they cannot
drift apart. Read-only: no sweep, table or migration changes.

VIK-1634
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The summary now also reads Ploeg's unsettled-accounts list in its own
try, keeps only the rows of Teams the user may read and sums the count
and held total from those rows. A failure never fails the summary: it
reports "Could not be loaded", and an older Ploeg that answers 404
reports "Not reported by this Ploeg". The demo reports zero without
asking Ploeg.

Now shows a severe callout, "3 Runs hold budget Ploeg cannot release",
with the held total, a "Show the 3 Runs" disclosure linking each Work
Item and Ploeg's "Reconcile uncertainty" runbook. Insights shows the
same count and total in a "Cannot release" tile. Vloer offers no settle,
release or retry action.

VIK-1635
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
chore(site): re-record the demo replay for the unsettled summary fields
Some checks are pending
[Workflow] On Pull Request / checks (pull_request) Waiting to run
[Workflow] On Pull Request / warnings (pull_request) Blocked by required conditions
[Workflow] On Pull Request / release-policy (pull_request) Waiting to run
857397edb0
The demo summary now carries unsettled and unsettledError, so the hosted
/demo replay is recorded again from Vloer's deterministic demo.

VIK-1635
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ryangr0 closed this pull request 2026-10-04 08:41:39 +00:00
Some checks are pending
[Workflow] On Pull Request / checks (pull_request) Waiting to run
[Workflow] On Pull Request / warnings (pull_request) Blocked by required conditions
[Workflow] On Pull Request / release-policy (pull_request) Waiting to run

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
webgrip/unfold!201
No description provided.