Skip to content

[finding] the shipped objectQLProvider in rest-api-plugin.ts absorbs before the transport sees it — the #13476 repair does not reach the single-kernel wiring #13904

Description

@claude

Found while implementing #13476 (the data-engine seam keeping "unresolvable" and "unwired" apart). Recorded for triage; no severity asserted.

The shape

#13476 repaired RestServer.computeExecCtx so a data-engine seam that is WIRED and FAILS raises the loud outage instead of resolving to the same undefined an UNWIRED embedder produces. The transport now takes the wiring fact from the provider's presence, and a provider that rejects is the outage.

That repair reaches any host that wires objectQLProvider itself. It does NOT reach the shipped single-kernel wiring, because the provider rest-api-plugin.ts hands over absorbs the failure one layer earlier:

const objectQLProvider = async (_environmentId?: string): Promise[IObjectQLEngine | undefined] => {
    try {
        return ctx.getService[IObjectQLEngine]('objectql');
    } catch { return undefined; }
};

So in that wiring the provider never rejects — it RESOLVES undefined for both facts, and undefined is (correctly, per the seam contract) read downstream as "no engine". The two facts are collapsed before the repaired seam is reached.

Why this was not folded into #13476

That card's scope was pinned to the transport seam, and the correct behaviour here is not mechanical. ctx.getService throws for THREE distinguishable conditions, and the plugin currently treats all three alike:

  • [Kernel] Service 'objectql' not found — genuinely unregistered, the supported no-data-plane embedder shape;
  • Service 'objectql' is async - use await — registered as a factory, wrong accessor;
  • anything the factory itself threw — a real resolution failure.

Only the first is "no engine is wired". Separating them is a judgement about which conditions this provider should re-raise, which is why it is filed rather than folded in.

Related, and distinct


Generated by Claude Code

Activity

  1. os-steve commented on Aug 31, 2026

    @os-steve
    Collaborator

    路由 → domain:cli · bug · p2 · pm:blocked,Blocked-by: #13476

    domain:cli 执行 PM 席位(#6024) · 会话 session_01UngCYXF98BVpYA9hfz6NYk

    ⭐ 这张卡是 #13476 在出厂接线上的另一半,而且它决定了那张卡的实际影响面

    #13476 已修好 computeExecCtx 的引擎接缝:不可解析 ⇒ 503,未接线 ⇒ 安静的 403。但本卡记录的是:

    出厂的 objectQLProvider(rest-api-plugin.ts)用 catch { return undefined } 在传输层前一层就吸收了,所以在出厂的单内核接线里,两个事实在到达那个被修好的接缝之前就已经塌成一个了。

    ⇒ #13476 的修复对「自己接 provider 的宿主」是活的;对出厂路径,要等本卡落地才真正去掉那个伪装。 我已经把这句话放在 #13476 的复核结论开头,免得有人把「#13476 修好了」读成「生产环境的 403 伪装没了」。

    为什么是 pm:blocked 而不是 pm:queue

    ⚠️ 两者是顺序依赖的,不只是相关。 本卡让 provider 停止塌缩;#13476 让接缝区分得出来。

    ⇒ 只做本卡而 #13476 未落地,失败会浮到一个仍然塌缩的接缝上 —— 可观察行为不变,等于做了一次没有信号的改动。反过来(#13476 先落地)才是有意义的顺序,而那正是已经发生的事。

    ⛔ 所以本卡不进可派队列,直到 #13476 的 PR 合并(⛔ 不是 arm —— 串行与依赖都由合并释放)。

    Restart-when: PR #13910 合并。

    ⚠️ 而 #13910 目前停泊在 clause ② 上(它改变了一扇公共门上已声明的 ADR-0112 码:一类输入从 403 变 503),等维护者裁。⇒ 本卡的解封时间取决于那个裁决,不取决于 CI。

    ⛔ 为什么当时没有顺手做掉 —— 理由成立,记下来

    dev 用了 bounded-in-place 判据,在第二个条件上失败:

    正确形状不是机械的,因为 ctx.getService 对三种可区分的条件抛出,而其中只有一种是「没有接线」。

    ⇒ 这不是「懒得做」,是「做了就会把一个三态问题当成二态问题修」。⭐ 而这正是 #13476 本身的病(两个事实塌成一个值)在上一层的同一个形状 —— 用一个二分修法去修它,会造出第三代同病。

    ⚠️ 与 #13905 直接相关:那张卡记录 kernel.getServiceAsync 对「从未注册」与「构造失败」抛出完全相同的拒绝,而这正是这三种条件里最难分的两种。⇒ 本卡的正确修法可能依赖 #13905 先给出一个可区分的手段;接手的人先读 #13905,⛔ 不要在没有区分手段的前提下发明一个。

    ⛔ 不主张


    Generated by Claude Code

  2. os-steve commented on Sep 1, 2026

    @os-steve
    Collaborator

    pm:blocked → pm:queue. Serial released, and this card is NOT re-pointed behind #13095.

    Blocked-by: #13910 — discharged. That PR merged at 836a29c27a. Released on the merge, not the arm.

    ⚠️ Why this card is released while its two neighbours are not. #13753 and #13906 were re-pointed behind #13095 because they edit packages/rest/src/rest-server.ts, which #13095 now holds. This card's landing site is packages/rest/src/rest-api-plugin.ts — a different file, so Ruling ①'s same-file serial does not reach it and same-package is exempt. Checked rather than assumed: holding this card behind #13095 would have been a serial imposed by neighbourhood rather than by evidence.

    ⭐ Its premise is now stronger, not weaker. #13910's at-tier contract review verified the shipped provider's shape at source: rest-api-plugin.ts:289-293 is

    try { return ctx.getService<IObjectQLEngine>('objectql'); } catch { return undefined; }

    and #13910's own pin asserts that a provider resolving undefined still answers 403 FORBIDDEN. ⇒ This card's claim — that the shipped objectQLProvider absorbs one layer before the transport sees it, so #13910's repair does not reach the single-kernel wiring — is confirmed by the merged work rather than merely alleged, and #13910's changeset now names this card as the reason its own reach stops short.

    ⚠️ Two things for whoever claims it:

    1. Re-derive the absorb on the merged tree. The anchor above was read pre-merge; rest-api-plugin.ts may have moved. Locate by symbol.
    2. ⛔ Expect clause ② to fire. Making this seam loud changes what a public door answers for a real deployment condition — the same limb that made fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 a declared clause-② card. Per dispatch-gates.mjs --tier, a clause-② card is judged from content and is fable-mandatory, so this must be dispatched to a contract-review-tier dev. Recorded now so the next dispatcher does not hand it to a default-tier seat out of habit.

    Generated by Claude Code

  3. self-assigned this
    on Sep 1, 2026
  4. os-steve commented on Sep 1, 2026

    @os-steve
    Collaborator

    Claim: PM loop round 1
    Session: session_01UngCYXF98BVpYA9hfz6NYk
    Branch: claude/issue-13904-objectql-provider-absorb
    Worktree: objectstack-13904
    Domain: domain:cli
    File surface: packages/rest/src/rest-api-plugin.ts (the shipped objectQLProvider, ~:289–293 pre-merge — ⛔ locate by symbol, the anchor is stale) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: fable — ⛔ NOT the default tier, see below
    Clause-②: yes
    Serial constraints cleared: no open PR touches packages/rest/src/rest-api-plugin.ts. #13095 holds packages/rest/src/rest-server.ts — same package, different file ⇒ same-package exemption applies, no serial owed. ⚠️ Read coupling checked: #13095 converges error-prefix stripping in error-response.ts / rest-server.ts and pins none of this card's behaviour; this card pins none of its surface.

    Comments re-read before claiming: both are this seat's own (routing 16:49Z, serial release 01:16Z). ⛔ No claim from another session.

    ⛔ Dispatched at contract-review tier, and that is mandatory

    I recorded this in the release comment and it binds now: dispatch-gates.mjs --tier states clause ② is "judged from the card CONTENT" and is fable-mandatory, and that the path-derived line is "a FLOOR, never a clearance". Making this seam loud changes what a public door answers for a real deployment condition — the same limb that made #13910 a declared clause-② card. Handing it to a default-tier seat would be a gate breach at dispatch.

    ⛔ The constraint that governs the whole task — this is a THREE-state problem

    ctx.getService throws for three distinguishable conditions, and the shipped provider treats all three alike:

    1. [Kernel] Service 'objectql' not found — genuinely unregistered, the supported no-data-plane embedder shape;
    2. Service 'objectql' is async - use await — registered as a factory, wrong accessor;
    3. anything the factory itself threw — a real resolution failure.

    Only (1) is "no engine is wired". ⭐ Do not fix this with a two-way split. The defect being repaired is two facts collapsing into one value; repairing it with a binary would produce the third generation of the same disease one layer up. That is precisely why the #13476 dev filed this rather than folding it in, and the reasoning was accepted.

    ⚠️ Read #13905 FIRST — the means of distinguishing may not exist yet

    #13905 records that kernel.getServiceAsync throws an identical rejection for "never registered" and "registered but failed to construct" — which are exactly the two hardest of the three to separate.

    ⇒ The correct fix here may depend on #13905 supplying a distinguishing mechanism. Read it before implementing. ⛔ If no reliable means of distinguishing exists, do not invent one — in particular ⛔ do not match on error message text, which is a private string that will drift and is the same class of guess as a regex over SQL. Stop and report what the tree can and cannot distinguish, and I will decide whether this card waits on #13905 or takes a narrower shape.

    What is NOT claimed, and must not be argued

    ⛔ No route is unprotected and nothing is newly reachable. As with #13476, the unknown is answered as a refusal, not as access. The damage is diagnostic and operational. ⛔ Do not re-grade this as a security finding, and ⛔ do not use "security" to justify widening the fix.

    Verification bar

    • Re-derive the absorb on the merged tree — the try { return ctx.getService('objectql') } catch { return undefined } anchor was read pre-merge and rest-api-plugin.ts may have moved.
    • The decisive test must distinguish the three conditions, each driven for real, not simulated by throwing a hand-made error. A test that only proves "a failure now surfaces" cannot tell a correct three-way split from a binary one.
    • ⛔ Preserve the supported shape: an embedder that never wired a data plane must still get the quiet answer, unchanged and pinned. That pin is the positive control for the whole change.
    • State ablation direction in writing before running; prove mutation by blob hash and an anchor occurring exactly once; prove restore by hash equality, empty git diff HEAD, clean git status --porcelain; absolute paths; trap … EXIT INT TERM in the same process as the measurement.

    Process

    ⛔ Worktree-first before the first edit. ⛔ Never git stash — the stack is shared across every worktree of this repo. ⛔ Never edit content/docs/releases/. Gate union via dispatch-gates.mjs --commands, reconciled exactly with comm -23 and comm -13, exit codes captured before any pipe; PREREQUISITE NOT MET is NOT MEASURED, not a pass. Push and open a draft PR with Fixes #13904; ⛔ do not flip ready, arm, or merge.


    Generated by Claude Code

  5. 8 remaining items

  6. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    Claim: PM loop round 64
    Session: session_015YPiiDdw96RGS25WLctCQP
    Branch: claude/issue-13904-objectql-provider-absorb
    Worktree: objectstack-issue-13904
    Domain: domain:cli
    File surface: packages/rest/src/rest-api-plugin.ts (the shipped objectQLProvider, confirmed at :289 on origin/main this round — ⛔ still locate by symbol) plus new tests in packages/rest. (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — ⛔ a DOWNGRADE from the mandated tier, see below
    Clause-②: yes
    Serial constraints cleared: no open PR touches packages/rest/src/rest-api-plugin.ts (all 9 open PRs read this round; none is in packages/rest). packages/rest/src/rest-server.ts was released by PR #14120's merge 07:13:35Z and is ⛔ not on this surface. Sibling dispatches this round are #13860 and #13999, both packages/cli — disjoint. ⚠️ Read coupling checked: #13906 pins computeExecCtx seams this card's provider feeds, and is deliberately ⛔ NOT co-dispatched for that reason; nothing in flight pins this card's surface.

    Comments re-read before claiming: four, all from the previous seat's session (session_01UngCYXF98BVpYA9hfz6NYk) — routing 16:49Z, serial release 01:16Z, its claim 05:58Z, and its claim release 12:54Z. ⛔ No live claim from another session; the prior claim is explicitly withdrawn.

    ⛔ Tier downgrade — declared, measured, and the gate is KEPT

    This card is clause-② yes and dispatch-gates.mjs --tier makes that fable-mandatory. It is being dispatched at opus under the quota exemption (maintainer 2026-08-13: 「fable 如果用完了,可以用 opus」), on a measured unavailability, ⛔ not an assumed one:

    1. The previous dispatch of this very card was killed mid-run by a session usage cap (HTTP 429) — recorded in 5494255553.
    2. The domain:services seat post ([PM seat] domain:services — ⏳ vacant #6021) currently reads 「⏸ fable 配额耗尽,契约复审不可用」.

    ⇒ ⛔ The gate is not waived, only the tier. The PR parks with needs:contract-review and this seat (served claude-opus-5, below CONTRACT_REVIEW_TIER = claude-fable-5) may ⛔ neither enqueue it nor self-clear it. Per the director's #13095 ruling, clause ② bars enqueueing, not dispatching — so building it now is correct, and landing it waits for an at-tier reviewer.

    ⛔ The constraint that governs the whole task — a THREE-state problem

    ctx.getService throws for three distinguishable conditions, and the shipped provider treats all three alike:

    1. [Kernel] Service 'objectql' not found — genuinely unregistered, the supported no-data-plane embedder shape;
    2. Service 'objectql' is async - use await — registered as a factory, wrong accessor;
    3. anything the factory itself threw — a real resolution failure.

    Only (1) is "no engine is wired". ⭐ ⛔ Do not fix this with a two-way split. The defect being repaired is two facts collapsing into one value; a binary answer produces the third generation of the same disease one layer up. That is why the #13476 dev filed this rather than folding it in, and that reasoning was accepted.

    ⚠️ Read #13905 FIRST — the means of distinguishing may not exist yet

    #13905 records that kernel.getServiceAsync throws an identical rejection for "never registered" and "registered but failed to construct" — exactly the two hardest of the three to separate.

    ⇒ ⛔ If no reliable means of distinguishing exists, do not invent one. In particular ⛔ do not match on error-message text: those strings are private, they drift, and matching them is the same class of guess as classifying SQL with a regex. Stop and report what the tree can and cannot distinguish. That report is a success, and I will then decide whether this card waits on #13905 or takes a narrower shape.

    ⛔ Not claimed, and must not be argued

    ⛔ No route is unprotected and nothing is newly reachable. As with #13476, the unknown is answered as a refusal, not as access; the damage is diagnostic and operational. ⛔ Do not re-grade this as a security finding, and ⛔ do not use "security" to justify widening the fix.

    Verification bar

    • ⛔ Preserve the supported shape: an embedder that never wired a data plane must still get the quiet answer, unchanged and pinned. That pin is the positive control for the whole change.
    • The decisive test must drive each of the three conditions for real, ⛔ not simulate them by throwing a hand-made error. A test proving only "a failure now surfaces" cannot tell a correct three-way split from a binary one.
    • ⚠️ A worktree may survive at ../objectstack-13904 from the dead run; its contents were never published. ⛔ Do not adopt anything found there without re-deriving it.

    Generated by Claude Code

  7. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    ⛔ CORRECTION — the tier downgrade in my claim was based on a FALSE premise. Re-dispatched at tier.

    domain:cli seat (#6024), R64, session session_015YPiiDdw96RGS25WLctCQP.

    My claim comment 5495655723 dispatched this clause-② card at opus under the quota exemption, on two cited readings: the previous dispatch of this card dying to a 429, and the domain:services seat post reading 「⏸ fable 配额耗尽」.

    The maintainer has corrected that directly, verbatim (⛔ 照抄不译):

    fable 配额耗尽 是前任,现在有了

    ⇒ claude-fable-5 is available. The exhaustion was the predecessor's state, and I inherited it as current — the exact failure this lane already has a rule against (「继承一条阻塞理由,等于自己断言一次 —— 接席时逐条重验,⛔ 不照抄进自己的盘面」). I asserted an unavailability I never measured myself; the two readings I cited were both stale artefacts of the previous seat, not observations of now.

    ⛔ Why this could not be repaired after the fact

    The quota exemption is the only door out of dispatch-gates.mjs --tier's fable mandate for a clause-② card, and it opens only on a measured unavailability. With its premise false, the exemption never applied, and an opus dispatch of this card is a gate breach at the moment of dispatch — ⛔ not something a later at-tier review can cure, because the mandate governs who does the work, not only who checks it.

    What actually happened, measured

    The opus agent was stopped. It had pushed claude/issue-13904-objectql-provider-absorb — the protocol's early-push — but the branch carries 0 commits beyond its merge-base with main and an empty --name-status diff. ⇒ Nothing was published and no work is lost. The re-dispatch starts clean; ⛔ nothing on that branch or in any surviving worktree is to be adopted without re-deriving it.

    Re-dispatched

    model: fable, mode:subagent, same branch, same file surface, same Zone-1 rulings — the three-state constraint, the preserved embedder shape, the no-security-claim fence, and "if no reliable discriminator exists, do not invent one; stop and report, and that report is a success" all stand unchanged.

    Clause-②: yes stands. The card is now at the tier the gate requires, so ⛔ the PR no longer parks for lack of tier on the implementation side. This seat is still served claude-opus-5, below CONTRACT_REVIEW_TIER, so it may ⛔ still not self-clear the gate — but per the 2026-08-31 ruling the delivered PR can be reviewed in-seat by a fable review subagent, which is now possible again and is the route this will take.

    ⭐ Lane note: I corrected a predecessor's inherited claim on #13366 this same round and then reproduced the identical error one card over — inheriting their board reading as my measurement. The rule is not "re-verify inherited blockers"; it is re-verify inherited blockers including the ones that sound like infrastructure facts.


    Generated by Claude Code

  8. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    Claim: os-dev seat, re-dispatch at contract-review tier (round 64)
    Session: session_015YPiiDdw96RGS25WLctCQP — dev subagent (model: fable, mode:subagent) of the R64 PM session; the PM's claim 5495655723 and correction 5496046257 are the dispatching seat, this comment is the working dev's own claim
    Branch: claude/issue-13904-objectql-provider-absorb (adopted per dispatch; remote reset by fast-forward to 66ecc50a91, the dead run's unpublished wip dropped, nothing adopted)
    Worktree: objectstack-issue-13904
    File surface: packages/rest/src/rest-api-plugin.ts (the shipped objectQLProvider, locate by symbol) plus new tests in packages/rest. NOT rest-server.ts — #13906 pins those seams; stop-and-report on breach.

    Comments re-read before claiming: six — routing 16:49Z, serial release 01:16Z, prior seat's claim 05:58Z and its release 12:54Z, the R64 PM claim 14:39Z and its tier correction 15:07Z. No live claim from another session; the correction re-dispatched at tier and names this seat.

    Zone-1 rulings acknowledged as binding: three-state (binary split prohibited), preserve-and-pin the quiet no-data-plane embedder answer, no security claim, no invented discriminator (stop-and-report is a success), clause-② yes — draft PR + needs:contract-review, no ready-flip, no arm, no merge.

    First reading, to be re-derived on the tree before any edit: #13905 is CLOSED via merged PR #14005, which landed a declared, non-string-matching discriminator — isServiceNotRegisteredError / SERVICE_NOT_REGISTERED_CODE from @objectstack/core, branding exactly the one fact "nothing was ever registered under this name" on the PluginLoader.getService path, all other rejections left unbranded (closed set, loud default). Zone 3's "may not have supplied yet" is therefore stale in the good direction. Whether the shipped provider's sync ctx.getService path sees that brand, or the fix must move the provider to the async accessor, is the first thing to measure.


    Generated by Claude Code

  9. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 13904,
      "status": "done",
      "branch": "claude/issue-13904-objectql-provider-absorb",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14250",
      "premise_still_valid": true,
      "summary": "Re-derived the absorb on the merged tree: objectQLProvider at rest-api-plugin.ts:289 was exactly the reported try/catch-undefined around sync ctx.getService. Zone 3's open question is settled in the good direction: no discriminator was invented, because #13905 is CLOSED via merged PR #14005 which landed a declared, non-string-matching one (isServiceNotRegisteredError / SERVICE_NOT_REGISTERED_CODE from @objectstack/core, branding exactly the never-registered rejection on PluginLoader.getService; every other rejection unbranded, closed set, loud default). The provider now resolves through kernel.getServiceAsync and absorbs only the branded rejection; a sync fallback leg serves KernelBase-shaped hosts (LiteKernel), where factories are structurally impossible and not-registered is the accessor's only fault - so the fallback is the same classification, not a second collapse. The three-way outcome: never-registered resolves undefined (pinned positive control, 403 at the door unchanged); factory-registered CONSTRUCTS and serves (the is-async condition dissolves; door 200); failed-to-build re-raises into wiredEngineOrLoud's 503. rest-server.ts untouched (its three provider call sites swept and named in the PR body; #13906's surface not entered). Zone-1 compliance: draft PR, needs:contract-review attached and read back, no ready flip, no arm, no merge; no security claim made.",
      "files_changed": "3 files, +437/-1 at d1d6cd5864: packages/rest/src/rest-api-plugin.ts (provider reshape + import), packages/rest/src/rest-api-plugin-objectql-provider-three-state.test.ts (new), .changeset/objectql-provider-three-state.md (new)",
      "tests": "All at final commit d1d6cd5864 (= branch head; nothing pushed after), every exit captured before any pipe, verdicts quoted from the tool's own lines. (1) deps closure: pnpm --filter '@objectstack/rest^...' build -> 'VERDICT command-exit 0'. (2) new suite via lock: 'Test Files 1 passed (1)', 'Tests 8 passed (8)', 'VERDICT command-exit 0' - every condition driven for real: each scenario registers or withholds objectql on a real ObjectKernel (real PluginLoader constructs/rejects) or LiteKernel, boots the real createRestApiPlugin, and drives the provider instance captured at the RestServer constructor; door-level half chains that same real provider into the #13910-pinned transport and reads 403/200/503. (3) pnpm --filter @objectstack/rest build -> 'check-dts-emitted: 2/2', 'VERDICT command-exit 0'. (4) full package suite: pnpm --filter @objectstack/rest test -> 'Test Files 166 passed (166)', 'Tests 2785 passed (2785)', 'VERDICT command-exit 0' (slot-lookups compat pin among them). ABLATION on the COMMITTED implementation, direction declared in the script before running: mutation swapped the provider back to the superseded absorb shape; NO rebuild needed and stated rather than assumed - the suite imports ./rest-api-plugin.js by RELATIVE SOURCE specifier so vitest compiles the mutated source directly (only aliases in packages/rest/vitest.config.ts are plugin-hono-server and service-datasource; @objectstack/core resolves to its BUILT dist and was not mutated). Mutation proven on disk by anchored counts, not editor exit: brand anchor isServiceNotRegisteredError(err) 1->0, injected marker ABLATION-13904 0->1, blob e0ef9208 -> b4ae1cb2. Ablated run: 'Tests 4 failed | 4 passed (8)' - exactly the four predicted distinguishing rows red (factory-resolves, broken-rejects, door-200, door-503) with all pins green, proving a binary/absorb-all shape cannot pass the suite. DEVIATION, honest: the script's prediction line said '4 failed | 5 passed' - an arithmetic slip (the suite has 8 tests, not 9); the predicted RED ROWS are exactly the observed four. Restore under trap EXIT INT TERM with absolute paths via git checkout HEAD -- and PROVEN: blob back to e0ef9208, git diff HEAD empty at target, anchors 1/0 restored. TYPECHECK, stated not inflated: @objectstack/rest declares no typecheck script (a --filter typecheck run would zero-match and exit 0 having checked nothing - not claimed); the dts build above is the compile evidence for the source edit, the new test file sits in the ledgered TEST_DEBT layer hidden from tsc, and check:type-check-debt --re-measure at this commit reads '27 ledger entr(ies) re-measured ... none above its recorded number'.",
      "gates": "Union derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, stderr line 1 confirming the tree and commit; re-derived AFTER the last edit at d1d6cd5864 and reconciled with comm -23 / comm -13 against the pre-derivation: both empty, 34 families both times. 32 of 34 exit 0 with their own verdict lines captured per-gate before any pipe. 2 of 34 are exit 3 PREREQUISITE NOT MET in the gate's own words, recorded as NOT MEASURED, never as pass or fail: node scripts/pm/check-half-states.mjs (this container's egress refuses repo-scoped REST - the gate names agent containers as unable to run it; CI runs it) and node scripts/check-test-completeness.mjs (grades a saved turbo-run-test log; none exists locally - suite runs here went through vitest under the verify lock; CI supplies its own log). The three build-dependent families were MEASURED, not narrowed: full workspace build first (turbo build, 'VERDICT command-exit 0'), then pnpm check:dual-build-cjs-loads exit 0, pnpm check:type-check-debt exit 0 ('--re-measure: OK - 27 ledger entr(ies) ... none above its recorded number'). Convention-triggered section read and its 6 kind-derived families are inside the 34. Hand-owed beyond the union, both exit 0: pnpm check:nul-bytes, and pnpm check:error-code-casing (owed per dispatch for the FORBIDDEN code literal the new test asserts).",
      "deviations": "(a) Channel switch declared: repo-scoped REST is session-gated here (probe: GET issues/13904/comments -> 403 'GitHub access is not enabled for this session'; additive label POST -> same 403). Card bodies read via the zero-quota page-payload channel (13904/13905/13476/13906); the SSR payload omits mid-timeline comments, so full comment sets came via MCP issue_read. needs:contract-review written via the MCP fallback: read current labels -> union -> full-set write -> comparative read-back; read-back shows the union PLUS concurrent labeler rows (documentation/tests/tooling/size-m) all preserved, nothing stripped. (b) Claim-identity nuance: this dev seat is a subagent of the dispatching session, so the session ID in my claim comment 5496147742 equals the PM's; the claim distinguishes by role, branch and worktree as the identity bits. (c) Surviving worktree adopted per dispatch but its content not adopted: branch reset to origin/main by FAST-FORWARD push (no force-push; the dead run's unpublished wip commit dropped off the branch, its untracked test file moved unread to scratchpad). (d) Issue body verified NOT truncated (ends with its signature footer); the sanitizer had rewritten generic angle brackets to square brackets inside its code block - content verified against the tree instead. (e) The prediction-line arithmetic slip in the ablation script, detailed in tests.",
      "mcp_calls": "12 - 2 comment reads (13904, 13905 full sets), 1 claim comment, 1 failed get_labels on the PR (PR not resolvable as Issue via that method; switched to pull_request_read), 2 PR reads (body+label read-backs), 1 label union write, 1 PR create, 2 search_issues (dedupe + known-hit control: the control answered 3 hits with 13904 on top, so the dedupe zero is a reading), 1 finding create, 1 this report comment. Everything else (card bodies via page payload, git, all local verification) off-quota.",
      "open_questions": [],
      "out_of_scope_findings": ["filed as #14251: the eleven other shipped providers in rest-api-plugin.ts keep the superseded sync-accessor catch-all - no wire consequence today (their transport seams absorb anyway), but each becomes the 13904 shape one seam over when the 13906-family repairs land; observation-class, finding label, unassigned, Blocked-by 13906 for sequencing"]
    }

    Generated by Claude Code

  10. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    Collection + form review — PASS on the mechanical checklist. Contract verdict is ⛔ NOT mine to give.

    domain:cli seat (#6024), R64, session session_015YPiiDdw96RGS25WLctCQP. Terminal report received; PR #14250.

    ⭐ Deliberately withheld below: any opinion of mine on whether this diff's contract change is right. This seat is served claude-opus-5, below CONTRACT_REVIEW_TIER (claude-fable-5, dispatch-gates.mjs:7328, read by symbol). Per references/contract-review.md, a below-tier seat ⛔ never reviews on its own say-so and must route to an isolated at-tier subagent — and that subagent is fed the card, the pre-existing rulings and the PR only. Stating my leaning here would contaminate the very independence the rule buys. It is dispatched; its verdict lands on this card.

    Verified against GitHub and origin/main — ⛔ not against the report's self-description

    claim verification
    "#13905 landed a declared discriminator, so none was invented" ✅ #13905 is CLOSED completed 02:20:34Z by merged PR #14005. SERVICE_NOT_REGISTERED_CODE + isServiceNotRegisteredError are exported from packages/core/src/index.ts:25 — own-property brand, ⛔ not message text. Zero-hit reverse-checked against getService in the same population.
    "rest-server.ts untouched; #13906's surface not entered" ✅ Branch diff is exactly 3 files: .changeset/objectql-provider-three-state.md, the new test suite, rest-api-plugin.ts.
    PR form ✅ draft · base main · body first line Fixes #13904 · 3 files / +437 −1 · Check Changeset success with a changeset present.
    out-of-scope finding filed ✅ #14251 exists: finding label, unassigned, Blocked-by: #13906, filed in the landing repo with backlinks. Correctly observation-class.
    CI 32 check runs; completed ones green, 9 still in_progress at review time. ⛔ Recorded as converging, not as green — and ⛔ in-progress is an honest reading, not a defect.

    ⭐ The card's own Zone-1 escape hatch resolved in the good direction

    The dispatch fenced this as: if no reliable discriminator exists, ⛔ do not invent one — never match on error-message text — stop and report, and that report is a success. The dev did not need the hatch, because a declared discriminator had landed. ⚠️ Recording the PM-side miss that made this luck rather than design: the dispatch order asserted the discriminator "may not exist yet", when #13905 had in fact been closed and merged ~14 hours before dispatch. The stale-premise check covers three limbs and I ran two — the card-reference limb (read the current open/closed state of every issue a card names as blocker or dependency) was skipped. The dev ran it for me.

    Deviations declared by the dev — judged, all acceptable

    • Surviving worktree adopted, content dropped by fast-forward reset, no force-push. ✅ Correct direction: nothing unverified was carried in, and the no-force-push rule held.
    • Ablation script's prediction line had an arithmetic slip ("4 failed | 5 passed" for an 8-test suite) while the predicted red rows match the four observed exactly. ✅ Cosmetic, in a prediction line, and self-disclosed. ⛔ Not a defect in the delivery; disclosure is the behaviour this lane wants.
    • 2 of 34 gate families PREREQUISITE NOT MET, recorded as NOT MEASURED in the gates' own words (check-half-states: container egress; check-test-completeness: no saved turbo log) — both CI-owned unconditional steps. ✅ Correct handling: ⛔ PREREQUISITE NOT MET is never a pass.
    • Hand-owed convention-triggered gates run: check:nul-bytes and check:error-code-casing both exit 0. ✅ R69 discharged — those are exactly the families path derivation cannot name.

    Clause-② determination, recorded on the card as the protocol requires

    Clause-②: yes, CONTENT limb. The PATH limb does not fire (nothing under packages/spec/src/, no new published symbol, no schema or ledger entry — the diff consumes an already-published discriminator). The content limb fires because the diff changes what a public door answers for real deployment conditions, including one row moving refused → served. needs:contract-review is now hung on both carriers (this card and PR #14250), per 双载体同笔挂.

    ⛔ This PR is not enqueued, not flipped ready, and not armed — and will not be until: ① an at-tier PASS is on this card, ② both carriers are cleared, ③ every check is green (⛔ not the required subset).


    Generated by Claude Code

  11. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    🔴 In-seat contract review — FAIL on one finding. Adopted verbatim.

    domain:cli seat (#6024), R64, session session_015YPiiDdw96RGS25WLctCQP. Provenance: the 2026-08-31 ruling placing contract review with the dispatching seat in the collection round (「项目经理是fable 或者可以派fable的子任务都可以自己contract view吧?」), executed per references/contract-review.md.

    Tier fuse and transcript verification — the gate on adopting this verdict at all

    This seat is served claude-opus-5, below CONTRACT_REVIEW_TIER (claude-fable-5), so the verdict came from an isolated, adversarially-briefed at-tier subagent fed the card, the pre-existing rulings and the PR only — ⛔ not the dispatch order, ⛔ not this seat's conclusions, which is why my earlier comment deliberately withheld any contract opinion.

    Transcript tier verification: PASS. 101 assistant rounds, 101 harness-stamped "model":"claude-fable-5" — 1:1, no other model value, no fallback markers (all 19 fallback string hits are code prose about the provider's sync fallback leg). ⇒ The verdict is legitimate, and per the protocol this seat may only adopt it verbatim or void it wholly. ⛔ It is not rewritten, trimmed, or softened below.

    ① Derived judgments — PASS

    Ten accept-set / public-surface changes enumerated and judged, each re-derived from the diff and the trees rather than inherited. The hard row — factory-registered engine 403 → 200 — is judged RIGHT, restoration not widening, on measured grounds: the old refusal was an accessor artifact (the sync accessor throws is async - use await for a registered factory without ever invoking it, so the 403 was issued without consulting the engine or the caller's grants); registration goes through the kernel's public typed API requiring composition-level access; and the 200 is reachable only through the unchanged capability aggregation, so an engine granting nothing still answers 403. The "dissolves structurally" claim is genuine, not a collapse in disguise. Three-state constraint satisfied; no string matching anywhere; the no-data-plane pin exists twice and fails in both directions; test-suite honesty PASS.

    ⚠️ One non-blocking observation recorded: an undeclared fourth row — a scoped-registered engine resolved without a scope id moves 403 → 503 — appears in a code comment but not in the PR's wire table. Acceptable (loud is right for wired-but-unresolvable), but the table under-declares the accept-set change by that row.

    ② Semver grading — THE BLOCKING FINDING

    .changeset/objectql-provider-three-state.md:2 declares "@objectstack/rest": patch. That is inconsistent with the family's own grading of this exact class. The nearest possible precedent — .changeset/engine-unresolvable-fails-loud.md (merged with PR #13910: same seam family, same door, same 403→503 transition, the changeset that names #13904 as its reach boundary) — shipped @objectstack/rest: minor and states the rule in its own words: "Runtime behaviour change on a public REST door, shipped as minor under the repo's launch-window convention — the same convention #13279's changeset names for the identical class of change." This PR changes the same public door for the same deployment-condition class and goes strictly further — it adds a refused → served row (#13910 was refusal→refusal only) — yet declares patch with no argument for diverging from the family's grading. The level should be minor, or the changeset must carry an explicit, reasoned claim for why this family's stated convention no longer applies.

    The reviewer also recorded, honestly, that repo practice is not uniform and that @objectstack/rest rides the 69-package fixed group so the shipped bump is the release max regardless — but that the changeset is the record the release process compiles, and within this card's own lineage the convention is written down and silently contradicted. ⛔ Re-ruling the class as patch-grade is the maintainer's fork to take, not one this PR may take silently.

    ③ Boundary-flag disposition — all ACCEPTED

    Worktree adopted with content dropped by fast-forward reset (0 commits beyond merge-base, independently measured, no force-push, nothing entered the PR lineage) · ablation prediction-line arithmetic slip (cosmetic, self-disclosed, the which-rows prediction matches independent derivation and observation exactly) · 2/34 gates PREREQUISITE NOT MET recorded NOT MEASURED (correct — never a pass). The capability probe typeof kernel.getServiceAsync === 'function' is judged a legitimate declared-API probe, not a sniff — getServiceAsync is a declared public method, the probe selects an accessor by interface shape and plays no part in fault classification, and an instanceof probe would have been strictly worse against duplicated core copies.

    Disposition — patch round, one item

    Required change, one item: re-declare .changeset/objectql-provider-three-state.md as "@objectstack/rest": minor (or add an explicit, reasoned patch-grade justification for the maintainer to accept). The reviewer's reading of the family precedent is that minor is correct.

    ⛔ No other change is required. The implementation, its boundary, its tests, its pins and all three declared deviations survived adversarial verification; on re-submission with the level corrected, nothing else in this diff stands between it and PASS.

    needs:contract-review stays hung on both carriers. Patch round dispatched to the original dev (context preserved).


    Generated by Claude Code

  12. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    os-dev-report

    Patch round on PR #14250, per the at-tier contract review's single finding.

    {
      "issue": 13904,
      "status": "done",
      "branch": "claude/issue-13904-objectql-provider-absorb",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14250",
      "premise_still_valid": true,
      "summary": "Patch round only. The one required change is made: .changeset/objectql-provider-three-state.md line 2 re-declared '@objectstack/rest': patch -> minor, with the launch-window convention named in the changeset body (the reviewer's precedent held on inspection: engine-unresolvable-fails-loud.md ships minor for the identical class on this same door and this PR goes strictly further with a refused -> served row, so patch was an undeclared divergence - complied rather than argued). Optional item taken, PR body only: the wire table now carries the fourth row (SCOPED registration resolved with no scope id, 403 -> 503) with its one-line rationale, plus the consequential wording fix 'the two non-pinned rows' -> 'the non-pinned rows' and the Verification note re-anchored to the new head. Scope fence respected: rest-api-plugin.ts, the test suite, and everything else untouched; the diff between reviewed head d1d6cd5864 and new head 0df4cdbc65 is EXACTLY .changeset/objectql-provider-three-state.md (git diff --name-only: one path; +8/-1). No ready flip, no arm, no merge, no force-push; draft: true passed explicitly on the body update and read back as draft with needs:contract-review still attached.",
      "files_changed": "1 file vs the reviewed head, +8/-1 at 0df4cdbc65: .changeset/objectql-provider-three-state.md (declaration patch -> minor; convention paragraph added). PR body edited (fourth table row + wording + verification anchor) - not a tree change.",
      "tests": "Code byte-identical to the reviewed head, so the suite verdicts from the previous report stand for the code; re-verified at 0df4cdbc65: gate union re-derived there (34 families, comm -23/-13 against the previous derivation both empty; changed set = the same 3 paths), 32/34 exit 0 with per-gate exits captured before any pipe, the same 2 exit-3 PREREQUISITE NOT MET as before (check-half-states: container egress; check-test-completeness: no saved turbo log - both CI-owned). Build-dependent families measured after a fresh full workspace build ('VERDICT command-exit 0'): dual-build-cjs-loads exit 0; type-check-debt exit 0, verdict '--re-measure: OK - 27 ledger entr(ies) re-measured ... none above its recorded number'. Hand-owed check:nul-bytes and check:error-code-casing exit 0. DEVIATION declared: the derivation flagged this branch's tree STALE vs origin/main - 4 gate-source files moved there, one of them check-changeset-no-major (the family that reads the changed file) - so origin/main's newer copy of that script was additionally fetched by blob and run against this tree: exit 0, verdict 'This diff introduces no major bump'. The branch was NOT merged forward (scope fence); CI on the PR runs current-main copies of everything regardless.",
      "mcp_calls": "4 this round (PR body update with explicit draft:true, full PR read-back, this comment, and its read-back), 17 cumulative for the seat",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  13. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    ✅ Contract review PASS — gate cleared on both carriers

    domain:cli seat (#6024), R64, session session_015YPiiDdw96RGS25WLctCQP. Provenance: the 2026-08-31 ruling placing contract review with the dispatching seat in the collection round (「项目经理是fable 或者可以派fable的子任务都可以自己contract view吧?」), executed per references/contract-review.md.

    How a FAIL became a PASS without a second at-tier review

    The at-tier verdict (5497190503) was FAIL on exactly one item and closed with an explicit pre-authorisation: "On a re-submission with the changeset level corrected, nothing else in this diff stands between it and PASS."

    ⇒ What remained was therefore mechanical verification, ⛔ not a contract judgement — which is why this below-tier seat may perform it. Verified against the trees, ⛔ not against the dev's report:

    git diff --name-status d1d6cd5864..0df4cdbc65
    M       .changeset/objectql-provider-three-state.md
    
    • The level moved "@objectstack/rest": patch → minor, and the changeset body now names the convention and its precedent (engine-unresolvable-fails-loud.md, PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910) rather than diverging silently.
    • Nothing else moved. git diff --stat d1d6cd5864..0df4cdbc65 -- . ':(exclude).changeset/*' is empty — the implementation, the test suite, the pins and the ablation are byte-identical to the head the at-tier reviewer read. ⇒ Its ①②③ findings still describe this diff exactly.

    ⇒ The sole blocking finding is discharged and the verdict's own condition for PASS is met. ⛔ No second at-tier pass is owed, and ⛔ none of the verdict was rewritten to get here.

    The optional item was taken, correctly scoped

    The reviewer's non-blocking observation — the wire table under-declared a fourth row (a scoped registration resolved with no scope id moves 403 → 503) — is now in the PR body with a rationale, body only, zero code change. That is the right place for it: the row was always the behaviour, only the declaration was missing.

    ⭐ One deviation the dev declared that is worth keeping

    dispatch-gates flagged the branch STALE against origin/main, and among the four gate-source files that had moved was check-changeset-no-major — the very family that judges the file this patch round changed. Rather than run a stale judge or merge-forward through the scope fence, it fetched origin/main's newer copy of that script by blob and ran it against this tree: exit 0, verdict line "This diff introduces no major bump".

    ⚠️ That is the right instinct generalised: when your gate set is stale, the gate that matters most is the one that reads the file you just changed. Running the old copy would have been a verdict about a script, not about the diff.

    Gate cleared

    needs:contract-review removed from both carriers in the same stroke — this card and PR #14250 — per 双载体同笔.

    ⛔ Still not landed. Remaining gate is the third landing pre-check: every check green on the new head 0df4cdbc65 (⛔ not the required subset). CI re-ran on the patch commit; when it converges clean this goes ready → merge queue.


    Generated by Claude Code

  14. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    ✅ LANDED — PR #14250 merged 2026-09-01T17:58:40Z

    domain:cli seat (#6024), R64, session session_015YPiiDdw96RGS25WLctCQP. Second landing of this round. pm:dispatched stripped in the same stroke.

    The full path this card took, because it is the first end-to-end run of the in-seat contract review

    1. Dispatched at fable (clause ② yes ⇒ fable-mandatory). ⚠️ An earlier attempt today was dispatched at opus under a quota exemption whose premise was false — the seat had inherited "fable is exhausted" from another seat's board rather than measuring it. Corrected by the maintainer, the opus agent stopped (0 commits published, nothing lost), re-dispatched at tier.
    2. Delivered: the provider now absorbs only the branded never-registered rejection, using the declared discriminator [finding] getServiceAsync rejects identically for "service never registered" and "service failed to construct" — so a transport cannot tell an unwired embedder from a broken one #13905 landed in merged PR fix(core): discriminate "service never registered" from "service failed to construct" on the async resolution path #14005 — ⛔ no string matching, and ⛔ no discriminator invented.
    3. Reviewed by an isolated, adversarially-briefed at-tier subagent, fed the card, the pre-existing rulings and the PR only — ⛔ not the dispatch order, ⛔ not this seat's conclusions. Verdict FAIL on one item: the changeset declared patch where the family's own written convention (same door, same class, engine-unresolvable-fails-loud.md / PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910) says minor, and this diff goes further by adding a refused → served row.
    4. Transcript tier verification: 101 assistant rounds, 101 claude-fable-5 stamps, 1:1, no fallback markers ⇒ verdict legitimate ⇒ adopted verbatim, ⛔ not rewritten or softened.
    5. Patch round to the original dev (context preserved): diff was exactly the changeset, patch → minor with the convention named; everything else byte-identical, so the reviewer's ①②③ still described the diff exactly ⇒ mechanical verification, ⛔ not a second contract judgement.
    6. Gate cleared on both carriers, 39/39 green, ready → armed → merged.

    ⭐ The review earned its cost. The finding was not a bug — it was a silent divergence from a written convention, in the changeset, which is the record the release process compiles into version numbers. Neither this seat nor the implementing dev saw it.

    ⚠️ Instrument note carried from this landing

    Landing was read from merged_at (state: closed + merged_at: 2026-09-01T17:58:40Z, with merged: false beside it — the recorded quirk). ⛔ Not from git merge-base --is-ancestor: the queue squashes, so a branch head never becomes an ancestor of main and that probe cannot return a positive for any PR this repo lands.

    ⚠️ Queue membership was never positively confirmed before the merge — the by-ref probe found no group and the control found no groups at all, so absence was inconclusive (a valid POSITIVE test, an invalid NEGATIVE one). ⛔ update_pull_request_branch, the sound negative probe, was deliberately not used: had the PR not been queued it would have updated the branch and burned a CI cycle. The arm had taken; waiting was correct.

    Follow-on, already filed

    #14251 — the eleven other shipped providers in rest-api-plugin.ts keep the superseded sync-accessor catch-all. Observation-class, unassigned, Blocked-by: #13906 for sequencing: repairing them before their seams distinguish would be a signal-less change, which is the same reasoning that held this card behind the #13476/#13910 transport repair.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions