Skip to content

Commit db42dd2

Browse files
committed
feat(runtime): package export names each connector map it withheld; install-local names each as incomplete
Claude-Session: https://claude.ai/code/session_01JQwxbYXRMR5S5B7JSmKkV7 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 744211f commit db42dd2

8 files changed

Lines changed: 669 additions & 30 deletions

‎packages/cloud-connection/src/marketplace-install-local-plugin.ts‎

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@
7575
* none of which writes, and before any write.
7676
* A mode the package's `installModes` does not permit answers
7777
* `422 INSTALL_MODE_NOT_PERMITTED`, before any write.
78+
* Either mode reads the body's `withheldMaps` — the connector header
79+
* and environment maps a package export withheld whole (#28640
80+
* ruling ③A) — accepts the package, and names each map in its
81+
* answer's `withheldMaps` and in one `warn` line; a list the export's
82+
* declaration refuses answers `PLUGIN_MANIFEST_INVALID` (400 inline,
83+
* 502 cloud), before any write.
7884
*
7985
* [ADR-0126 §6 wall 3] A managed RE-install over an installed package
8086
* asks the security plugin's access upgrade check
@@ -187,6 +193,9 @@ import {
187193
unmanagedInstallValueCarries,
188194
type InstallMode,
189195
} from '@objectstack/spec/kernel';
196+
// [#28640 ruling ③A] The export's list of the connector maps it withheld whole,
197+
// read by both install modes through its one declaration.
198+
import { PackageExportManifestSchema, type PackageExportManifest } from '@objectstack/spec/api';
190199
// [#21762] ADR-0087 D1's protocol handshake, its brand predicate and the ONE
191200
// answer both package-install doors give its refusal. Imported from the
192201
// producer, never restated: `POST /api/v1/packages` answers through the same
@@ -602,6 +611,57 @@ function isPlainObject(value: unknown): value is Record<string, any> {
602611
return value !== null && typeof value === 'object' && !Array.isArray(value);
603612
}
604613

614+
/** One entry of an export's `withheldMaps` ({@link PackageExportManifestSchema}). */
615+
type WithheldMap = NonNullable<PackageExportManifest['withheldMaps']>[number];
616+
617+
/**
618+
* [#28640 ruling ③A] The connector maps the export that produced this body
619+
* withheld whole — its `withheldMaps`, parsed through the export's own
620+
* declaration ({@link PackageExportManifestSchema}), by reference. Read by
621+
* BOTH install modes before any write, so each answer names every map that
622+
* arrives incomplete instead of letting it read as one the package never had.
623+
*
624+
* An absent list is `[]`: a body that is not an export, or an export that
625+
* withheld nothing. A list the declaration refuses is refused here, the way
626+
* an invalid `installModes` is ({@link judgeInstallMode}): it can only have
627+
* been written by hand, and guessing which maps it meant is the silent answer
628+
* the list exists to replace. The import never refuses a package FOR an
629+
* incomplete map (the ruling: accept, and name each one).
630+
*/
631+
function judgeWithheldMaps(
632+
manifest: Record<string, any>,
633+
manifestId: string,
634+
invalidManifestStatus: 400 | 502,
635+
): { ok: true; withheldMaps: WithheldMap[] } | { ok: false; status: number; error: { code: string; message: string } } {
636+
const declared = PackageExportManifestSchema.shape.withheldMaps.safeParse(manifest.withheldMaps);
637+
if (!declared.success) {
638+
const [issue] = declared.error.issues;
639+
return {
640+
ok: false,
641+
status: invalidManifestStatus,
642+
error: {
643+
code: 'PLUGIN_MANIFEST_INVALID',
644+
message: `Package '${manifestId}' carries an invalid \`withheldMaps\`: ${issue?.message ?? 'refused by its declaration'}`
645+
+ `${issue && issue.path.length > 0 ? ` (at \`withheldMaps.${issue.path.join('.')}\`)` : ''}. `
646+
+ 'Send the list as the package export wrote it, or omit it.',
647+
},
648+
};
649+
}
650+
return { ok: true, withheldMaps: declared.data ?? [] };
651+
}
652+
653+
/**
654+
* [#28640 ruling ③A] The operator's line for an install that received
655+
* withheld maps: each one named, once, at `warn` — the connector is installed
656+
* and runs, without the headers or environment variables the export held back.
657+
*/
658+
function withheldMapsLogLine(manifestId: string, mode: InstallMode, withheldMaps: readonly WithheldMap[]): string {
659+
const named = withheldMaps.map((m) => `${m.type} '${m.name}' ${m.path}`).join(', ');
660+
return `[MarketplaceInstallLocal] installed ${manifestId} ${mode} with ${withheldMaps.length} connector map(s) the `
661+
+ `package export withheld whole, each now absent: ${named}. The connector sends none of those headers or `
662+
+ 'environment variables until each map is supplied again where the connector is authored.';
663+
}
664+
605665
/**
606666
* [ADR-0131 D6] The package manifest an install body DECLARES — where each of
607667
* the two single-package shapes this door accepts carries it: a compiled
@@ -1925,6 +1985,15 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
19251985
}, UNRUNNABLE_REFUSAL_STATUS);
19261986
}
19271987

1988+
// 1d'. [#28640 ruling ③A] THE MAPS THE EXPORT WITHHELD, read for both
1989+
// modes before any write: each answer names every connector map
1990+
// that arrives incomplete ({@link judgeWithheldMaps}). Accepted,
1991+
// never refused for, as the ruling has it; only a list the export's
1992+
// declaration refuses is.
1993+
const withheld = judgeWithheldMaps(manifest, manifestId, inlineManifest ? 400 : 502);
1994+
if (!withheld.ok) return c.json({ success: false, error: withheld.error }, withheld.status as any);
1995+
const { withheldMaps } = withheld;
1996+
19281997
// ── [ADR-0131 D6] The `unmanaged` install: a copy, not a registration.
19291998
// The package's metadata is copied ONCE into a new writable base in
19301999
// the environment ledger by the import verb, and the steps below —
@@ -1940,6 +2009,7 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
19402009
version,
19412010
versionId: resolvedVersionId,
19422011
userId,
2012+
withheldMaps,
19432013
});
19442014
}
19452015

@@ -2308,6 +2378,7 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
23082378
// The door is named only when it can store one of them: a listing that
23092379
// says none is writable (a walled posture) gets the other places.
23102380
const door = described?.writable === undefined || unsetRefs.some((r) => described.writable!.has(r));
2381+
if (withheldMaps.length > 0) ctx.logger?.warn?.(withheldMapsLogLine(manifestId, 'managed', withheldMaps));
23112382

23122383
return c.json({
23132384
success: true,
@@ -2333,6 +2404,10 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
23332404
// that declares none, so that answer's key set is unchanged.
23342405
...(datasources !== undefined ? { datasources } : {}),
23352406
...(unsetRefs.length > 0 ? { credentialNote: installedCredentialNote(unsetRefs, { door }) } : {}),
2407+
// [#28640 ruling ③A] Each connector map the package export
2408+
// withheld whole, so it arrived absent. Omitted when there are
2409+
// none, so that answer's key set is unchanged.
2410+
...(withheldMaps.length > 0 ? { withheldMaps } : {}),
23362411
upgradedFrom: conflict === 'marketplace' ? 'previous-marketplace-version' : null,
23372412
// [ADR-0146 D2, #28410] Names installed dependents already held,
23382413
// undeclared, that this version now ships (step 2d). Omitted when
@@ -3538,14 +3613,21 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
35383613
* either spelling, and the copy does not keep, with the package's
35393614
* reason — is the verb's own
35403615
* list, relayed as the verb states it, so the installer reads why the
3541-
* vendor locked an item before editing it.
3616+
* vendor locked an item before editing it;
3617+
* - `withheldMaps` — every connector map the package export withheld
3618+
* whole, read by {@link handleInstall} before the verb runs — is on the
3619+
* answer and on a `warn` line: the copy carries each map absent.
35423620
*/
35433621
private installUnmanaged = async (
35443622
c: any,
35453623
ctx: PluginContext,
3546-
args: { manifest: Record<string, any>; manifestId: string; version: string; versionId: string; userId: string | null },
3624+
args: {
3625+
manifest: Record<string, any>; manifestId: string; version: string; versionId: string; userId: string | null;
3626+
/** [#28640 ruling ③A] The connector maps the export withheld ({@link judgeWithheldMaps}). */
3627+
withheldMaps: readonly WithheldMap[];
3628+
},
35473629
): Promise<Response> => {
3548-
const { manifest, manifestId, version, versionId, userId } = args;
3630+
const { manifest, manifestId, version, versionId, userId, withheldMaps } = args;
35493631
let protocol: UnmanagedImporter | undefined;
35503632
try { protocol = ctx.getService<UnmanagedImporter>('protocol'); } catch { /* no protocol service */ }
35513633
if (!protocol || typeof protocol.importUnmanagedPackage !== 'function') {
@@ -3598,6 +3680,7 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
35983680
+ `${installedFrom.packageId}${installedFrom.version ? `@${installedFrom.version}` : ''}`
35993681
+ `${userId ? ` by ${userId}` : ''}. Nothing is registered under '${manifestId}'; the copy follows no upgrade.`,
36003682
);
3683+
if (withheldMaps.length > 0) ctx.logger?.warn?.(withheldMapsLogLine(manifestId, 'unmanaged', withheldMaps));
36013684
const declaresSeeds = unmanagedInstallValueCarries(packageBody.data);
36023685
return c.json({
36033686
success: true,
@@ -3613,6 +3696,10 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
36133696
copiedCount: result.copiedCount,
36143697
copied: result.copied,
36153698
strippedLocks: result.strippedLocks,
3699+
// [#28640 ruling ③A] Each connector map the package export
3700+
// withheld whole, so its copy carries it absent. Omitted when
3701+
// there are none, so that answer's key set is unchanged.
3702+
...(withheldMaps.length > 0 ? { withheldMaps: [...withheldMaps] } : {}),
36163703
...(declaresSeeds
36173704
? {
36183705
seeded: {
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#28640 ruling ③A] install-local reads a package export's `withheldMaps` —
5+
* the connector header and environment maps the export withheld whole — in
6+
* BOTH install modes, at the real install seam (the plugin's own route,
7+
* mounted through `start()` + `kernel:ready`).
8+
*
9+
* Pinned:
10+
* - a managed install of a body carrying the list installs, and its answer
11+
* names each map, as the export wrote it, with one `warn` line naming them;
12+
* - an unmanaged install of such a body copies it, and its answer names each
13+
* map the same way;
14+
* - a body without the list answers with no `withheldMaps` key at all, in
15+
* either mode — the answer's key set is unchanged;
16+
* - a list the export's declaration refuses (a position outside the four, a
17+
* header name) answers `PLUGIN_MANIFEST_INVALID`, 400 inline, before any
18+
* write: nothing registered, ledgered or imported.
19+
*/
20+
21+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
22+
import { mkdtempSync, rmSync } from 'node:fs';
23+
import { join } from 'node:path';
24+
import { tmpdir } from 'node:os';
25+
// The runtime binder the door's step 1d loads lazily — paid here, at module
26+
// top, never inside a clocked `it` (`scripts/check-test-source-alias.mjs`).
27+
import '@objectstack/runtime';
28+
import { MarketplaceInstallLocalPlugin } from './marketplace-install-local-plugin.js';
29+
import { installerAuthService, withInstallerGrants } from './install-local-principal.fixtures.js';
30+
import { LocalManifestSource } from './local-manifest-source.js';
31+
32+
const ROUTE = '/api/v1/marketplace/install-local';
33+
const INSTALLER = 'usr_installer';
34+
35+
type Handler = (c: any) => Promise<any>;
36+
37+
function makeC(body: any) {
38+
const json = vi.fn((payload: any, status?: number) => ({ payload, status: status ?? 200 }));
39+
return {
40+
req: {
41+
url: `http://localhost:3000${ROUTE}`,
42+
raw: new Request(`http://localhost:3000${ROUTE}`),
43+
json: async () => body,
44+
param: () => undefined,
45+
},
46+
json,
47+
};
48+
}
49+
50+
async function mount(dir: string) {
51+
const routes = new Map<string, Handler>();
52+
const rawApp = {
53+
get: (p: string, h: Handler) => routes.set(`GET ${p}`, h),
54+
post: (p: string, h: Handler) => routes.set(`POST ${p}`, h),
55+
delete: (p: string, h: Handler) => routes.set(`DELETE ${p}`, h),
56+
};
57+
const hooks = new Map<string, any>();
58+
const register = vi.fn(async () => undefined);
59+
const importUnmanagedPackage = vi.fn(async (request: any) => ({
60+
sourcePackageId: request.manifest.id,
61+
sourceVersion: request.manifest.version,
62+
targetPackageId: `${request.manifest.id}-copy`,
63+
namespace: request.manifest.namespace,
64+
copiedCount: request.manifest.connectors?.length ?? 0,
65+
copied: (request.manifest.connectors ?? []).map((c: any) => ({ type: 'connector', name: c.name })),
66+
strippedLocks: [],
67+
}));
68+
const services: Record<string, any> = {
69+
manifest: { register },
70+
auth: installerAuthService(INSTALLER),
71+
objectql: withInstallerGrants({
72+
syncSchemas: vi.fn(async () => undefined),
73+
manifests: new Map(),
74+
registry: { getAllPackages: () => [], getNamespaceOwners: () => [], getPackage: () => undefined },
75+
}, INSTALLER),
76+
protocol: { importUnmanagedPackage },
77+
};
78+
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
79+
const ctx = {
80+
hook: (e: string, h: any) => hooks.set(e, h),
81+
getService: (name: string) => {
82+
if (name === 'http-server') return { getRawApp: () => rawApp };
83+
const svc = services[name];
84+
if (svc === undefined) throw new Error(`no ${name}`);
85+
return svc;
86+
},
87+
logger,
88+
};
89+
const plugin = new MarketplaceInstallLocalPlugin({ controlPlaneUrl: 'off', storageDir: dir });
90+
await plugin.start(ctx as any);
91+
await hooks.get('kernel:ready')?.();
92+
register.mockClear();
93+
return {
94+
install: (body: unknown) => routes.get(`POST ${ROUTE}`)!(makeC(body)),
95+
register,
96+
importUnmanagedPackage,
97+
logger,
98+
ledgerHas: (id: string) => new LocalManifestSource(dir).read(id).entry !== null,
99+
};
100+
}
101+
102+
const WITHHELD = [
103+
{ type: 'connector', name: 'crm_billing', path: 'providerConfig.headers' },
104+
{ type: 'connector', name: 'crm_billing', path: 'providerConfig.defaultHeaders' },
105+
{ type: 'connector', name: 'crm_tools', path: 'providerConfig.transport.env' },
106+
];
107+
108+
/** A package as `GET /packages/:id/export` writes it: connectors served, their maps absent. */
109+
function exported(extra: Record<string, unknown> = {}): Record<string, unknown> {
110+
return {
111+
id: 'com.example.crm',
112+
name: 'crm',
113+
version: '4.0.0',
114+
namespace: 'crm',
115+
installModes: ['managed', 'unmanaged'],
116+
connectors: [
117+
{ name: 'crm_billing', label: 'Billing', type: 'api', provider: 'rest', providerConfig: { baseUrl: 'https://billing.example.com' } },
118+
{ name: 'crm_tools', label: 'Tools', type: 'api', provider: 'mcp', providerConfig: { transport: { kind: 'stdio', command: 'crm-tools' } } },
119+
],
120+
...extra,
121+
};
122+
}
123+
124+
let dir: string;
125+
beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'mil-withheld-maps-')); });
126+
afterEach(() => {
127+
rmSync(dir, { recursive: true, force: true });
128+
vi.restoreAllMocks();
129+
});
130+
131+
describe('install-local — the maps a package export withheld (`withheldMaps`)', () => {
132+
it('a managed install accepts the package and names each withheld map in its answer and one warn line', async () => {
133+
const h = await mount(dir);
134+
const res = await h.install({ manifest: exported({ withheldMaps: WITHHELD }) });
135+
expect(res.status, JSON.stringify(res.payload)).toBe(200);
136+
expect(res.payload.data.withheldMaps).toEqual(WITHHELD);
137+
expect(h.register).toHaveBeenCalledTimes(1);
138+
expect(h.ledgerHas('com.example.crm')).toBe(true);
139+
const lines = h.logger.warn.mock.calls.map((call) => String(call[0])).filter((l) => l.includes('withheld whole'));
140+
expect(lines).toHaveLength(1);
141+
for (const m of WITHHELD) expect(lines[0]).toContain(`connector '${m.name}' ${m.path}`);
142+
});
143+
144+
it('an unmanaged install copies the package and names each withheld map in its answer', async () => {
145+
const h = await mount(dir);
146+
const res = await h.install({ manifest: exported({ withheldMaps: WITHHELD }), installMode: 'unmanaged' });
147+
expect(res.status, JSON.stringify(res.payload)).toBe(200);
148+
expect(res.payload.data.installMode).toBe('unmanaged');
149+
expect(res.payload.data.withheldMaps).toEqual(WITHHELD);
150+
expect(h.importUnmanagedPackage).toHaveBeenCalledTimes(1);
151+
expect(h.register).not.toHaveBeenCalled();
152+
const lines = h.logger.warn.mock.calls.map((call) => String(call[0])).filter((l) => l.includes('withheld whole'));
153+
expect(lines).toHaveLength(1);
154+
expect(lines[0]).toContain('unmanaged');
155+
});
156+
157+
it.each([['managed'], ['unmanaged']] as const)('a %s install of a body without the list answers no `withheldMaps` key, and warns nothing', async (installMode) => {
158+
const h = await mount(dir);
159+
const res = await h.install({ manifest: exported(), installMode });
160+
expect(res.status, JSON.stringify(res.payload)).toBe(200);
161+
expect(res.payload.data).not.toHaveProperty('withheldMaps');
162+
expect(h.logger.warn.mock.calls.some((call) => String(call[0]).includes('withheld whole'))).toBe(false);
163+
});
164+
165+
it.each([
166+
['a position outside the four', [{ type: 'connector', name: 'crm_billing', path: 'auth.token' }]],
167+
['a list of header names', [{ type: 'connector', name: 'crm_billing', path: 'providerConfig.headers', keys: ['Authorization'] }]],
168+
['a value that is not a list', 'providerConfig.headers'],
169+
])('refuses %s — PLUGIN_MANIFEST_INVALID, 400 inline, nothing written in either mode', async (_label, withheldMaps) => {
170+
for (const installMode of ['managed', 'unmanaged'] as const) {
171+
const h = await mount(dir);
172+
const res = await h.install({ manifest: exported({ withheldMaps }), installMode });
173+
expect(res.status, JSON.stringify(res.payload)).toBe(400);
174+
expect(res.payload.error.code).toBe('PLUGIN_MANIFEST_INVALID');
175+
expect(res.payload.error.message).toContain('withheldMaps');
176+
expect(h.register).not.toHaveBeenCalled();
177+
expect(h.importUnmanagedPackage).not.toHaveBeenCalled();
178+
expect(h.ledgerHas('com.example.crm')).toBe(false);
179+
}
180+
});
181+
});

0 commit comments

Comments
 (0)