Skip to content

docs(review): rule 24 gains the update shape of the strict-subdocument drop - #1900

Merged
lilyshen0722 merged 5 commits into
mainfrom
kai/rule24-strict-subdoc-instance
Sep 25, 2026
Merged

lilyshen0722 merged 5 commits into
mainfrom
kai/rule24-strict-subdoc-instance

Conversation

@lilyshen0722

@lilyshen0722 lilyshen0722 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

What. docs/development/review-checklist.md rule 24 gains a second earned instance: the same silent-drop defect in its update shape, which the rule did not previously carry.

Why this instance and this rule. vera asked at 74186 where #1898's strict-subdocument finding belongs, and suggested rule 37's family. The class is rule 24's: a strict schema silently drops an undeclared path on the write, so a fixture that builds its own object proves the reader can read a field and never that the field can be there. Rule 37 answers a read-side question — whether a read tells you the KEY exists, the VALUE is non-empty, or the value can AUTHENTICATE. Filing a write-side drop under the read-side rule would hide it from the next reviewer grepping for it, so it goes where the class already lives.

The instance. In #1898 the writer was an updateOne $set rather than a constructor: $set { 'config.webhookUrlRef': ref } on a row the Discord connect route had just created was dropped in silence because Integration.config is a strict subdocument that did not declare the path. backend/models/Integration.ts now declares webhookUrlRef: String beside the other Discord config paths. Same tell, same instrument as #1734/TASK-043's constructor case: the route suite stayed green on inline-built fixtures, and the only assertion that saw the ref missing is against real Mongo after the create call (backend/__tests__/unit/routes/integrations.discordTokenCopy.test.js).

For the gate (@wren). Two things worth your scepticism rather than mine:

  1. Is rule 24 the right home, or does the write-side drop deserve its own number? My argument is that the class is identical (strict schema + undeclared path + a green read-side test) and only the writer's shape differs; if you read it as two rules, say so and I will cut the second properly.
  2. Is the instance accurate? The locators are checkable: the declaration in backend/models/Integration.ts, the real-Mongo assertion above, and the feat(discord): encrypt the webhook URL at rest, keyed by (integrationId, kind) (TASK-124 part 2) #1898 diff for the drop itself.

Docs-only, one file, no code. Rule 37 landed as #1894; this adds no number.

Resolved after the gate's hold (272ca93d → 8aa22c85). wren: rule 24 is the right home (no new number), the real-Mongo suite claim holds, and two locators did not hold — both cited paths contain the ref only on the unmerged #1898 (git grep webhookUrlRef origin/main -- backend frontend docs returns nothing), so the clause now names #1898 as the carrier of both, describes the witness by mechanism (create route against a MongoMemoryServer-backed model, read back with .lean()) and carries the measured census (5 suites name the ref; the other 4 mock Integration or seed the ref themselves). Third correction: my "grepping webhookUrlRef returned nothing" sentence described main before #1898 lands, not the rev the lesson came from. It now says the instructive thing instead: the name already appeared across the backend and the schema declaration was the one place it was missing, so a grep for the name cannot find this defect — the comparison rule 24's check opens with can.

Fourth correction (720e6d21 → this head), two swaps only: the count is dropped ("the name already appeared across the backend") and "this rule opens with" becomes "this rule's check opens with" — the rule opens with its thesis; the comparison is the first step of its check ("The check is two greps and one round-trip…"). Reverse-applying those two phrases to the new line reproduces 720e6d21 byte-for-byte, so nothing else moved.

Second hold round (8aa22c85 → this head). wren: the writer is not updateOne (it is findByIdAndUpdate on both Model writers, routes/integrations.ts:474 and discordService.ts:345; the migration's updateOne runs on mongoose.connection.collection() and strict mode never touches it), and config is a nested path of a strict schema, not a subdocument (schema.path('config') undefined, schema.nested.config true, schema.get('strict') true — measured from the model, and the wording rule 34 already uses). The clause now teaches the split — Model writes are stripped, raw-collection writes are not — so a grep for updateOne cannot lead a reader to the one writer that cannot drop. The backfill's drop is marked derived, not observed.

Merge order: after #1898 — this line cites code that only #1898 puts on main. Enqueue #1898 (cleared at 715e187c) first, then this at 8aa22c85.

…t drop

vera asked (74186) where #1898's strict-subdocument finding belongs. The class is
already rule 24's — a `strict` schema silently drops an undeclared path on the
write, so a fixture that builds its own document proves the reader can read a
field and never that the field can be there — and rule 37 answers a different
question (whether a read tells you the key exists, the value is non-empty, or the
value can authenticate). Filing a write-side drop under the read-side rule would
hide it from the next reviewer grepping for it, so the instance goes where the
class already lives.

The added instance is the update shape of the same defect, which rule 24 did not
carry: `$set { 'config.webhookUrlRef': ref }` on a row the Discord connect route
had just created was dropped in silence because `Integration.config` is a strict
subdocument that did not declare the path. Same tell as the constructor case and
the same instrument closes it — the only test that saw it asserts against real
Mongo after the create call and reads the ref back.

Docs-only, one file.
…its locators

wren's hold at 272ca93: rule 24 is the right home and the suite claim holds,
but two locators do not, and the PR merges after #1898. Both halves are the same
fact — the declaration (`webhookUrlRef: String` in the `config` subdocument) and
the witnessing assertion (`integrations.discordTokenCopy.test.js`) exist only on
the unmerged #1898: `git grep webhookUrlRef origin/main -- backend frontend docs`
returns nothing.

So the clause now names #1898 as the carrier of both, states the pre-merge grep
as the rev this was earned at (true forever, and it is the state a reader
arriving early would find), and says the merge order outright. The witness is
described by its mechanism — the create route against a MongoMemoryServer-backed
model, read back with `.lean()` — and the census is the measured one: of the five
suites that name the ref, the other four mock `Integration` or seed the ref
themselves, so none of them writes it through the route.

Docs-only, one line.
…oun for `config`

wren's two corrections (74203/74204, relayed by connector-ops at 74205) applied,
both verified at the premise first:

1. The writer is not `updateOne`. On #1898 the connect route writes with
   `Integration.findByIdAndUpdate(..., { $set: { 'config.webhookUrlRef': ref } })`
   (`routes/integrations.ts:474`) and the backfill does the same
   (`services/discordService.ts:345`). The only `updateOne` is the migration
   script's, and it runs on `mongoose.connection.collection()` — a raw-collection
   write, which strict mode never touches. So the clause now teaches the split
   instead: **a write through the Model is stripped, a raw-collection write is
   not**, and it says why the obvious grep points the wrong way (grepping
   `updateOne` finds the one writer that cannot drop).

2. `config` is not a subdocument. Measured from the model at 715e187 rather than
   restated: `schema.path('config')` is `undefined`, `schema.nested.config` is
   true, `schema.paths` carries no `config` key, and `schema.get('strict')` is
   true. The clause now says "nested path of a strict schema" and points at rule
   34, which already describes this same `config` that way.

The backfill's drop is marked *derived*, not observed (same schema, same `$set`,
and both writers landed in one PR, so the declaration fixed both before either
merged). #1898 itself is untouched: its model comment repeats "subdocument", and
moving a cleared head that is in the enqueue for a noun is not worth it.

Docs-only, one line.
…tance

wren's third correction: the instance was earned ON #1898, and there
`webhookUrlRef` appears in 15 backend files, not zero. My sentence described main
before #1898 lands — a state nobody reading the merged checklist will ever see,
and not the rev the lesson came from. Re-measured to confirm her premise:
`git grep -l webhookUrlRef origin/kai/124-webhookurl-encryption -- backend` = 15.

The replacement says the thing that is actually instructive: the name was already
everywhere (readers, both Model writers, five suites) and the schema declaration
was the one place it was missing, so a grep for the name cannot find this defect —
what finds it is rule 24's opening comparison, every path the code reads against
the schema that must declare it.

Docs-only, one line.
…erent

wren's two swaps, exactly as given:

  "at the rev this was earned the name already appeared in **15 backend files**"
  -> "the name already appeared across the backend"

  "the comparison this rule opens with" -> "the comparison this rule's check opens with"

She is right on the second: the rule opens with its thesis ("A fixture that builds
its own document proves the reader can read a field"), and the comparison is the
first step of its check ("The check is two greps and one round-trip: for each field
the code reads cross-layer, confirm the writing schema declares that exact path").

Nothing else changed: no other path, no other word on the line.
@lilyshen0722
lilyshen0722 added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 242709a Sep 25, 2026
19 of 22 checks passed
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