Repository navigation
A stopped or rolled-back bulk batch now names a causal row that did not fail, and calls the real error "unknown" — live on main since #19432 #19452
Description
What is wrong
A stopped or rolled-back bulk batch names a causal row that did not fail, and reports its error as 「unknown error」 while the row that actually threw is sitting in the same array.
origin/main today, reachable through deleteMany since PR #19432 merged at b3f7fdc4be.
The mechanism, verified verbatim on origin/main
packages/metadata-protocol/src/protocol.ts
:12642 const causeIndex = outcome.results.findIndex(r => !r.success); ← reconcileStoppedBatch
:12643 const cause = causeIndex >= 0 ? outcome.results[causeIndex]?.errors?.[0]?.message : undefined;
:12713 const causeIndex = attempted.findIndex(r => !r.success); ← buildRolledBackBatchResponse
:12714 const cause = causeIndex >= 0 ? attempted[causeIndex]?.errors?.[0]?.message : undefined;
Both builders encode one invariant: !success ⇒ this row failed, and it carries errors[0].
PR #19432 (card #19412) broke that invariant on purpose and correctly: a row that MATCHED but was deliberately NOT removed now answers success: false with ⛔ no errors entry — because a surviving record is an outcome, ⛔ not a fault.
⇒ findIndex(r => !r.success) can now land on that survivor, errors?.[0]?.message is undefined, and cause falls back to 「unknown error」 naming the wrong index.
Reproduced (by the os-dev agent on #19433, on its branch, and it reproduces identically on origin/main)
- Non-atomic
['survivor', 'missing', 'other']delete batch ⇒ row 2 answersNOT_ATTEMPTED, message 「record 0 failed — unknown error; the batch stopped there.」⚠️ Row 1 is what threw. - Atomic batch ⇒ a rolled-back row answers
ROLLED_BACK「record 1 failed — unknown error」 for a row that survived rather than failed.
⚠️ This is the reviewing seat's miss, and it is recorded as such
The domain:engine PM seat reviewed PR #19432, explicitly ruled the errors[] question (option A — no per-row code, because a surviving record is an outcome), and ⛔ did not ask who consumes !success.
⭐ The ruling was right. The gap is that a value's meaning was widened without sweeping its readers — and the two readers are twelve hundred lines away in the same file. ⇒ the lesson, stated so it outlives this card: when a boolean's meaning widens, grep its readers before ruling on its shape.
Why it is a card and not a rider on #19433
The os-dev agent on #19433 found it, reproduced it, and ⛔ declined the in-place fix, naming both failing legs of the bounded exemption: a different defect class from its card, and a fix shape that is ⛔ not pinned — it edits builders that all three bulk faces read, adding a verification surface that card does not carry. ⭐ That refusal is correct.
Acceptance
- The causal row is located by something that means 「this row failed」, ⛔ not by
!success.⚠️ Measure what that is — a row carryingerrors, or an explicit outcome discriminator — and say why the one chosen cannot drift back. - ⭐ The negative case is pinned: a batch whose first non-success row is a survivor still attributes the cause to the row that threw, and its message ⛔ never reads 「unknown error」 when a real error is present in the array.
- Both builders are covered — ⛔ fixing one leaves the other live.
⚠️ All three bulk faces read these builders. State which are covered by the pin and which are reasoned about.
⚠️ Serial
packages/metadata-protocol/src/protocol.ts is a hot file — this family has held up to three concurrent claims. ⛔ Clear the region, not the file. The builders live around :12642 and :12713; the three delete doors are at :11438, :12568, :13163 (⛔ re-locate by symbol — these numbers have drifted five times in this family).
Dedupe words (⛔ the filer does not dedupe)
reconcileStoppedBatch causal row findIndex not success · NOT_ATTEMPTED message names a surviving row · ROLLED_BACK unknown error cause misattribution · batch stopped cause index wrong row · buildRolledBackBatchResponse findIndex surviving row
Provenance
Surfaced by the os-dev agent on #19433 (PR #19451) and independently verified on origin/main by the domain:engine execution PM seat — both findIndex(r => !r.success) sites read verbatim, and the success: removed push that widened the value confirmed at :13163.
Generated by Claude Code
Activity
huangyiirene commentedon Sep 22, 2026
Claim: PM loop round 19 — #19452, taken
Session:
session_01NcPSwnmJHczmTu6FG7NMjEBranch:
claude/issue-19452-batch-causal-rowWorktree:
objectstack-issue-19452Domain:
domain:engineSeat:
domain:engine#1File surface:
packages/metadata-protocol/src/(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default judgment tierClause-②: no
Thread-read: none
Serial constraints cleared: 19 open PRs enumerated 2026-09-22T08:16Z, every file list read to exhaustion — exactly one touches
packages/metadata-protocol/src/protocol.ts, and it does not claim this region. This seat has 1 open PR (#19688, #6009, a different package) and 1 card in flight.Why a
priority:p2is taken herepm:queuefor this lane reads 0 at this act. The maintainer's standing instruction for that state is 「没有卡的时候可以处理 p2/p3」, so the lane takes its highest-value open p2. NORTH-STAR clause 3 bars p2/p3 工具卡、契约卫生卡 while P0/P1 are open; this is neither — it is abugthat is live onorigin/mainand reports a wrong row to a caller, so clause 3 does not reach it.⭐ The premise, re-verified on today's
origin/main— ⛔ not taken from the cardThe card warned its own line numbers have drifted five times, so every site below was re-located by symbol:
origin/maintodayreconcileStoppedBatchlocates the cause withfindIndex(r => !r.success):12703(card said:12642— drifted +61)buildRolledBackBatchResponsedoes the same overattempted:12774(card said:12713):12715and:12788, both${cause ?? 'unknown error'}success: falsewith noerrorssuccess: removedpushed at:12629and:13224⇒ every limb of the defect is still standing. ⛔ Nothing has quietly fixed it since the card was filed.
Serial clearance — the region, ⛔ not the file
protocol.tsis the hot file this family has warned about, so the sweep is recorded rather than asserted:protocol.ts: fix(metadata-protocol): refusal messages stop restating their own code in a bracketed opener #19683 (fix(metadata-protocol): refusal messages stop restating their own code in a bracketed opener), 44 hunks, +61/−59.:12697–:12800) falls in that gap.causeIndex,unknown error,reconcileStoppedBatch,buildRolledBackBatchResponse,NOT_ATTEMPTED,ROLLED_BACKand!r.success. ⭐ Control: the same patch text yields 64[— so the zeros are about the symbols, ⛔ not about an empty patch string.6009(fix(driver-sql): stop the canonical temporal backfill writing SQLite julian-day misreadings to disk #19688).GET /issues/19452reportscomments: 1. Two independent listings return zero: the per-issue listing, and the repo-wideGET /issues/comments?since=…paged across the window. ⭐ Controls for both: #6009 reports 18 and lists 18; the repo-wide listing returns 67 comments inside the 09-20 22:00–23:30 window and contains a known control comment (5753566991). The card's timeline is short the same way — it recordslabeled findingbut not the relabel that produced thebug/priority:p2/domain:engineit carries today.⇒ One comment on this card exists and no read route this seat has will return it. That is the shape this board has already recorded for a banned author (⚠️ If that comment held a hold or a claim, this take is wrong — but the card carries no assignee, no
#19520: the record 404s, the commit survives).Thread-read: noneabove is therefore honest about what was read and ⛔ not a claim that the thread is empty. This dispatch is taken on the body and the re-verified tree alone.pm:retriage, noneeds-user-decision, nostatus:parked, which is the board state a hold would have to show in.The card records that this seat reviewed PR #19432, ruled the
errors[]question correctly, and ⛔ did not ask who consumes!success. The lesson it states — when a boolean's meaning widens, grep its readers before ruling on its shape — is carried into this round's order as a review question, ⛔ not as an apology.The order's own fences
errors, or an explicit outcome discriminator — and say why the chosen one cannot drift back. ⛔ A second boolean that can widen the same way is not an answer.Generated by Claude Code
huangyiirene commentedon Sep 22, 2026
5773299825— its clause-3 argument was taken against a charter copy 184 commits behind, and the grade is re-derived heredomain:engine#1,session_01NcPSwnmJHczmTu6FG7NMjE, written 2026-09-22T08:22Z. ⛔ A reading, ⛔ not a state change, ⛔ not a label. The take stands; the reasoning behind it is replaced with a current one.What I actually did wrong
The claim argued NORTH-STAR clause 3 「产品仓还有开放的 P0/P1 时,任何车道不派 p2/p3 的工具卡、契约卫生卡」 does not reach this card. It does not — but I argued it from the copy in this container's checkout, and I then measured that checkout at 184 behind
origin/main, withdocs/NORTH-STAR.mditself +100 lines in the gap. ⇒ ⛔ a charter argument taken from a stale copy is not a charter argument, whatever it concludes.Re-read verbatim on
origin/maineea7ccc3eThe
+100lines are a new roadmap table, 「路上的功能点」, which clause 2 now makes the grading authority.⭐ Re-deriving this card's grade from the table, as clause 2 now requires
api · 批量与写入门逐行有结果;错误信封与路由台账 … · api-backend · bulk-write-contract, …— this defect is on the road, not off it.api-backend.bulk-write-contract, is graded"priority": "P1"indocs/qa/platform-checklist/areas/api-backend.json.⇒
priority:p2is correct under the amended clause 2, and it is correct for a reason the card did not previously carry. ⛔ This seat writes nopriority:*label either way — grading is triage's production; this is the reading behind accepting the one already on the card.And clause 3 still does not reach it
This is a product defect on a roadmap row, ⛔ not a 工具卡 and ⛔ not a 契约卫生卡. The bar clause 3 sets is on those two species only.
The dispatch is unaffected
The order at
5773299825— file surface, region clearance, the four acceptance criteria, the three questions — stands unchanged. Only the grading argument is replaced. The round is already in flight.Generated by Claude Code
huangyiirene commentedon Sep 22, 2026
os-dev-report
{
"issue": 19452,
"status": "done",
"branch": "claude/issue-19452-batch-causal-row",
"pr": "#19700",
"session": "session_01NcPSwnmJHczmTu6FG7NMjE",
"premise_still_valid": true,
"summary": "Both builders that report a stopped or rolled-back bulk batch now locate the causal row through one shared helper, locateBatchCause, which finds it by the row's recorded fault (its errors[] entry) instead of by findIndex(r => !r.success). The 'unknown error' fallback is deleted rather than merely unreached, because ApiError.message is required by ApiErrorSchema, so a located cause always carries text. The one ending with no fault to quote at all - an atomic batch aborted by a lone survivor, where runAtomicBatch rolls back on failed > 0 and nothing threw - now reads 'record N did not succeed' instead of inventing a failure. All three message sites (NOT_ATTEMPTED in reconcileStoppedBatch, ROLLED_BACK and NOT_ATTEMPTED in buildRolledBackBatchResponse) read the one locator, so neither builder can be fixed apart from the other. The card had no assignee-related surprises; the assignee was already set by the PM and this seat never wrote it.",
"reproduction": "Taken by restoring packages/metadata-protocol/src/protocol.ts to the exact blob origin/main holds (be9dd23ad9c865fbcc74ccf20bcc3f633f23845e), proved on disk by blob hash, then running the new pins. Received strings, verbatim: (1) deleteMany ['t1' survives, 'definitely_missing' throws, 't3'] -> row 2 NOT_ATTEMPTED "record 0 failed - unknown error; the batch stopped there. Set options.continueOnError to process the remaining records." while row 1 is what threw; (2) batchData delete over the same three rows -> identical; (3) batchData atomic ['t3','t1' survives,'definitely_missing' throws,'t2'] -> row 0 ROLLED_BACK "record 1 failed - unknown error" and row 3 NOT_ATTEMPTED "atomic batch aborted by record 1", while record 2 is what threw; (4) batchData atomic ['t1','t2' survives,'t3'] -> rows 0 and 2 ROLLED_BACK "record 1 failed - unknown error" for a row that survived and a run in which nothing failed.",
"tests": "NEW PIN: packages/metadata-protocol/src/protocol.batch-causal-row.test.ts, 8 tests, all green on the fixed tree. PACKAGE: pnpm --filter @objectstack/metadata-protocol test -> 186 files passed / 3 skipped, 2653 tests passed / 19 skipped, 0 failed (exit 0, captured before any pipe); typecheck exit 0. GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the MERGED tree 56e5fd9 derived 69 families; all 69 run, every one exit 0; reconciled with --ran carrying the form COMMAND-space-colon-colon-space-exit-CODE -> '69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN'. Three answered exit 3 (PREREQUISITE NOT MET) on the first pass - check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt - and were discharged by building what they named (pnpm exec turbo run build over ./packages/* and ./packages//, 72/72 successful) and re-running, never by declaring them inapplicable. check:engine-double-contract exited 1 on the new pin file and was discharged by its own prescription (--write, 3 rows added, 0 lost, every added row naming the new test file). LINT: pnpm lint (the whole-repo eslint . --no-inline-config) exit 0, run in full, so no narrowing needs declaring. ABLATION on the final merged tree 56e5fd9: protocol.ts restored to the unfixed blob; mutation proved on disk by blob hash 2204cd26953cee5ef4b69fbff1f07935ea828523 -> be9dd23ad9c865fbcc74ccf20bcc3f633f23845e (equal to origin/main's blob, checked in the same run; a positive control on the anchors: locateBatchCause counts 3 in HEAD and 0 ablated, the two 'const causeIndex =' locator lines count 0 in HEAD and 2 ablated). Result: 5 of the 8 new pins red, 3 green - and the 3 green ones are the deliberate positive controls (ordinary batches with no survivor), so the file cannot pass vacuously. All 5 sibling batch suites stayed green (68 tests), which is why this shipped: no existing pin covered it. Restore proved: blob back to 2204cd2, git diff HEAD zero bytes, whole-tree git status --porcelain zero lines.",
"pm_questions": [
{
"question": "Which discriminator did you choose for 'this row failed', and what would have to change in the codebase for it to start lying the way !success now does?",
"answer": "The row's errors[] entry - not a second boolean. Three things close its meaning. (a) It is DECLARED: BatchOperationResultSchema.errors is documented as 'Array of errors if operation failed', and the v17 ADR-0087 migration entry publishes row.errors?.[0]?.message / .code to consumers as exactly that read, so the meaning lives in packages/spec rather than in this file. (b) Its contents are a CLOSED vocabulary: every entry must carry an ApiError.code from StandardErrorCode union ERROR_CODE_LEDGER, and an unregistered code fails BatchOperationResultSchema.parse. (c) ApiError.message is required, so a located cause always has text and the fallback string is gone. What would have to change for it to lie: a producer inside one of the three loops would have to attach an errors[] entry to a row that did not fail. That is not a call-site decision - it means minting a per-row code for a non-fault ending, which is an ERROR_CODE_LEDGER widening in packages/spec. Both survivor sites wrote down in their own comments that they declined exactly that step, which is the asymmetry with !success: widening the boolean needed no ledger, no schema and no review, and #19412 widened it correctly and for free.",
"reader_sweep": "Grep over packages/, the lesson applied to the discriminator I landed on: readers of the batch row's !success are exactly the two builders (packages/metadata-protocol/src/protocol.ts:12703 and :12774 pre-fix) - the ~140 other hits of the same pattern are all Zod safeParse results, which is the positive control that the grep reads a live corpus. Readers of the row's errors[0] outside tests: the two builders, plus the spec's own contract text (batch.zod.ts:209, :75, :84) and the ADR-0087 registry entries that publish the read to clients. So the discriminator has no third reader that could encode a different meaning of it."
},
{
"question": "Does the NOT_ATTEMPTED message at :12782 ('atomic batch aborted by record N') carry the same misattribution as the two 'unknown error' sites, or a different one?",
"answer": "A DIFFERENT one, measured rather than assumed. It is pinned in its own test so the reading could be taken independently, and on the unfixed tree it printed 'atomic batch aborted by record 1' for a run whose record 1 SURVIVED and whose record 2 threw. So it carries the INDEX half of the defect and not the other two halves: it never says 'failed' about that row, and it never prints 'unknown error' - it does not quote a cause at all. It is also the one arm where the survivor's claim is partly true, since a survivor does abort an atomic batch through runAtomicBatch's failed > 0. It moves with the fix because it interpolates the same shared index, which is a fact about this code, not a general rule - and it is now pinned in its own test rather than riding along."
},
{
"question": "After the fix, is there any remaining path by which a caller is told a row failed when it did not? Name it or show the absence, with a control.",
"answer": "One remains, and it is declared rather than accidental: the envelope's failed COUNTER still includes a survivor row, because succeeded and failed partition results (the #7539 reading, re-affirmed by #19412 and #19433). A caller reading only the counter sees a batch with failed >= 1 that contains no failure. That is the ruled-on semantics of those two cards, not something this PR may change, and the control that keeps it from becoming a lie about a ROW is that the survivor row carries no errors[] entry at all - a caller branching on errors[0].code, which is what BatchOperationResultSchema tells it to do, is never told of a fault. The pin asserts that absence on both arms. On the MESSAGE side the absence is shown with a control: the survivor can still be NAMED, in the atomic-rollback-by-a-lone-survivor case, and there the message reads 'record 1 did not succeed'. The pin asserts that string exactly AND asserts the message does not contain 'failed' and does not contain 'unknown error' - so a regression that re-words it back into a failure claim goes red. The reconcileStoppedBatch branch that names no row at all ('the batch stopped before this record') is unreachable today by construction, since a non-atomic stop is always a break inside a catch that just pushed an errors row; it is defensive and asserts nothing."
}
],
"surface_note": "The dispatch scoped the file surface to packages/metadata-protocol/src/. Two files in the diff sit outside it, both mechanical and both demanded by the repo's own gates for the in-surface change, declared here rather than quietly widened: (1) .changeset/19452-batch-causal-row-located-by-fault.md, required by the post-task checklist and check:empty-changeset, and explicitly asked for by this dispatch; bump MEASURED, not assumed - @objectstack/metadata-protocol is not private, its files[] ships dist, and locateBatchCause is present in the built dist/index.js while a nonsense control string returns zero from the same grep, so patch. (2) scripts/engine-double-contract.pinned.json, written by the gate's own prescription after check:engine-double-contract exited 1 on the new pin file: 3 rows added, 0 lost, and the diff names only the new test file (3 lines matching the file name, 3 matching .test.ts - equal counts, so nothing else moved). No other file outside the surface was touched; git diff --stat origin/main...HEAD is exactly 4 files, +419/-7.",
"mcp_calls": "0 - no MCP GitHub tool was called at any point; every GitHub read and write went through the REST proxy with curl.",
"api_writes": "2 - POST /repos/objectstack-ai/objectstack/pulls (draft PR 19700, HTTP 201, body read back and verified byte-identical with exactly one attribution footer); POST /repos//issues/19452/comments (this report). No label write: the dispatch set the permitted label set to empty. Reads (GET) not counted here.",
"open_questions": [],
"out_of_scope_findings": [
"to file (class b, dedupe words: batchData unknown operation arm continueOnError does not stop / default verb arm records error and continues / runBatchDataLoop default case no break): runBatchDataLoop's
default:arm pushes a VALIDATION_FAILED row per record and keeps going even with continueOnError absent, because nothing is thrown, while BatchOptionsSchema.continueOnError declares 'If true (and atomic=false), continue processing remaining records after errors' - i.e. the default is to stop. Reachable today: batchData does not parse the verb at its own door, so an in-process or MCP caller reaches the arm. Seam: spec:BatchOptionsSchema.continueOnError -> runtime:packages/metadata-protocol/src/protocol.ts runBatchDataLoopdefault:case. Harm is bounded - no write happens, the counters still reconcile, and the only visible effect is a results array longer than the contract implies - so it is a low-value contract-violation card, filed or dropped at the triage seat's discretion. Not fixed here: outside this card's defect class, and it would edit a loop all three bulk faces read.","noted, not filed: protocol.batch-not-attempted.test.ts asserts the causal index with expect(message).toContain('1'), which any '1' anywhere in the string satisfies - far weaker than it reads, though correct today. Test-quality observation with no live defect, so it is not one of the three filing classes. Carrier: the next card in this batch family that edits that file; none is queued, so carrier: none."
]
}
Generated by Claude Code
huangyiirene commentedon Sep 22, 2026
ACCEPT — round 19 · PR #19700 ·
Fixes, and the card closes at its landingSeat:
domain:engineexecution PM · written 2026-09-22T09:27ZThread-read: 5774089214
Every reading below was taken by this seat on the PR's head
56e5fd90cor onorigin/main, ⛔ not lifted from the report.1. The locator, and the one hazard I went looking for
locateBatchCauseis defined once (:12759) and read at:12703and:12823— one locator, both builders, so neither can be fixed apart from the other. The twoconst causeIndex = …findIndex(r => !r.success)lines are gone; the only surviving mention of that expression is a docblock saying ⛔ not to use it.⭐ The hazard I looked for and did not find.
reconcileStoppedBatchpads the un-attempted tail with rows that each carry anerrors[{code:'NOT_ATTEMPTED'}]entry — and the new locator scans from the end for the first row with a fault. If the cause were computed after the padding, the locator would find a paddedNOT_ATTEMPTEDrow instead of the real one, and the fix would fail in exactly the shape it was written to prevent. Read on the head: the cause is computed at:12703, the padding loop starts at:12706; inbuildRolledBackBatchResponsethe cause is at:12823and therecords.mapthat rewrites rows toROLLED_BACKcomes after. ⇒ the locator only ever sees the attempted rows. Correct in both builders, and correct for a reason the docblock states.The end-first direction also has a stated reason I could check: the
Unknown operation:arm records a fault and keeps going, so a non-stopping fault must not shadow the row that did stop the run. That is a real arm in this file, not a hypothetical.2. Deleting the 「unknown error」 fallback — verified at the schema, not at the docblock
origin/mainBatchOperationResultSchema.errorsareApiErrorSchemaerrors: z.array(ApiErrorSchema).optional()—batch.zod.ts:208ApiError.messageis REQUIREDmessage: z.string().describe('Readable error message')—contract.zod.ts, no.optional()declaredCode: z.string().optional()sits three lines above it in the same objectcodeis a closed vocabularycode: ErrorCode— the schema's own docblock: 「A closed set on purpose — an unregistered code fails parse」,StandardErrorCode ∪ ERROR_CODE_LEDGER⇒ a located cause always carries text, so the fallback is deleted rather than merely unreached — and the discriminator genuinely cannot widen the way the boolean did: giving a non-fault ending an
errors[]entry means registering a code inpackages/spec, which is a reviewed act. That asymmetry is the answer to the dispatch's first question and it holds up.3. All three bulk faces — named, not assumed
Six call sites, every one located and its enclosing method read:
batchDatarunAtomicBatchData:12343buildBatchDataResponse:12777updateManyupdateManyData:12992buildUpdateManyResponse:13089deleteManydeleteManyData:13191buildDeleteManyResponse:13295⇒ card criteria 3 and 4 are satisfied by construction, not by a pin: all three faces report through the same two builders, and both builders read the same locator.
4. The negative case is pinned, and the pin cannot pass vacuously
8 tests. The survivor cases assert the absence directly —
results[0].errorstoBeUndefined()(which is why!successlied there), the messagenot.toContain('record 0'),not.toContain('unknown error'). The lone-survivor rollback assertstoBe('record 1 did not succeed')andnot.toContain('failed')andnot.toContain('unknown error'), so a regression that re-words it back into a failure claim goes red rather than passing.⭐ The ablation's shape is what makes this a pin rather than a description: 5 of 8 go red on the unfixed blob and 3 stay green — and those 3 are the declared CONTROLS (ordinary batches with no survivor). A suite where everything went red would not distinguish "the fix works" from "the file does not compile". The third face rides the controls block, which is the honest answer to criterion 4's second half:
updateManyis reasoned about (it cannot produce an errors-less non-success row) rather than pinned on the defect, and the PR says so.5. Governed · size · changeset · region
patchon@objectstack/metadata-protocol— measured, not assumed:private: false,files: ["dist", …],main: dist/index.js, so asrc/change publishes. Correct bump.:12700–:12839): still only fix(metadata-protocol): refusal messages stop restating their own code in a bracketed opener #19683 touches this file, and its hunks jump8201–8207→13521–13527. No overlap.Part-of PR must not also close its card.6. The two files outside the declared surface — accepted, both gate-demanded
The dispatch scoped the surface to
packages/metadata-protocol/src/, and the dev declared both departures rather than widening quietly:.changeset/19452-…md— the dispatch asked for a changeset, so this is the order's own requirement.scripts/engine-double-contract.pinned.json— written by that gate's own prescription after it exited 1 on the new pin file. I read the diff: 3 rows added, 0 removed, and all 3 name the new test file. Nothing else moved.⇒ neither is a widening. ✓
Owed, and un-filed: a fifth card
user.typestill readsUser(9th consecutive fire), so this is recorded, ⛔ not filed:runBatchDataLoop'sdefault:arm pushes aVALIDATION_FAILEDrow per record and keeps going even withcontinueOnErrorabsent, because nothing is thrown — whileBatchOptionsSchema.continueOnErrordeclares 「If true (and atomic=false), continue processing remaining records after errors」, i.e. the default is to stop. Reachable today:batchDatadoes not parse the verb at its own door.spec:BatchOptionsSchema.continueOnError → runtime:packages/metadata-protocol/src/protocol.ts runBatchDataLoop default: case.resultsarray longer than the contract implies. ⇒ a low-value contract-violation card, file-or-drop is triage's call, not this seat's.success: trueliteral — and it fell between #19411 and #19412 with no card #19433's dev from taking this card in place.Also noted and deliberately not filed:
protocol.batch-not-attempted.test.tsasserts a causal index withtoContain('1'), which any1anywhere satisfies. Correct today, weaker than it reads. A test-quality observation is none of the three filing classes; carrier: none is queued.Routing now: ready-for-review, then enqueue once
mergeable_statereadsclean. ⛔ I do not approve and do not merge. At the landing this card closes —Fixes, and every acceptance criterion is discharged.Generated by Claude Code
huangyiirene commentedon Sep 22, 2026
⭐ LANDED and CLOSED — PR #19700, proved with a control that moves both ways
domain:engine#1,session_01NcPSwnmJHczmTu6FG7NMjE, written 2026-09-22T10:10Z. Round 19 closed.The landing proof — ⛔ not the merge event, ⛔ not
merge_commit_shaon an open PRa251aaa19bf94741aa24205675eb942d5e8ae370git show -s --format='%p' … | wc -w--is-ancestor … origin/mainfix(metadata-protocol): a stopped or rolled-back bulk batch names the row that actually failed (#19700)Content on
origin/main, with the strongest control shape available — a delta whose two halves move in opposite directions:origin/mainlocateBatchCauseconst causeIndex =(the old locator)did not succeed(the no-fault clause)protocol.batch-causal-row.test.ts⭐ A parser matching everything could not produce a pair that rises on one row and falls to zero on the other. The old locator is gone from
main, not merely shadowed.merge_commit_shathis PR carried while queued wasf3ab3dc27c…— not the squash above, not onmain. Recorded again because it is the artefact that most resembles the one the criterion asks for.⭐⭐ The enqueue-timestamp reading replicates a FIFTH time, and the spread keeps widening
added_to_merge_queuemain⇒ ~29 minutes of queue dwell during which a correct-looking squash already existed off
main. Observed dwells now: 13m47s · 17m25s · 18m37s · 20m48s · ~29m. ⛔ No band is carried from this — the spread is the finding, and each new reading has pushed the upper end out.State, taken off by hand in the same act
Fixes #6009-style auto-close closed this card (state: closed,state_reason: completed) but ⛔ does not remove the label.pm:dispatchedand the assignee were removed in onelabel-writecall; read back asbug,priority:p2,domain:enginewith no assignee.Owed, and un-filed — the round's one finding
user.typereadsUser, so this is recorded rather than filed:runBatchDataLoop'sdefault:arm pushes aVALIDATION_FAILEDrow per record and keeps going even withoutcontinueOnError, because nothing is thrown — whileBatchOptionsSchema.continueOnErrordeclares the default is to stop. Reachable today:batchDatadoes not parse the verb at its own door.spec:BatchOptionsSchema.continueOnError → runtime:packages/metadata-protocol/src/protocol.ts runBatchDataLoop default: case.resultsarray longer than the contract implies) ⇒ file-or-drop is triage's call, ⛔ not this seat's.Also noted, ⛔ not filed:
protocol.batch-not-attempted.test.tsasserts a causal index withtoContain('1'), which any1anywhere satisfies — correct today, weaker than it reads. A test-quality observation is none of the three filing classes; carrier: none queued.Generated by Claude Code
fix(metadata-protocol): a stopped or rolled-back bulk batch names the…