Skip to content

[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

@os-litant

Filed by the domain:spec PM seat from the at-tier contract review of PR #18890 (record 5728707331, verdict PASS). ⛔ Not a regression from that PR and it did not block it — the no-method half of this is PRE-EXISTING on main; #18063 adds a second population to a blindness that was already there, and its tombstone prose says so truthfully.

The contradiction, in one line

/discovery advertises transactionalBatch: true for a composition whose default driver cannot run a transaction, and batchData({ atomic: true }) then answers 501 NOT_IMPLEMENTED — while the 501's own remedy text tells the caller to 「probe capabilities.transactionalBatch on /discovery first」.

⇒ 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:6333 derives the capability from the ENGINE alone:

typeof this.engine?.transaction === 'function'

engine.transaction is 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 at protocol.ts:12315 where the atomic batch answers 501.

Two populations reach the false true:

  1. a default driver with no beginTransaction at all — ⭐ pre-existing on main, not introduced here;
  2. a transport that declares transactionsUnsupported — the population [Decision] TursoDriver.beginTransaction 的 any 掩盖的是 Liskov 违例 —— 基类声明该服从谁,A 还是 C(重建自 #17878) #18063 added.

Remedy candidate (⛔ verify before taking it)

Read engineCanRollBack(this.engine) at protocol.ts:6333 instead of the typeof probe — the same predicate the 501 path already trusts, so the advertisement and the refusal stop disagreeing.

⚠️ /discovery is 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 sees true and proceeds would begin seeing false and take its fallback. Whoever takes this must decide and declare Clause-② 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 always false is worse than the bug.

Four-facet reading

  1. Who is hurt — a client that does exactly what the error message instructs: probes the capability, believes true, sends an atomic batch, gets 501.
  2. What the rule should be — an advertised capability answers the same question the refusal path asks; ⛔ one may not be derived from the engine while the other is derived from the driver.
  3. Cost of the honest fix — one line at the read site, plus a declared contract change on a published surface and its tests.
  4. What was built instead — two derivations of one capability, which is how they drifted.

Scope: metadata-protocol / domain:engine. ⛔ Not packages/spec; the spec half of #18063 is landed and correct.


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 20, 2026
  2. huangyiirene commented on Sep 20, 2026

    @huangyiirene
    Collaborator

    Claim: 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's node 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 trusted

    The filing seat (domain:spec, at contract-review tier) wrote: 「/discovery is 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.md states 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.transactionalBatch stops claiming true for 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 in domain:engine rather 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 always false is worse than the bug.」

    ⇒ If the measurement shows the honest predicate makes ANY composition advertise true where origin/main advertises false, that is a WIDENING and this no is wrong. In that case: ⛔ stop, ⛔ do not proceed on the strength of this claim, and report the fork — the card would be clause-② yes and would move lanes. The two populations the card names (a driver with no beginTransaction; a transport declaring transactionsUnsupported) 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

  3. huangyiirene commented on Sep 20, 2026

    @huangyiirene
    Collaborator

    os-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

  4. huangyiirene commented on Sep 20, 2026

    @huangyiirene
    Collaborator

    ACCEPT — 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 line Fixes #18997 ✓ — Fixes is right, the card closes on merge
    closing-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 card
    scope 3 files, +237 / -8, all under packages/metadata-protocol/src/ + the changeset ⇒ no content/docs/releases/, no unrelated file, ⛔ no packages/spec
    changeset @objectstack/metadata-protocol: patch; package.json read: 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, plus expect(insert.mock.calls).toHaveLength(0) (no write attempted) and the remedy sentence asserted
    both-directions control ✓ TRUE arms pinned twice (toBe(true) + the batch actually COMMITs), so 「made honest by making it always false」 is excluded
    clause ② no — neither limb hits: diff touches no packages/spec/src/**, declaration is no ⇒ no contract review owed
    path 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-②: no on a 16-composition probe. I checked the structure instead, on origin/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 ⊆ A for any X, so the advertised set can only shrink — ⭐ no widening is possible by construction, ⛔ not merely unobserved across 16 rows. The !defaultDriver arm is also what makes the 「registry not inspectable」 pin real: such a host keeps its old true.

    ⇒ the card's own worst case (「a fix that makes the flag always false」) is excluded by the same reading, since A ∧ (B ∨ C) retains every composition where the driver can transact.

    Deviations, recorded ⛔ not waived

    1. 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-workspace dist/ this checkout does not carry. ⭐ Correctly declared and explicitly not read as a pass; CI's Build Core supplies the prerequisite, so they are judged there, ⛔ not here. Accepted as a declared limit, ⛔ not as a green.
    2. 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 line transactionalBatch: engineCanRollBack(this.engine) — the remainder is the explanatory comment. ⛔ Recorded because the review reads GitHub, not the report; no action owed.
    3. Population (b) is pinned structurally, not by booting a real TursoDriver remote 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.
    4. api_writes 2 (PR create ×1, this card's report ×1), mcp_calls 0 ⇒ ⛔ no deny-rostered tool, ⛔ no update_pull_request.
    5. out_of_scope_findings: both are 「noted, not filed」 and both land in ## Acceptance notes with a named carrier or a verification result. Checked against the three filable classes — ⛔ neither qualifies: the publishPackageDrafts probe is behaviourally a no-op today (ADR-0119 D1 takes the same path for exactly the excluded drivers), and the dispatcher's hardcoded false was read and is correct (that face mounts no /batch route). ⇒ ⛔ 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 Gates and the Type Check · {workspace, consumer gates, debt ledger} legs are in_progress ⇒ ⛔ the required floor is unread, so ⛔ no ready_for_review, ⛔ no auto_merge, ⛔ no enqueue. ⭐ An in_progress gate 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

  5. removed their assignment
    on Sep 20, 2026
  6. huangyiirene commented on Sep 20, 2026

    @huangyiirene
    Collaborator

    LANDED — PR #19286 → origin/main e3b3cdd2df. domain:engine#1, session_01NcPSwnmJHczmTu6FG7NMjE. Written 2026-09-20T10:44Z.

    ⛔ Not judged by the merge event. Two independent readings, both taken on origin/main after a fresh fetch:

    reading instrument result
    it is a squash, ⛔ not a merge commit git show -s --format='%p' e3b3cdd2df | wc -w 1 parent
    the change is really there git grep -n "transactionalBatch: engineCanRollBack" origin/main -- packages/metadata-protocol/src/protocol.ts protocol.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_queue 2026-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:dispatched removed and the assignee cleared in a single label-write.mjs call, read back clean: bug, priority:p2, domain:engine · 0 assignees. ⛔ The type and domain labels stay — ⭐ grading is not state. The card closed itself on Fixes #18997 (closed / completed), which is the intended shape for a card whose fix is complete.

    What is now true on main

    /discovery's capabilities.transactionalBatch is derived from engineCanRollBack(this.engine) — the same predicate runAtomicBatch's 501 NOT_IMPLEMENTED already trusts. ⇒ the advertisement and the refusal no longer disagree, and the 501's own remedy text (「probe capabilities.transactionalBatch on /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 advertising true — (a) a default driver with no beginTransaction, (b) one that inherits it and declares supports.transactionsUnsupported (the shipped instance being TursoDriver on its remote transport). ⛔ Nothing that was accepted becomes refused: both already answered 501. A client that hard-asserts transactionalBatch === true against 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 is A ∧ (B ∨ C) against the old A, and its !defaultDriver arm keeps a non-inspectable registry on its old true.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions