Skip to content

docs(ADR-016): add production cardinality — the draft reasons from code only - #812

Merged
lilyshen0722 merged 1 commit into
mainfrom
docs/adr-016-cardinality
Aug 4, 2026
Merged

docs(ADR-016): add production cardinality — the draft reasons from code only#812
lilyshen0722 merged 1 commit into
mainfrom
docs/adr-016-cardinality

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Adds a §Cardinality section with the live pod-type distribution, queried from commonly.me on 2026-08-04.

The draft is sound and this changes none of its decisions. It adds the one thing the ADR could not get from reading code: how many pods each judgment actually governs.

finding why it matters
chat = 124, team = 35 the instance is not "team pods and DMs"; a future migration is mostly a chat migration
agent-ensemble = 1 7 gated endpoints + a dedicated subdocument for a single pod. Still must not be collapsed — but branch-keyed and nearly unused are different facts, and the exception shouldn't be read as evidence the type is load-bearing
agent-admin = 31 vs agent-dm = 6 admin-room is the third-largest type; the draft justified it on reachable states, the counts agree
communityListed = 3 Discover UI should be sized to 3 rows, not to the browse experience the tier vocabulary makes possible

Ratification input for #768.

…de only

Queried commonly.me 2026-08-04. Three findings that change what to do next
without changing the decision:

- chat is 124 pods, the dominant room type; team is 35. The instance is not
  "team pods and DMs" however it reads at the creation surface.
- the agent-ensemble exception governs exactly 1 pod. Seven gated endpoints
  for one pod is branch-keyed AND nearly unused — different facts, and the
  ADR should not be read as evidence the type is load-bearing.
- agent-admin is 31 vs agent-dm's 6, so admin-room is the third-largest type.
  The draft justified that kind on reachable states; the counts agree.

Also records that Discover has 3 listed pods, so discovery UI should be sized
to that rather than to what the tier vocabulary makes possible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016s8YysVUNmmiNXw3cTrZFJ

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every number in the table is exact. Reproduced against production, not accepted. Queried commonly.me 2026-08-04T11:4xZ, at head 96b111aa:

byType  chat 124 · team 35 · agent-admin 31 · agent-room 27
        agent-dm 6 · study 6 · games 3 · agent-ensemble 1     total 233
communityListed 3 · publicRead 5 · joinPolicy invite-only 152

All eight type counts, the total, and all three visibility figures match the section character-for-character. The three consequences follow from the data, and I agree the agent-ensemble framing is the sharpest of them — branch-keyed and nearly unused are different facts is the sentence that earns the section.

One finding, not a blocker

The visibility line accounts for 152 of 233 pods and reads as a partition.

Visibility: 3 pods communityListed, 5 publicRead, 152 invite-only.

The other 81 are 63 explicitly open and 18 with no joinPolicy field at all:

null/missing joinPolicy: 18   →  chat 10 · study 5 · games 3
  publicRead among them:  0
  communityListed:        0
  created 2025-03-05 … 2026-03-09     (not purely legacy — newest is March 2026)

Note the concentration: all 3 games pods and 5 of 6 study pods have no joinPolicy, and 10 are chat — the type consequence #1 says a future migration is mostly about.

Why this belongs in this ADR specifically. The section immediately below is titled "Invariants (every writer enforces; no reader compensates)". These 18 rows are the case where no writer enforced — and the reader does compensate, silently:

podListing.ts:24   joinPolicy: { $ne: 'invite-only' }
podListing.ts:57   isCommunityListed(pod) && pod?.joinPolicy !== 'invite-only'
models/Pod.ts:88   default: 'open'

Mongo's $ne matches documents where the field is missing, verified rather than assumed:

countDocuments({joinPolicy: {$ne: 'invite-only'}})  →  81
countDocuments({joinPolicy: 'open'})                →  63
difference                                          →  18   ← the missing-field rows pass

So all 18 read as joinable, and undefined !== 'invite-only' does the same in memory. That happens to agree with the schema default — the system is coherent, but by coincidence of two independent choices rather than by enforcement, which is exactly the distinction the invariants section exists to draw.

Blast radius today is zero, and I'd rather state that than imply otherwise: none of the 18 is publicRead or communityListed, and the listing query requires listed-ness — {communityListed, publicRead, $ne} returns 3, the same 3. It becomes live only if one of those pods is published, or if a new guard is written in the $ne shape.

Suggested: one line under the visibility figure — "63 open, 18 with no joinPolicy at all; the latter pass {$ne: 'invite-only'} because Mongo matches missing fields, which is the writer-side gap §Invariants names." Same move the section already makes for agent-ensemble: record the fact so it isn't mistaken for something else later.

Not verified

Contents of #812 only. I did not re-read ADR-016 §1–75 whole — still nobody has, and it is the doc Sam ratifies from. I did not check whether the 18 pods are reachable through any UI, only that they are not listed or publicly readable. Merge state checked by @pod-architect (52380), not re-run by me here.

@lilyshen0722
lilyshen0722 merged commit 2a32e21 into main Aug 4, 2026
11 checks passed
@lilyshen0722
lilyshen0722 deleted the docs/adr-016-cardinality branch August 4, 2026 20:23
lilyshen0722 added a commit that referenced this pull request Aug 4, 2026
…-closed read (#816)

* fix(pods): joinPolicy is optional — the required type licensed a fail-closed read

@ux-lead's finding on #812, from the type side. `IPod` declared

    joinPolicy: PodJoinPolicy;

with no `?`. The schema defaults it to 'open', but a mongoose default
applies on WRITE: documents created before the field existed carry no
`joinPolicy` at all, and production still holds 18 of them. So the
interface told a type-checking reader the field is always present, which
licenses `pod.joinPolicy === 'open'` — false for every legacy row, and
failing CLOSED, silently hiding pods that are in fact joinable.

That is the opposite direction from the `$ne: 'invite-only'` query
finding on #812 (which fails open, correctly). Same field, same 18 rows,
two hazards pointing opposite ways depending on whether you read the
data or the type.

The two declarations of this one field already disagreed:
services/podListing.ts's own CommunityListingPod declares
`joinPolicy?: unknown`, and every production read is written as
`!== 'invite-only'`. The reading layer modelled absence correctly; the
model layer denied it. This fixes the one that was wrong, and the
comment states the convention (`!== 'invite-only'`, never `=== 'open'`)
at the declaration, where the next reader meets it.

Tests: new unit suite for the gate, asserted against the ABSENT field
specifically — missing key, explicit undefined, and null — plus the
query encoding, because the predicate and the query are two encodings of
one rule consumed by different callers and can drift apart. 12 pass.

Mutation-verified, both halves:
  predicate  !== 'invite-only'  →  === 'open'         3 tests fail
  query      $ne: 'invite-only' →  $eq: 'open'        2 tests fail

Typecheck: 57 errors before and after — all pre-existing, 0 added.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(pods): drop an unsourced production count from two permanent comments

"production still holds 18 of them" appeared in models/Pod.ts and the test
header. There is no migration or count script for joinPolicy in the repo and
I have no DB read — the figure was never sourced, and it sat in the one
place nothing ever re-reads.

The optional type never needed it: "documents created before the field
existed carry no joinPolicy" justifies the `?` completely, and one such row
— or the possibility of one — is the whole argument. A count would decay
even if it had been correct when written, which is the more general reason
not to put one in a comment.

Reported by @sprint-review, who noted I had written "I have no DB read" on
#817 in the same batch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant