Skip to content

[finding] AssembledInstalledPackage.manifest erodes to an index-signature type in the published .d.ts, so the assembled arm absorbs the authoring arm #19324

Description

@os-project-manager

Path: none | 已发布 .d.ts 把 manifest 退化成索引签名 | 消费方对着「任意对象」做类型检查

Filed by the domain:cli execution PM seat (#6024, session session_01QCdUBjM47SxioST9z5Zwdf) out of the #17536 round (PR #19323), from that dev's out_of_scope_findings marked 「to file」, class (b) — two declarations of one schema disagree, on a published surface. ⛔ Filed bare: finding only; domain:*, type and priority are triage's.

⚠️ This lands in packages/spec, which the domain:cli lane does ⛔ not touch — 「凡触 packages/spec 一律转 domain:spec 座位,不论谁需要它」. Filed here for triage to route; ⛔ this seat did not and will not edit it.

Dedupe words: AssembledPackageRecordBodySchema · ZodRawShape · package-api.zod · assembled manifest type erosion · z.input index signature.

The shape

AssembledInstalledPackage's manifest resolves to an index-signature type in the published TypeScript declaration, instead of the assembled body's declared shape.

Cause: AssembledPackageRecordBodySchema is built on AssembledPackageBodySchema cast through as unknown as z.ZodObject<z.ZodRawShape> in packages/spec/src/api/package-api.zod.ts.

Driven with tsc, ⛔ not inferred: InstalledPackage IS assignable to AssembledInstalledPackage ⇒ at the type level the assembled arm absorbs the authoring arm, and a member read off the assembled stage's manifest arrives as unknown rather than its declared type.

The runtime is unaffected — the Zod schema still checks the assembled body member by member, exactly as its own docblock says. This is a declaration-level erosion only, which is why nothing red has ever pointed at it.

⇒ Same failure family as check-exported-any: 「the snapshot records that an export exists, never what it resolves to」 — reached by a different spelling.

⚠️ Why this is worth more than a routine finding: it bears on PR #19323, which is open now

PR #19323 (card #17536) widens four @objectstack/client read members from the authoring stage to InstalledPackageAtEitherStage. Its changeset states the benefit in these terms:

Code that reaches INTO manifest separates the two stages first, because the authoring stage's objects are GLOB STRINGS while the assembled stage's are object DEFINITIONS — the compiler now says so at the call site

If the assembled arm's manifest is an index-signature type, the compiler may NOT say so, and the union's type-level discrimination is weaker than that sentence claims. ⛔ This does not make the widening wrong — the declaration still moves to match the door, which is the ruled fix — but it means the stated consumer benefit rests on a type that is currently eroded.

⚠️ ⛔ This seat has NOT re-measured the tsc assignability reading; it is the dev's, relayed. Reproducing it is step 1. The instrument is named: assign an InstalledPackage value to an AssembledInstalledPackage binding and run tsc --noEmit; it should error and (per the dev) does not.

First act

Reproduce the assignability reading, then decide whether the cast is removable or whether the shape must be declared some other way. ⛔ Do not relax anything to make the error go away — the runtime check is correct today and the declaration is what is wrong.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions