Lesson 0001 · The merge decision

Is a sprint a timebox or a work item?

One distinction decides the merge question, and your schema already contains the evidence. Ten minutes.

The win

By the end you can state what a sprint is allowed to store in this schema, name the two columns to delete first, and give a one-sentence answer to "should we merge Sprints & Agile with Tasks?" that survives a code review.

The distinction

A work item has an owner, a status, and a life. It moves through columns. It gets assigned, estimated, commented on, and closed.

A timebox has a window, a goal, and a membership question. It does not get assigned. It does not move through columns. You do not close a timebox — it ends, or you cancel it.

A sprint is a timebox. Everything interesting about it is a query: "which work items fell inside this window?" That query is a WHERE clause over tasks, not a row in a sibling table.

Read your own schema with that lens and the shape stops being arbitrary:

Why this decides the merge

Merging a timebox into a work item is a category error: you would be adding columns with a different lifecycle to a table with its own. The row would have to answer "who owns this?" and "is this done?" and "is this finished?" — three questions with three different vocabularies — and you would get exactly what status_column already gave you.

So: do not merge the tables. pm_sprints is a correctly-shaped table. The error is not its existence.

The two columns to delete first

Here is the part that changes your product, not just your schema. The 2020 Scrum Guide is the normative source, and in it the Sprint's commitment is the Sprint Goal — a qualitative objective. There is no numeric commitment. There is no velocity. There is no story points.

Sutherland, on why the Guide's Commitment was removed in 2017 and reintroduced qualitatively in 2020:

"Unfortunately, some managers weaponized team velocity. They criticized teams for delivering a point less or sometimes even for delivering a point more… Systems have variability and if you try to control normal variability, the system will spin out of control."

Now look at what you have built:

capacity_points     numeric(6,2) DEFAULT 40
committed_points    numeric(6,2) DEFAULT 0
completed_points    numeric(6,2) DEFAULT 0

Those three columns are a velocity weapon, pre-aimed, with a default capacity of 40 points. They are rendered on the screen (sprints page lines 100 and 107). They are never computed from anything. They are typed constants waiting for someone to start comparing people against them — which is precisely the outcome Sutherland is describing, shipped before a single sprint exists.

Goodhart's law: "When a measure becomes a target, it ceases to be a good measure." LVC WorkOS is sold to HR buyers. A points number rendered next to a person's name on a delivery board will be gamed, and you will have shipped the gaming surface. The SPACE framework (ACM Queue 2021) exists to argue that productivity needs five dimensions in tension, not one — and one of its cautionary tales is a team that set a PR turnaround SLA and destroyed its own coding time.

What is already answering the capacity question

You do not need points to know whether a team is overloaded. capacity.service.ts already computes it, in hours, from real HR data: base capacity, minus holidays, minus approved leave, compared against allocations plus task estimates plus logged timesheet hours. It emits OVERALLOCATED / OPTIMAL / UNDERALLOCATED / BENCH.

That is a measure of effectiveness, not a proxy. It cannot be gamed by re-pointing a ticket. capacity_points is a proxy for something you already measure directly — which is the definition of a redundant metric.

Verdict

QuestionAnswerWhy
Merge pm_sprints into tasks? No Different lifecycles, no owner, different cardinality. A category error.
Merge the /pm/sprints and /tasks screens? Yes Two boards over one table with two status vocabularies is the actual bug.
Keep the three points columns? No Velocity as a commitment. Capacity is already measured in hours, properly.
Keep the sprint table at all? Yes, if Only if it holds a goal and a window. That is worth building.
Keep story points? Yes, if For planning inside the team. Not for reporting up, never per person, and drop the DEFAULT 3.

How it should be interconnected

Two connections are already in the schema and correct. Three are missing. One is wrong.

  1. Project → Sprint → Milestone. pm_sprints.project_id and pm_sprints.milestone_id are right. A sprint terminates at a milestone; that is the only honest relationship between a window and a point.
  2. Task → Sprint, as membership. The column exists, the index exists, the FK exists. It has zero writers. This is the gap, and it is a one-field zod schema plus a loader fix.
  3. Sprint → Capacity, in hours, not points. Replace capacity_points with a call to the capacity service scoped to the sprint window. The sprint then answers "did this team have room?" — the only question its capacity field was ever asked to answer.
  4. Sprint → Flow. Report cycle time for tasks completed inside the window. It needs no estimation at all, so it cannot be gamed by re-pointing tickets, and it is the metric the SPACE framework's "efficiency and flow" dimension actually wants.
  5. Wrong Tenancy. pm_sprints.organization_id is nullable while project_id is NOT NULL, and RLS on this table is USING (true) like the other 95 policies in the schema. A sprint written without an org is unfilterable and therefore visible to every tenant. Add NOT NULL plus a backfill before the first writer lands.

Check yourself

Retrieval, not recognition. Answer before you expand anything.

In the 2020 Scrum Guide, what is the Sprint's commitment?

The board filters with t.sprint_id === s.id || t.project_id === s.project_id. What does it show?

Should pm_sprints be merged into the tasks table?

What already answers "can this team take on more work?" in this codebase?

Before you go

Retrieve this from memory first

Without opening the wiring map: which four columns or tables in this module have zero writers, and which one column in pm_sprints would be the tenancy hazard?

Then check Reference 0002 §3 and §6.

Read this next

The primary source, and it is short: the 2020 Scrum Guide. Read the Sprint section and Commitment: Sprint Goal. Note what is absent — no points, no velocity, no capacity. Then read Sutherland's answers in the InfoQ interview, especially the one on weaponised velocity, and confirm for yourself that it is a direct description of your schema.

Ask me anything. Specifically worth asking: how to model the sprint goal so it is not another number; whether to delete the points columns or just stop rendering them; whether milestone_id on pm_sprints is redundant given tasks.milestone_id; and how to sequence the migration so no tenant sees a blank sprint.

Next lesson: Action Required. The docs call it your signature concept; the code admits it has no column. Deciding whether to actually build it is the same argument again — a distinct concept needs a distinct home, not a field faked from an existing one.