You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b964ea1
Browse filesBrowse the repository at this point in the historyBrowse files
feat(spec,core,objectql): a merged artifact's package entry states its own source, so a control-plane install registers as marketplace, not code (#25359)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao26JYdNaCZQJBiyvajaDVdq)_Fixes#25232
Clause-②: yes
Metadata-protocol: added
## What
One merged release artifact may now state a **per-package** origin, per
the maintainer's ruling on objectstack-ai/cloud#3138, letter A (「3138
A」): "One merged artifact may carry a per-package `source`. The
`manifest.register()` / `AppPlugin` seam accepts it, and the registry
stops defaulting such a package to `code`."
- **spec** (`packages/spec/src/stack.zod.ts`): `ArtifactPackageSchema`
(one `packages[]` entry of an assembled artifact) gains an optional
`source` beside `manifest`. Its values are the new
`ArtifactPackageSourceSchema` =
`InstalledPackageSourceSchema.extract(['code', 'marketplace'])`. No new
enum value: it is the door-statable subset of the one closed origin set,
and `authored` is excluded because `registerApp` already refuses it. The
key sits on the wrapper, not the body, because it says how the package
reached the artifact.
- **engine, core** (`packages/core/src/artifact-packages.ts`): new
`resolveArtifactPackageEntries(artifact)` returns `{ manifest, source?
}[]` in dependency order, with `source` read off the entry the gate
already parsed and never defaulted. `resolveArtifactPackageOrder` is now
that function projected to the bodies, so the metadata door and every
other caller are unchanged.
- **engine, objectql** (`packages/objectql/src/plugin.ts`):
`manifest.register` stamps each package with its entry's `source`, else
the door's `options.source`, else `registerApp`'s `code` default. An
entry's own statement wins over the door's because it is the more
specific producer statement.
- **No AppPlugin change**: `AppPlugin.init` hands a bundle that carries
`packages[]` to `manifest.register` as it arrived, and the ADR-0087
forward conversion (`replayPackageBodies`) spreads entry keys, so
`source` survives both.
## Acceptance
Card: "A kernel booted from a merged artifact whose packages carry
`source: 'marketplace'` lists them as `marketplace` in `GET
/api/v1/packages`, and `developer:packages?source=marketplace` shows
them. Packages without a per-package source keep today's behaviour."
- `packages/runtime/src/domains/packages-artifact-entry-source.test.ts`:
a real `LiteKernel` with the real `ObjectQLPlugin` +
`AppPlugin(mergedArtifact)`, then the real dispatcher's `GET /packages`,
parsed through `ListInstalledPackagesResponseSchema`. The marketplace
entry is listed `marketplace` beside a `code` sibling. Control: an
artifact whose entries state nothing lists both `code`.
- `developer:packages?source=marketplace` is the client-side filter over
that row's `source` (objectui `views/metadata-admin/packageSource.ts`);
there is no server-side `source` query. So the served row is the seam
this PR owns, and nothing in objectui changes.
- `packages/objectql/src/package-source-stamp.test.ts` (kernel level,
the `manifest` service): mixed entries; explicit `code`; entry over
door; the no-source control; `authored` and `cloud` on an entry refused
at the gate as `INVALID_ARTIFACT_PACKAGE_ENTRY` / 422.
## Ablation (one-time, not a kept test)
Mutation: delete the line in `resolveArtifactPackageEntries` that
carries `verdict.data.source` onto the node, via
`scripts/ablation-replace.mjs` (anchor 1 → 0, blob `8149e550` →
mutated). Then rebuild `@objectstack/core`, and `ablation-dist-preflight
--absent` confirmed the marker gone from `dist/`. Result: the runtime
acceptance test went **red**: `"com.example.merged.installed": "code"`
where `marketplace` was expected. The control case stayed green.
Restore: the tool restored the blob to HEAD with `git diff HEAD` empty.
After rebuilding, the preflight found the marker back in `dist/` and
both tests passed. My first attempt mutated `plugin.ts`; it was a no-op
because the build failed on the unused destructured binding, so it was
discarded and redone on the core line.
## Gates
Local subset derived by `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` (95 commands), run on head
`c2d4316084`, with exit codes captured to disk before any pipe.
`dispatch-gates --ran`: "95 derived famil(ies) accounted for — 95 run, 0
NOT-MEASURED (a DERIVED zero — all 95 recorded an exit code and none of
them is 3)"; all 95 exit 0. Five `@objectstack/spec` gates
(`check:api-surface`, `check:dual-source-exports`,
`check:entry-nameability`, `check:exported-any`, `check:generated`)
first answered exit 3 or 1 on a stale `packages/spec/dist`
(declaration-stamp mismatch after the pin-test edit). After `pnpm
--filter @objectstack/spec build` they were re-run on the same head and
all answered exit 0. The commit-message trailer check and
`check:nul-bytes` are clean. Every other family is CI's, cited from the
PR's check-runs.
- `pnpm --filter @objectstack/{spec,core,objectql,runtime} typecheck`:
exit 0.
- `pnpm --filter @objectstack/core test` 112 files / 2663 tests; `pnpm
--filter @objectstack/objectql test` 464 / 8692; `pnpm --filter
@objectstack/spec test` 709 / 21552 (1 todo): all pass.
- `@objectstack/runtime`: the full suite exceeds the container's
10-minute foreground cap (`exit 124`). Narrowed to
`src/domains/packages*.test.ts` + `src/app-plugin*.test.ts`: 54 files /
811 tests pass. The full run is CI's.
- `pnpm --filter @objectstack/spec check:generated --fix` regenerated
`api-surface/` and `export-origins/` (the new export); every other
artifact was already current.
## Acceptance notes
- The precedence rule (entry over door) is a choice this PR makes. The
ruling says the registry stops defaulting such a package to `code`; it
does not address a door that states one origin while an entry states
another. No producer does that today: the marketplace install-local
plugin states `marketplace` for bundles that carry no entry `source`.
- Cloud's half (the per-environment factory stamping control-plane
installs `marketplace`) follows the pin that carries this, on
objectstack-ai/cloud#3138.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DfWYSwCSiPUfqza6ajXVxs)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(spec,core,objectql): one package entry of a release artifact may state its own `source`, and the registry records it
8
+
9
+
Clause-②: yes
10
+
Metadata-protocol: added
11
+
12
+
`ArtifactPackageSchema` (one `packages[]` entry of an assembled release artifact) gains an optional `source: 'code' | 'marketplace'` beside `manifest`. The values are the new `ArtifactPackageSourceSchema`, the subset of `InstalledPackageSourceSchema` a load path can register; `authored` and any other value are refused at the entry gate as `INVALID_ARTIFACT_PACKAGE_ENTRY` (422).
13
+
14
+
The `manifest` service registers each package with the `source` its entry states, so a merged artifact that carries marketplace installs beside the deployment's own code lists them as `marketplace` on `GET /api/v1/packages`. An entry that states nothing takes the registering door's statement as before (`register(artifact, { source })`, `code` when absent), so existing artifacts register unchanged. An entry's own statement wins over the door's.
15
+
16
+
`@objectstack/core` adds `resolveArtifactPackageEntries(artifact)`, which returns each package to register as `{ manifest, source? }` in dependency order. `resolveArtifactPackageOrder` is unchanged and still returns the bodies alone.
0 commit comments