Files
pad/internal/cli
xarmian 342679a364 Standardize plan-limit error envelope across HTTP/MCP/CLI/UI (TASK-788) (#628)
* fix: limit-hit responses were actively broken — garbled toasts, no upgrade signal

The limit enforcement responses (plan_limit_exceeded on 403) used a flat
body shape {"error": "plan_limit_exceeded", ...} that is incompatible with
every consumer: the frontend PadApiError parser, the CLI parseError path,
and the MCP classifyHTTPStatusKind all expect {"error": {"code": ...,
"message": ...}}. As a result, hitting any of the 5 plan limits (items,
members, workspaces, api_tokens, webhooks) produced garbled toasts with
undefined message text and zero upgrade signal.

Fix:
- writePlanLimitError now emits the standard nested error envelope with a
  human-readable message sentence and limit details in error.details.
- CLI parseError now correctly surfacing the message (net positive, no
  code change needed).
- MCP classifyHTTPStatusKind: adds ErrPlanLimitExceeded to the taxonomy
  and the allowedStructuredErrorCodes whitelist so 403 plan-limit errors
  pass through with code + details rather than collapsing to
  ErrPermissionDenied (TASK-788).
- Frontend: exports isPlanLimitError() type-guard and planLimitMessage()
  formatter from client.ts; all 4 limit-hit write call sites (item create,
  member invite, workspace create, token create) now branch on the code and
  show an upgrade-signal message pointing at /console/billing.
- Test: updates handlers_workspace_cap_test.go to the new body shape; adds
  TestPlanLimitError_ResponseShape covering members_per_workspace limit hit.

TASK-788

* fix(R1): cover MCP stdio path, 5 more item-create sites, polish message wording

Finding A — MCP stdio transport was missing plan-limit coverage:
- cli/client.go: add PlanLimitDetails struct, AsPlanLimit() helper, and
  WritePlanLimitError() that emits the pad-structured-error/v1 marker so
  the MCP stdio classifier can lift code + details instead of falling
  through to ErrServerError.
- cmd/pad/main.go: wire the WritePlanLimitError branch into all three
  CreateItem call sites (item create, convention activate, playbook activate).
- internal/mcp: add TestClassifyHTTPStatus_PlanLimitPreservesCodeAndDetails,
  TestClassifyHTTPStatus_Generic403FallsToPermissionDenied,
  TestClassifyExecError_PlanLimitMarkerLiftsStructuredPayload, and
  TestClassifyExecError_PlanLimitWithoutMarkerFallsThrough.

Note: extractUpstreamErrorEnvelope already parses details (json.RawMessage
field) — the codex concern about it being silently empty was a false alarm;
no fix needed there.

Finding B — 5 more item-create entry points were unguarded:
- EditorBubbleMenu.svelte (inline wiki-link capture)
- Sidebar.svelte (quick-add)
- roles/+page.svelte (board new-item, was console.error only; adds toastStore)
- conventions/+page.svelte
- playbooks/+page.svelte (both create and duplicate paths)

B1/B2 polish — server message is now statement-of-fact only, no doubled
upgrade verb. planLimitMessage() drops "Upgrade to Pro to add more." (each
surface appends its own CTA). limitStr uses hyphenated adjective form
"3-member" / "10-item" (compound modifier before "limit").

TASK-788

* feat(task-788): extend MCP-stdio plan-limit coverage to workspace, invite, webhook

Wire WritePlanLimitError into three additional CLI command error paths so
the MCP stdio classifier surfaces ErrPlanLimitExceeded with details instead
of falling through to ErrServerError:

- workspaceCreateCmd: check before fmt.Errorf wraps the APIError
- inviteCmd: check before returning the raw error
- webhooksCreateCmd: check before returning the raw error

Add TestClassifyExecError_PlanLimitWorkspaceCreate to exercise the full
workspace-create stdio round-trip through classifyExecError, asserting
ErrPlanLimitExceeded code, feature="workspaces", limit, and upgrade_url.

Token create intentionally left bare (agents don't drive token creation).
2026-05-25 11:46:41 -04:00
..
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00