Skip to content

Direction: native cross-workspace rollup, or is CLI fan-out the pattern? #1047

Description

@thomasthinks

What problem does this solve?

I run several repos as a single operator, each its own Pad workspace. I want one view of "what's ready, what's blocked, what's stale" across all of them — the pad project ready / stale / standup output, unioned.

Today that's strictly one workspace at a time. --workspace is a single-value persistent flag (StringVar, cmd/pad/main.go:126), and the CLI docs describe the model as "All commands operate on the workspace detected from .pad.toml in your directory tree."

The only account-wide read I could find is GET /api/v1/auth/export (handleExportAccount), and it isn't a substitute for a dashboard-style query: it only dumps workspaces where ws.OwnerID == user.ID, it's wrapped in a 60-second timeout, and it 403s the entire export if the caller has restricted collection access in any owned workspace. That's a GDPR dump, not a query surface.

We currently solve this by fanning the CLI out per workspace and merging the --format json output in a wrapper. It works fine. My question is whether that fan-out is the intended pattern or an actual gap.

Proposed solution

I'd rather ask about direction than push a shape. The rough space, no strong preference between them:

  • accept a list on --workspace (--workspace a,b,c)
  • a separate aggregate subcommand, leaving single-workspace commands' contracts untouched
  • a read-only aggregate endpoint the CLI sits on

One design detail worth surfacing early: item numbers are workspace-unique (internal/store/items.goGetItemByRef scopes by workspace_id), so every workspace has a TASK-1. Any aggregate output has to namespace refs somehow. Our wrapper does it client-side, but a native version would need to pick a convention, and that's a decision better made deliberately than discovered.

I also recognize this cuts against where v0.11.0 went — SSE membership checks at connect time (TASK-264), OAuth-consent scoping of pad_workspace list (BUG-2102). Those exist specifically to stop workspaces bleeding into each other. Anything aggregate has to resolve per workspace against the caller's real membership rather than short-circuiting it, which is genuine work and a reasonable reason to decline.

To be clear: "the fan-out wrapper is the intended pattern" is a completely useful answer. If that's the design intent I'll keep ours and stop wondering, and it may be worth a line in the docs for the next person. If you would want this in core, I'm happy to implement it — I'd want to know what shape you'd accept before anyone writes Go.

Component

CLI

Alternatives considered

  • Per-workspace CLI fan-out + client-side JSON merge. Shipped, working, and what prompted the question — a thin wrapper over pad --workspace <slug> project ... --format json.
  • GET /api/v1/auth/export as a poor man's rollup. Wrong shape: owner-only, 60s timeout, all-or-nothing under restricted access.
  • Querying SQLite directly. Rejected — bypasses every access check and couples to the schema.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions