Skip to content

finding(spec): AssembledPackageBodySchema declares callable and custom branches an inert-JSON artifact cannot hold, and every schema embedding it loses its JSON Schema #17518

Description

@os-bill

Found while landing #17431. Filed rather than fixed: the remedy moves AssembledPackageBodySchema's own shape, which is #14242's ruling territory, and #17431 is bound to the read API one layer up.

The contract, verbatim

ADR-0130 D4's own docblock on ArtifactPackageEntrySchema (packages/spec/src/stack.zod.ts) states why plugins and devPlugins are envelope keys that no package body may carry:

An artifact is inert JSON: a plugin written inside packages[i].manifest could never be constructed by a loader, so a reader that resolved it there would register garbage where it used to skip in silence.

AssembledPackageBodySchema — the body half of ArtifactPackageSchema, i.e. one package as assembled into a release artifact — nevertheless carries two collections whose declarations admit values that same sentence says can never exist in an artifact:

  • functions, whose entry schema FlowFunctionEntrySchema (automation/flow-function.zod.ts) has z.function() as its first union branch;
  • hooks, whose HookSchema (data/hook.zod.ts) has a z.custom() branch.

A callable in an artifact is exactly the case the quoted sentence excludes: the artifact is JSON on disk, so the branch describes a value the surface cannot hold.

Measured consequence

Of the assembled body's 55 shape members, exactly those two have no JSON Schema form. Measured with z.toJSONSchema(member, { io: 'input' }) over every key of AssembledPackageBodySchema.shape:

functions: Function types cannot be represented in JSON Schema
hooks:     Custom types cannot be represented in JSON Schema

and, on the whole body:

probe result
AssembledPackageBodySchema FAIL — Function types cannot be represented in JSON Schema
.omit({ functions: true }) FAIL — Custom types cannot be represented in JSON Schema
.omit({ hooks: true }) FAIL — Function types cannot be represented in JSON Schema
.omit({ functions: true, hooks: true }) OK

That is why ArtifactPackage and ObjectStackDefinition publish no JSON Schema at all, and it propagates: any published export that embeds the assembled body loses its own JSON Schema and its content/docs/references/** page with it. #17431 hit exactly that — binding the body into ListInstalledPackagesResponseSchema and GetInstalledPackageResponseSchema made both disappear from json-schema/api/, which build-schemas.ts's disappearance ratchet refuses.

So the cost is not local to the artifact schema. It is a standing tax on every future surface that wants to declare the assembled stage — and declaring the assembled stage is the ruled remedy for the #14242 class.

Why this is a contract violation and not a preference

The two branches are not merely unused here: the surface's own declared semantics say they are unreachable. A declaration that admits what its surface cannot hold is a claim nothing enforces, and here it has a measured price paid by unrelated schemas.

⛔ Not proposing the shape. Two obvious directions exist (narrow the two collections at the assembled body, or give artifact-stage variants of the two entry schemas) and both move a declaration #14242 ruled on, so the choice is the maintainer's.

Related: #14242 (the ruling that created AssembledPackageBodySchema) · #17431 (where this was measured) · #11072 (the sibling axis: the same tree also carries a Node-only import, which is a separate matter)


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