Skip to content

Commit b964ea1

Browse 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>
1 parent 303aef0 commit b964ea1

9 files changed

Lines changed: 252 additions & 15 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/core': minor
4+
'@objectstack/objectql': minor
5+
---
6+
7+
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.

‎packages/core/src/artifact-packages.ts‎

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
*/
137137

138138
import { resolvePluginOrder, type OrderablePlugin } from './plugin-order.js';
139-
import { ArtifactPackageSchema } from '@objectstack/spec';
139+
import { ArtifactPackageSchema, type ArtifactPackageSource } from '@objectstack/spec';
140140

141141
/**
142142
* Refusals raised by {@link resolveArtifactPackageOrder}, as ADR-0112 envelopes
@@ -181,26 +181,61 @@ export function artifactPackageId(manifest: unknown): string | undefined {
181181
return typeof id === 'string' && id !== '' ? id : undefined;
182182
}
183183

184-
/** The ordering-relevant projection of one artifact package. */
185-
interface ArtifactPackageNode extends OrderablePlugin {
184+
/**
185+
* One package to register, as {@link resolveArtifactPackageEntries} answers it:
186+
* the body, and the origin its artifact entry stated for it.
187+
*/
188+
export interface ArtifactPackageRegistration {
186189
/** The caller's ORIGINAL manifest body — never a parsed clone. */
187190
manifest: unknown;
191+
/**
192+
* The entry's own `source` (`ArtifactPackageSchema`), as written; absent
193+
* when the entry states none, and always absent on the single-`manifest`
194+
* branch, which has no entry to state one. ⛔ Never defaulted here: the
195+
* registering door owns the default, so an absent value stays absent.
196+
*/
197+
source?: ArtifactPackageSource;
188198
}
189199

200+
/** The ordering-relevant projection of one artifact package. */
201+
interface ArtifactPackageNode extends OrderablePlugin, ArtifactPackageRegistration {}
202+
190203
/**
191204
* Resolve an artifact into the manifests to register, in dependency-topological
192205
* order (ADR-0130 D4 + D5).
193206
*
207+
* The bodies alone, for readers that register no package row (the metadata
208+
* door). A door that records a row reads {@link resolveArtifactPackageEntries},
209+
* which carries each entry's stated `source` beside its body.
210+
*
194211
* @param artifact - A release artifact (`{ packages: [...] }`), or a bare
195212
* manifest / single-`manifest` artifact — both shapes are read.
196213
* @returns The manifest bodies to register, in the order to register them.
214+
* @throws As {@link resolveArtifactPackageEntries}.
215+
*/
216+
export function resolveArtifactPackageOrder(artifact: unknown): unknown[] {
217+
return resolveArtifactPackageEntries(artifact).map((entry) => entry.manifest);
218+
}
219+
220+
/**
221+
* Resolve an artifact into the packages to register, in dependency-topological
222+
* order (ADR-0130 D4 + D5), each with the `source` its entry states.
223+
*
224+
* One merged artifact may carry packages of different origins, so the origin is
225+
* read per entry, off the entry the gate below has already parsed. The same
226+
* gate and the same order as {@link resolveArtifactPackageOrder} — that
227+
* function IS this one, projected to the bodies.
228+
*
229+
* @param artifact - A release artifact (`{ packages: [...] }`), or a bare
230+
* manifest / single-`manifest` artifact — both shapes are read.
231+
* @returns The packages to register, in the order to register them.
197232
* @throws An ADR-0112 envelope (`code` + `status: 422`):
198233
* `INVALID_ARTIFACT_PACKAGES` for a `packages` that is present but is not an
199234
* array (`null` included), `INVALID_ARTIFACT_PACKAGE_ENTRY` for a malformed entry, and
200235
* `DUPLICATE_ARTIFACT_PACKAGE` for a duplicate package id. Also
201236
* `resolvePluginOrder`'s own error for a cycle.
202237
*/
203-
export function resolveArtifactPackageOrder(artifact: unknown): unknown[] {
238+
export function resolveArtifactPackageEntries(artifact: unknown): ArtifactPackageRegistration[] {
204239
const declared = (artifact as { packages?: unknown } | null | undefined)?.packages;
205240

206241
// D4, second branch: no `packages` key → the artifact carries one package and
@@ -212,7 +247,7 @@ export function resolveArtifactPackageOrder(artifact: unknown): unknown[] {
212247
// `.optional()` refuses it, so reading it as absent here would answer for an
213248
// artifact the declaration calls malformed (#19926). It falls to the refusal
214249
// below with every other non-array value.
215-
if (declared === undefined) return [artifact];
250+
if (declared === undefined) return [{ manifest: artifact }];
216251

217252
// Present but not an array: malformed, never absent. The rule is stated
218253
// once, beside `AssembledPackageBodySchema`.
@@ -300,8 +335,11 @@ export function resolveArtifactPackageOrder(artifact: unknown): unknown[] {
300335
(manifest as { dependencies?: Record<string, unknown> }).dependencies ?? {},
301336
),
302337
manifest,
338+
// Read off the PARSED entry: the gate above admitted only a declared
339+
// origin, so this is never an unchecked value. Absent stays absent.
340+
...(verdict.data.source !== undefined ? { source: verdict.data.source } : {}),
303341
});
304342
});
305343

306-
return resolvePluginOrder(nodes).map((node) => node.manifest);
344+
return resolvePluginOrder(nodes).map(({ manifest, source }) => (source !== undefined ? { manifest, source } : { manifest }));
307345
}

‎packages/objectql/src/package-source-stamp.test.ts‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*/
2121

2222
import { describe, it, expect, afterEach } from 'vitest';
23-
import { ObjectKernel, type Plugin, type PluginContext } from '@objectstack/core';
23+
import { ObjectKernel, resolveArtifactPackageEntries, type Plugin, type PluginContext } from '@objectstack/core';
2424
import { ObjectQLPlugin } from './plugin.js';
2525
import type { ObjectQL } from './engine.js';
2626
import { SchemaRegistry } from './registry.js';
@@ -94,6 +94,55 @@ describe('the manifest service stamps the registering caller\'s source (#23972)'
9494
});
9595
});
9696

97+
describe('an artifact entry states its own package\'s source (ADR-0130 D4 entry, `ArtifactPackageSchema.source`)', () => {
98+
// A merged artifact: the control plane folds an environment's marketplace
99+
// installs into the one artifact the environment boots from, beside its code.
100+
const merged = (entries: Array<{ manifest: unknown; source?: unknown }>) => ({ ...body(BUNDLE_A, 'srcbundle'), packages: entries });
101+
102+
it('a package whose entry states marketplace records marketplace; its sibling stating nothing records code', async () => {
103+
const ql = await boot([[merged([
104+
{ manifest: body(BUNDLE_A, 'srcbundle') },
105+
{ manifest: body(BUNDLE_B, 'srcbundlex'), source: 'marketplace' },
106+
]), undefined]]);
107+
expect([sourceOf(ql, BUNDLE_A), sourceOf(ql, BUNDLE_B)]).toEqual(['code', 'marketplace']);
108+
});
109+
110+
it('an entry that states code is stamped code; one stating marketplace is stamped marketplace', async () => {
111+
const ql = await boot([[merged([
112+
{ manifest: body(BUNDLE_A, 'srcbundle'), source: 'code' },
113+
{ manifest: body(BUNDLE_B, 'srcbundlex'), source: 'marketplace' },
114+
]), undefined]]);
115+
expect([sourceOf(ql, BUNDLE_A), sourceOf(ql, BUNDLE_B)]).toEqual(['code', 'marketplace']);
116+
});
117+
118+
it('the entry\'s statement is the more specific one: it is stamped over the door\'s, which still covers the entries that state none', async () => {
119+
const ql = await boot([[merged([
120+
{ manifest: body(BUNDLE_A, 'srcbundle') },
121+
{ manifest: body(BUNDLE_B, 'srcbundlex'), source: 'code' },
122+
]), { source: 'marketplace' }]]);
123+
expect([sourceOf(ql, BUNDLE_A), sourceOf(ql, BUNDLE_B)]).toEqual(['marketplace', 'code']);
124+
});
125+
126+
it('CONTROL: an artifact none of whose entries states a source keeps today\'s behaviour — code', async () => {
127+
const ql = await boot([[merged([
128+
{ manifest: body(BUNDLE_A, 'srcbundle') },
129+
{ manifest: body(BUNDLE_B, 'srcbundlex') },
130+
]), undefined]]);
131+
expect([sourceOf(ql, BUNDLE_A), sourceOf(ql, BUNDLE_B)]).toEqual(['code', 'code']);
132+
});
133+
134+
it.each(['authored', 'cloud'])('an entry stating `%s` is refused at the entry gate the load path reads', (source) => {
135+
let refusal: (Error & { code?: string; status?: number }) | undefined;
136+
try {
137+
resolveArtifactPackageEntries(merged([{ manifest: body(BUNDLE_A, 'srcbundle'), source }]));
138+
} catch (err) {
139+
refusal = err as Error & { code?: string; status?: number };
140+
}
141+
expect(refusal).toMatchObject({ code: 'INVALID_ARTIFACT_PACKAGE_ENTRY', status: 422 });
142+
expect(refusal?.message).toContain('source');
143+
});
144+
});
145+
97146
describe('SchemaRegistry.installPackage requires the origin and refuses an unknown one (#23972)', () => {
98147
it('records the source it is handed on the row', () => {
99148
const registry = new SchemaRegistry({ logLevel: 'silent' } as never);

‎packages/objectql/src/plugin.ts‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { assembleMetadataProtocol } from '@objectstack/metadata-protocol';
55
import { isTenantAuthored, readLegacyOrganizationRows } from '@objectstack/metadata-core';
66
import type { MetadataAuthoringChannel } from '@objectstack/metadata-protocol';
77
import { Plugin, PluginContext } from '@objectstack/core';
8-
import { resolveArtifactPackageOrder, artifactPackageId, readDeploymentOrgScopingEntitlement } from '@objectstack/core';
8+
import { resolveArtifactPackageEntries, artifactPackageId, readDeploymentOrgScopingEntitlement } from '@objectstack/core';
99
// [ADR-0130 D4] The residual rule's decision half — which top-level items of a
1010
// multi-package stack no body declares, and the id they take. See
1111
// `unclaimedTopLevelObjects` below.
@@ -705,7 +705,7 @@ export class ObjectQLPlugin implements Plugin {
705705
// [ADR-0130 D4/D5] The load path reads BOTH artifact shapes and registers
706706
// the packages inside one artifact in dependency-TOPOLOGICAL order.
707707
//
708-
// `resolveArtifactPackageOrder` is the whole of that decision: it reads
708+
// `resolveArtifactPackageEntries` is the whole of that decision: it reads
709709
// `packages` when present and falls back to the caller's own object as a
710710
// single-element list when absent (D4), and it orders through
711711
// `resolvePluginOrder` — the platform's ONE topological sorter (ADR-0116).
@@ -723,6 +723,12 @@ export class ObjectQLPlugin implements Plugin {
723723
// from, stamped on each registry row (`InstalledPackageSchema.source`).
724724
// Absent means `'code'`, the deployment's own code (`registerApp`'s
725725
// default); the marketplace install-local plugin states `'marketplace'`.
726+
//
727+
// One merged artifact may carry packages of different origins, so an
728+
// entry may state its own (`ArtifactPackageSchema.source`): the producer
729+
// that merged it is the one that knows. A package's own statement is the
730+
// more specific one and is stamped as written; a package that states none
731+
// takes the door's, as above.
726732
register: (registeredArtifact: any, options?: { source?: 'code' | 'marketplace' }) => {
727733
// ADR-0087 D2 at the registration door, for an artifact that carries
728734
// `packages[]`: its bodies are strictly parsed by the ordering below
@@ -736,7 +742,8 @@ export class ObjectQLPlugin implements Plugin {
736742
const artifact = Array.isArray(registeredArtifact?.packages)
737743
? ql.convertPackageBodyForward(registeredArtifact)
738744
: registeredArtifact;
739-
const ordered = resolveArtifactPackageOrder(artifact) as any[];
745+
const entries = resolveArtifactPackageEntries(artifact);
746+
const ordered = entries.map((entry) => entry.manifest) as any[];
740747
// [ADR-0130 D4] The objects of the stack's RESIDUAL — top-level objects
741748
// no package body declares — as the residual rule answers it. See
742749
// {@link unclaimedTopLevelObjects}.
@@ -802,8 +809,8 @@ export class ObjectQLPlugin implements Plugin {
802809
if (refusal) throw refusal;
803810
}
804811

805-
for (const manifest of ordered) {
806-
ql.registerApp(manifest, scope, options?.source);
812+
for (const { manifest, source } of entries as Array<{ manifest: any; source?: 'code' | 'marketplace' }>) {
813+
ql.registerApp(manifest, scope, source ?? options?.source);
807814
ctx.logger.debug('Manifest registered via manifest service', {
808815
id: manifest.id || manifest.name
809816
});
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* A kernel booted from a MERGED artifact lists each package with the origin
5+
* its artifact entry states (`ArtifactPackageSchema.source`).
6+
*
7+
* The producer this serves is a control plane that folds an environment's
8+
* marketplace installs into the one artifact the environment boots from,
9+
* beside the environment's own code. It boots that artifact through
10+
* `AppPlugin` → the `manifest` service → `ObjectQL.registerApp`, the door the
11+
* deployment's own code uses, whose default is `code` — so without a per-entry
12+
* statement every such package was served as `code`, and Setup's marketplace
13+
* filter (`source === 'marketplace'` on the `GET /packages` row) showed none.
14+
*
15+
* Driven end to end on the real chain: a real kernel, the real `AppPlugin` and
16+
* `ObjectQLPlugin`, and the real dispatcher's `GET /packages` over the engine
17+
* that boot produced.
18+
*/
19+
20+
import { describe, it, expect, afterEach } from 'vitest';
21+
import { LiteKernel } from '@objectstack/core';
22+
import { ObjectQLPlugin, type ObjectQL } from '@objectstack/objectql';
23+
import { ListInstalledPackagesResponseSchema } from '@objectstack/spec/api-assembled';
24+
import { AppPlugin } from '../app-plugin.js';
25+
import { HttpDispatcher } from '../http-dispatcher.js';
26+
27+
const HOST = 'com.example.merged.host';
28+
const INSTALLED = 'com.example.merged.installed';
29+
30+
const body = (id: string, namespace: string) => ({ id, name: namespace, namespace, version: '1.0.0', type: 'app' });
31+
32+
const kernels: LiteKernel[] = [];
33+
34+
afterEach(async () => {
35+
while (kernels.length) await kernels.pop()!.shutdown();
36+
});
37+
38+
/** Boot `artifact` through `AppPlugin`, and answer `GET /packages` over the engine it produced. */
39+
async function listBootedFrom(artifact: Record<string, unknown>): Promise<Record<string, unknown>> {
40+
const kernel = new LiteKernel({ logger: { level: 'error' } });
41+
kernels.push(kernel);
42+
kernel.use(new ObjectQLPlugin({}));
43+
kernel.use(new AppPlugin(artifact));
44+
await kernel.bootstrap();
45+
46+
// The route reads `.registry` and `.manifests` off the engine slot — the
47+
// same handle `packages-writable-verdict.test.ts` hands it.
48+
const objectql = kernel.getService<ObjectQL>('objectql');
49+
const dispatcher = new HttpDispatcher({ context: { getService: (name: string) => (name === 'objectql' ? objectql : null) } } as any);
50+
const r = await dispatcher.handlePackages('/', 'GET', undefined, {}, {
51+
request: {},
52+
environmentId: 'pkg-artifact-entry-source-test',
53+
executionContext: { userId: 'u_admin', isSystem: false, systemPermissions: ['manage_metadata', 'studio.access'] },
54+
} as any);
55+
expect(r.response?.status ?? 200).toBe(200);
56+
// The served body is the declared contract, `source` included.
57+
const rows = ListInstalledPackagesResponseSchema.parse(r.response?.body).data.packages;
58+
return Object.fromEntries(rows
59+
.filter((row) => row.manifest.id === HOST || row.manifest.id === INSTALLED)
60+
.map((row) => [row.manifest.id, row.source]));
61+
}
62+
63+
describe('GET /packages on a kernel booted from a merged artifact', () => {
64+
it('lists a package whose entry states marketplace as marketplace, beside the code package that states nothing', async () => {
65+
const sources = await listBootedFrom({
66+
...body(HOST, 'mergedhost'),
67+
packages: [
68+
{ manifest: body(HOST, 'mergedhost') },
69+
{ manifest: body(INSTALLED, 'mergedinst'), source: 'marketplace' },
70+
],
71+
});
72+
expect(sources).toEqual({ [HOST]: 'code', [INSTALLED]: 'marketplace' });
73+
});
74+
75+
it('CONTROL: packages whose entries state no source keep today\'s behaviour — code', async () => {
76+
const sources = await listBootedFrom({
77+
...body(HOST, 'mergedhost'),
78+
packages: [
79+
{ manifest: body(HOST, 'mergedhost') },
80+
{ manifest: body(INSTALLED, 'mergedinst') },
81+
],
82+
});
83+
expect(sources).toEqual({ [HOST]: 'code', [INSTALLED]: 'code' });
84+
});
85+
});

‎packages/spec/api-surface/root.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"ArtifactPackageEntrySchema (const)",
1616
"ArtifactPackageParsed (type)",
1717
"ArtifactPackageSchema (const)",
18+
"ArtifactPackageSource (type)",
19+
"ArtifactPackageSourceSchema (const)",
1820
"ArtifactStagePackageBody (type)",
1921
"ArtifactStagePackageBodyParsed (type)",
2022
"ArtifactStagePackageBodySchema (const)",

‎packages/spec/export-origins/root.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"ArtifactPackageEntrySchema": "src/stack.zod.ts#ArtifactPackageEntrySchema (const)",
1616
"ArtifactPackageParsed": "src/stack.zod.ts#ArtifactPackageParsed (type)",
1717
"ArtifactPackageSchema": "src/stack.zod.ts#ArtifactPackageSchema (const)",
18+
"ArtifactPackageSource": "src/stack.zod.ts#ArtifactPackageSource (type)",
19+
"ArtifactPackageSourceSchema": "src/stack.zod.ts#ArtifactPackageSourceSchema (const)",
1820
"ArtifactStagePackageBody": "src/stack.zod.ts#ArtifactStagePackageBody (type)",
1921
"ArtifactStagePackageBodyParsed": "src/stack.zod.ts#ArtifactStagePackageBodyParsed (type)",
2022
"ArtifactStagePackageBodySchema": "src/stack.zod.ts#ArtifactStagePackageBodySchema (const)",

0 commit comments

Comments
 (0)