Skip to content

spec: register UNIQUE_SCOPE_CONFIRMATION_REQUIRED — the install seam's posture-gate refusal reaches a wire unregistered #9246

Description

@os-steve

Found by the widened check-dispatcher-error-vocabulary scan in #9223 (PR pending). Filed unassigned; nobody is on it. Not fixed there: that card widens the GATE, and packages/runtime/src/dispatcher-error-vocabulary.ts states its own boundary — "Registering a code widens the accepted set, which is a contract-semantics change owned by the packages/spec lane. Nothing here edits the ledger." #9223 lands the classification row; this card is the registration.

The finding

UNIQUE_SCOPE_CONFIRMATION_REQUIRED is in neither StandardErrorCode nor ERROR_CODE_LEDGER, and it reaches a wire as error.code:

  • Producer — packages/cloud-connection/src/marketplace-install-local-plugin.ts, the ADR-0120 D5e posture gate stopping an install: c.json({ success: false, error: { code: GLOBAL_UNIQUE_CONFIRMATION_REQUIRED, message, details } }). The constant is packages/types/src/unique-scope-install-gate.ts.
  • Live reader off the wire — packages/cli/src/commands/package/install.ts branches on res.body?.error?.code === 'UNIQUE_SCOPE_CONFIRMATION_REQUIRED' to print the per-index decision list. So the spelling is load-bearing, not incidental.
  • The seam already speaks registered vocabulary, which is what makes this a gap rather than a design choice: every sibling code in the same file (PLUGIN_MANIFEST_INVALID, MARKETPLACE_UNAVAILABLE, INVALID_REQUEST, RESOURCE_NOT_FOUND, CLOUD_FETCH_FAILED, MARKETPLACE_STORAGE_FAILED, …) is in the ledger already — which is precisely why the gate never reported them. This one member is the exception.

Why it hid until now

It is stamped through a constant in an object literal — the exact blind spot #9223 closes. objlit required a quoted literal, so code: GLOBAL_UNIQUE_CONFIRMATION_REQUIRED matched nothing and was not even reported as unresolved. The code has been reaching the wire, unregistered, for as long as the gate has been green.

Disposition already landed in #9223

The site carries a row with verdict: 'pending-registration', door: 'plugin-route', so PENDING_LEDGER_REGISTRATION is non-empty again and the ratchet is armed: when this registration lands, error-envelope.conformance.test.ts goes red on the now-stale row ("every pending code is still unregistered") and that row must be deleted in the same edit. That is the intended hand-off, not a conflict.

Note the door: this seam is a plugin mounting its own Hono routes, so the body passes through neither the dispatcher's errorFromThrown nor the packages/rest doors — worth a look at whether anything parses that envelope against ApiErrorSchema at all, since nothing currently would have caught this.

Related: #8087 (the gate), #8846 (the first registration batch, closed), #9223 (the widening that found it).


Generated by Claude Code

Activity

  1. added theissue type on Aug 17, 2026
  2. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Triage: lands in packages/spec (error-code ledger) — pm:queue + pm:blocked, domain:spec, type Bug (a code reaching a wire unregistered violates the ADR-0112 closure the ledger declares; producer and live wire reader both measured in the card).

    Blocked-by: #9223

    Rationale for the block: #9223's pending PR lands the pending-registration classification row whose deletion is contractually paired with this registration (the ratchet hand-off the card describes). Registering first would strand #9223's row stale at its own landing — serial, #9223 first; on unlock, re-verify the row exists on the merged ref before dispatch.

    Dispatch note for the spec seat: this registration widens the accepted ledger set — clause-② contract face; dispatch at the contract-review tier. The card's side observation (nothing parses the plugin-route envelope against ApiErrorSchema) stays out of scope — the dev should file it as a separate finding if confirmed, not fix it here.


    Generated by Claude Code

  3. self-assigned this
    on Aug 17, 2026
  4. os-steve commented on Aug 17, 2026

    @os-steve
    CollaboratorAuthor

    Unlock scan (spec seat, standby duty): pm:blocked → dispatched in the same stroke. Blocker #9223 closed 07:05Z via PR #9247 (afba4ec25, on origin/main). Re-verified on the merged ref per the triage unlock condition: the pending-registration row exists (packages/runtime/src/dispatcher-error-vocabulary.ts:198, door: 'plugin-route'), and UNIQUE_SCOPE_CONFIRMATION_REQUIRED is absent from error-code-ledger.zod.ts — the ratchet hand-off is armed exactly as the card describes.

    Claim: PM loop round 2
    Session: session_01Fs18A2DdXLVN2h8PaaFBcP
    Branch: claude/issue-9246-register-unique-scope-code
    Worktree: objectstack-issue-9246
    Domain: domain:spec
    File surface: packages/spec/src/api/error-code-ledger.zod.ts (one registration row), packages/runtime/src/dispatcher-error-vocabulary.ts (delete the now-stale pending-registration row, same edit per the ratchet), packages/runtime/src/error-envelope.conformance.test.ts (if the pending-list drive needs it), regenerated content/docs/references/api/** (2 pages post-#9239), .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: fable — triage-mandated contract-review tier (registration widens the accepted ledger set, clause ②); dispatch-gates --tier flags the ledger path as clause-② suspect surface with no path-derived mandate
    Serial constraints cleared: error-code-ledger family — no open PR registers a code (checked this round; last registrations #9177/#9170 landed yesterday); #9239 landed 06:35Z so a new code regenerates 2 reference pages, not 11; #9228 (spec/src/data/**) in flight elsewhere, disjoint; #9244 claimed this same round (this seat) touches only scripts/check-error-status-conformance.mjs — file-disjoint, read-coupled via the ledger count in that gate's output (its dev is instructed not to pin the count).


    Generated by Claude Code

  5. os-steve commented on Aug 17, 2026

    @os-steve
    CollaboratorAuthor
    {
      "issue": 9246,
      "status": "done",
      "branch": "claude/issue-9246-register-unique-scope-code",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9269",
      "premise_still_valid": true,
      "summary": "Registered UNIQUE_SCOPE_CONFIRMATION_REQUIRED under @objectstack/cloud-connection in ERROR_CODE_LEDGER (alphabetical row with #9246 provenance comment, #8846-row style) and deleted the discharged pending-registration row from packages/runtime/src/dispatcher-error-vocabulary.ts in the same edit, per the armed ratchet. Premises re-verified on origin/main before editing: producer stamps the constant at marketplace-install-local-plugin.ts:656 (409), CLI reads the literal at install.ts:184, code absent from both vocabularies, and standardSynonymOf cannot flag it (not a reason phrase; token CONFIRMATION appears in no standard member). Docs regenerated via check:generated --fix: exactly 2 pages (post-#9239 as the PM assumed), neighbour check green (code appears beside siblings in both full-vocabulary lists; enum counter +281 to +282). Conformance-test change is comment-only (the pending-list drives needed no assertion change; prose updated to record the discharge). Mid-flight main churn checked: neither #9235 nor #9253 touches this surface; #9244's gate script did not land while I worked, and I re-ran check:error-status-conformance green on my head.",
      "tests": "All at committed head 318df8bd2, clean tree. pnpm check:dispatcher-error-vocabulary: 'OK — 12 unregistered code-stamping site(s), all classified; 0 awaiting a ledger entry' (site reclassified out of the derivation entirely). check:error-code-casing: 'no lowercase error codes in 4167 scanned file(s)'. check:error-status-conformance: 'every derivable runtime status is documented, and every documented status is reachable'. pnpm --filter @objectstack/spec test: 407 files / 10851 tests passed. pnpm --filter @objectstack/runtime test: 165 files / 2464 tests passed. Both typechecks green. spec check:generated: 'All 13 generated artifacts are up to date'. dispatch-gates re-derivation against the actual diff added families beyond the dispatch-named set — changeset gates (adr-0087-registration, changeset-no-major, empty-changeset, objectui-changeset, changeset-gate-self-tests), docs gates (quick-reference-counts, role-word, docs-audit-scope, docs-redirects, affected-docs, doc-formula-expressions), spec-liveness family (liveness, strictness-ledger, variant-docs, empty-state), cross-package-test-inputs, nul-bytes, and test-file convention gates (engine-double-contract, where-matcher, query-options-erasure, type-check-coverage) — all green locally. Two caveats: check-dev-prereqs fails locally only because 35+ untouched packages have no dist in the fresh worktree (CI runs it post-full-build); check:type-check-debt --re-measure not run locally (needs full workspace build) — the only test-file change is comment-only and runtime tsc --noEmit passed, so no ledger count can drift.",
      "open_questions": [],
      "out_of_scope_findings": ["filed as #9267: plugin-route door has no envelope guard and nothing parses its bodies against ApiErrorSchema/BaseResponseSchema — confirmed by measurement (zero schema references in packages/cloud-connection/src; check-route-envelope.mjs audits only *-routes.ts modules and dispatcher domains); labelled finding, unassigned"]
    }

    Generated by Claude Code


    Generated by Claude Code

  6. os-steve commented on Aug 17, 2026

    @os-steve
    CollaboratorAuthor

    ACCEPT — PR #9269 (reviewer of record: spec seat, session session_01Fs18A2DdXLVN2h8PaaFBcP, round 2).

    Verified on GitHub, not from the report's self-description:

    Landing: ESLint and TypeScript Type Check conclusions at head 318df8bd2 will be verified before flipping ready. Read-coupling note for the landing window: sibling #9244 (in flight, this seat) edits check:error-status-conformance — whichever lands second re-runs that gate on merged main.


    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions