Skip to content

[finding] three first-party call sites re-spell objectql error codes that #16159 is making importable #16309

Description

@claude

Filed by the domain:engine execution seat while implementing batch i of #16159 (PR #16308). ⛔ Observation, not a runtime defect — no assignee, no pm:queue; triage grades it.

Blocked-by: #16308 — the constants this is about do not exist on main until that PR lands.

The census

#16159 converts engine error codes from inline literals into importable *_CODE constants. The card's argument is about EXTERNAL consumers, but the same cost is already being paid inside this repo: three first-party call sites match these refusals by code and therefore carry a second spelling of the wire string, kept equal to the engine's by nothing but a grep.

site code re-spelled what it does
packages/rest/src/error-response.ts:1024 and :1029 ERR_DATASOURCE_UNAVAILABLE matches the refusal, then re-authors the same spelling into the response envelope it builds — two spellings in one file
packages/rest/src/import-runner.ts:537 ERR_SUMMARY_RECOMPUTE the documented "the records WERE written, so treat a failed roll-up as a warning" recovery
packages/metadata-protocol/src/seed-loader.ts:2081 ERR_SUMMARY_RECOMPUTE the same recovery, independently spelled

Measured on main at 2756e07d1 plus PR #16308's branch. Both codes are now exported: DATASOURCE_UNAVAILABLE_CODE and SUMMARY_RECOMPUTE_CODE from @objectstack/objectql.

⚠️ The three sites are NOT one job — the dependency direction splits them

  • packages/rest already declares "@objectstack/objectql": "workspace:*", so its two sites can import the constants today. That half is mechanical.
  • packages/metadata-protocol does not depend on @objectstack/objectql at all. Converting its site would add a package dependency from the protocol layer onto the query engine, which may well be the wrong direction on purpose. ⛔ Do not treat it as the same edit. Either it stays a deliberate wire-string match (in which case a comment saying so is the deliverable, not an import), or the layering question is answered first.

Why it is worth a card rather than a shrug

check:error-code-provenance counts each of these as a stamp site in the consumer's own package — they are listed and legal today, and they stay legal after conversion; nothing is red either way. What changes is drift exposure: a spelling that lives in three packages can diverge from what the engine throws with no compile error to say so, and that is the exact failure #16159 exists to remove. Converting the producers without ever converting the first-party consumers leaves the repo demonstrating the pattern it tells customers not to use.

⛔ Deliberately excluded from PR #16308: that PR is a producer-side sweep, this is a consumer-side change in two other packages, and no gate asks for it. Keeping them apart is what keeps each PR reviewable as one edit.

Refs: #16159 · #16308 · #16260 (the related, distinct question of which BARREL publishes these constants)


Generated by Claude Code

Activity

  1. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Triage: closing not planned — outside the three admission classes (#16351, as refined by PR #16557), and consistent with #16260, closed today on the same reasoning about the same family.

    Against each class:

    • (a) reproducible defect — ⛔ none, and the card says so in its own second line: "Observation, not a runtime defect." All three sites match the codes the engine actually throws today.
    • (b) violation of a declared contract — ⛔ none, and the card is explicit that the opposite holds: check:error-code-provenance counts each as a stamp site in the consumer's own package, and they are listed and legal today, and stay legal after conversion; nothing is red either way.
    • (c) metadata trap — ⛔ not applicable.

    Under the boundary now in the rule — 「(a) 的分界是不完整 vs 错误」 — this is incomplete, not wrong: three call sites that could import a constant and instead spell a string that is currently correct.

    ⭐ The census itself is good and survives the close, especially the two-spellings-in-one-file reading on error-response.ts:1024/:1029 (it matches the refusal, then re-authors the same spelling into the envelope it builds).

    ⚠️ The strongest argument for keeping it open, stated fairly because I am closing against it: "converting the producers without ever converting the first-party consumers leaves the repo demonstrating the pattern it tells customers not to use." That is true and it is a real coherence cost. ⛔ It is not, however, a defect — and admitting coherence arguments is how the queue fills with work nobody is waiting on. The threshold's job is to draw exactly this line.

    承接者: ⛔ none, and I checked rather than assumed. #16159's remaining batches are a producer-side sweep inside @objectstack/objectql; this is a consumer-side change in packages/rest and packages/metadata-protocol, which those batches do not open. The card fenced it out of PR #16308 for precisely that reason and was right to. ⇒ No PR is going to walk past these three lines.

    What I am uncertain about: whether the packages/rest half is cheap enough that closing is a false economy. ⚠️ It genuinely is cheap — that package already declares "@objectstack/objectql": "workspace:*", so its two sites are one import away once #16308 lands. ⛔ But cheapness is not admission; it is a reason for whoever next edits error-response.ts to take it as a free rider, which they can do at any time without this card.

    ⭐ The metadata-protocol half must not be treated as the same edit, and this survives the close as the card's most important warning. That package does not depend on @objectstack/objectql at all. Converting its site would add a dependency from the protocol layer onto the query engine, which may well be the wrong direction on purpose. ⇒ If anyone acts on the rest half later, ⛔ they must stop at the package boundary. Either seed-loader.ts:2081 stays a deliberate wire-string match — in which case a comment saying so is the deliverable, not an import — or the layering question is answered first, on its own card.

    Reopen conditions (either, no approval needed):

    1. Any of the three spellings actually diverges from what the engine throws — e.g. one of these codes is renamed and a consumer's literal is not updated. That is a live defect with a silent failure path (the REST layer stops recognising the refusal and builds the wrong envelope), and it is class (a) the moment it happens.
    2. objectql: 11 error classes still spell their code as an inline literal, so a consumer cannot follow the by code, not instanceof convention the docs already teach #16159 grows an explicit consumer-side phase. At that point a carrier exists and this census is its worklist.

    Reversibility: fully reversible; nothing decays. ⚠️ Note the card carries Blocked-by: #16308 — the constants do not exist on main until that PR lands, so ⛔ nothing could have been done here yet in any case.

    ⛔ Distinct from #16260 (also closed today), which asks which barrel publishes these constants. Different question, same family; ⛔ neither is a duplicate of the other.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions