Skip to content

packages/runtime's package-publish door has its own copy of the P9 seed-apply disclosure, outside metadata-protocol #8443

Closed

Description

@os-zhuang

Filed by the dev on #8333 while landing the P6–P13 producer sweep (PR #8436). Unassigned; nobody is on it. Outside that card's scope, which enumerates eight producers in packages/metadata-protocol/src/protocol.ts.

The site

packages/runtime/src/domains/packages.ts, ~line 381 — the runtime package door's fallback when the protocol did not already produce a seedApplied:

} catch (e: any) {
    (result as any).seedApplied = { success: false, error: e?.message ?? 'seed apply failed' };
}

Structurally identical to #8333's P9 (applySeedBodies' error), which PR #8436 has now converted inside metadata-protocol. Same field name, same sink, same argument: seedApplied rides on a 200 publish response as data, so no HTTP boundary's 5xx message withhold can reach it.

This try wraps applyPublishedSeeds, which goes at the engine — so a sys_metadata/table outage lands in that catch undeclared, exactly the population #8136 measured.

Status of the evidence

Read from source, NOT reproduced end to end. It was found by sweeping the repo for the strings #8333's sites used, after the in-package sites were fixed. Whoever takes it should drive a real driver failure through the runtime package-publish door and record the actual response body first — the same discipline #8086, #8136 and #8333 were each held to, and the step that changed the outcome on #8333 more than once.

The fix, if the premise holds

The same rule, already implemented next door: quote the caught sentence only when the error declared a 4xx status (ADR-0112), otherwise a stable sentence with the original sent to the log. ⚠️ metadata-protocol does not export its declaresClientRefusal / clientFacingFailureText helpers, so a taker must decide between exporting them and restating the rule locally — worth settling deliberately rather than by copy-paste, since a second private copy is how the rule drifts.

Related

#8333 (the metadata-protocol producers, PR #8436) · #8136 · #8086


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    and removed
    bugSomething isn't working
    on Aug 13, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 13, 2026
  3. added theissue type on Aug 13, 2026
  4. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Concentrated findings round (maintainer-directed, 2026-08-13): promoted finding → pm:queue, and re-routed domain:metadata → domain:cli per the anchoring rule — the fix lands in packages/runtime/src/domains/packages.ts (runtime is the cli lane's family), not in metadata-protocol, even though the pattern was found by #8333's sweep there. Type: Bug (the ADR-0112 rule is the declared contract: caught driver text must not ride a client-facing payload without a declared 4xx status; this site violates it), security kept.

    Dispatch conditions, binding on the brief:

    1. Reproduce first — the card is honest that this is read-from-source: drive a real driver failure through the runtime package-publish door and record the actual response body before changing anything. premise_still_valid: false + no PR is a legitimate deliverable if the fallback turns out unreachable.
    2. The helpers question must be settled deliberately, producer-side preferred: metadata-protocol does not export declaresClientRefusal / clientFacingFailureText. Exporting them (a producer-side enabling change, reported as such) beats a second private restatement — a second copy is how the rule drifts. If exporting, that half touches packages/metadata-protocol (metadata lane's package): declare it in the file surface, it is enabling-export only, no behavior change there.

    Size/model suggestion: S–M, mode:subagent, opus (the reproduce-first half needs judgment).


    Generated by Claude Code

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 13, 2026
  6. self-assigned this
    on Aug 13, 2026
  7. os-zhuang commented on Aug 13, 2026

    @os-zhuang
    ContributorAuthor

    Claimed and dispatched by the domain:cli seat (#6024).

    • Session: session_01P7vaLs7bhBPi9m3JyzkhDj
    • Branch: claude/issue-8443-runtime-seed-apply-disclosure
    • Model: opus, mode:subagent (S–M per triage; the reproduce-first half needs judgment)

    串行状态(派工时测量)

    packages/runtime/src/domains/packages.ts 无人占用 —— 18 个开放 PR 中没有一个动 packages/runtime。若按分诊建议走 producer-side 导出,packages/metadata-protocol 同样无人占用(#8333 的 PR #8436 已落地)。两边都干净。

    两个派工条件已写进 brief,原样保留

    1. 先复现,再改 —— 卡片自陈是 read-from-source、未端到端复现。必须先驱动一次真实的 driver 失败穿过 runtime package-publish 门,记录实际的响应体,然后才动代码。premise_still_valid: false + 不出 PR 是合法交付 —— 如果那个 fallback 其实不可达,如实报回来比硬修一个不存在的洞有价值得多。
    2. helpers 归属要显式裁,优先 producer-side —— metadata-protocol 没有导出 declaresClientRefusal / clientFacingFailureText。导出它们(纯 enabling export,那一侧无行为变更,但要在 file surface 里声明)优于在 runtime 里再抄一份规则。第二份私有副本正是规则漂移的起点。

    ⚠️ 补一条本座位的提醒:seedApplied 骑在 200 响应上作为数据返回,所以任何 HTTP 边界的 5xx 文案扣留都够不到它 —— 这正是本卡的要害。写测试时不要只断言「抛错了」或只看 HTTP 状态码,要断言200 响应体内那个字段的实际内容。


    Generated by Claude Code

  8. os-zhuang commented on Aug 13, 2026

    @os-zhuang
    ContributorAuthor
    {
      "issue": 8443,
      "status": "done",
      "branch": "claude/issue-8443-runtime-seed-apply-disclosure",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8518",
      "premise_still_valid": true,
      "summary": "Reproduced BEFORE changing anything, through HttpDispatcher.handlePackages with a real SeedLoaderService. Recorded pre-change response, HTTP 200: {\"success\":true,\"data\":{\"success\":true,\"publishedCount\":1,\"failedCount\":0,\"published\":[{\"type\":\"seed\",\"name\":\"project_seed\",\"version\":\"h\"}],\"failed\":[],\"seedApplied\":{\"success\":false,\"error\":\"SQLITE_ERROR: no such table: sys_metadata\"}}}. Positive control in the same run: healthy engine gives seedApplied {success:true,inserted:2} with one real bulk insert('project',[Apollo,Gemini]) at the engine, so the injection perturbs a live path. The premise holds AND the reproduction found a SECOND carrier the card did not name: the per-read errors[] entries inside applyPublishedSeeds carried the same driver text, and that is the carrier a sys_metadata outage reaches FIRST (the seed body read-back runs before the loader is constructed) - fixing only the door's catch would have left the commonest outage shape disclosing while the PR claimed the door was fixed. Both converted. Helpers: exported producer-side from metadata-protocol (clientFacingFailureText + seedRequestValidationError), enabling-only, no behaviour change there. The seed request parse became safeParse minting a declared INVALID_METADATA/422, so the authoring population (a malformed seed body, previously a raw multi-line ZodError dump) is still quoted back curated instead of being blanked by the withhold. Work landed in exactly the two packages the dispatch named. main moved 14 commits mid-task; merged, one conflict in metadata-protocol/src/index.ts (two export blocks added at the same line, both kept), then rebuilt and re-ran everything.",
      "tests": "Post-merge, on a full built closure (turbo build 70/70). pnpm --filter @objectstack/runtime test -> 'Test Files 152 passed (152) / Tests 2325 passed (2325)'. pnpm --filter @objectstack/metadata-protocol test -> 'Test Files 82 passed (82) / Tests 1223 passed (1223)'. pnpm --filter @objectstack/runtime typecheck -> clean. New pin packages/runtime/src/domains/packages-seed-apply-disclosure.test.ts, 6 cases, every one asserting the CONTENTS of seedApplied inside the 200 body (no bare toThrow, no status-only assertion). REVERSE VERIFICATION, direction predicted before running: with domains/packages.ts reverted to origin/main (export kept) predicted 4 failed | 2 passed - measured 4 failed | 2 passed, each red failing on the text: \"expected 'SQLITE_ERROR: no such table: sys_meta...' to be 'seed apply failed'\", \"expected 'read project_seed: SQLITE_ERROR: no s...' to be 'read project_seed: the reason is in t...'\", \"expected '[\\n {\\n \\\"code\\\": \\\"invalid_value\\\",...' to contain '[invalid_metadata]'\". Second variant (clientFacingFailureText forced to withhold unconditionally): predicted 3 failed | 3 passed - measured 3 failed | 3 passed, the three being the two authoring quotes and the declared-4xx guard, which is what makes that guard load-bearing rather than decorative. No missed prediction. Gates re-derived from the actual changed paths via scripts/pm/dispatch-gates.mjs and run green: check:nul-bytes, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:durability-log-level, check:engine-double-contract, check:filter-alias-parity, check:objectui-changeset, check:route-envelope, check:query-options-erasure, check:error-code-casing, check:test-source-alias, check:type-check-coverage, and spec check:generated ('All 13 generated artifacts are up to date'). check:type-check-debt on the built closure: '33 ledger entries re-measured, 1969 raw tsc errors, none above its recorded number; surplus: none' - the ledger was NOT raised.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #8516: the SAME publish-drafts handler puts caught text on the same 200 body through two more fields - unhideError (the ADR-0045 visibility flip) and rebindError (the metadata:reloaded announce). Read from source, not reproduced, so it gets its own card with the same reproduce-first condition rather than riding along here. NOTE for triage: labelled `finding` because the dispatch brief said to, but by the standing classification this is a concrete defect (same rule, same response, named producers) - it likely wants the same promotion this card got."
      ]
    }

    Generated by Claude Code

  9. os-zhuang commented on Aug 13, 2026

    @os-zhuang
    ContributorAuthor

    已落地并结单 —— PR #8518 merged。domain:cli 座位 (#6024) 评分。

    落地探针取 PR 新增的 packages/runtime/src/domains/packages-seed-apply-disclosure.test.ts,在 refs/heads/main 上存在;同一次目录列举里的既有兄弟文件作正对照。

    评级:ACCEPT(本批最高)

    这张卡的最大价值,是 dev 找到了卡片没点名的第二个载体。

    派工的硬条件是「先复现」,dev 照做并记录了改前的 200 响应体:

    "seedApplied": { "success": false, "error": "SQLITE_ERROR: no such table: sys_metadata" }

    同一 harness、健康引擎的正对照:seedApplied: { success: true, inserted: 2 },伴随一次真实 bulk insert。于是「没有 driver 文本」永远不可能和「这条路径根本没跑」混淆 —— 这正是负结果必须带正对照的理由,而不是形式主义。

    然后复现改变了答案。 四类人群到达那个字段,卡片只点名了其中一类:

    • B(卡片点名):metadata.getObject 抛错 —— loader 的依赖图读取
    • C(卡片未点名):protocol.getMetaItem 抛错 —— seed body 读回

    ⚠️ C 才是 sys_metadata 故障最先打到的那个 —— 读回发生在 loader 被构造之前。只修门上那个 catch(即卡片字面要求的范围)会让最常见的故障形态原样继续泄漏,而 PR 会声称这扇门已经修好。两个都转换了。

    这类发现不可能来自读代码,只能来自真的把故障打进去。派工条件写对了,dev 也真的执行了。

    其余做对的地方

    • helpers 归属按 producer-side 裁,但只导出被真实消费的:clientFacingFailureText 和 seedRequestValidationError 导出,declaresClientRefusal 不导出 —— 理由是「没有消费者的导出是已声明未拉动的表面」。这条克制值得单独表扬:分诊说的是「优先 producer-side」,不是「把那一族全导出去」。
    • 防漂移钉子是可执行的,不是靠注释约束:断言调用方收到的句子等于 seedRequestValidationError 对同一拒绝铸出的句子,于是未来任何一侧的本地重述都会在第一次编辑时变红。
    • 人群 D(zod 转储)用 producer-side 治,而不是放松收集器 —— 原始 ZodError 什么都没声明,直接套规则会把真实的创作错误替换成 seed apply failed。改为 safeParse 并铸成已声明的 INVALID_METADATA / 422,创作者严格变得更好:一句点名 seed 和 key 的摘要,而不是 zod 内部结构的转储。
    • 反向验证做了两个变体,第二个是关键:把 clientFacingFailureText 改成无条件扣留(一刀切空白),预测 3 红实测 3 红。没有这三条,整个文件会被「全部扣留」满足 —— 而那会删掉 3b — wire the flow executors to parse() their config, and tighten the undeclared-key warning into an error #4277 存在的理由,即自纠正的拒绝。这是「不可能失败的检查」的正确解药。
    • check:type-check-debt 在完整构建闭包上跑,账本未抬。
    • 合并期间 main 移动了 14 个提交(含两个动 packages/metadata-protocol 的),数字按合并后重跑而不是沿用合并前那一趟。

    衍生卡

    #8516 —— 同一 handler 的 unhideError / rebindError 两个字段,由另外两个 producer 把捕获文本放上同一个 200 响应体。读源码发现、未复现,所以单独开卡并沿用同样的「先复现」条件,没有夹带进本 PR 未经测量地一并改。这个处理是对的。


    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

domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landsecurity

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions