Skip to content

ApiEndpointSchema 无法对未知键收紧 —— 同一个 schema 既解析作者声明又解析存量行(行上带 packageId / state) #5309

Closed

Description

@os-zhuang

越范围发现,记录于 #5271(api 进 DEFAULT_METADATA_TYPE_REGISTRY)实现期,不在该 PR 内修 —— 修法是元数据层的信封/正文分离,那是另一个量级的改动。

事实

api 在 #5271 成为注册类型后,ApiEndpointSchema 落入 #4001 收紧运动的不变量。尝试用 strictObject 关闭它,实测结果是 packages/metadata 里 10 条测试转红:

unrecognized_keys: ['packageId', 'state']

原因是这个 schema 不只是作者面,它也是存量行的解析器:

  • buildEndpointIndex(packages/metadata/src/endpoint-matcher.ts)对每条存量 api 行跑 ApiEndpointSchema.safeParse;
  • MetadataManager.publishPackage 的 gateApiItemsForPublish 同样。

而存量行带着元数据层自己的记账键 —— packageId 与 state,由 MetadataManager.register(metadata-manager.ts:542)与 publishPackage(:1438、:1574)写入,又被 publishPackage 自己的包过滤器(:1337)读回。这两个键不是端点词表。

关闭后的实际后果(不是推测,是跑出来的):装载期兜底把端点整条排除出索引(它声明的路由答 404),publish 门则报一条 schema 错误、而不是它本该给出的 ADR-0121 D6 判决。

代价(为什么这不是「无所谓」)

留着 strip,一个 cacheTTL / outputMappings / objectParam 的拼写错误会解析通过、publish 通过,然后端点上线时不带作者写的那条策略或投影 —— ADR-0078 的静默失效元数据,而且是在 AI 作者(ADR-0033)会写的面上。

形状与 view 同类

这正是 STILL_STRIP 里 view 的理由换了身衣服:一个类型名同时承担作者文档与线上行。#5271 已把 api 加进 STILL_STRIP 并把上述实测写在该列表的注释里,所以这条债有名有主、不是静默豁免。

建议方向(不预判)

修法应当是把存量信封与正文分开,让匹配器与 publish 门解析正文、记账键待在信封上 —— 而不是反过来教 ApiEndpointSchema 认两个存储层的键(那等于让作者契约去描述存储层,正是这场运动拒绝的交易)。做完之后 api 就能从 STILL_STRIP 上摘下来。

关联

#5271(本发现的出处)、#5206(父单)、#4001(收紧运动)、ADR-0078、ADR-0010。

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    维护者裁定(2026-08-05,spec 车道 PM session_018fxLGQdatPbBUvCgiVxg6D 记录):挂 pm:on-hold,GA 后实施。本单(信封/正文分离)是 #5384 的硬前置,重启条件与实施顺序见 #5384 的裁定评论:v17 GA(changeset pre exit)后作为 #4001 战役批次,本单先行。届时按跨域协议分诊 packages/metadata 半边的车道归属。


    Generated by Claude Code

  2. hotlong commented on Aug 7, 2026

    @hotlong
    Contributor

    Triage (maintainer-directed batch, 2026-08-07): routed domain:engine-core — per the body the fix is the envelope/body split in the metadata layer (packages/metadata's manager + endpoint matcher), so authored declarations and stored rows stop sharing one schema; packages/metadata* anchors engine-core. target:v17 retained (08-07 board ruling). Anchored by code read; triage seat may veto.


    Generated by Claude Code

  3. hotlong commented on Aug 7, 2026

    @hotlong
    Contributor

    迁移:domain:engine-core → domain:metadata(维护者 2026-08-07 批准拆分;座位贴 #6367,SKILL PR #6370)。落点证据:envelope/body 拆分落在 metadata 层 —— endpoint-matcher.ts + metadata-manager.ts 的存量行携带 packageId/state 记账键。全量分类审计 49 卡三判,本卡 MOVE/high。分诊会话:session_01BickTBKm2JYSNnrtPT8ysa。


    Generated by Claude Code

  4. self-assigned this
    on Aug 8, 2026
  5. baozhoutao commented on Aug 8, 2026

    @baozhoutao
    Contributor

    Claim: PM loop round 2 (domain:metadata seat, sticker #6367)
    Session: session_01KDU3qAuJyajAQm3GkUXdfA
    Branch: claude/issue-5309-endpoint-envelope-body-split
    Worktree: objectstack-issue-5309
    Domain: domain:metadata
    File surface: packages/metadata/src/endpoint-matcher.ts (buildEndpointIndex) + packages/metadata/src/metadata-manager.ts (publishPackage / gateApiItemsForPublish parse sites) + tests in packages/metadata. ⛔ NOT packages/spec (the STILL_STRIP removal is #5384's step, spec seat). Stop on breach; explain in the report.
    Serial constraints cleared: the metadata-manager.ts same-file predecessor PR #6547 (#6043) MERGED 2026-08-08 04:40Z — this claim was deliberately held until then; #4513 in flight this seat (surface: metadata-protocol protocol.ts read exits + new metadata-core module + objectql/registry.ts re-export — different package, no overlap); #6215 in flight this seat (sys-metadata-repository.ts + objectql tripwire test — no overlap); #5839 in flight (engine-core seat — no overlap).
    Dispatch basis: the 2026-08-07 window re-ruling on this thread (v17 window, hard prerequisite of #5384, internal refactor only) + triage's 2026-08-07 11:37Z routing of the metadata half to this lane (migrated 15:30Z). The 08-05 GA-后 hold clause is superseded on this thread's own record.
    Container class: M — mode:subagent, shared container.


    Generated by Claude Code

  6. baozhoutao commented on Aug 8, 2026

    @baozhoutao
    Contributor

    ACCEPT (PM review, domain:metadata seat, session session_01KDU3qAuJyajAQm3GkUXdfA) — PR #6576.

    Shipped: peelStoredEnvelope (packages/metadata/src/stored-envelope.ts) splits the storage envelope (packageId/state/version/published*) from the authored body, and both ApiEndpointSchema parse sites — buildEndpointIndex and gateApiItemsForPublish — now judge the body. Views only, no mutation; envelope readers and publishedDefinition snapshot bytes untouched; packages/spec untouched (the strictObject probe was reverted, never committed).

    Review verified against GitHub: 7 files, all inside packages/metadata (the two new-module files beyond the enumerated surface are accepted — the import cycle argument for a sibling module is sound and stated); ESLint and TypeScript Type Check success, 24 checks zero failures; changeset (patch). The prerequisite is demonstrated, not asserted: strictObject probe reds 11 → 1, and the survivor is precisely #5384's own fixture (authored phantom namespace, refusal changes mouth not verdict) — measurement recorded on #5384 per attach-don't-scatter. The one behavioral delta (a wrapped publish envelope now indexes off its body instead of 404ing post-publish) is declared, pinned, matches the issue's direction verbatim, and nothing pinned the old verdict. Q2 confirmed: register() storage byte-unchanged, #6043's registry peeks untouched and green.

    PR flipped ready and queued (auto-merge). #5384 (spec seat) is now unblocked at cost 11-reds → 1.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions