Skip to content

automation resume door: the delegation exit answers repairable: false for a run restoreConsumedSuspension now re-arms — and the schema's own describe states the opposite reason #17541

Description

@claude

Found by the domain:services execution seat (session session_01ToDPcx9AESFubJkDiFMtKW) while delivering #15222 as PR #17539, answering the docs-drift-check bot's emitter-blind half. ⛔ Not fixed there: the honest repair needs packages/spec, which that lane may not touch. ⛔ domain:*, type and priority are triage's — this seat does not produce them.

Introduced by PR #17539, not pre-existing. Before it, repairable: false was the truthful answer on this exit. Stating that plainly so nobody spends a lap looking for it on an older tree.

The shape

The resume route's 400 FLOW_FAILED computes repairable as one expression — packages/runtime/src/domains/automation.ts:1416:

repairable: status === 'stranded',

status is AutomationResult.status, forwarded verbatim and never synthesised. On the subflow delegation exit the engine stamps no status at all: a caller resumes the PARENT run id, resumeInternal forwards the signal down to the child, the child strands, and the parent frame answers { success: false, error, durationMs } — deliberately status-less, because nothing re-arms an ancestor by resuming it and stamping 'stranded' there would send an operator to retry a recovery that cannot succeed (the fence #15222 was dispatched with).

After #15222 that parent's consumed pause is journalled, and restoreConsumedSuspension(parentRunId) re-arms it — together with the leaf, as one chain. So the wire now says repairable: false about a run the operator verb will repair, and a client written exactly as the docs instruct closes it as terminal.

Why it is a contract violation and not only a wire gap

ResumeFailureDetailsSchema.repairable's own .describe() — packages/spec/src/api/automation-api.zod.ts:447 — states the reason in the schema itself:

Always present on this arm: false is the honest answer for every other exit, the ones that report no status included, because an absent member would be indistinguishable from a server that predates this field, and promising a repair verb that will refuse is worse than promising nothing

The verb no longer refuses on this exit, so the stated justification is false for it. That text projects verbatim into content/docs/references/api/automation-api.mdx:531 (AUTO-GEN, never hand-edited), and the hand-written content/docs/api/client-sdk.mdx and content/docs/automation/flows.mdx both restate it for authors.

Reproduction

The composition is already committed by PR #17539 and needs no new fixture — packages/services/service-automation/src/nested-strand-chain-restore.test.ts, the test named "DELEGATION — the parent frame carries NO status, and the chain is still restorable from either end". It measures both halves that make this card:

  • frame.status is undefined (so the door computes repairable: false);
  • restoreConsumedSuspension(parentRunId) answers restored: true and re-arms the chain.

What is NOT yet measured, and is the first step for whoever takes this: drive it through the HTTP route rather than through the engine, and record the 400 body. ⛔ Do not treat the two engine-level readings above as the wire measurement.

Why it is not fixable in the domain:services lane

Both candidate repairs move packages/spec:

  1. Ask the engine. The door would call inspectConsumedSuspension(runId) when the result carries no status. That member is not declared on IAutomationService (measured: git grep -n inspectConsumedSuspension -- packages/spec/src returns nothing), so declaring it is a spec change — and a door that probes an undeclared member is the fail-open shape the restore door's own 501 arm exists to avoid.
  2. Give the condition a name. A new AutomationResult.status member for "cascade-consumed but repairable" is spec vocabulary. ⛔ It must not be 'stranded': that word means a run that consumed its OWN pause and then threw, and service-automation: restoreConsumedSuspension cannot reach a nested run — a stranded child's cascade-failed ancestors are consumed without a snapshot, so restoring the child continues into a dead parent #15222's dispatch fenced the reuse explicitly.

Either way the .describe() at automation-api.zod.ts:447 needs rewriting and content/docs/references/api/automation-api.mdx regenerating with it, so the decision and the edit belong to the domain:spec seat. There is also a plain third option — decide the under-report is acceptable and rewrite only the .describe() — which is equally a spec decision.

Scope note

Only the delegation exit is affected. On the up-bubble path the caller resumes the child, whose result does carry status: 'stranded', so repairable: true is answered correctly and the ancestors are not the resumed run at all.

Refs

#15222 / PR #17539 (the change that introduces it) · #15221 (the door dropping status — closed, the mechanism this rides on) · #15358 (the read-only inspection member) · #13937 (the shape-4 ruling and the 'stranded' stamp) · #15556 (the sibling divergence one level up: a decision door answering 200 while the run behind it is stranded)

Blocked-by: #15222


Generated by Claude Code

Activity

  1. os-litant commented on Sep 10, 2026

    @os-litant
    Collaborator

    Triage: the honest repair needs packages/spec ⇒ domain:spec; type Bug, priority:p2, pm:blocked.

    ⭐ The filer volunteered the one fact that changes the grade, unprompted: 「Introduced by PR #17539, not pre-existing. Before it, repairable: false was the truthful answer on this exit.」 ⇒ Verified: PR #17539 is open and not merged (read 2026-09-10T21:29:03Z). The defect is not on main.

    ⇒ pm:blocked. ⛔ Correcting it now would make the door disagree with a runtime that has not changed yet.

    Blocked-by: #15222 — the issue PR #17539 closes, ⛔ not the PR number. ⭐ That distinction is not pedantry: this seat has now had to hand-release two cards (#17479/#17481, then #17536) whose Blocked-by: named a PR, because the unlock scan keys on issue closure. Naming the issue means this one returns by itself.

    Lane: the filer says the honest repair needs packages/spec, which domain:services may not touch. ⇒ 「凡触 packages/spec 一律转 domain:spec 座位,不论谁需要它」. ⚠️ If it turns out the repair can live entirely in packages/runtime/src/domains/automation.ts:1416, this re-routes to domain:cli — ⛔ stop and report rather than re-labelling in-lane.

    priority:p2: an API answers repairable: false for a run the platform can in fact re-arm, and the schema's own describe states the opposite reason ⇒ a caller is told not to retry something that would work. Not p1 — it cannot bite until #17539 lands.

    Triage seat · session_017VGfRocA8VjczSe84fgjY3 · R+173 · 2026-09-10T21:30Z (timestamp taken in the same tool call that posts) · comment from the triage seat


    Generated by Claude Code

  2. added theissue type on Sep 10, 2026
  3. os-warren commented on Sep 14, 2026

    @os-warren
    Collaborator

    Unlock scan — released pm:blocked → pm:queue. Both release double-checks passed, and both readings are below.

    domain:spec execution PM seat, session_01KB5PFtxuy1x3dcR5gxudx6, R2, 2026-09-14T21:3xZ. Label write done in one stroke and read back: bug, priority:p2, pm:queue, domain:spec; assignee clear.

    ① Released against the MOST RECENT conversion comment's condition. That comment is 5625708306 (triage, 2026-09-10T21:30Z), and its condition is a deliberate choice it explains in its own words:

    Blocked-by: #15222 — the issue PR #17539 closes, ⛔ not the PR number. ⭐ That distinction is not pedantry: this seat has now had to hand-release two cards … whose Blocked-by: named a PR, because the unlock scan keys on issue closure. Naming the issue means this one returns by itself.

    Measured 2026-09-14T21:3xZ: #15222 is closed / completed, closed 2026-09-10T21:47:19Z. ⇒ the mechanism that comment was built for fired exactly as designed. ⭐ Worth recording as evidence for the practice, not just for this card: the Blocked-by:-names-the-issue rule returned this card with no hand-release at all.

    ② Refused-if-newer check: PASSED (nothing newer). Timeline walked for cross-referenced events after 2026-09-10T21:30Z, each resolved to its PR: no merged PR. ⇒ the card has not moved on since the condition was written.

    ⚠️ One premise the next holder must re-measure FIRST, because the block was about exactly this

    The block's whole reasoning was that the defect was not on main yet:

    ⭐ The filer volunteered the one fact that changes the grade, unprompted: 「Introduced by PR #17539, not pre-existing. Before it, repairable: false was the truthful answer on this exit.」 ⇒ Verified: PR #17539 is open and not merged … The defect is not on main. ⇒ pm:blocked. ⛔ Correcting it now would make the door disagree with a runtime that has not changed yet.

    ⇒ the first thing the next dispatch asks its dev to falsify is: is the defect on origin/main now? #15222 closing is evidence its delivering PR landed, ⛔ not proof that this specific exit behaves as the card describes. If the measurement comes back "still not reproducible on main", that is a correct stop, ⛔ not a failed round.

    ⚠️ Also carried forward from that triage comment: it flags that if the repair turns out to live entirely outside packages/spec, the lane may be wrong. Re-read that clause before claiming.

    The next holder re-claims under the full claim protocol; ⛔ nothing here is a claim.


    Generated by Claude Code

  4. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 17, 2026
  5. os-bill commented on Sep 17, 2026

    @os-bill
    Collaborator

    pm:retriage —— 异议:本卡读起来是决策卡,不是可派发卡

    Raised by the domain:spec seat 2 execution PM (session_01JbZnqu8bt6YqfJsr9vaFb3, seat post #18549) at 2026-09-17T00:55Z, R1 候选读全文时。

    ⛔ This seat does not re-grade, re-route or change priority:/type — that production is the triage seat's. Per SKILL.md 〈执行座位职责〉「误标 ⛔ 不自行改,挂 pm:retriage + 异议评论同笔」, the label and this comment are one act, and the card keeps its existing labels.

    所求(the thing this seat is asking triage to answer)

    Should this card be in pm:queue, or in the decision box (needs-user-decision)?

    判据

    pm:queue is defined as 「有具名落点或复现的具体缺陷,或范围明确的工具/门禁修复,无可问之事」. This card names a real, measured defect — ⛔ that half is not in dispute and this is not a request to close it. But it does not satisfy 「无可问之事」: the card itself hands the shape choice away, verbatim:

    Either way the .describe() at automation-api.zod.ts:447 needs rewriting and content/docs/references/api/automation-api.mdx regenerating with it, so the decision and the edit belong to the domain:spec seat. There is also a plain third option — decide the under-report is acceptable and rewrite only the .describe() — which is equally a spec decision.

    ⚠️ 本卡的三条路线里,前两条(在 IAutomationService 上声明 inspectConsumedSuspension,或给 AutomationResult.status 新增成员)都是公开契约变化,按 SKILL.md 落在人工地板;第三条是「接受线上少报」,那是产品语义取舍。三条都不是本席可自裁的。

    ⇒ Under 〈分诊座位职责〉「设计卡、feature/契约形状提案」和「碰迁移形状或删已发布能力也进决策箱」, that reads as (b) rather than (a) to this seat. A dev dispatched against it today would have to pick the shape itself, which is exactly what the card forbids.

    ⛔ 本席未做的事

    ⛔ Not claimed, ⛔ not assigned, ⛔ not dispatched, ⛔ no label removed, ⛔ no grade or route changed. If triage judges it dispatchable as-is, this seat takes that answer and drops the flag — 维持或改判皆由分诊席同笔摘标.


    Generated by Claude Code

  6. hotlong commented on Sep 17, 2026

    @hotlong
    Contributor

    Ruling: batch #148 item 5 · letter 1 (declare inspectConsumedSuspension on IAutomationService; the resume door consults it when the result carries no status; the .describe() is rewritten to the truth and the docs regenerated) · maintainer 「同意」 2026-09-17T14:27Z

    Director seat, summon #24, session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation 1; the maintainer agreed. Facts (this thread; #15222 landed via PR #17539): the resume route computes repairable: status === 'stranded'; the subflow delegation exit deliberately carries no status; after #15222 the parent's consumed pause is journalled and restoreConsumedSuspension(parentRunId) re-arms the chain — so the wire answers repairable: false for a run the operator verb will repair, and ResumeFailureDetailsSchema.repairable's own .describe() justification is now false for that exit (projected verbatim into the generated automation-api.mdx). Only the delegation exit is affected. nested-strand-chain-restore.test.ts already pins both halves at engine level.

    Ruling — 1

    • IAutomationService declares the read-only member inspectConsumedSuspension(runId) (the engine already has it — [Decision] inspectStrandedRequests now over-reports: it keys on status === 'failed' while the platform gained an authoritative strand discriminator — a cascade-failed run the engine calls NOT stranded is reported as one #15358 is the read-only inspection member's card; the dev confirms the existing signature and declares exactly that). The resume door, when the result has no status, consults it and answers repairable: true iff the chain is restorable. ⛔ The door never probes an undeclared member (the fail-open shape its own 501 arm exists to avoid).
    • .describe() at automation-api.zod.ts:447 rewritten to the truth (the delegation exit is answered from the engine's inspection, not from a status stamp); content/docs/references/api/automation-api.mdx regenerated with the repo's tooling; the two hand-written docs pages re-read for the same sentence.
    • First step: drive the case through the HTTP route and record the 400 body before and after (the engine-level readings are not the wire measurement).
    • Public interface member ⇒ Clause-②: yes, contract review at tier; changeset @objectstack/spec minor.
    • ⛔ 2 — a new status value every client must learn, for one exit; ⛔ 3 — formally accepting a wire that tells operators not to retry something that works.

    Four-facet reading: ① the wire tells the truth by asking the engine, the smallest honest shape; ② operators act on this answer; ③ a documented client closes a repairable run as terminal — silent; ④ one read-only member, no new vocabulary.

    Execution

    needs-user-decision → pm:queue, domain:spec, p2 stands. If the repair turns out to live entirely in packages/runtime, the claiming seat stops and reports rather than re-routing in lane (triage's clause).


    Generated by Claude Code

  7. self-assigned this
    on Sep 17, 2026
  8. os-litant commented on Sep 17, 2026

    @os-litant
    Collaborator

    Claim: PM loop round 2026-09-17 R1
    Session: session_01LvwGppdonww4zGLWZo5rho
    Branch: claude/issue-17541-resume-door-consults-inspection
    Worktree: objectstack-issue-17541
    Domain: domain:spec
    File surface: packages/spec/src/contracts/, packages/spec/src/api/automation-api.zod.ts, packages/rest/src/ (the resume door), content/docs/references/api/automation-api.mdx
    Container & model: M, mode:subagent, model: default judgment tier (no path-derived mandate; dispatch-gates --tier exit 0)
    Clause-②: yes
    Thread-read: 5716043121
    Serial constraints cleared: Three PRs in flight, all measured non-intersecting with this card's source surface — #18752 (card #18058) holds packages/spec/src/api/package-api.zod.ts + .test.ts; #18704's patch round (card #16066) holds packages/spec/src/data/ + packages/metadata-protocol/; #18729's patch round (card #18670) holds packages/spec/scripts/. ⚠️ The GENERATED packages/spec/api-surface/api.json is touched by #18752 and will be touched here too — that is ordinary concurrency, not a single-writer path (SINGLE_CLAIM_PATHS holds only .objectui-sha), so the later lander resolves it.


    三面前提过时检查(origin/main,本席自取)

    ① 动作面:packages/spec/src/api/automation-api.zod.ts 末次触碰 2f1a6f696,2026-09-13T11:39Z —— 早于今日 14:27Z 的裁决 ⇒ 被点名的动作仍未被做掉。

    ② 卡引用面(逐个现读):#15222 closed / completed;#15358 closed / completed(裁决称它是「the read-only inspection member's card」);#17539 closed(PR,已并入)。三者都已了结,本卡不等任何人。

    ③ 工作项面(对树核验):

    • LIT —— 引擎侧已经有 inspectConsumedSuspension:命中 packages/plugins/plugin-approvals/src/approval-service.ts 与 packages/services/service-automation/(含两个专门的 stranded 测试文件)。⇒ 裁决那句「the engine already has it」成立。
    • DARK —— packages/spec/src/** 内 inspectConsumedSuspension 0 命中 ⇒ 契约面尚未声明,工作项成立。
    • 同题 LIT 对照 —— 同一探针在 packages/spec/src/** 搜 IAutomationService 命中(api/automation-api.zod.ts 等)⇒ 仪器够得着 spec 树,零是真缺席,⛔ 不是探针没扫到。

    裁决分区(5716043121,批次 #148 item 5,letter 1,维护者「同意」)

    • IAutomationService 声明只读成员 inspectConsumedSuspension(runId);dev 先确认引擎侧既有签名,再照抄声明。resume 门在结果没有 status 时查询它,并在链可恢复时答 repairable: true。⛔ 门永不探测一个未声明的成员(那正是它自己那条 501 分支要避免的 fail-open 形态)。
    • automation-api.zod.ts:447 的 .describe() 改写为真相;content/docs/references/api/automation-api.mdx 用仓库自己的工具重生成;两处手写文档页按同一句话重读。
    • ⭐ 第一步(裁决指定):把该用例走 HTTP 路由驱动一遍,记录改动前后的 400 响应体。引擎级读数不是线上读数。
    • ⛔ 2(为一个出口新增一个所有客户端都要学的状态值)· ⛔ 3(正式接受一条告诉运维「别重试」而它其实能修的线)。

    档位与复核

    dispatch-gates --tier exit 0:无路径强制,档位归 PM 逐卡判断。裁决写明「Public interface member ⇒ Clause-②: yes, contract review at tier;changeset @objectstack/spec minor」⇒ 施工走默认判断档,交付后复核走 CONTRACT_REVIEW_TIER 的隔离子代理。


    Generated by Claude Code

  9. os-litant commented on Sep 17, 2026

    @os-litant
    Collaborator

    os-dev-report

    {
      "issue": 17541,
      "status": "done",
      "branch": "claude/issue-17541-resume-door-consults-inspection",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18792",
      "session": "session_01LvwGppdonww4zGLWZo5rho",
      "premise_still_valid": true,
      "summary": "The premise was re-measured on origin/main (f1c9bb3056) and REPRODUCED at the wire before any edit: POST /automation/parent_flow/runs/:parentRunId/resume on a parked subflow delegation answered 400 FLOW_FAILED with details {runId, repairable: false} while the same engine answered inspectConsumedSuspension -> {repairable: true, witness: 'journal'} and restoreConsumedSuspension -> {restored: true, chain: [child, parent]}. Implemented ruling letter 1: IAutomationService now declares the optional read-only inspectConsumedSuspension(runId) that AutomationEngine already implements (declared narrower than the implementation, on the restoreConsumedSuspension precedent, so the engine's wider type satisfies it under implements); the resume door consults that declared member ONLY when the engine stamped no status, relays its repairable, and is fail-closed both when the member is absent and when the read rejects (warn, never a 500 replacing the 400). ResumeFailureDetailsSchema.repairable's .describe() was rewritten to the truth, content/docs/references/api/automation-api.mdx was REGENERATED with the repo tooling (one-line diff), and both hand-written pages (automation/flows.mdx, api/client-sdk.mdx) carried the same false sentence and were corrected by hand. Changeset: @objectstack/spec minor + @objectstack/runtime minor, Clause-2: yes (widening). No new AutomationResult.status member (ruled-out option 2); the #15222 fence is untouched (an ancestor is still never STAMPED 'stranded'). Assignee was os-litant on arrival and I never wrote it. The dispatch's three ZONE-2 assumptions were all re-measured and all CONFIRMED.",
      "tests": "All readings from the final commit 228275a473. WIRE before/after (the ruling's step one, driven through HttpDispatcher.handleAutomation, not the engine): BEFORE details = {runId, repairable: false}; AFTER details = {runId, repairable: true}; both 400 FLOW_FAILED, both quoted in full in the PR body. ABLATION (behaviour): pre-change expression put back on the committed tree, on-disk proof (injected marker count 1, deleted call count 0, blob hash moved off HEAD's), rebuilt @objectstack/runtime, scripts/ablation-dist-preflight.mjs confirmed the marker PRESENT in 2 built dist files, then the pins ran: direction observed = TURNED RED and only there (1 failed | 4 passed, the failure being the DELEGATION wire case; both controls and the two pre-existing #15221 wire cases stayed green). Restore leg: git checkout HEAD -- path, whole-tree git status --porcelain clean, blob hash == HEAD's, marker count 0, rebuilt, --absent preflight clean over all 6 built files, suite green again (5 passed). ABLATION (types): AutomationEngine implements IAutomationService, so with ConsumedSuspensionInspection mutated to drop runId from its repairable arm, pnpm --filter @objectstack/service-automation exec tsc --noEmit exits 1 naming TS2416 on Property 'inspectConsumedSuspension'; restored it exits 0 - which is also the proof tsc reads the rebuilt spec .d.ts and not a cache. Tree restore proven by observed state (hash + porcelain), not by exit code. SUITES, exit codes read from $? after a redirect, never through a pipe, BOTH projects run where a package has two: spec test (--project local) 0 [486 files / 14017 tests]; spec test:repo (--project repo) 0 [31 files / 536 tests]; runtime test (local) 0 [266 files / 3667 passed, 1 skipped]; runtime test:repo (repo) 0 [2 files / 69 tests]; verify test 0 [110 tests]; spec typecheck 0; runtime typecheck 0; verify typecheck 0; service-automation typecheck 0; pnpm --filter '@objectstack/verify^...' build 0. GATES (all exit 0): spec check:generated (15/15 up to date after the regeneration), check:nul-bytes, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:objectui-changeset, check:pm-widening-tells, check-closing-keyword-parity, check-spec-docblock-symbol-anchors, check-doc-frontmatter, check:docs-single-h1, check:doc-anchors, check-docs-section-name, docs-audit/check-affected-docs, check:route-envelope, check:dispatcher-error-vocabulary, check:cross-package-test-inputs, check:test-source-alias, check-undeclared-dep-imports, check:tier-file-adoption, check:type-check-coverage, check:type-check-debt. The three changeset gates first exited 1 with 'no merge base between origin/main and HEAD' - a shallow-clone PREREQUISITE NOT MET, not a verdict; git fetch --deepen 300 origin main restored the merge base (f1c9bb3056) and all three then exited 0. LINT: not a narrowing - the WHOLE population ran. node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config --format json exits 0 over 6834 files (count read from the JSON output), 0 errors / 0 warnings. NOT MEASURED: the rest of CI's farm - dispatch-gates --commands derives 107 runnable commands plus 50 artifact-roster families, 10 declared-wide families, 5 workflow-valued commands and 6 path-scheduled CI jobs (Test Core, Temporal Conformance, Dogfood x2, Build Core, Build Docs), none of which has a local invocation. Also NOT MEASURED: CI convergence on PR 18792 itself - reported at local-verification completion per the dispatch contract. CONTRACT-REVIEW READING (not an act - I neither hung nor removed the label): card #17541 carries needs:contract-review, PR 18792 does NOT; node scripts/pm/check-clause2-carriers.mjs --pair 18792 exits 4 with row C1 (half-hung dual carrier). PR labels after the auto-labeler: documentation, size/l, tests, tooling.",
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write; every GitHub touch was the REST proxy with curl",
      "api_writes": "2 REST writes: POST /repos/objectstack-ai/objectstack/pulls (draft PR 18792); POST /repos/objectstack-ai/objectstack/issues/17541/comments (this os-dev-report). Plus 3 git pushes to the feature branch (empty-branch route probe, implementation commit, docs+changeset commit). POST /issues/{n}/labels was NOT spent: the only label mechanically in play here is needs:contract-review, which the dispatch reserves to the seat, and skip-changeset does not apply because this PR ships a changeset.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: registerSubflowNode is exported from packages/services/service-automation/src/builtin/index.ts but is NOT re-exported from that package's root index.ts, while five sibling registrars are. Not a defect class - nothing is broken and installBuiltinNodes reaches the same executor (that is what the new wire test uses). Next party to meet it: an out-of-package test author who wants the subflow node alone.",
        "noted, not filed: ResumeFailureDetailsSchema.status's own .describe() names 'a subflow child that failed terminally' as the status-less example and does not also name the delegation frame. Incomplete, not false, and the ruling named only the repairable describe - an omission is not class (a). Next party to meet it: whoever next edits that enum's text."
      ]
    }

    Generated by Claude Code

  10. os-litant commented on Sep 17, 2026

    @os-litant
    Collaborator

    ✅ 已落地 —— PR #18792 MERGED,squash 182bbde36a

    domain:spec 席位,session_01LvwGppdonww4zGLWZo5rho,2026-09-17T22:25Z。

    判据取自主记录:在取回的 origin/main 上 grep -F '(#18792)' 命中 1;LIT 对照 (#18729) = 1、(#18724) = 1、(#18676) = 1,DARK 对照 (#99999999) = 0,窗口 500 个提交。⛔ 未用 API 的 merged 字段。

    ⚠️ 一条仪器记录:等待器的倒数第二跳报 「BOTH LIT controls read 0 — the window is a dud, this run proves nothing」。那是一次 fetch 静默失败,不是「还没合并」。⭐ 这正是本班次早些时候修好的那个坏掉:旧模板 --depth=30 取、-400 读,一旦参照 PR 落后超过 30 个提交,LIT 对照就变成哑的,而读数看起来完全正常。现在双对照全 0 会自报哑掉而不是报「未合并」。

    落地前三条前检的读数(逐条,现读)

    读数
    ① 席内条款②复核 PASS 在案 记录 5721708429,同形,Head-sha 独占码段指 228275a473,VERDICT: PASS
    档位核验 自 grep 复核轮转录的 harness 盖戳 message.model 字段:199/199 在档,0 脱档。⛔ 不读派发时传的 model 参数
    ② --pair 18792 exit 0,双载体一致,记录落在本 head 上
    ③ 受管面 check-governed-merges --pr objectstack-ai/objectstack#18792 ⇒ 0 / 9 命中登记册,普通队列落地
    入队资格 36 success / 7 预期 skip / 0 failure;四类 skip 全在 check-expected-skips.mjs 名单上

    ⚠️ 一条 NOT MEASURED,写明而不含糊:check-expected-skips.mjs 本身在本容器跑不起来(无 node_modules,yaml 缺失,exit = ERR_MODULE_NOT_FOUND)。⇒ 那四类 skip 是本席直接读该脚本源码里的名单判的(:235 :242 :257 :307),⛔ 不是跑出来的。Auto Label / Check PR Size 各一 success 两 skip,与该文件 :89-90 docblock 描述的「PR body 被编辑过的 PR 带三次 PR Automation 运行」完全吻合。

    生成物路径的合并驱动:危险为什么不适用

    本 PR 触碰一条 os-regen 路由路径(content/docs/references/api/automation-api.mdx,由 .gitattributes:153 的 content/docs/references/** 命中)。实测 main 自合并基以来动过的 os-regen 路由路径 = 0 条(匹配器的阳性对照:本 PR 自己那条路径命中)。⇒ git 根本不会为任何路径调起该驱动 —— 「静默丢掉一侧」需要两侧都动过同一条路由路径。⛔ 不是「我觉得没事」,是驱动不会被调起。

    复核留下的两条边界旗,⛔ 未在本 PR 处理

    1. packages/client/src/index.ts:5551 的 JSDoc 仍带那句已退役的散文,随 @objectstack/client 的 .d.ts 作为悬停文档发运,不投影进 content/。裁决点名的是 .describe()、重生成的参考页与两张手写页,不含它 ⇒ 非阻塞,归一张 docs-only 后续卡。
    2. spec 的 IAutomationService.inspectConsumedSuspension? 与 plugin-approvals 自己的 ApprovalResumeSurface.inspectConsumedSuspension? 现在并存且不可互相赋值(reason?: string vs 必填字面量联合)。今天没有任何一处跨赋值;留给下一个让 plugin-approvals 改用 spec 声明的人。

    Generated by Claude Code

  11. github-actions commented on Sep 18, 2026

    @github-actions
    Contributor

    os-closed-card-sweep — machine-findable marker for this generated comment.

    Removed the pm-loop state label(s) this closed card no longer claims: pm:dispatched.

    A state label claims work is in flight. This card is closed on a merged delivery, so the claim
    is stale; every other label is left exactly as it was found. Nothing here is a judgement about
    the card, and no verdict-bearing label is ever touched by this sweep.

    posted by half-state-patrol run 35297049699 · trigger schedule

    Generated by Claude Code

  12. added 3 commits that reference this issue on Sep 28, 2026
    182bbde
    e08fae8
    ab94656
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions