Squad and Project Multi-Perspective Kanban: Dual-Axis Work Surfaces over One Ledger
Abstract
Industry work trackers separate an execution boundary (team, repo, board) from a cross-cutting initiative (project, epic, roadmap). Mupot already stores that dual axis in D1 — every task has exactly one home squad and an optional project. This paper specifies three Kanban projections over the same ledger (squad board, project multi-squad board, tenant matrix), the authorization query, and what is already live versus deferred.
Abstract
To build a multi-squad and multi-project Kanban for Mupot, we mapped how industry platforms model execution units (teams/squads), strategic goals (projects/initiatives), and work items (tasks/issues). The dual-axis data model is already live in Mupot D1. This paper specifies the surface projection layer over that substrate.
1. Industry Taxonomy
| System | Execution Boundary | Cross-Cutting Initiative |
|---|---|---|
| Linear | Team | Project (multi-team) |
| GitHub Projects | Repository | Project Board v2 (org-level) |
| Jira | Project / Board | Initiative / Advanced Roadmap |
| GitLab | Project | Group Epic / Group Board |
| Mupot | Squad (squad_id) | Project (project_id) |
Linear — the one-home-team invariant
Every issue belongs to exactly one team. Workflow state machines, triage rules, and cycle cadences are team-specific. Projects do not own work directly; they are cross-team aggregators. A team board shows that team’s tasks; a project board aggregates contributing teams with swimlanes by team.
GitHub Projects v2 — matrix dimensions
Issues live in repositories. Projects exist as workspace-level metadata above repositories, with custom field projections that slice data across any dimension.
2. Mupot Dual-Axis Model
┌──────────────────────────────┐
│ TENANT / ORG │
└──────────────┬───────────────┘
│
┌──────────────────────┴──────────────────────┐
▼ ▼
╔══════════════════════╗ ╔══════════════════════╗
║ EXECUTION AXIS ║ ║ STRATEGIC AXIS ║
║ (Squads) ║ ║ (Projects) ║
╠══════════════════════╣ ╠══════════════════════╣
║ • Token Authority ║ ║ • Business Goal / OKR║
║ • RBAC Boundaries ║ ║ • Multi-Squad Scope ║
║ • Gate Owners ║ ║ • Milestones ║
║ • Mailbox / Spool ║ ║ • Cross-team Budget ║
╚══════════╦═══════════╝ ╚══════════╦═══════════╝
│ │
│ ┌─────────────────────────┐ │
└────────►│ TASK │◄────────┘
│ • squad_id (Home) │
│ • project_id (Goal) │
│ • status / priority │
└─────────────────────────┘Invariants:
- Single home squad (
task.squad_idNOT NULL). Guarantees RBAC, Durable Object routing, mailbox lease integrity, and token authority. - Nullable project (
task.project_idNULLABLE). Tasks without a project are squad maintenance or untriaged discovery. - Cross-squad subtask inheritance. Child tasks that inherit
project_idmust verify the assigned child squad holdswriteoradmininproject_squad_access. Absent access fails closed. - No double-homing with flights. Project boards surface tasks. The flight command deck surfaces governed release flights. Flights are not mixed into task Kanban swimlanes.
3. Three Projections
Projection 1 — Squad board (execution). Columns by status; cards tagged with optional project and assignee.
Projection 2 — Project multi-squad board (delivery). Swimlanes by contributing squad over the same status columns.
Projection 3 — Tenant matrix (executive, deferred). Squads as columns, aggregate counts and blocked items.
4. Authorization at Query Time
Authorization is checked using resolveAccessibleSquadIds(env, auth):
- Org admins can view any squad or project board.
- Squad members are filtered to tasks belonging to squads where the member holds an active capability grant.
SELECT
t.id, t.squad_id, t.project_id, t.priority, t.parent_task_id,
t.title, t.body, t.status, t.assignee_agent_id, t.github_issue_url,
t.result, t.completed_at, t.gate_owner, t.done_when, t.created_at, t.updated_at,
s.name AS squad_name, s.slug AS squad_slug,
a.name AS assignee_name, a.slug AS assignee_slug
FROM tasks t
JOIN squads s ON t.squad_id = s.id
LEFT JOIN agents a ON t.assignee_agent_id = a.id
WHERE t.project_id = ?1
AND (?2 IS NULL OR t.squad_id IN (SELECT CAST(value AS TEXT) FROM json_each(?2)))
ORDER BY s.name ASC, t.updated_at DESC;5. What Is Live
Phase 1 shipped:
- Web UI
GET /dashboard/kanbanwith?squad=and?project=switcher and RBAC scoping. - REST
GET /api/kanban/board. - MCP tool
kanban_board. - Seeded fixture coverage in
tests/kanban-routes.test.ts.
Phase 2 deferred: tenant matrix projection, canvas flowchart wiring, automatic cross-squad project-access verification on subtask create.