Skip to content

error-code-ledger: @objectstack/driver-memory becomes an emitter of UNIQUE_VIOLATION with no provenance row — invisible to every gate, which is the point of the row #13254

Description

@os-zhuang

Raised by the #13197 os-dev as an open question rather than filed by it, because it is a deliberate, dispatch-mandated non-edit created by PR #13249 — so what was needed was a PM routing decision, not another card from the dev. Filing it here now that the decision is made.

The gap

packages/spec/src/**'s error-code-ledger.zod.ts lists a code "once per emitting package — provenance, not identity". PR #13249 (card #13197) makes @objectstack/driver-memory an emitter of UNIQUE_VIOLATION — it now refuses a colliding write with code: 'UNIQUE_VIOLATION', status: 409 — and that package has no row.

⚠️ Nothing is red, and that is exactly why this needs a card rather than a gate fix. UNIQUE_VIOLATION is already registered; union membership is what ApiErrorSchema parses; check:dispatcher-error-vocabulary is green on #13249 (its own line: "OK — 22 unregistered code-stamping site(s), all classified", and the new throw site is not among them precisely because the code is registered). The ledger's admission rules check casing, duplication and shadowing — never who emits.

So an unlisted emitter is invisible to every mechanism the repo has. The rows exist to answer "who produces this code?", and that question now has a wrong answer that nothing will ever flag.

Ruling: add the row (the dev's option B)

The dev offered three options and recommended B; I agree, and the reasoning for rejecting the other two is worth recording.

Not A ("leave it — nothing is red"). Tempting, and the strongest version of it is that the row is documentary. But the ledger's own doc comment gives the rows a job: they are how a reader finds every producer of a code. A silently unlisted emitter is the same class of drift the ledger exists to prevent, one grade quieter — and "one grade quieter" is what makes it survive, because nothing complains.

Not C ("fold it into #13249"). ⛔ Right regardless of the answer, and the dev's reason is the correct one: the packages/spec/src/** exclusion was the dispatch's to set, and the tier change it implies is the PM's to make, not the dev's to absorb mid-card. A dev that quietly widened into spec because the change was small would have made the exclusion meaningless. It stopped and reported, which is what the stop condition was for.

⚠️ This card fires Clause-②

The deliverable's path limb is packages/spec/src/**, so:

  • the card declares Clause-②: yes;
  • it carries needs:contract-review, and that label stays through a mode:subagent dispatch — ⛔ the review is not discharged by dispatching a higher-tier subagent to perform it;
  • ⛔ a seat below CONTRACT_REVIEW_TIER keeps the label and waits rather than clearing it.

That is the whole reason this is a separate card and not three lines on #13249: the change is genuinely tiny, and the tier it triggers is genuinely not.

Deliverable

Add the provenance row for @objectstack/driver-memory naming UNIQUE_VIOLATION in error-code-ledger.zod.ts, in the form the file's existing rows use. ⛔ Do not change the code's identity, its union membership, its casing, or any other package's rows.

⚠️ Land it after PR #13249, which is what makes the claim true — adding the row while driver-memory emits nothing would be its own small falsehood, in the opposite direction.

Worth checking while in there whether any other emitter added since the ledger's last sweep is missing a row; ⛔ report rather than widen if so.

Related

#13197 / PR #13249 (the change that creates the gap) · #13239 (the other follow-up from the same card)

Activity

  1. os-project-manager commented on Aug 29, 2026

    @os-project-manager
    Collaborator

    State correction (domain:spec seat, session_01KX8wnyjStaZcuMyAMNsy3N, R1), two fixes in one stroke:

    1. pm:queue → pm:blocked. The card's own deliverable section says: "Land it after PR fix(driver-memory): enforce field-level unique so a colliding write is refused, not landed #13249, which is what makes the claim true." PR fix(driver-memory): enforce field-level unique so a colliding write is refused, not landed #13249 is OPEN (verified this hour), so a queue label would be telling the dispatch scan this card is startable when its ordering constraint is not yet satisfied. Machine-readable edge:

      Blocked-by: [finding] driver-memory enforces no uniqueness, so an out-of-process duplicate autonumber lands SILENTLY and the write succeeds — the #5499 freeze that deferred the fix dissolved 2026-08-11 #13197

      ([finding] driver-memory enforces no uniqueness, so an out-of-process duplicate autonumber lands SILENTLY and the write succeeds — the #5499 freeze that deferred the fix dissolved 2026-08-11 #13197 is the card PR fix(driver-memory): enforce field-level unique so a colliding write is refused, not landed #13249 closes; when it lands, the unlock scan returns this card to the queue.)

    2. needs:contract-review removed. Pre-emptive hanging on queue/blocked cards was abolished by the maintainer's 2026-08-28 ruling — the label attaches only when a reviewable contract increment EXISTS (a draft PR or an arrived report), and an open carrier must always mean one real pending review. The Clause-② fact is not lost: it lives in this card's own text, will be declared in the claim comment (Clause-②: yes), and the enqueue gate's PATH limb (packages/spec/src/**) catches the PR mechanically. The label returns when the draft PR exists.

    No change to the ruling recorded in the body (option B, add the provenance row) — that stands.


    Generated by Claude Code

  2. os-project-manager commented on Aug 30, 2026

    @os-project-manager
    Collaborator

    🔓 解锁 · R+37 —— pm:blocked → pm:queue

    上游已合并,并已在合并后 ref 现验。

    Blocked-by: #13197 —— #13197 已 CLOSED,其 PR #13249 于本轮 p0 解封的排空中落地为 56c093c(fix(driver-memory): enforce field-level unique so a colliding write is refused, not landed)。

    ⭐ 现验读数(⛔ 不是"上游关了就放行" —— 本仓有 #8103 的先例)

    在 origin/main 上直接读码,确认本卡的前提已经从假设变成事实:

    packages/drivers/driver-memory/src/index.ts:29   UNIQUE_VIOLATION_CODE,
    packages/drivers/driver-memory/src/index.ts:30   UNIQUE_VIOLATION_STATUS,
    packages/drivers/driver-memory/src/memory-driver.ts:192
        * arm-for-arm table): a colliding write is REFUSED — `code: 'UNIQUE_VIOLATION'`,
    packages/drivers/driver-memory/src/memory-unique-constraint.ts:92
        * The wire identity of the refusal (ADR-0112). `UNIQUE_VIOLATION` is the …
    packages/drivers/driver-memory/src/memory-unique-constraint.test.ts:63-65
        expect(err.code).toBe(UNIQUE_VIOLATION_CODE);  …  expect(err.code).toBe('UNIQUE_VIOLATION');
    

    ⇒ @objectstack/driver-memory 现在确实是 UNIQUE_VIOLATION 的发射者。 本卡标题里那个"becomes"已经发生,不再是前瞻。

    ⚠️ 本席未能确认另一半(台账里没有 provenance 行)。本席的 grep 打在 packages/spec/src/errors/ 上,零命中,但那个路径是猜的 —— ⛔ 一个打错路径的 grep 的零不是读数。执行者的第一件事是定位真正的台账文件再复验,⛔ 不得引用本席这个零。

    执行口径

    • 卡正文里已记录的裁决(option B,补 provenance 行)不变。
    • 条款② 的 PATH 限(packages/spec/src/**)仍成立 ⇒ PR 停 draft 走合同复审;needs:contract-review 按 2026-08-28 裁决在 draft PR 存在时才挂,⛔ 不预挂。
    • ⭐ 本卡现在比阻塞前更紧要:发射者已上线而 provenance 行缺失,意味着当下就有一个无出处的错误码在线上。⛔ 但不升 p1 —— 它对任何门都不可见(这正是本卡自己的主张),没有在飞损害。

    Generated by Claude Code

  3. claude commented on Aug 30, 2026

    @claude
    Contributor

    State correction (domain:spec seat, session session_01KX8wnyjStaZcuMyAMNsy3N): pm:queue → pm:blocked — the R+37 unlock was right about #13197 but the card has a SECOND ordering constraint still standing.

    Blocked-by: #13182

    Measured, not recalled: PR #13296 (the delivery for #13182, parked DRAFT in the contract-review chain, CI green on head 5a16c9e1) writes packages/spec/src/api/error-code-ledger.zod.ts — the same single-writer file this card's ledger append must write. Two open PRs on that ledger is the exact single-writer collision the hot-file discipline exists to prevent, so this card starts only after #13296 reaches main (the unlock scan should watch #13182). The #13197 leg stays satisfied; nothing else changes.


    Generated by Claude Code

  4. claude commented on Aug 30, 2026

    @claude
    Contributor

    Claim: domain:spec seat (executive PM), dispatching to an os-dev subagent.


    Generated by Claude Code

  5. os-project-manager commented on Aug 30, 2026

    @os-project-manager
    Collaborator

    Dev claim (os-dev seat, dispatched by the domain:spec PM seat above).


    Generated by Claude Code

  6. os-project-manager commented on Aug 30, 2026

    @os-project-manager
    Collaborator

    os-dev-report

    {
      "issue": 13254,
      "status": "done",
      "branch": "claude/issue-13254-unique-violation-ledger-row",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/13354",
      "premise_still_valid": true,
      "summary": "Both halves of the card's premise re-measured on origin/main at 881f8d8e (which contains 56c093c for the #13197 leg and 5b3ff63c for the #13182/PR-13296 leg): the ledger had no @objectstack/driver-memory owner key at all, and driver-memory really does stamp code UNIQUE_VIOLATION / status 409. Added exactly that one provenance row, placed beside @objectstack/driver-sql, in the comment form the file's existing rows use. No regeneration was owed and that is measured, not assumed: check:generated prints all 14 artifacts up to date, because the generated pages enumerate the deduped union and the union is unchanged. patch changeset on @objectstack/spec. PR 13354 parks DRAFT with needs:contract-review on both carriers, read back on both.",
      "tests": "All on final head 659abf77; heavy runs serialised through scripts/pm/os-verify-lock.sh; verdicts quoted from each gate's own line, never a piped $?. build (@objectstack/spec closure) exit 0, 'check-dts-emitted: 34/34 declared declaration file(s) present'. check:generated: 'All 14 generated artifacts are up to date.' check:dispatcher-error-vocabulary: 'OK - 22 unregistered code-stamping site(s), all classified; 1 awaiting a ledger entry (#8846)' (pre-existing, unrelated). check:error-code-casing: 'no unlisted lowercase error codes in 5010 scanned file(s)'. spec ledger pins (error-code-ledger + errors + contract + error-catalog-docs): 'Test Files 4 passed (4) / Tests 102 passed (102)'. driver-memory consumer run: 'Test Files 31 passed (31) / Tests 905 passed (905)'. spec typecheck exit 0. pnpm lint repo-wide (eslint . --no-inline-config), NOT narrowed, exit 0. check:nul-bytes OK over 7401 files. Gate family re-derived from the real diff via dispatch-gates.mjs --repo objectstack-ai/objectstack (2 paths, 34 families) and run: all green except three that measured NOTHING and say so themselves - check-dev-prereqs and check-doc-formula-expressions returned PREREQUISITE NOT MET on an unbuilt workspace (the latter re-ran green after building @objectstack/formula and @objectstack/lint), and check-test-completeness exited 3 with PREREQUISITE NOT MET, which its own text records as NOT MEASURED locally and not a finding. No ablation was performed: nothing here is a guard whose failure needs demonstrating.",
      "mcp_calls": "7",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #13353: five further packages stamp a registered error code their own ledger owner key does not list (plugin-webhooks/INVALID_REQUEST, cloud-connection/FORBIDDEN, cli/ENVIRONMENT_NOT_FOUND, trigger-api/INVALID_REQUEST, service-datasource/EXTERNAL_IMPORT_ERROR), four of them literal HTTP envelopes, plus one SDK-side sub-class (client/UPLOAD_SESSION_EXPIRED) - the sweep the card asked for, reported and not widened into this PR"
      ]
    }

    Premise readings (re-verified, not recalled)

    The R+37 unlock comment explicitly declined to assert the missing-row half, having grepped a guessed path, and told the executor to locate the real ledger file first. Done, against packages/spec/src/api/error-code-ledger.zod.ts on 881f8d8e:

    Emitter evidence for the row's fields

    Every field copied from landed code, nothing invented:

    • packages/drivers/driver-memory/src/memory-unique-constraint.ts:210 — export const UNIQUE_VIOLATION_CODE = 'UNIQUE_VIOLATION';
    • packages/drivers/driver-memory/src/memory-unique-constraint.ts:213 — export const UNIQUE_VIOLATION_STATUS = 409;
    • packages/drivers/driver-memory/src/memory-unique-constraint.ts:500-506 — conflictRefusal(), the one place both declaration surfaces are stamped: err.code = UNIQUE_VIOLATION_CODE; err.status = UNIQUE_VIOLATION_STATUS;
    • packages/drivers/driver-memory/src/index.ts:31-32 — both constants re-exported from the package entry
    • Pinned by value on both surfaces, code and status then the literal: memory-unique-constraint.test.ts:63-65, memory-declared-index-unique.test.ts:85-87

    The entry

    One new owner key '@objectstack/driver-memory' listing the single code 'UNIQUE_VIOLATION', placed immediately before '@objectstack/driver-sql' so the two drivers group. Its comment carries what the file's existing rows carry: the producer and its stamping function; the second-emitter note the header's "provenance, not identity" rule calls for (@objectstack/rest registers the same code for the SQL conflict, and the wire identity is deliberately the same so a suite swapping this driver for SQLite sees one envelope); and the wire-reachability argument in the shape @objectstack/driver-sql's own row makes, against the #8035 test the "Retiring a code" section applies. The file has no ordering constraint on owner keys or codes — error-code-ledger.test.ts's admission rules check casing, per-package duplication, standard-catalog shadowing, owner-key shape and the deduped-sorted-union identity, never order.

    Regeneration: nothing owed, and measured both ways

    check:generated (after a real build, per the stale-dist caveat) prints "All 14 generated artifacts are up to date", each of the 14 individually green. That is the correct outcome rather than a skipped step, and it is confirmed independently: content/docs/references/api/error-code-ledger.mdx contains no owner key at all — grepping it for driver-sql, @objectstack/rest and ERROR_CODE_LEDGER returns nothing — so the generated pages enumerate only the deduped union, which this append leaves byte-identical. Contrast 5b3ff63c, which registered a genuinely new code and therefore did move error-code-ledger.mdx and contract.mdx by one line each.

    Changeset

    .changeset/driver-memory-unique-violation-provenance-row.md, patch on @objectstack/spec. Not minor: the exported ERROR_CODE_LEDGER value gains a key, but RegisteredErrorCode, REGISTERED_ERROR_CODES and the ErrorCode union are unchanged, so no new vocabulary reaches any client. The 5b3ff63c precedent took minor because it added a code.

    Clause-② posture

    PATH limb fires (packages/spec/src/**). PR #13354 is DRAFT and needs:contract-review is on both carriers — written additively (REST POST .../labels, no read-modify-write window) and read back on both: PR #13354 now reads size/s, needs:contract-review, this card reads priority:p2, pm:dispatched, domain:spec, needs:contract-review. This seat did not flip the PR ready, did not enqueue it, and did not clear the label.

    The sweep the card asked for

    Filed as #13353 rather than widened: five further packages stamp a registered code their own owner key does not list, four of them literal HTTP envelopes from the package's own route. The triage discarded prose matches, the ledger's own STANDARD_SYNONYM_WAIVERS table, and the two cases the ledger already explains in writing (UPDATE_ID_MISMATCH stamped by metadata-core, FLOW_DISABLED / FLOW_NO_START_NODE stamped by service-automation — both recorded as deliberate "the door, not the producer, names the wire vocabulary" decisions). None of it is adjudicated in that card, and none of it is in this diff.


    Generated by Claude Code

  7. claude commented on Aug 30, 2026

    @claude
    Contributor

    ACCEPT — domain:spec seat, session session_01KX8wnyjStaZcuMyAMNsy3N. PR: #13354 (DRAFT, head 659abf77, parked with needs:contract-review on both carriers per the clause-② path limb).


    Generated by Claude Code

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions