Skip to content

finding(spec/runtime): GET /packages rows are the ASSEMBLED package body, but InstalledPackageSchema types them as the AUTHORING manifest — the #14242 stage mismatch, one layer up on the read API #17431

Description

@os-justin

Found while landing #16781 (deliverable 2). Filed rather than fixed: the remedy is a packages/spec declaration, and #16781 is explicitly routed away from packages/spec.

The contract, verbatim

ListInstalledPackagesResponseSchema (packages/spec/src/api/package-api.zod.ts) types every row of data.packages as InstalledPackageSchema, whose manifest is ManifestSchema. ManifestSchema declares:

objects: z.array(z.string()).optional().describe('Glob patterns for ObjectQL schemas files'),

— i.e. the authoring stage, where manifest.objects names file patterns. ManifestSchema is additionally a strictObject.

What the door actually serves

SchemaRegistry.installPackage records what it is handed, and ObjectQL.registerApp is handed manifest.objects as object definitions — it iterates them and calls registerObject(objDef, …). That is the assembled body, and it is what a defineStack() host produces (examples/app-showcase/objectstack.config.ts: objects: [...Object.values(objects), ExternalCustomer, ExternalOrder]). GET /packages projects those rows through toPackageResponse and serves them.

Measured

Driven against the real SchemaRegistry + the real dispatcher door, after #16781 added the missing hasMore:

package authored as ListInstalledPackagesResponseSchema.safeParse(body)
glob patterns (objects: ['./src/objects/*.object.yml']) success
defineStack() shape (objects: [{ name, fields }]) failure, one issue: data.packages.0.manifest.objects.0expected string, received object

So the shipped open-core path serves a payload its own declared response schema refuses, and the single surviving reason is the manifest stage.

Why this is the same thing as #14242, not a new class

#14242 identified exactly this authoring-vs-assembled split one layer down, and the ruling is quoted in packages/spec/src/stack.zod.ts at ArtifactPackageSchema:

This key is read at LOAD time, so the authoring-time ArtifactPackageEntrySchema (whose manifest.objects are glob patterns) cannot describe it: that mismatch was #14242, and the maintainer's decision (2026-09-02) was to declare the assembled stage rather than widen the authoring one.

#14242 declared the assembled stage for the artifact load path. The read API never got the same treatment: InstalledPackageSchema still wraps the authoring manifest, so the API contract inherits the mismatch.

Consequence today

packages/runtime/src/route-ledger.ts's GET /packages row is left with no responseSchema by #16781, deliberately, and the blank is a measured verdict. The ledger header forbids filling a row without conformance coverage, and a name there would promise conformance the door keeps only for glob-authored packages. The boundary is pinned in both directions in packages/runtime/src/domains/packages-read-delete-response-conformance.test.ts:

  • the glob row parses end to end;
  • the assembled row does not, and the test asserts the surviving issue list is exactly ['data.packages.0.manifest.objects.0'].

⇒ Whoever declares the assembled stage for this surface gets a red test telling them the ledger row has become fillable. That is the intended pickup path.

Suggested shape (not a ruling)

Follow #14242's ruling one layer up: an assembled-stage counterpart of InstalledPackageSchema in @objectstack/spec/api whose manifest is the assembled body, bound to the GET /packages and GET /packages/:id response declarations. ⛔ Not widening ManifestSchema — that is the option #14242 already rejected.

Related: #14242 (the ruling) · #16781 (where this was measured) · #16628 · #3877 (the responseSchema programme).

Activity

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions