Skip to content

ArtifactPackageEntrySchema's body half cannot describe the payload the load path actually registers #14242

Closed

Description

@hotlong

Found while implementing #14162 (PR #14240). Filed rather than worked around there: widening a spec surface is not a loader's decision.

The fact

ArtifactPackageEntrySchema (packages/spec/src/stack.zod.ts, landed by #14161) wraps the package body as manifest: ManifestSchema. ManifestSchema.objects is z.array(z.string()) — glob patterns, the authoring-time shape.

What actually reaches the ADR-0130 load path is an assembled payload whose objects are object definitions: AppPlugin flattens an artifact into { ...bundle.manifest, ...bundle } before calling manifest.register(), and ObjectQL.registerApp iterates those bodies as definitions.

So a full parse of a real artifact entry refuses it. Measured on cfa99f3d against the landed schema:

ArtifactPackageEntrySchema.safeParse({ manifest: assembledPackageBody })
  → success: false
  → manifest.objects.0: Invalid input: expected string, received object

The entry schema therefore describes the authoring-time manifest, while the key it was added for (packages[] on ObjectStackDefinitionSchema) is read at load time, where the body has a different shape. One schema is being asked to describe two different lifecycle stages of the same noun.

Why it matters

Any consumer that reaches for the obvious ArtifactPackageEntrySchema.safeParse(entry) as its gate will refuse exactly the artifacts the path exists to register — and it will look correct while doing so, because the schema is the surface's one declaration and the refusal names a real key.

PR #14240 works within it deliberately and narrowly: it applies the schema as a gate on the wrapper only (issues at the entry root or on manifest itself), and hands registerApp the caller's original body rather than a parsed clone. That is written down in the module header with this measurement, so the next reader does not discover it again. But it is a local accommodation of a surface-level mismatch, and the accommodation is the kind of thing that quietly becomes convention if the surface is never revisited.

What the decision is

Not obvious, hence a card rather than a patch. Roughly:

  • A. Leave the schema authoring-shaped and make "the wrapper is the only part this schema gates at load time" an explicit, documented property of the surface — the status quo PR ADR-0130 D5+D7: register an artifact's N packages in topological order, reusing resolvePluginOrder #14240 assumes, but stated on the schema instead of in one consumer.
  • B. Give the assembled/registered form its own declaration, so both stages are describable and a load-time consumer has something correct to parse against.
  • C. Widen ManifestSchema.objects to accept both spellings — cheapest to write, and the one that most deserves scrutiny: a union that accepts both stages makes neither stage checkable, which is the tolerance-at-the-consumer shape this repo generally refuses.

No packages[]-shaped artifact is produced anywhere yet, so nothing is broken in the field today and there is time to decide this properly rather than under a deadline.

Activity

  1. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    决定:取 B —— 装配 / 注册态的包体有自己的声明

    维护者 2026-09-02 在 PM 会话(session_01UHvF5hyiZjnCyExFnfQB8m)对"立多包编译路径卡、顺带把 #14242 按 B 方向收口"答复「同意」。据此:

    实施与关闭在 #14439(Fixes #14242 与该卡同一 PR)。摘除 needs-user-decision,保留 priority:p2 / domain:spec。


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions