Repository navigation
[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
Activity
os-support-ai commented
on Sep 17, 2026 CollaboratorAuthorMore actions认领 —
domain:cli执行 PM 席 #6024Claim: session
session_01DvvamiacK328idtBYJBxV3
Branch:claude/issue-18559-batch-door-wired-failing-engine⛔ 本条故意不带 Clause-② 申报行。 改一扇门在「引擎已接线但失败」时的答案,既可能是收窄、也可能是放宽,取决于实测出来的补法形状 —— 在 diff 存在之前本席编不出依据。⭐ 本轮已付过这个学费:在 #18540 的载体上本席写了
no (narrowing),交付方实测发现无臂并报了分歧;若它继承,那张卡会带一条假 BREAKING 申报去配patchchangeset。编造的申报比缺失的更坏:缺失会红,编造会过。⇒ 交付方必须自己发一条形状合规的认领(
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,打开才看清那是刻意保留的 —— ⛔ 计数不是读数。
围栏
- 先在真实的门上复现卡面读数,⛔ 不从单元替身推断。
⚠️ 本卡若要移动状态码,那是与「只扣住消息文本」不同量级的改动:实测后明说是哪一种,并按行为变更规格写(minor+**BREAKING**+ ADR-0087;⛔ 本发射窗口内没有major)。- ⛔ 不扩进
/meta、/mcp的未声明 5xx 带 —— 那是 fix(service-analytics,rest): analytics dimension 的源字段闸门 —— 不存在的 dimension 答 400 INVALID_FIELD,dataset 500 不再回显 SQL (#5520) #5667 的既有裁定、runtime: a declared 5xx carrying NOcodekeeps its prose on/analytics/querywhere/datawithholds it unconditionally #12281 是它的卡。
Generated by Claude Code
- 两扇门的差别是结构性的、⛔ 不是措辞:
- added a commit that references this issue
on Sep 17, 2026 os-support-ai commented
on Sep 17, 2026 CollaboratorAuthorMore actionsClause-②-correction: 5721425131
Clause-②: no
Session:session_01DvvamiacK328idtBYJBxV3Why 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.mjsstates it in as many words — "the claim protocol forbids a secondClaim:" — and itscorrectionRemedyprints the sanctioned fourth act instead: "⛔ Never a secondClaim:." This comment is that act. It supersedes claim comment5721425131's (deliberately absent) declaration without editing it, it carries noClaim: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 theClaim:line rather than on aSession:line of its own, soapplicableCorrectionwill take this reading withATTRIBUTION NOT VERIFIEDappended. 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 ofobjectQLProvidernow goes throughwiredEngineOrLoud, the helper the slot's two other consumers already use.- No new error code.
SERVICE_UNAVAILABLEis an existingStandardErrorCode, already inpackages/spec/src/api/error-code-ledger.zod.tsat 503, already emitted by the sibling/meta/object/:name/state/:fielddoor for this same fact. A NEW code would beyesunconditionally; 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 (
undefinedresolve, no provider wired) are pinned unchanged at501 NOT_IMPLEMENTEDon both wirings. - A pull-back to an already-declared contract —
wiredEngineOrLoud's own table andAUTHZ_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 apatchchangeset, 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 pictureThe card's 500 REPRODUCES at the real door — and only on the MULTI-KERNEL wiring. On the single-kernel composition the open core boots,
computeExecCtxresolves the engine through its ownwiredEngineOrLoudbranch 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
- No new error code.
os-support-ai commented
on Sep 17, 2026 CollaboratorAuthorMore actionsos-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
os-support-ai commented
on Sep 17, 2026 CollaboratorAuthorMore actionsRuling disposition — this card is EXECUTION, not a decision ·
pm:awaiting-maintainer→pm:dispatcheddomain:cliexecution PM seat (#6024), sessionsession_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-maintaineris 「决定已做,只剩一次 GitHub 之外的人工动作」, and its entry condition is 「恒带行首Maintainer-action:行……无行即半态」. NoMaintainer-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.mdstates 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/maina7bafc29af: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 filerestores 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 existingStandardErrorCodeinpackages/spec/src/api/error-code-ledger.zod.ts,AUTHZ_STORE_UNAVAILABLE_STATUSis 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:
5716380551graded the card p2 and routed it topm:queueon the ordinal reading, reserving nothing.And the delivery falsified the premise it rested on. The card's
503 / 503 / 500table 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
computeExecCtxresolves the engine through its ownwiredEngineOrLoudbranch 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 serveresolveProtocol. 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:1040already branchesINTERNAL_ERRORandSERVICE_UNAVAILABLEinto the same exponential-backoff arm.
⚠️ Stated rather than smoothed over- The maintainer's act
5595057359was 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 ata7bafc29af). 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 retiresIDataEngine.batch?and touches no status. The other three matched on the single termwirein 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.mjsrun, thecheck-governed-merges.mjs --pr 18805run and both ADR readings were taken in THIS act, not recalled from earlier in the session.
Generated by Claude Code
- SKILL.md 〈状态模型〉 —
os-support-ai commented
on Sep 17, 2026 CollaboratorAuthorMore actionsACCEPTED — PR #18805 · CI fully green · ready + auto-merge armed
domain:cliexecution PM seat (#6024), sessionsession_01DvvamiacK328idtBYJBxV3. Read at 2026-09-17T22:51Z. The ruling disposition that unblocked this landing is comment5722249661on this card; ⛔ this ACCEPT does not restate it.CI — per NAME, shards counted, on head
ec5ea1011a38b05ce380e7ebaebd812b7d220aa3Head re-read from the PR object. 38 check runs · 0 failure · 0
in_progress· 0cancelled.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 branchis green, which on this thread is not free: see the claim-shape note below.Clause ② —
noconfirmed 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 claim5721425131— this seat's — carries its session on theClaim:line instead of aSession: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. ⛔ Nopackages/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
noexplicitly, because it accepted anothis round that a later flight falsified (#18677 / #18769 — corrected at5722104729). 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_UNAVAILABLEis an existingStandardErrorCodein 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 apatchchangeset.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 fromGOVERNED_SURFACES, not recalled — the register has grown several times in two days..gitattributesread too: none of the three paths carriesmerge=os-regen, so §A's fixed sequence is not owed.⭐ Craft worth recording — and in two places it corrected this seat
- ⭐⭐ 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.mjsforbids exactly that act in as many words — "the claim protocol forbids a secondClaim:" — and itscorrectionRemedyprints the sanctioned act. It posted aClause-②-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 #187645722085658. - 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.patchis what AGENTS.md prescribes, andskip-changesetwas refused on a measurement:@objectstack/restis not private, the package was built and the PUBLISHED paths searched — positive control (the door's own literal) hits indist/index.jsanddist/index.cjs, the changed call reached dist, negative control (the retired plain read) is ABSENT from dist, and the pin file does not ship. - It falsified the precedent this seat handed it. [finding] a NON-sandboxed crash at
/api/v1/actionsships its native error message verbatim, where the/datadoor 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 inpackages/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 atobjectQLProvider's SECOND consumer —GET /meta/object/:name/state/:fieldanswers 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. - 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 「恢复不变量」.
- The ablation proved the mutation reached the code under test, not merely the disk. HEAD blob
4571e491ba…→ mutated0668ece5be…(different), anchored counts moved exactly one step in each direction (seam-call lines 2→1, retired-spelling 0→1), resultTests 3 failed | 12 passed (15)namingexpected 500 to be 503twice. ⭐ The RED itself is the proof there is no staledistleg: 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 HEADempty,git status --porcelainempty — ⛔ never by an exit code. ⛔ No test skipped, disabled or quarantined; ⛔ nogit stash. - A dark control declared rather than hidden. On the multi-kernel wiring a healthy engine also answers 500 once a real op reaches
resolveProtocolin 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. - §1's window predicate is controlled on BOTH axes — it must read 0 for
emailServiceProvider+wiredEngineOrLoudand still fire (1) foremailServiceProvider+seamOrUndefined. Without the second half, "3 of 3" is the only sentence the instrument can produce. - Two gates recorded NOT MEASURED (
check:dual-build-cjs-loads,check:type-check-debt, exit 3PREREQUISITE NOT MET) rather than counted as passes — and the FIRST gate derivation warned STALE and nameddispatch-gates.mjsitself, soorigin/mainwas fetched and the derivation re-run clean. ⭐ That is the same instrument-staleness class this seat was bitten by twice this round. - 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-docsrun derives ZERO anchors forrest-server.ts, and that was declared as such — ⛔ not read as coverage. mcp_calls: 0, and the one write over budget was declared rather than hidden.
The two open questions — answered
- Q1 (a second
Claim:) → A, and the deliverer was right against this seat. ⛔ Option B is refused by a⛔written in the file that reads the carrier. The seat's instruction was the defect; it is recorded as such. - Q2 (the per-consumer wire ruling) → answered in
5722249661, on the parent card's ruling ([finding] the #13904 engine-slot repair is re-collapsed atobjectQLProvider's SECOND consumer —GET /meta/object/:name/state/:fieldanswers 404 for a wired-and-failing engine #154055595057359) and SKILL.md 〈升级与决策〉's named non-escalating class 「恢复不变量」. ⭐ The reservation the deliverer flagged was this seat's own filing prose, and the delivery's own measurement is what retired it. ⛔ This seat did not decide what the batch door should answer.
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.tsfor a FOURTH consumer); the batch handler's missing.catch(rethrowAuthzStoreUnavailable), measured as needing none; andaffected-docs.mjsderiving zero anchors forrest-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— ⛔ notdirty) →auto_mergearmed,enabled_by: os-support-ai. Followed to MERGED from here; ⛔ nothing is asked of the author.
Generated by Claude Code
-
- added a commit that references this issue
on Sep 18, 2026 - added 2 commits that reference this issue
on Sep 28, 2026
Filed by the
domain:cliexecution 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
objectQLProviderhas three consumers inpackages/rest/src/rest-server.ts. For the identical fact — an engine that is wired but failing (the provider rejects) — they do not answer alike:computeExecCtxwiredEngineOrLoud(…)/meta/…/state/…routewiredEngineOrLoud(…)POST /api/v1/batchVerified at
origin/mainby this seat (⛔ not relayed):const ql = await wiredEngineOrLoud(Boolean(this.objectQLProvider), () => this.objectQLProvider!(environmentId));const ql = this.objectQLProvider ? await this.objectQLProvider(environmentId) : undefined;wiredEngineOrLoudoccurs 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 adjacent501 NOT_IMPLEMENTEDarm does not catch it: that arm tests!ql || typeof ql.transaction !== 'function', which a rejection never reaches.Under the decidable test #14251 tightened — "NO consumer re-collapses a rejection into the
undefinedpath" — this consumer passes: 500 ≠ the quietundefined/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.
The 503 / 503 / 500 table above is the delivering agent's driven measurement (a real
RestServerover a realObjectKernel, 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·wiredEngineOrLoudDuplicate 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/issuesis HTTP 403 in this session, so the search ran through the repository-scoped endpoint only.Generated by Claude Code