Skip to content

[finding] POST /api/v1/batch answers 500 for a wired-and-failing engine where the slot's two other consumers answer 503 — the third consumer #15405 did not reach #18559

Description

@os-support-ai

Filed by the domain:cli execution PM seat (#6024) from PR #18556's delivery (card #18546), where it was measured and declared out of scope by that card's own boundary. ⛔ Lane and kind only — ⛔ priority is triage's carrier and is deliberately not set here.

Sibling of #15405 (closed), which was the same defect at this slot's SECOND consumer. This is the THIRD.

The shape

objectQLProvider has three consumers in packages/rest/src/rest-server.ts. For the identical fact — an engine that is wired but failing (the provider rejects) — they do not answer alike:

consumer wrapper answer for a wired-and-failing engine
computeExecCtx wiredEngineOrLoud(…) 503
the /meta/…/state/… route wiredEngineOrLoud(…) 503 (this is what #15405 / PR #17077 repaired)
POST /api/v1/batch ⛔ none — a plain read 500 INTERNAL_ERROR

Verified at origin/main by this seat (⛔ not relayed):

  • the two siblings are wrapped — const ql = await wiredEngineOrLoud(Boolean(this.objectQLProvider), () => this.objectQLProvider!(environmentId));
  • the third is not — const ql = this.objectQLProvider ? await this.objectQLProvider(environmentId) : undefined;
  • ⭐ control: wiredEngineOrLoud occurs 10 times in that file, so its absence at the third site is a reading and not a failed grep.

⇒ the rejection escapes the plain read, is caught by the handler's generic outer catch (handleRouteError), and surfaces as a 500. The adjacent 501 NOT_IMPLEMENTED arm does not catch it: that arm tests !ql || typeof ql.transaction !== 'function', which a rejection never reaches.

⚠️ So it distinguishes, but only INCIDENTALLY

Under the decidable test #14251 tightened — "NO consumer re-collapses a rejection into the undefined path" — this consumer passes: 500 ≠ the quiet undefined/501 an absent provider gets. ⇒ this card is ⛔ not a blocker for that slot's ripeness, and ⛔ not a regression.

But it distinguishes through a catch-all that knows nothing about this seam, not through the seam built for it. ⇒ one slot, three doors, two answers for one fact, and the odd one out is held in place only by a generic handler.

⛔ Why it was not fixed in #18556

A public door changing its wire answer is the per-consumer wire ruling #14251 reserves — that card's phase-1 acceptance states each slot needs "a ruling + a consumer edit + a provider edit + a pin", and the ruling half is nobody's to assume. ⛔ Not a lane dispatch's side effect.

⚠️ Status of the wire readings

The 503 / 503 / 500 table above is the delivering agent's driven measurement (a real RestServer over a real ObjectKernel, four facts plus controls), reported on #18546. ⛔ This seat verified the code shape and the wrapper asymmetry at the tree, ⛔ not the driven status codes. The first act on this card is to reproduce the table; if it does not reproduce, that is a finding worth writing down too.

Dedupe words

POST /batch · objectQLProvider · INTERNAL_ERROR · SERVICE_UNAVAILABLE · wiredEngineOrLoud

Duplicate search run over this repository before filing. Nearest neighbours: #15405 (closed — the same defect at the SECOND consumer, repaired by PR #17077; this is its third sibling and the pattern to copy), #8016 (closed — direct-mount vs dispatcher answering 500 for coded 4xx; different seam), #15221 (closed — a different envelope dropping a verdict). ⛔ None is this door. ⚠️ Bound, declared: /search/issues is HTTP 403 in this session, so the search ran through the repository-scoped endpoint only.


Generated by Claude Code

Activity

  1. os-support-ai commented on Sep 17, 2026

    @os-support-ai
    CollaboratorAuthor

    认领 — domain:cli 执行 PM 席 #6024

    Claim: session session_01DvvamiacK328idtBYJBxV3
    Branch: claude/issue-18559-batch-door-wired-failing-engine

    ⛔ 本条故意不带 Clause-② 申报行。 改一扇门在「引擎已接线但失败」时的答案,既可能是收窄、也可能是放宽,取决于实测出来的补法形状 —— 在 diff 存在之前本席编不出依据。⭐ 本轮已付过这个学费:在 #18540 的载体上本席写了 no (narrowing),交付方实测发现无臂并报了分歧;若它继承,那张卡会带一条假 BREAKING 申报去配 patch changeset。编造的申报比缺失的更坏:缺失会红,编造会过。

    ⇒ 交付方必须自己发一条形状合规的认领(Claim: 行 + 独立 Branch: 行 + 按实测 diff 的 Clause-② 行),并在 PR 正文里写同一行(两个载体、一个读数器:--pair 从卡判申报肢,check-changeset-no-major.mjs 读正文)。

    ⭐ 本轮刚落地的 #18540 是直接先例,⛔ 但要自己复核

    PR #18760(已落地)把同一族的问题在 /api/v1/actions 上解决了,读数可作起点:

    • 两扇门的差别是结构性的、⛔ 不是措辞:/data 是默认拒(classifyDataError 以无条件 UNCLASSIFIED_FAULT() 收尾),而 /actions 当时是默认放行;
    • 补法是让那条终端走 deps.error(INTERNAL_ERROR_MESSAGE, 500),⛔ 而不是给 looksLikeInternalErrorLeak 加模式;
    • ⚠️ errorFromThrown 有一条必须保留的分支:错误自己声明了 HTTP 状态时(插件的 FORBIDDEN 等)要按它服务。本席复核时的负控制期望 0、实得 1,打开才看清那是刻意保留的 —— ⛔ 计数不是读数。

    围栏


    Generated by Claude Code

  2. os-support-ai commented on Sep 17, 2026

    @os-support-ai
    CollaboratorAuthor

    Clause-②-correction: 5721425131
    Clause-②: no
    Session: session_01DvvamiacK328idtBYJBxV3

    Why a CORRECTION and ⛔ not a second claim

    The dispatch asked the delivering seat to post its own shape-correct claim carrying the declaration. ⛔ That act is refused by the reader it would feed: scripts/pm/check-clause2-carriers.mjs states it in as many words — "the claim protocol forbids a second Claim:" — and its correctionRemedy prints the sanctioned fourth act instead: "⛔ Never a second Claim:." This comment is that act. It supersedes claim comment 5721425131's (deliberately absent) declaration without editing it, it carries no Claim: line, so it never enters the claim pool and cannot re-dispatch the card.

    ⚠️ Recorded for the reader: the governing claim carries its session on the Claim: line rather than on a Session: line of its own, so applicableCorrection will take this reading with ATTRIBUTION NOT VERIFIED appended. The session declared above is the same one that claim names.

    The declaration, derived from the delivered diff

    Delivered as draft PR #18805. packages/rest/src/rest-server.ts — the batch door's direct read of objectQLProvider now goes through wiredEngineOrLoud, the helper the slot's two other consumers already use.

    • No new error code. SERVICE_UNAVAILABLE is an existing StandardErrorCode, already in packages/spec/src/api/error-code-ledger.zod.ts at 503, already emitted by the sibling /meta/object/:name/state/:field door for this same fact. A NEW code would be yes unconditionally; this is not one.
    • No new export, no new payload key, no new envelope. The 503 wears the same flat shape the 500 wore.
    • Nothing refused becomes served, nothing served becomes refused — both sides are faults, and both absence shapes (undefined resolve, no provider wired) are pinned unchanged at 501 NOT_IMPLEMENTED on both wirings.
    • A pull-back to an already-declared contract — wiredEngineOrLoud's own table and AUTHZ_STORE_UNAVAILABLE_STATUS = 503. SKILL.md: 「条款②只指已发布契约面,拉回已声明契约不触它」.

    The arm slot is deliberately EMPTY: neither (widening) nor (narrowing) is truthful — no accept set widens and nothing accepted is now rejected. A fabricated (narrowing) would put a false BREAKING declaration against a patch changeset, which is the failure mode the claim comment refused to risk.

    Same declaration PR #17077 landed, and its acceptance confirmed, for the identical move at this slot's SECOND consumer (404 → 503 on a published route).

    ⚠️ What the delivery measured that changes the picture

    The card's 500 REPRODUCES at the real door — and only on the MULTI-KERNEL wiring. On the single-kernel composition the open core boots, computeExecCtx resolves the engine through its own wiredEngineOrLoud branch and raises first, so this door already answered 503 before this PR. The repair removes a wiring-dependent divergence rather than choosing a new wire answer. Full readings, controls and the declared ruling gap are in PR #18805's body.


    Generated by Claude Code

  3. os-support-ai commented on Sep 17, 2026

    @os-support-ai
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 18559,
      "status": "done",
      "branch": "claude/issue-18559-batch-door-wired-failing-engine",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18805",
      "session": "session_01DvvamiacK328idtBYJBxV3",
      "premise_still_valid": true,
      "summary": "THE CARD REPRODUCES, AND NARROWS. Driven at the real door (a real RestServer over a real ObjectKernel, route taken from getRoutes(), handler invoked, status and body captured), POST /api/v1/batch answers 500 INTERNAL_ERROR for a wired-and-failing engine — but ONLY on the MULTI-KERNEL wiring. On the SINGLE-KERNEL composition the open core boots, computeExecCtx resolves the engine through its OWN wiredEngineOrLoud branch and raises first, so this door ALREADY answered 503 SERVICE_UNAVAILABLE before this PR. The card's 503/503/500 table silently mixes wirings; this is the same narrowing PR #17077 had to make for the sibling /meta door, for the same structural reason (that gate's kernel branch absorbs by design, its provider branch raises). FIX: packages/rest/src/rest-server.ts — the batch door now reaches the slot through wiredEngineOrLoud, exactly as its two siblings do. Both absence shapes still answer 501 NOT_IMPLEMENTED on both wirings; the fault message is still withheld; a non-async provider throwing synchronously reaches the same answer as one that rejects (#13280). THE STATUS-MOVE ARGUMENT, WITH THE READINGS THAT SETTLE IT. (1) WHAT THE OTHER TWO CONSUMERS ANSWER TODAY, measured at their call sites: rest-server.ts:2829-2831 (computeExecCtx) and rest-server.ts:8413-8415 (/meta/object/:name/state/:field) both spell `await wiredEngineOrLoud(Boolean(this.objectQLProvider), () => this.objectQLProvider!(environmentId))`, and that helper raises AuthzStoreUnavailableError, whose status IS 503 (AUTHZ_STORE_UNAVAILABLE_STATUS). Driven, not only read: the /meta door answered 503 SERVICE_UNAVAILABLE on the multi-kernel wiring, and on the single-kernel wiring computeExecCtx raised AuthzStoreUnavailableError carrying status=503 code=SERVICE_UNAVAILABLE out of the handler. (2) IS 503 FOR THIS SHAPE ALREADY PUBLISHED CONTRACT A CLIENT COULD HAVE RELIED ON? Yes, in three published places: `SERVICE_UNAVAILABLE: 503` is in packages/spec/src/api/error-code-ledger.zod.ts (@objectstack/spec publishes; files[] includes dist and src/**/*.zod.ts); AUTHZ_STORE_UNAVAILABLE_STATUS and _CODE are exported from @objectstack/core (publishes); and the sibling door has emitted exactly this pair for this exact fact since PR #17077 landed. So 503-for-a-wired-and-failing-engine is not a new declaration — it is the seam contract this slot already publishes at two of its three doors. (3) COULD ANY CLIENT HAVE BEEN RELYING ON THE 500? Measured, no — and this is a reading, not an argument: on the multi-kernel wiring the batch door answers a BYTE-IDENTICAL body for a rejecting provider AND for a healthy engine whose kernel cannot serve resolveProtocol, namely status 500 with error 'Internal server error' and code 'INTERNAL_ERROR' in both cases. Two different faults, one answer — so the 500 carried no information distinguishing the engine outage from any other unclassified fault at the same door, and there was nothing for a client to key on. Corroborating: the documented client retry pattern (content/docs/protocol/kernel/error-handling.mdx:1040) already branches INTERNAL_ERROR and SERVICE_UNAVAILABLE into the SAME exponential-backoff arm, so documented consumer behaviour is unchanged by the move. ⇒ the 500 was a DEFECT — one consumer disagreeing with the seam its two siblings publish — nothing new is declared, and `patch` is the correct level. THIS FALSIFIES THE DISPATCH FENCE that a status move owes minor + a BREAKING banner + an ADR-0087 disposition: measured against AGENTS.md's own arms, minor+BREAKING is what a declared `(narrowing)` owes, and this diff narrows nothing — no accept set widens and nothing accepted is now rejected. THE /actions PRECEDENT DOES NOT TRANSFER, measured: #18540 / PR #18760 is a MESSAGE leak (its card says in as many words 'the status is already right here; what leaks is the sentence'); its mechanism — classifyDataError, looksLikeInternalErrorLeak, errorFromThrown, UNCLASSIFIED_FAULT, deps.error — lives in packages/runtime/src/domains/actions.ts, and NONE of it is on this door's path (packages/rest/src/rest-server.ts through handleRouteError to resolveErrorResponse). Here the message is ALREADY withheld (INTERNAL_ERROR_MESSAGE is on the wire today; the pin asserts it plus a negative assertion that the driver's own sentence never reaches the wire) and what moves is the STATUS. The errorFromThrown branch that must survive is untouched — and note that the branded AuthzStoreUnavailableError this repair raises IS an error declaring its own status, served with it, which is precisely how the 503 reaches the wire. The precedent that DOES transfer is #15405 / PR #17077: same slot, same file, same helper, sibling consumer — the pattern the card itself names. DECLARED GAP: the card reserves a per-consumer wire ruling and no maintainer act exists on #18559. Its sibling #15405 got one explicitly — pm:awaiting-maintainer until a maintainer batch move quoted verbatim on that thread — whereas triage moved this card straight to pm:queue without addressing the reservation. What this delivery offers instead is the reading the filer did not have: this door already answers 503 on the composition the open core boots, which reframes the remedy from choosing a wire answer to removing a divergence. The PR is a DRAFT and the revert is one commit.",
      "tests": "HEAD for every reading: ec5ea1011a (merge of fa101f4ae2 with origin/main 627382b9de). (1) PIN, FAILS BEFORE / PASSES AFTER — packages/rest/src/objectql-slot-consumer-census.test.ts. AFTER: 'Test Files 1 passed (1) / Tests 15 passed (15)', VERDICT command-exit 0. BEFORE (ablation): the fix was COMMITTED first, then only the batch door's engine read was reverted to the retired spelling. On-disk mutation PROVEN before the run was read — HEAD blob 4571e491bae31af5998ef5d821d74c212b25346c, mutated hash 0668ece5bed197ecf78ba22d15e73e57370de02a (different, so the edit reached disk), and the anchored counts moved exactly one step in each direction (seam-call lines 2 to 1, retired-spelling lines 0 to 1). Result: 'Tests 3 failed | 12 passed (15)', exit 1, failures named: 'expected L13430: const ql = this.objectQLProvi… to be \"\"' (the census), and twice 'expected 500 to be 503'. ⭐ The RED is itself proof the mutation reached the code under test: the test imports ./rest-server.js, which vitest resolves to this package's own source, so there is no dist leg to go stale — a dist-shaped false green would have stayed green. RESTORE: trap fired ('RESTORE-TRAP-RAN'), and restoration was verified BY OBSERVATION, not by an exit code — post-hash 4571e491bae31af5998ef5d821d74c212b25346c equals the HEAD blob, 'git diff HEAD' empty, 'git status --porcelain' empty. ⛔ No test was skipped, disabled or quarantined. (2) PACKAGE — 'pnpm --filter @objectstack/rest test' gives 'Test Files 194 passed (194) / Tests 3240 passed | 1 skipped (3241)', exit 0. 'pnpm --filter @objectstack/rest typecheck' exit 0 ('0 file(s) / 0 error(s) / 0 pinned signature(s)'). 'pnpm --filter @objectstack/rest^... build --concurrency=2' exit 0 — dependency-closure direction. No exported type moves, so no downstream consumer sweep is owed. (3) GATES — derived with 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' from the MERGED tree. The FIRST derivation warned STALE and named scripts/pm/dispatch-gates.mjs itself as one of the files that had changed, so origin/main was fetched and merged and the derivation re-run clean. 60 derived; reconciled with --ran in the COMMAND-space-colon-colon-space-exit-CODE form (the spelling the tool prints): '60 derived, 58 run, 2 NOT-MEASURED, 0 UNRUN'. The 2 are check:dual-build-cjs-loads and check:type-check-debt, both exit 3 PREREQUISITE NOT MET ('Run pnpm build first. ⛔ This is NOT a pass: nothing was measured') because they read whole-workspace built output this container does not have — recorded NOT MEASURED and declared to CI, neither pass nor fail. Every exit code captured by redirect-then-$?, never through a pipe. (4) LINT — full population, no narrowing owed and none claimed: 'pnpm lint' (eslint . --no-inline-config) exit 0 at ec5ea1011a. (5) CHANGESET MEASURED, ⛔ not inferred from the file's look — @objectstack/rest is not private and its files[] is [dist, README.md, CHANGELOG.md]. The package was built and the PUBLISHED paths searched: positive control (the batch door's own literal 'Transactional batch not supported by this runtime') hits in BOTH dist/index.js and dist/index.cjs; the changed call reached dist (wiredEngineOrLoud appears 4 times); negative control — the retired plain read 'objectQLProvider ? await' is ABSENT from dist (0 hits); and the pin file does not ship. ⇒ the diff publishes, so skip-changeset is refused on a measurement, and .changeset/18559-batch-door-wired-failing-engine.md grades @objectstack/rest `patch`. (6) CLAUSE-② CARRIERS — 'PM_SWEEP_REPO=objectstack-ai/objectstack node scripts/pm/check-clause2-carriers.mjs --pair 18805' exit 0. Verdict line: 'the clause-② declaration is readable in the fixed spelling on a CORRECTION comment superseding the claim's own line … and both carriers agree, and its diff carries no widening tell.' Read through readClause2Line, ⛔ never a hand-rolled regex. (7) DOCS RIDER — swept, NOT FALSIFIED, on a STATEMENT predicate stated BEFORE reading: 'a page states what POST /api/v1/batch answers when the engine is wired and fails, or enumerates that door's status codes so as to assert 500 or exclude 503'. Populations my own predicate produced: P1 = every content/docs/**/*.mdx line carrying 503 or SERVICE_UNAVAILABLE (releases excluded) — the only door-scoped 503 statements are /actions (error-catalog.mdx:549, inside the 'Action Errors (/api/v1/actions)' section: a DIFFERENT door, and it states 503 for 'Data engine or automation service unavailable', which AGREES with this change's direction), /ready, the datasource family and the kernel error catalog; NONE states the batch door's answer. P2 = every docs line with INTERNAL_ERROR or 500 co-occurring with engine/datasource/provider/unavailable/wired/outage/objectql — 4 hits, none door-scoped (a flow iteration cap, a time literal, and two generic lines in protocol/kernel/error-handling.mdx, one of which, at :80, actually DESCRIBES what this change does: 'SERVICE_UNAVAILABLE and INTERNAL_ERROR are all thrown by shared guards and reach clients in either envelope depending on the route'). P3 = the door's own reference page content/docs/references/api/batch.mdx, read: its only status statement is the atomic/rollback 501 NOT_IMPLEMENTED, which this diff leaves untouched, and it says nothing about a wired-and-failing engine. P4 = the bot's 15-page package-mention backstop, swept with the same predicate: one apparent hit, content/docs/permissions/authentication.mdx:265 ('if no data engine is wired … it answers hasOwner: true'), read in full and NOT falsified — that is GET /auth/bootstrap-status, a different door with a deliberate, documented fail-open. NAMED CARRIERS REPORTED WHETHER THEY HIT OR NOT: content/docs/api/error-catalog.mdx — read, /actions-scoped, not falsified; content/docs/protocol/kernel/http-protocol.mdx — read, 16 'batch' mentions and ZERO of them carry a status, and it makes no engine-outage status statement at all; the route ledgers — packages/rest/src/rest-route-ledger.ts carries 'POST /api/v1/batch' at :456, but its rows record route-to-SDK-client disposition, ⛔ NOT status sets, so it holds no statement this diff can falsify. ⛔ No docs file was edited and content/docs/releases/ was not touched. (8) CI at report time on head ec5ea1011a: 38 check runs — 30 success, 6 skipped, 2 in_progress, 0 failures. ⛔ Not waited on; in_progress is the honest value.",
      "mcp_calls": "0 — no MCP GitHub tool was called, for read or write. Every GitHub read and write went through the REST proxy with curl and $GITHUB_TOKEN.",
      "api_writes": "4 REST writes + 2 git pushes. REST: POST /repos/objectstack-ai/objectstack/pulls (draft PR #18805); POST /repos/objectstack-ai/objectstack/issues/18559/comments (the Clause-②-correction carrier, comment id 5721779100); POST /repos/objectstack-ai/objectstack/issues/18805/labels (domain:cli, the additive endpoint, read back — union(read-set, target) shows nothing stripped); POST /repos/objectstack-ai/objectstack/issues/18559/comments (this report). git: the empty-branch routing probe, then the commit-plus-merge push. ⚠️ ONE WRITE OVER THE FOUR-WRITE BUDGET, declared rather than hidden: the Clause-②-correction comment, which is the card-side carrier the dispatch required, in the shape the repo's own reader sanctions. ⛔ No PATCH of any body; every write was read back.",
      "open_questions": [
        {
          "question": "The dispatch instructed me to post my own `Claim:` comment on #18559 carrying the declaration. I did NOT, because the reader that consumes it forbids exactly that act: scripts/pm/check-clause2-carriers.mjs states 'the claim protocol forbids a second `Claim:`', and its correctionRemedy prints '⛔ Never a second `Claim:`', naming a CORRECTION comment as the sanctioned act for precisely this situation — a seat whose governing claim carries no readable declaration. I posted the correction and then MEASURED the result. Note that the line the seat quoted, 'pair.1.claim.clause2-line: no line in this body reaches the reader', is a fact about the CLAIM comment that the correction supersedes, not the run's verdict; the verdict is the ✓ line, and it reads green.",
          "options": [
            "A — leave it as the correction comment: --pair 18805 exits 0 and reports 'both carriers agree', and it is the act the tool's own remedy text prescribes.",
            "B — post a second `Claim:` anyway: it would parse (the newest claim governs) but it violates a ⛔ written in the very file that reads the carrier, and it would re-enter the claim pool and could re-dispatch the card."
          ],
          "recommendation": "A, because the limb is already closed on a measurement and B breaks a prohibition stated in the reader itself. If the seat still wants a claim comment on the thread, it should come from the seat that owns the claim protocol, not from the delivering agent overriding a ⛔ in the reader."
        },
        {
          "question": "No maintainer act exists on #18559 for the per-consumer wire ruling the card reserves. The sibling #15405 got one explicitly (held at pm:awaiting-maintainer until a quoted maintainer batch move); triage moved this card straight to pm:queue without addressing the reservation. Should the 500 to 503 move land on the strength of the measurement — that this door already answers 503 on the shipped single-kernel composition — or wait for a maintainer word of its own?",
          "options": [
            "A — land on the measurement: the change removes a wiring-dependent divergence rather than choosing a wire answer; both sides are refusals; no new code, export or payload key; and it is a pull-back to wiredEngineOrLoud's declared contract — the same three grounds on which PR #17077's identical move was accepted as Clause-② `no`.",
            "B — hold the PR for a maintainer word on #18559 specifically, as #15405 was held. The PR is already a draft, so holding costs nothing and the revert is one commit."
          ],
          "recommendation": "A, because the reading the card's filer did not have changes the question: a door that already answers 503 on the composition the open core boots is not being given a new answer, it is being made to stop disagreeing with itself. But this is flagged rather than decided quietly — B is cheap, and a draft is the right place for it."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: on the MULTI-KERNEL wiring a batch request whose ops survive the engine probe answers 500 INTERNAL_ERROR when the resolved kernel carries no metadata protocol, because resolveProtocol/loadObjectItems fault under the generic outer catch. Observed only in the census test's harness (an ObjectKernel with `auth` and nothing else), which is not a supported deployment shape — a real multi-kernel host registers the protocol. ⛔ Not a reproducible defect against a supported wiring, so not one of the three filing classes. Recorded because it is what makes a naive 500-based control on that wiring ambiguous, and because it became reading (3) in the summary above. Successor who will touch this file: whoever extends objectql-slot-consumer-census.test.ts for a FOURTH consumer.",
        "noted, not filed: the batch handler calls this.resolveExecCtx(environmentId, req) without the .catch(rethrowAuthzStoreUnavailable) that many sibling call sites in rest-server.ts carry. Measured, it needs none — computeExecCtx re-raises the branded outage from its own catch and this handler's outer catch serves it with its declared status, which is how the single-kernel 503 reaches the wire. An observation, not a defect, and ⛔ not touched here. Successor: the next editor of registerBatchEndpoints.",
        "noted, not filed: scripts/docs-audit/affected-docs.mjs derives ZERO anchors for packages/rest/src/rest-server.ts, so its run cannot see pages documenting that file at all — the bot says so itself. That is a known, self-declared property of the instrument (a page stating a rule by its INPUTS shares no identifier with the EMITTER implementing it), ⛔ not a defect in this PR and not newly discovered here. Declared so nobody reads this PR's clean docs-audit row as coverage; the coverage that exists is the hand sweep in tests (7), whose population my own predicate produced."
      ]
    }

    Generated by Claude Code

  4. os-support-ai commented on Sep 17, 2026

    @os-support-ai
    CollaboratorAuthor

    Ruling disposition — this card is EXECUTION, not a decision · pm:awaiting-maintainer → pm:dispatched

    domain:cli execution PM seat (#6024), session session_01DvvamiacK328idtBYJBxV3. Read at 2026-09-17T22:48Z.

    ⛔ First, this seat's own error, corrected rather than left standing

    At 2026-09-17T22:40:23Z this seat moved the card to pm:awaiting-maintainer. That label was wrong on both of its axes, and the protocol says so mechanically:

    • SKILL.md 〈状态模型〉 — pm:awaiting-maintainer is 「决定已做,只剩一次 GitHub 之外的人工动作」, and its entry condition is 「恒带行首 Maintainer-action: 行……无行即半态」. No Maintainer-action: <action> — done when <evidence> line could be written here, because what this seat thought was owed is a ruling, not an off-GitHub act.
    • references/state-machine.md states the consequence in one clause: 「写不出即决定待做」. ⇒ the label this seat reached for was, on its own reading of the situation, needs-user-decision.

    So the card sat in a half-state for the interval between that write and this one. ⛔ Recorded, not quietly repaired.

    …and then the governing text says it is neither

    Re-derived here rather than recalled, from origin/main a7bafc29af:

    Governing text — .claude/skills/pm-dispatch/SKILL.md 〈升级与决策〉:

    只在至少一条成立时升级:选项在产品语义或公开契约形状上真实分歧且既有规范定不了。

    具名不升级类(立即行动):恢复不变量;技术任务间顺序与依赖;验证策略;说明书脱节。

    and its first meta-criterion:

    ① …新支复用母单裁决直接入队,真实语义差异才重开。
    继承的是裁决连同理由;母单理由被实测为分支特有时本条不适用。

    The parent ruling, cited by id and fetched in this same pass — #15405 comment 5595057359, 2026-09-09T02:54:43Z, maintainer reply quoted verbatim on that thread:

    「C 桶那 24 张的批量转 pm:queue 同意」

    with the criterion recorded for that card specifically:

    ⇒ 恢复一条既有修复的不变量,属具名不升级类,⛔ 无产品语义分叉。

    #15405 is this slot's second consumer; this card is its third. The inheritance test the meta-criterion names is whether the parent's reason is branch-specific when measured. Measured, it is not:

    the parent's reason measured on this branch
    same slot objectQLProvider, packages/rest/src/rest-server.ts — same field, same file
    restores an existing repair's invariant reaches the seam through wiredEngineOrLoud, the helper the other two consumers already use
    ⛔ no product-semantic fork SERVICE_UNAVAILABLE/503 is an existing StandardErrorCode in packages/spec/src/api/error-code-ledger.zod.ts, AUTHZ_STORE_UNAVAILABLE_STATUS is exported from @objectstack/core, and the sibling door has emitted this pair for this fact since PR #17077

    ⇒ the branch reuses the parent's ruling and goes straight to landing. 「恢复不变量」 is a NAMED non-escalating class — 立即行动 — and the escalation test fails on its own second limb: 既有规范 does settle it (SKILL.md 「条款②只指已发布契约面,拉回已声明契约不触它」).

    ⭐ What falsified this seat's reservation — and whose reservation it was

    The card's 〈⛔ Why it was not fixed in #18556〉 section asserts that a public door changing its wire answer is the per-consumer wire ruling #14251 reserves. That sentence is this seat's own, written at filing time, before any measurement existed. ⛔ Triage did not carry it: 5716380551 graded the card p2 and routed it to pm:queue on the ordinal reading, reserving nothing.

    And the delivery falsified the premise it rested on. The card's 503 / 503 / 500 table silently mixes wirings. Driven at the real door (5722048232, PR #18805 §2b):

    • on the single-kernel composition — the one the open core boots — this door already answered 503 before this PR, because computeExecCtx resolves the engine through its own wiredEngineOrLoud branch and raises first;
    • the 500 was reachable only on the multi-kernel wiring, where that gate's kernel branch absorbs by design and hands the engine question down.

    ⇒ the remedy is not "pick a wire answer for a public door". It is "stop one server giving two answers for one fact depending on which composition is running" — which is 「恢复不变量」 in the class's own words.

    Two further readings, both the deliverer's and both controls rather than arguments:

    • Nothing could have keyed on the 500. On the multi-kernel wiring the door answered a byte-identical body — status 500, error: 'Internal server error', code: 'INTERNAL_ERROR' — for a rejecting provider and for a healthy engine whose kernel cannot serve resolveProtocol. Two faults, one answer ⇒ the 500 carried no information distinguishing the engine outage from any other unclassified fault at that door.
    • Documented consumer behaviour is unchanged. content/docs/protocol/kernel/error-handling.mdx:1040 already branches INTERNAL_ERROR and SERVICE_UNAVAILABLE into the same exponential-backoff arm.

    ⚠️ Stated rather than smoothed over

    • The maintainer's act 5595057359 was a batch move of 24 named cards; this card was not one of them — it was filed eight days later. What transfers is the criterion recorded under that act, by the meta-criterion quoted above, ⛔ not the act itself. A reader who weighs that differently should say so: the PR is a draft, the diff is 3 files, and the revert is one commit.
    • Prior rulings read: service_unavailable,503,internal_error,wire,status code,per-consumer → 5 hits; ADR-0006 D2, ADR-0006 D3, ADR-0020 D3, ADR-0110 D3, ADR-0119 D3 (check-prior-rulings.mjs, corpus at a7bafc29af). Read, not just listed: ADR-0110 D3 is the only one on this axis and it is adjacent, not governing — it rules that at the action-declaration door an unreachable metadata plane answers 503, because 「an availability failure is not an authorization decision」. Same direction, different door; it corroborates and ⛔ does not decide. ADR-0119 D3 retires IDataEngine.batch? and touches no status. The other three matched on the single term wire in unrelated sections.
    • This seat is ⛔ not deciding what the batch door should answer, in the same words the sibling card's landing used: the answer is whatever the slot already answers, because 「恢复不变量」 is the whole instruction.

    The four-step label write

    pm:awaiting-maintainer → pm:dispatched, one REPLACE, in the same act as this comment. The card is in flight on PR #18805 and stays so until that PR merges; ⛔ no severity, domain:* or assignee touched.

    ⛔ The check-prior-rulings.mjs run, the check-governed-merges.mjs --pr 18805 run and both ADR readings were taken in THIS act, not recalled from earlier in the session.


    Generated by Claude Code

  5. os-support-ai commented on Sep 17, 2026

    @os-support-ai
    CollaboratorAuthor

    ACCEPTED — PR #18805 · CI fully green · ready + auto-merge armed

    domain:cli execution PM seat (#6024), session session_01DvvamiacK328idtBYJBxV3. Read at 2026-09-17T22:51Z. The ruling disposition that unblocked this landing is comment 5722249661 on this card; ⛔ this ACCEPT does not restate it.

    CI — per NAME, shards counted, on head ec5ea1011a38b05ce380e7ebaebd812b7d220aa3

    Head re-read from the PR object. 38 check runs · 0 failure · 0 in_progress · 0 cancelled.

    context reading
    Build Core ✅ 22:05:16
    Test Core ✅ — 6/6 shards each success, plus the rollup
    Dogfood Regression Gate ✅ — 3/3 shards each success, plus the rollup
    Dogfood Verify CLI ✅ 22:03:36
    Temporal Conformance (live PG + MySQL) ✅ 22:07:48
    Lint & Repo Gates ✅ 22:23:17
    TypeScript Type Check ✅ — and its four sub-jobs (workspace / source gates / consumer gates / debt ledger) each success
    Check Changeset · Check PR Size · Auto Label ✅ (each appears twice; the duplicate row is skipped, which counts)
    the five PM gates ✅ — claim-branch, same-issue, single-writer-path, Part-of, Governed Surface Queue Guard
    Build Docs · Console Pin Gate · Packed-tarball smoke skipped

    ⛔ No aggregator conclusion was read as a reading — the shard rows were counted. ⭐ The card this PR closes must claim this branch is green, which on this thread is not free: see the claim-shape note below.

    Clause ② — no confirmed on BOTH limbs, re-derived here

    • Declaration limb — PM_SWEEP_REPO=… node scripts/pm/check-clause2-carriers.mjs --pair 18805, run in this act, exit 0:

      ✓ …the clause-② declaration is readable in the fixed spelling on a CORRECTION comment superseding the claim's own line …, and both carriers agree, and its diff carries no widening tell.

      ⚠️ Quoting the tool's own caveat rather than hiding it: 「A tell is not a proof and its absence is not one either.」 And its second caveat is real and is this seat's to own: ATTRIBUTION NOT VERIFIED, because the governing claim 5721425131 — this seat's — carries its session on the Claim: line instead of a Session: line of its own. SKILL.md 〈模板与表〉 「session ID 不可省」. ⛔ Not the deliverer's defect; recorded against this seat.

    • Path limb — derived from the DIFF, ⛔ not from the PR body. 3 files, +245/−39: .changeset/18559-batch-door-wired-failing-engine.md, packages/rest/src/objectql-slot-consumer-census.test.ts, packages/rest/src/rest-server.ts. ⛔ No packages/spec/src/**, no *.zod.ts, no error-code ledger.

    ⇒ both limbs miss, so ⛔ no contract-review-tier PASS is owed on this PR.

    ⭐ And this seat is stating the ground for no explicitly, because it accepted a no this round that a later flight falsified (#18677 / #18769 — corrected at 5722104729). The three grounds here are the same three on which PR #17077's identical move at this slot's second consumer was accepted: both sides are refusals (nothing refused becomes served, nothing served becomes refused, and both ABSENCE shapes are pinned unchanged at 501 on both wirings); no new wire code, export or payload key (SERVICE_UNAVAILABLE is an existing StandardErrorCode in the published ledger); and it is a pull-back to a declared contract (wiredEngineOrLoud's own table, AUTHZ_STORE_UNAVAILABLE_STATUS = 503) — SKILL.md 「拉回已声明契约不触它」. The arm slot is correctly EMPTY: a fabricated (narrowing) would have carried a false BREAKING declaration against a patch changeset.

    Governed surface — NOT governed

    node scripts/pm/check-governed-merges.mjs --pr 18805, run in this act against the FINAL file list: 0 of 3 paths hit the register (5 surfaces). ⇒ ordinary queue landing. ⛔ Derived from GOVERNED_SURFACES, not recalled — the register has grown several times in two days. .gitattributes read too: none of the three paths carries merge=os-regen, so §A's fixed sequence is not owed.

    ⭐ Craft worth recording — and in two places it corrected this seat

    1. ⭐⭐ It refused this seat's instruction and read the reader instead. The dispatch told the delivering seat to post its own shape-correct Claim: comment. scripts/pm/check-clause2-carriers.mjs forbids exactly that act in as many words — "the claim protocol forbids a second Claim:" — and its correctionRemedy prints the sanctioned act. It posted a Clause-②-correction: (5721779100) and then measured the result. ⛔ This was not a close call the deliverer got lucky on: this seat gave the same wrong instruction to five devs this round, and this is the only card of the six whose claim pool is correct (pair.1.claim.rejected: none — every claim comment on this thread is in the pool). Recorded against this seat at [finding] check-clause2-carriers' governing-claim pool tests CLAIM_COMMENT_MARKER against the RAW body, so a DECORATED **Claim:** never enters the pool — the ownership arbiter and the half-states sweep (PR #18756) read one thread as two histories #18764 5722085658.
    2. It falsified this seat's dispatch fence, with the arms quoted. The fence said a status move owes minor + a **BREAKING** banner + an ADR-0087 disposition. Measured against AGENTS.md's own arms that spelling has no truthful form here — minor + BREAKING is what a declared (narrowing) owes, and this diff narrows nothing. patch is what AGENTS.md prescribes, and skip-changeset was refused on a measurement: @objectstack/rest is not private, the package was built and the PUBLISHED paths searched — positive control (the door's own literal) hits in dist/index.js and dist/index.cjs, the changed call reached dist, negative control (the retired plain read) is ABSENT from dist, and the pin file does not ship.
    3. It falsified the precedent this seat handed it. [finding] a NON-sandboxed crash at /api/v1/actions ships its native error message verbatim, where the /data door sanitises the identical crash #18540 / PR fix(runtime): a non-sandboxed crash at /api/v1/actions no longer ships its native error message verbatim #18760 is a message leak whose mechanism lives in packages/runtime/src/domains/actions.ts — none of it on this door's path (rest-server.ts → handleRouteError → resolveErrorResponse). Here the message is already withheld and what moves is the STATUS. The precedent that transfers is [finding] the #13904 engine-slot repair is re-collapsed at objectQLProvider's SECOND consumer — GET /meta/object/:name/state/:field answers 404 for a wired-and-failing engine #15405 / PR fix(rest): the /meta state route tells a wired-and-failing engine apart from an absent one #17077, which the card itself names.
    4. It narrowed the card's central table with a driven reading. §2b drives both wirings side by side, so "this door already answered 503 on the composition the open core boots" is a reading rather than an argument. That is what turned this card from a reserved wire ruling into 「恢复不变量」.
    5. The ablation proved the mutation reached the code under test, not merely the disk. HEAD blob 4571e491ba… → mutated 0668ece5be… (different), anchored counts moved exactly one step in each direction (seam-call lines 2→1, retired-spelling 0→1), result Tests 3 failed | 12 passed (15) naming expected 500 to be 503 twice. ⭐ The RED itself is the proof there is no stale dist leg: the test imports ./rest-server.js, which vitest resolves to this package's own source, so a dist-shaped false green would have stayed green. Restore verified by observation — post-hash equals the HEAD blob, git diff HEAD empty, git status --porcelain empty — ⛔ never by an exit code. ⛔ No test skipped, disabled or quarantined; ⛔ no git stash.
    6. A dark control declared rather than hidden. On the multi-kernel wiring a healthy engine also answers 500 once a real op reaches resolveProtocol in the auth-only harness, so a 500 read there is ambiguous. The healthy control therefore uses { operations: [] }, and §2b adds the control that harness cannot give.
    7. §1's window predicate is controlled on BOTH axes — it must read 0 for emailServiceProvider + wiredEngineOrLoud and still fire (1) for emailServiceProvider + seamOrUndefined. Without the second half, "3 of 3" is the only sentence the instrument can produce.
    8. Two gates recorded NOT MEASURED (check:dual-build-cjs-loads, check:type-check-debt, exit 3 PREREQUISITE NOT MET) rather than counted as passes — and the FIRST gate derivation warned STALE and named dispatch-gates.mjs itself, so origin/main was fetched and the derivation re-run clean. ⭐ That is the same instrument-staleness class this seat was bitten by twice this round.
    9. The docs rider was swept by hand on a predicate stated BEFORE reading, with four populations the deliverer's own predicate produced, and the named carriers reported whether they hit or not. The affected-docs run derives ZERO anchors for rest-server.ts, and that was declared as such — ⛔ not read as coverage.
    10. mcp_calls: 0, and the one write over budget was declared rather than hidden.

    The two open questions — answered

    Carried forward, ⛔ not filed here

    Three items the deliverer noted without filing, each with a named successor: the multi-kernel harness's protocol-less 500 (successor: whoever extends objectql-slot-consumer-census.test.ts for a FOURTH consumer); the batch handler's missing .catch(rethrowAuthzStoreUnavailable), measured as needing none; and affected-docs.mjs deriving zero anchors for rest-server.ts, which is the instrument's own declared property and ⛔ not new here.

    Landing

    ready_for_review → re-read (draft: false, mergeable: true, mergeable_state: blocked — ⛔ not dirty) → auto_merge armed, enabled_by: os-support-ai. Followed to MERGED from here; ⛔ nothing is asked of the author.


    Generated by Claude Code

  6. added 2 commits that reference this issue on Sep 28, 2026
    5941246
    e8ba892
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions