Repository navigation
[finding] /discovery advertises transactionalBatch: true for a composition whose driver cannot roll back — and the 501's own remedy tells the caller to trust that flag #18997
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Sep 18, 2026 huangyiirene commented
on Sep 20, 2026 CollaboratorMore actionsClaim: PM loop round 6
Session:session_01NcPSwnmJHczmTu6FG7NMjE
Branch:claude/issue-18997-discovery-rollback-honest
Worktree:objectstack-issue-18997
Domain:domain:engine
Seat:domain:engine#1
File surface:packages/metadata-protocol/src/(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier— quoting this act'snode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/metadata-protocol/src/protocol.ts: 「Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s)… The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable).」 ⇒ a published-surface value changes and two populations must be told apart ⇒ default judgment tier, ⛔ not the floor.
Clause-②: no
Thread-read: none
Serial constraints cleared:lane in flight was 0 at claim; 26 open PRs enumerated — the only one inside this package is #19272 (claude/issue-19143-dataset-runtime-author-rules), whose files were READ, not inferred: packages/metadata-protocol/src/protocol.adr0005-org-override-rollback.test.ts and …/runtime-authoring-gate.dataset-writes.test.ts — ⛔ it does NOT touch protocol.ts, so this is ordinary concurrency, ⛔ not a serial constraint; same-batch siblings #18877 and #19082 are in packages/objectql and file-disjoint
⛔ Why
Clause-②: no, stated so it can be audited rather than trustedThe filing seat (
domain:spec, at contract-review tier) wrote: 「/discoveryis a published surface, so changing what it advertises is a contract change… ⛔ not assume 「it was a bug so it is free」」. That warning is taken, and it is the reason this line carries a justification instead of a bare word.Clause ② is not 「any contract change」. Its criterion is 「放宽接受集或扩大公开面」 — loosen the accept set, or widen the public surface — and
SKILL.mdstates the negative limb explicitly: 「条款②只指已发布契约面,拉回已声明契约不触它」.limb reading accept set unchanged — batchData({ atomic: true })answers 501 today in exactly the compositions whose advertisement is being corrected. ⛔ Nothing becomes acceptable that is refused today.public surface narrows — capabilities.transactionalBatchstops claimingtruefor a composition that cannot roll back. The key, its type and its meaning are untouched; only a lying value becomes honest.⇒ narrowing, ⛔ not widening ⇒
no, and the card stays indomain:enginerather than becoming spec-lane work under 「强制条款②…命中即 spec 车道的活」.⭐⭐ The falsifier that would overturn the line above — this is an instruction, ⛔ not a caveat
The card demands a control in both directions: 「a composition that CAN roll back must still advertise
true. A fix that makes the flag honest by making it alwaysfalseis worse than the bug.」⇒ If the measurement shows the honest predicate makes ANY composition advertise
truewhereorigin/mainadvertisesfalse, that is a WIDENING and thisnois wrong. In that case: ⛔ stop, ⛔ do not proceed on the strength of this claim, and report the fork — the card would be clause-②yesand would move lanes. The two populations the card names (a driver with nobeginTransaction; a transport declaringtransactionsUnsupported) are to be measured separately, because they may not warrant the same disposition.⛔ This is a premise the dev falsifies, ⛔ not a fence it works around.
Generated by Claude Code
- added a commit that references this issue
on Sep 20, 2026 huangyiirene commented
on Sep 20, 2026 CollaboratorMore actionsos-dev-report
{ "issue": 18997, "status": "done", "branch": "claude/issue-18997-discovery-rollback-honest", "pr": "https://github.com/objectstack-ai/objectstack/pull/19286", "session": "session_01NcPSwnmJHczmTu6FG7NMjE", "premise_still_valid": true, "head_commit": "1cb3e76ef", "summary": "packages/metadata-protocol/src/protocol.ts getDiscovery() now derives capabilities.transactionalBatch from engineCanRollBack(this.engine) instead of `typeof this.engine?.transaction === 'function'`, so the advertisement asks the same question the 501 NOT_IMPLEMENTED refusal in runAtomicBatch already asks (and whose remedy text sends the caller to that very bit). One line at the read site, a 6-case both-directions pin, and a patch changeset. PM mechanism assumption 1 (Clause-2 = no) was MEASURED, not assumed, and HOLDS: over all 16 compositions of the four inputs the two predicates read, 0 go false->true and 3 go true->false, so the accept set is untouched and only the advertisement narrows; no fork to report. Assumption 2 (landing site) held after re-locating by symbol: the derivation sits at protocol.ts:6333 and engineCanRollBack was already imported at line 6 for the 501 path. Assumption 3 held: PR #19272 does not touch protocol.ts. Card assignee was already set (huangyiirene) and the newest Claim comment (5748792120) names this branch; no assignee write was made.", "clause_2": { "declared": "no", "measured": "holds", "compositions_enumerated": 16, "widened_false_to_true": 0, "narrowed_true_to_false": 3, "evidence": "node probe over the 4 inputs both predicates read, comparing main's derivation with engineCanRollBack; VERDICT line: 'NO WIDENING - accept set untouched, advertisement narrows only'" }, "populations_measured_separately": { "a_no_beginTransaction_preexisting": "advertised true on main, now false; 2 of the 16 enumerated compositions; pinned as its own case, which also asserts the 501 arrives with the 'probe capabilities.transactionalBatch on /discovery first' sentence and that no write was attempted", "b_declares_transactionsUnsupported_from_18063": "advertised true on main, now false; 1 of the 16 enumerated compositions; pinned as its own case; shipped instance is TursoDriver on its remote transport (turso-driver.ts: transactionsUnsupported: this.transportMode === 'remote')", "control_can_roll_back": "still true - pinned twice (fake driver with beginTransaction: the atomic batch then COMMITS; real shipped InMemoryDriver against the built dist: driverSupportsTransactions=true and /discovery reports {\"enabled\":true}), plus the not-inspectable-registry host which keeps its old true" }, "tests": "PRE-FIX REPRODUCTION (pin file written first, run unmodified against unmodified protocol.ts): 'Tests 3 failed | 3 passed (6)' - population (a), population (b) and the predicate-agreement case red; the two TRUE-arm controls already green. POST-FIX: 'Test Files 1 passed (1) | Tests 6 passed (6)'. ABLATION from the committed state via scripts/ablation-replace.mjs with on-disk proof: anchor 'transactionalBatch: engineCanRollBack(this.engine),' 1 -> 0, blob 0b04d1ff5d3a -> 7c63e7631f05, run under the shared verify lock gave '3 failed | 3 passed'; restore verified by the tool AND by hand: blob after restore == blob at HEAD (0b04d1ff5d3a) and 'git diff HEAD' empty, 'git status --porcelain' empty. No dist preflight owed: the pin imports './protocol.js', so the mutated file IS on the test's resolution path (no dist hop). PACKAGE SUITE pnpm --filter @objectstack/metadata-protocol test :: exit 0 - 'Test Files 181 passed | 3 skipped (184) | Tests 2591 passed | 19 skipped (2610)'. TYPECHECK pnpm --filter @objectstack/metadata-protocol typecheck :: exit 0; coverage of the new file proven with 'tsc --noEmit --listFiles' (750 files listed, the new pin file among them). CROSS-PACKAGE CONSUMER (public runtime behaviour changed, so the real-engine pins were run): metadata-protocol + driver-memory rebuilt, then pnpm --filter @objectstack/objectql exec vitest run src/protocol-discovery.test.ts :: exit 0 - '32 passed (32)', including the real-ObjectQL-engine pin that must keep advertising true. REAL-DRIVER PROBE against the built dist: 'real MemoryDriver: typeof beginTransaction = function | supports.transactionsUnsupported = undefined | driverSupportsTransactions = true' and '/discovery capabilities.transactionalBatch = {\"enabled\":true}'. REPO-WIDE pnpm lint (eslint . --no-inline-config) :: exit 0 - run in full, so no narrowing is claimed and none is owed. All heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-18997; every verdict read from the wrapper's VERDICT line, every gate exit code captured before any pipe.", "gates": [ "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0", "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0", "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0", "node scripts/check-changeset-no-major.mjs --self-test :: exit 0", "node scripts/check-ci-filter-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0", "node scripts/check-comment-mask-adoption.mjs :: exit 0", "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0", "node scripts/check-comment-mask-corpus.mjs :: exit 0", "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0", "node scripts/check-empty-changeset.mjs --self-test :: exit 0", "node scripts/check-keyed-text-bounds.mjs :: exit 0", "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0", "node scripts/check-plugin-teardown-shape.mjs :: exit 0", "node scripts/check-registry-log-declared.mjs :: exit 0", "node scripts/check-registry-log-declared.mjs --self-test :: exit 0", "node scripts/check-rest-log-spy-declared.mjs :: exit 0", "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0", "node scripts/check-system-context-census.mjs :: exit 0", "node scripts/check-system-context-census.mjs --self-test :: exit 0", "node scripts/check-undeclared-dep-imports.mjs :: exit 0", "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0", "node scripts/docs-audit/check-affected-docs.mjs :: exit 0", "node scripts/docs-audit/check-drift-comment.mjs :: exit 0", "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0", "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0", "pnpm check:changeset-gate-self-tests :: exit 0", "pnpm check:cross-package-test-inputs :: exit 0", "pnpm check:dispatcher-error-vocabulary :: exit 0", "pnpm check:doc-authoring :: exit 0", "pnpm check:driver-memory-census :: exit 0", "pnpm check:dts-closure :: exit 0", "pnpm check:dual-build-cjs-loads :: exit 3", "pnpm check:durability-log-level :: exit 0", "pnpm check:engine-double-contract :: exit 0", "pnpm check:filter-alias-parity :: exit 0", "pnpm check:lean-entry-closure :: exit 3", "pnpm check:logger-receiver-detach :: exit 0", "pnpm check:nul-bytes :: exit 0", "pnpm check:objectql-double-limit :: exit 0", "pnpm check:objectui-changeset :: exit 0", "pnpm check:org-identifier :: exit 0", "pnpm check:page-declaration-shape :: exit 0", "pnpm check:pm-changeset-deadline-census :: exit 0", "pnpm check:published-files :: exit 0", "pnpm check:query-options-erasure :: exit 0", "pnpm check:refd-timer-probe :: exit 0", "pnpm check:slot-lookup :: exit 0", "pnpm check:sourcemap-no-sources-content :: exit 0", "pnpm check:test-source-alias :: exit 0", "pnpm check:tier-file-adoption :: exit 0", "pnpm check:type-check-coverage :: exit 0", "pnpm check:type-check-debt :: exit 3", "pnpm check:watch-hint-literal :: exit 0", "pnpm check:where-matcher :: exit 0", "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0" ], "gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ... :: exit 0 - '59 derived famil(ies) accounted for - 56 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)'. The 3 exit-3s are PREREQUISITE NOT MET (nothing measured, never a pass): check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt each read a whole-workspace dist/ that this checkout does not carry (69, 2 and 19 packages named respectively); CI's Build Core supplies that prerequisite. check:plugin-teardown-shape --self-test first exited 3 as well (shallow clone could not reach its pinned fixture commit 621a4876); after 'git fetch --depth=1 origin 621a4876...' it exits 0 - recorded here as exit 0, and the diff-judging half (the same script without --self-test) was exit 0 throughout. Derivation ran against this branch's head; a STALE TREE warning names two commits that landed on origin/main meanwhile (#19162, #19242) - the only file among them that feeds the derivation is dispatch-gates.mjs, whose change is to its own --self-test roster, so the 59 families are unchanged for this diff.", "line_budget": null, "api_writes": "2 - POST /repos/objectstack-ai/objectstack/pulls (draft PR #19286) x1, POST /repos/objectstack-ai/objectstack/issues/18997/comments (this report) x1. No label write: the dispatch budgeted 2 endpoints and named no label, so the intersection is empty. No assignee write. GET reads only besides those.", "mcp_calls": "0 - no MCP GitHub tool was called, read or write.", "files_changed": [ "packages/metadata-protocol/src/protocol.ts (+21 / -8: the derivation line plus its comment)", "packages/metadata-protocol/src/protocol.discovery-transactional-batch-honesty.test.ts (new, 6 cases, both directions)", ".changeset/18997-discovery-transactional-batch-honest.md (new, @objectstack/metadata-protocol patch)" ], "deviations": [ "3 derived gate families NOT MEASURED (exit 3, PREREQUISITE NOT MET): check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt. Each needs a whole-workspace build this checkout does not have; a repo-scale build is CI's run (Build Core), not this card's. Declared, not silently narrowed - and explicitly NOT read as a pass.", "origin/main advanced 2 commits (#19162, #19242) after the branch point and was NOT merged down: neither touches packages/metadata-protocol, and the merge queue rebuilds on current main. AGENTS.md multi-agent discipline section 10 scopes the full re-run to overlapping packages or behaviour, and there is no overlap.", "Population (b) is pinned structurally (a driver that inherits beginTransaction and declares supports.transactionsUnsupported) rather than by booting the real TursoDriver remote transport, which needs a live remote client. The shipped instance is cited from source, not executed." ], "open_questions": [], "out_of_scope_findings": [ "noted, not filed: publishPackageDrafts (same file) keeps an engine-only `typeof this.engine?.transaction === 'function'` probe for its inTxn helper. Not a defect today and not in the three filable classes - it advertises nothing to a caller and both branches deliver the same guarantee, because engine.transaction() takes its declared non-transactional path (ADR-0119 D1) for exactly the drivers the probe would exclude, so swapping the predicate there is behaviourally a no-op. Carrier: whoever makes draft-publish atomicity a promise rather than a best effort; if nobody does, there is no carrier and nothing rots.", "noted, not filed: packages/runtime/src/http-dispatcher.ts hardcodes transactionalBatch { enabled: false } for the dispatcher producer. Read and confirmed correct - that face mounts no /batch route - so this is a verification note, not a finding." ] }
Generated by Claude Code
huangyiirene commented
on Sep 20, 2026 CollaboratorMore actionsACCEPT —
domain:engine#1,session_01NcPSwnmJHczmTu6FG7NMjE. PR #19286. Reviewed against GitHub, ⛔ not against the report's self-narrative. Written 2026-09-20T09:48Z.Checklist conclusion
item reading PR shape draft ✓ · base main✓ · first lineFixes #18997✓ —Fixesis right, the card closes on mergeclosing-keyword full-body scan 6 keyword hits, only one adjacent to an issue number ( Fixes #18997); the rest are prose (「closed capability vocabulary」, 「Pre-fix reproduction」…) ⇒ ⛔ no keyword sits beside another open cardscope 3 files, +237 / -8, all underpackages/metadata-protocol/src/+ the changeset ⇒ nocontent/docs/releases/, no unrelated file, ⛔ nopackages/specchangeset @objectstack/metadata-protocol: patch;package.jsonread:private: false⇒ published ⇒ changeset owed and present ✓refusal assertions (ADR-0112) ✓ both limbs present — toMatchObject({ status: 501, code: 'NOT_IMPLEMENTED' })on all three FALSE cases, plusexpect(insert.mock.calls).toHaveLength(0)(no write attempted) and the remedy sentence assertedboth-directions control ✓ TRUE arms pinned twice ( toBe(true)+ the batch actually COMMITs), so 「made honest by making it always false」 is excludedclause ② no— neither limb hits: diff touches nopackages/spec/src/**, declaration isno⇒ no contract review owedpath surface for landing no governed path; 237 + 8 = 245≪ 5000 ⇒ ordinary queue landing, ⛔ not the four-part human-merge route⭐ Spot-check that did NOT rely on the dev's enumeration
The dev grounds
Clause-②: noon a 16-composition probe. I checked the structure instead, onorigin/main,packages/core/src/utils/migration-journal.ts:105:typeof e?.transaction !== 'function' → false …otherwise → !defaultDriver || driverSupportsTransactions(defaultDriver)⇒ new predicate =
A ∧ (B ∨ C); old predicate =A.A ∧ X ⊆ Afor anyX, so the advertised set can only shrink — ⭐ no widening is possible by construction, ⛔ not merely unobserved across 16 rows. The!defaultDriverarm is also what makes the 「registry not inspectable」 pin real: such a host keeps its oldtrue.⇒ the card's own worst case (「a fix that makes the flag always
false」) is excluded by the same reading, sinceA ∧ (B ∨ C)retains every composition where the driver can transact.Deviations, recorded ⛔ not waived
- 3 gate families NOT MEASURED —
check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debt, each exit 3 = PREREQUISITE NOT MET. Each reads a whole-workspacedist/this checkout does not carry. ⭐ Correctly declared and explicitly not read as a pass; CI'sBuild Coresupplies the prerequisite, so they are judged there, ⛔ not here. Accepted as a declared limit, ⛔ not as a green. - Self-report understates its own diff: the report says
protocol.ts (+21 / -8); GitHub reads+26 / -8. Totals reconcile (237 / 8), and the behavioural change is the single linetransactionalBatch: engineCanRollBack(this.engine)— the remainder is the explanatory comment. ⛔ Recorded because the review reads GitHub, not the report; no action owed. - Population (b) is pinned structurally, not by booting a real
TursoDriverremote transport (which needs a live remote client); the shipped instance is cited from source (turso-driver.ts,transactionsUnsupported: this.transportMode === 'remote'), ⛔ not executed. Declared by the dev; accepted — the structural pin is what a unit tier can carry. api_writes2 (PR create ×1, this card's report ×1),mcp_calls0 ⇒ ⛔ no deny-rostered tool, ⛔ noupdate_pull_request.out_of_scope_findings: both are 「noted, not filed」 and both land in## Acceptance noteswith a named carrier or a verification result. Checked against the three filable classes — ⛔ neither qualifies: thepublishPackageDraftsprobe is behaviourally a no-op today (ADR-0119 D1 takes the same path for exactly the excluded drivers), and the dispatcher's hardcodedfalsewas read and is correct (that face mounts no/batchroute). ⇒ ⛔ no card owed.
⏸️ Landing is NOT taken yet — CI has not converged
Read at PR head
1cb3e76ef8639c7b24d1cb474ada9a92d245fa42, latest-run-per-check-name (31 raw runs == 31 names, so no name is double-counted): 12 success · 16 in_progress · 3 skipped · 0 failure.Lint & Repo Gatesand theType Check · {workspace, consumer gates, debt ledger}legs arein_progress⇒ ⛔ the required floor is unread, so ⛔ noready_for_review, ⛔ noauto_merge, ⛔ no enqueue. ⭐ Anin_progressgate is an honest reading, ⛔ not a defect in the delivery — the dev's contract ends at the draft PR. This seat carries it to MERGED from here.
Generated by Claude Code
- 3 gate families NOT MEASURED —
huangyiirene commented
on Sep 20, 2026 CollaboratorMore actionsLANDED — PR #19286 →
origin/maine3b3cdd2df.domain:engine#1,session_01NcPSwnmJHczmTu6FG7NMjE. Written 2026-09-20T10:44Z.⛔ Not judged by the merge event. Two independent readings, both taken on
origin/mainafter a fresh fetch:reading instrument result it is a squash, ⛔ not a merge commit git show -s --format='%p' e3b3cdd2df | wc -w1 parent the change is really there git grep -n "transactionalBatch: engineCanRollBack" origin/main -- packages/metadata-protocol/src/protocol.tsprotocol.ts:6351⭐ The second reading is the one a merge event cannot give: it says the behaviour is on the branch, ⛔ not merely that a PR closed.
Queue timing for this lane's record:
added_to_merge_queue2026-09-20T10:15:05Z → merged 2026-09-20T10:43:39Z ⇒ ~28.5 min, inside the 25–29 min band this seat carries.State cleared in one stroke
pm:dispatchedremoved and the assignee cleared in a singlelabel-write.mjscall, read back clean:bug,priority:p2,domain:engine· 0 assignees. ⛔ The type and domain labels stay — ⭐ grading is not state. The card closed itself onFixes #18997(closed/completed), which is the intended shape for a card whose fix is complete.What is now true on
main/discovery'scapabilities.transactionalBatchis derived fromengineCanRollBack(this.engine)— the same predicaterunAtomicBatch's501 NOT_IMPLEMENTEDalready trusts. ⇒ the advertisement and the refusal no longer disagree, and the 501's own remedy text (「probecapabilities.transactionalBatchon /discovery first」) now routes the caller to a signal that is right in the case the remedy exists for.⚠️ Consumer-visible, and stated rather than assumed: two compositions stop advertisingtrue— (a) a default driver with nobeginTransaction, (b) one that inherits it and declaressupports.transactionsUnsupported(the shipped instance beingTursoDriveron its remote transport). ⛔ Nothing that was accepted becomes refused: both already answered 501. A client that hard-assertstransactionalBatch === trueagainst such a composition now fails at startup instead of at its first atomic batch.⭐ The 「always
false」 failure mode the card warned about is excluded structurally, ⛔ not just by the 6-case pin: the new predicate isA ∧ (B ∨ C)against the oldA, and its!defaultDriverarm keeps a non-inspectable registry on its oldtrue.
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
Filed by the
domain:specPM seat from the at-tier contract review of PR #18890 (record5728707331, verdict PASS). ⛔ Not a regression from that PR and it did not block it — the no-method half of this is PRE-EXISTING onmain; #18063 adds a second population to a blindness that was already there, and its tombstone prose says so truthfully.The contradiction, in one line
/discoveryadvertisestransactionalBatch: truefor a composition whose default driver cannot run a transaction, andbatchData({ atomic: true })then answers 501 NOT_IMPLEMENTED — while the 501's own remedy text tells the caller to 「probecapabilities.transactionalBatchon/discoveryfirst」.⇒ The gate's prescribed remedy routes the caller to a signal that is wrong in exactly the case the remedy exists for.
Measured
packages/*/metadata-protocol/protocol.ts:6333derives the capability from the ENGINE alone:engine.transactionis always a function. The question that decides the outcome is whether the DEFAULT DRIVER can roll back, which the repo already has a predicate for —engineCanRollBack(packages/core/src/utils/migration-journal.ts), now itself declaration-aware, and consumed atprotocol.ts:12315where the atomic batch answers 501.Two populations reach the false
true:beginTransactionat all — ⭐ pre-existing onmain, not introduced here;transactionsUnsupported— the population [Decision] TursoDriver.beginTransaction 的 any 掩盖的是 Liskov 违例 —— 基类声明该服从谁,A 还是 C(重建自 #17878) #18063 added.Remedy candidate (⛔ verify before taking it)
Read
engineCanRollBack(this.engine)atprotocol.ts:6333instead of thetypeofprobe — the same predicate the 501 path already trusts, so the advertisement and the refusal stop disagreeing./discoveryis a published surface, so changing what it advertises is a contract change, even when the new value is the honest one: a consumer that today seestrueand proceeds would begin seeingfalseand take its fallback. Whoever takes this must decide and declareClause-②on that basis, ⛔ not assume 「it was a bug so it is free」. Measure the two populations separately — the pre-existing one and #18063's — because they may not warrant the same disposition.⭐ Prove it with a control in both directions: a composition that CAN roll back must still advertise
true. A fix that makes the flag honest by making it alwaysfalseis worse than the bug.Four-facet reading
true, sends an atomic batch, gets 501.Scope: metadata-protocol /
domain:engine. ⛔ Notpackages/spec; the spec half of #18063 is landed and correct.Generated by Claude Code