@agentic-kit/pi 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/host.d.ts CHANGED
@@ -38,6 +38,16 @@ export type PreviewToken = {
38
38
  accessTokenExpiresAt?: string;
39
39
  userId?: string;
40
40
  };
41
+ /**
42
+ * Overlay layered over the pinned base preset when provisioning a database.
43
+ * Structurally the `ProvisionOverlay` from `provision-database/resolve`; typed
44
+ * loosely here to keep `host.ts` free of provision-internal imports.
45
+ */
46
+ export interface HostProvisionOverlay {
47
+ preset?: string;
48
+ add?: (string | [string, Record<string, unknown>])[];
49
+ remove?: string[];
50
+ }
41
51
  export interface PiToolsHost {
42
52
  /** Signed-in platform account, or null/undefined when signed out. */
43
53
  account(): HostAccount | null | undefined;
@@ -49,6 +59,13 @@ export interface PiToolsHost {
49
59
  previewToken?(): Promise<PreviewToken | null>;
50
60
  /** Treat tokens expiring within this window as already expired. Default 30s. */
51
61
  dataTokenSkewMs?: number;
62
+ /**
63
+ * Optional provision overlay: pick a base preset and/or layer module
64
+ * add/remove on top of it. The base module list always comes from the pinned
65
+ * `node-type-registry` preset — this only customizes it. Distributed as data
66
+ * (e.g. materialized from appstash / a pinned git ref), never as code.
67
+ */
68
+ provisionOverlay?(): HostProvisionOverlay | null | undefined | Promise<HostProvisionOverlay | null | undefined>;
52
69
  }
53
70
  export declare const DEFAULT_DATA_TOKEN_SKEW_MS = 30000;
54
71
  export declare function configureHost(host: PiToolsHost): void;
package/esm/index.d.ts CHANGED
@@ -5,7 +5,10 @@ export declare const dbTools: ExtensionFactory;
5
5
  export declare function createDbTools(host: PiToolsHost): ExtensionFactory;
6
6
  export { type ConfirmGate, type ConfirmGateDeps, createConfirmGate } from './confirm-gate';
7
7
  export { type ModulesClient, type ProjectContext, resolveDataToken, resolveProjectContext, } from './context';
8
- export { type ActiveDataToken, configureHost, type DataAuthBroker, getHost, type HostAccount, type HostBackendConfig, type PiToolsHost, type PreviewToken, } from './host';
8
+ export { type ActiveDataToken, configureHost, type DataAuthBroker, getHost, type HostAccount, type HostBackendConfig, type HostProvisionOverlay, type PiToolsHost, type PreviewToken, } from './host';
9
+ export { loadProvisionManifest, parseProvisionManifest, PROVISION_MANIFEST_FILE, type ProvisionManifest, } from './provision-database/manifest';
10
+ export { allModulePresets, DEFAULT_PROVISION_PRESET, getModulePreset, type ModulePreset, type ProvisionModule, } from './provision-database/presets';
11
+ export { moduleKey, type ProvisionOverlay, resolveProvisionModules, } from './provision-database/resolve';
9
12
  export { toolSchema } from './tool-schema';
10
13
  export { createTemplatePreviewTables } from './tools/templates';
11
14
  export default dbTools;
package/esm/index.js CHANGED
@@ -43,6 +43,9 @@ export function createDbTools(host) {
43
43
  export { createConfirmGate } from './confirm-gate';
44
44
  export { resolveDataToken, resolveProjectContext, } from './context';
45
45
  export { configureHost, getHost, } from './host';
46
+ export { loadProvisionManifest, parseProvisionManifest, PROVISION_MANIFEST_FILE, } from './provision-database/manifest';
47
+ export { allModulePresets, DEFAULT_PROVISION_PRESET, getModulePreset, } from './provision-database/presets';
48
+ export { moduleKey, resolveProvisionModules, } from './provision-database/resolve';
46
49
  export { toolSchema } from './tool-schema';
47
50
  export { createTemplatePreviewTables } from './tools/templates';
48
51
  export default dbTools;
@@ -1,4 +1,4 @@
1
- import type { ProvisionModule } from './modules';
1
+ import type { ProvisionModule } from './presets';
2
2
  export declare function createDatabaseProvision(args: {
3
3
  endpoint: string;
4
4
  bearer: string;
@@ -0,0 +1,10 @@
1
+ import type { ProvisionOverlay } from './resolve';
2
+ export declare const PROVISION_MANIFEST_FILE = "provision.json";
3
+ export type ProvisionManifest = ProvisionOverlay;
4
+ /** Validate an untrusted value as a provision manifest, throwing on bad shape. */
5
+ export declare function parseProvisionManifest(value: unknown): ProvisionManifest;
6
+ /**
7
+ * Load `<dir>/provision.json` as an overlay, or `null` when absent. A malformed
8
+ * file throws so a broken overlay is surfaced rather than silently ignored.
9
+ */
10
+ export declare function loadProvisionManifest(dir: string): Promise<ProvisionManifest | null>;
@@ -0,0 +1,48 @@
1
+ // `provision.json` overlay manifest.
2
+ //
3
+ // A pure-data overlay, distributed the same way skills are: written into a
4
+ // config/app directory (locally, or materialized there from appstash / a pinned
5
+ // git ref) and read at provision time. It NEVER installs or executes a package
6
+ // — the base preset is a pinned build-time dependency; this file only selects a
7
+ // preset and layers add/remove on top.
8
+ import { readFile } from 'node:fs/promises';
9
+ import path from 'node:path';
10
+ import { z } from 'zod';
11
+ export const PROVISION_MANIFEST_FILE = 'provision.json';
12
+ const ModuleSchema = z.union([
13
+ z.string(),
14
+ z.tuple([z.string(), z.record(z.string(), z.unknown())]),
15
+ ]);
16
+ const ProvisionManifestSchema = z
17
+ .object({
18
+ preset: z.string().optional(),
19
+ add: z.array(ModuleSchema).optional(),
20
+ remove: z.array(z.string()).optional(),
21
+ })
22
+ .strict();
23
+ /** Validate an untrusted value as a provision manifest, throwing on bad shape. */
24
+ export function parseProvisionManifest(value) {
25
+ return ProvisionManifestSchema.parse(value);
26
+ }
27
+ /**
28
+ * Load `<dir>/provision.json` as an overlay, or `null` when absent. A malformed
29
+ * file throws so a broken overlay is surfaced rather than silently ignored.
30
+ */
31
+ export async function loadProvisionManifest(dir) {
32
+ const file = path.join(dir, PROVISION_MANIFEST_FILE);
33
+ let raw;
34
+ try {
35
+ raw = await readFile(file, 'utf8');
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(raw);
43
+ }
44
+ catch (err) {
45
+ throw new Error(`Invalid ${PROVISION_MANIFEST_FILE} at ${file}: ${err instanceof Error ? err.message : String(err)}`);
46
+ }
47
+ return parseProvisionManifest(parsed);
48
+ }
@@ -0,0 +1,4 @@
1
+ import { allModulePresets, getModulePreset, type ModulePreset } from 'node-type-registry';
2
+ export type ProvisionModule = string | [string, Record<string, unknown>];
3
+ export declare const DEFAULT_PROVISION_PRESET: string;
4
+ export { allModulePresets, getModulePreset, type ModulePreset };
@@ -0,0 +1,13 @@
1
+ // Provision module presets — the canonical source of truth.
2
+ //
3
+ // The module list is NEVER hand-copied here. It is imported from the pinned
4
+ // `node-type-registry` package (the same presets the backend and dashboard use),
5
+ // so bumping that dependency is how the base module set moves. Hosts customize
6
+ // on top with an ordered overlay (see `resolve.ts`) rather than editing a list.
7
+ import { allModulePresets, getModulePreset, PresetB2bStorage, } from 'node-type-registry';
8
+ // Default base preset. `b2b:storage` = the full multi-tenant B2B shape plus file
9
+ // storage, matching Constructive's app shape. Crucially it already includes
10
+ // `config_secrets_module` — the dependency the old hand-copied list dropped,
11
+ // which made the backend reject provisioning with `REQUIRES (config_secrets_module:app)`.
12
+ export const DEFAULT_PROVISION_PRESET = PresetB2bStorage.name;
13
+ export { allModulePresets, getModulePreset };
@@ -0,0 +1,27 @@
1
+ import { type ProvisionModule } from './presets';
2
+ export interface ProvisionOverlay {
3
+ /**
4
+ * Base preset name to resolve from (e.g. 'b2b:storage', 'full'). The last
5
+ * layer that sets `preset` wins; if none set it, `DEFAULT_PROVISION_PRESET`
6
+ * is used.
7
+ */
8
+ preset?: string;
9
+ /**
10
+ * Modules to add. An entry whose key matches an existing module OVERRIDES it
11
+ * (e.g. re-declaring `storage_module` with app-specific options); a new key
12
+ * is appended.
13
+ */
14
+ add?: ProvisionModule[];
15
+ /**
16
+ * Module names or `name:scope` keys to drop from the base. A bare name
17
+ * removes every scope of that module; `name:scope` removes just that one.
18
+ */
19
+ remove?: string[];
20
+ }
21
+ /** Stable identity for a module entry: `name` or `name:scope` when scoped. */
22
+ export declare function moduleKey(m: ProvisionModule): string;
23
+ /**
24
+ * Resolve the effective module list from a pinned preset plus ordered overlays.
25
+ * Pure: no I/O, no host access. Later overlays win (last-write-wins by key).
26
+ */
27
+ export declare function resolveProvisionModules(layers?: ProvisionOverlay[]): ProvisionModule[];
@@ -0,0 +1,49 @@
1
+ // Ordered-overlay resolution for provision modules.
2
+ //
3
+ // Mirrors the skills overlay model (`@agentic-kit/harness` skills manifest): a
4
+ // pinned base (a named preset from `node-type-registry`) is layered under zero
5
+ // or more overlays, later layers winning by module key. An empty overlay list
6
+ // resolves to the base preset unchanged.
7
+ import { allModulePresets, DEFAULT_PROVISION_PRESET, getModulePreset, } from './presets';
8
+ function moduleName(m) {
9
+ return Array.isArray(m) ? m[0] : m;
10
+ }
11
+ function moduleScope(m) {
12
+ if (!Array.isArray(m))
13
+ return undefined;
14
+ const scope = m[1]?.scope;
15
+ return typeof scope === 'string' ? scope : undefined;
16
+ }
17
+ /** Stable identity for a module entry: `name` or `name:scope` when scoped. */
18
+ export function moduleKey(m) {
19
+ const scope = moduleScope(m);
20
+ return scope ? `${moduleName(m)}:${scope}` : moduleName(m);
21
+ }
22
+ /**
23
+ * Resolve the effective module list from a pinned preset plus ordered overlays.
24
+ * Pure: no I/O, no host access. Later overlays win (last-write-wins by key).
25
+ */
26
+ export function resolveProvisionModules(layers = []) {
27
+ const presetName = [...layers].reverse().find((l) => l.preset)?.preset ?? DEFAULT_PROVISION_PRESET;
28
+ const preset = getModulePreset(presetName);
29
+ if (!preset) {
30
+ const known = allModulePresets.map((p) => p.name).join(', ');
31
+ throw new Error(`Unknown provision preset "${presetName}". Known presets: ${known}.`);
32
+ }
33
+ // Ordered map keyed by module identity preserves position while allowing
34
+ // overrides (same key) and appends (new key).
35
+ const resolved = new Map();
36
+ for (const m of preset.modules)
37
+ resolved.set(moduleKey(m), m);
38
+ for (const layer of layers) {
39
+ for (const target of layer.remove ?? []) {
40
+ for (const key of [...resolved.keys()]) {
41
+ if (key === target || key.split(':')[0] === target)
42
+ resolved.delete(key);
43
+ }
44
+ }
45
+ for (const m of layer.add ?? [])
46
+ resolved.set(moduleKey(m), m);
47
+ }
48
+ return [...resolved.values()];
49
+ }
@@ -9,8 +9,9 @@ import { probeDatabase } from '../db-probe';
9
9
  import { createDatabaseProvision } from '../provision-database/create-database-provision';
10
10
  import { selectProvisionCredential } from '../provision-database/credential';
11
11
  import { archiveBindingKeys, ARCHIVED_BINDING_KEYS, mergeEnv, provisionEnvVars, } from '../provision-database/env-file';
12
- import { DEFAULT_PROVISION_MODULES } from '../provision-database/modules';
12
+ import { loadProvisionManifest } from '../provision-database/manifest';
13
13
  import { applySqlFixups } from '../provision-database/pg-fixups';
14
+ import { resolveProvisionModules } from '../provision-database/resolve';
14
15
  const DEFAULT_API_ENDPOINT = 'http://api.localhost:3000/graphql';
15
16
  const DEFAULT_MODULES_ENDPOINT = 'http://modules.localhost:3000/graphql';
16
17
  // The per-DB endpoints are subdomain.<domain>; the provisioning domain is the api
@@ -186,6 +187,24 @@ export const provisionDatabaseTool = {
186
187
  ok: false,
187
188
  out: err instanceof Error ? err.message : String(err),
188
189
  }));
190
+ // Resolve the module set: pinned base preset from node-type-registry, then
191
+ // ordered overlays (host default < project-local provision.json). No list is
192
+ // hand-copied here; overlays are pure data. A bad preset/manifest fails the
193
+ // run rather than provisioning a wrong module set.
194
+ let modules;
195
+ try {
196
+ const layers = [];
197
+ const hostOverlay = await host.provisionOverlay?.();
198
+ if (hostOverlay)
199
+ layers.push(hostOverlay);
200
+ const projectOverlay = await loadProvisionManifest(ctx.cwd);
201
+ if (projectOverlay)
202
+ layers.push(projectOverlay);
203
+ modules = resolveProvisionModules(layers);
204
+ }
205
+ catch (err) {
206
+ return fail(`Cannot resolve provision modules: ${err instanceof Error ? err.message : String(err)}`);
207
+ }
189
208
  const physicalDb = process.env.CONSTRUCTIVE_DB || 'constructive';
190
209
  // Provision on the MODULES endpoint: createDatabaseProvisionModule creates
191
210
  // the database owned by ownerId, provisions the module set, and bootstraps
@@ -200,7 +219,7 @@ export const provisionDatabaseTool = {
200
219
  databaseName,
201
220
  domain: provisionDomain(apiEndpoint),
202
221
  ownerId,
203
- modules: DEFAULT_PROVISION_MODULES,
222
+ modules,
204
223
  })));
205
224
  }
206
225
  catch (err) {
package/host.d.ts CHANGED
@@ -38,6 +38,16 @@ export type PreviewToken = {
38
38
  accessTokenExpiresAt?: string;
39
39
  userId?: string;
40
40
  };
41
+ /**
42
+ * Overlay layered over the pinned base preset when provisioning a database.
43
+ * Structurally the `ProvisionOverlay` from `provision-database/resolve`; typed
44
+ * loosely here to keep `host.ts` free of provision-internal imports.
45
+ */
46
+ export interface HostProvisionOverlay {
47
+ preset?: string;
48
+ add?: (string | [string, Record<string, unknown>])[];
49
+ remove?: string[];
50
+ }
41
51
  export interface PiToolsHost {
42
52
  /** Signed-in platform account, or null/undefined when signed out. */
43
53
  account(): HostAccount | null | undefined;
@@ -49,6 +59,13 @@ export interface PiToolsHost {
49
59
  previewToken?(): Promise<PreviewToken | null>;
50
60
  /** Treat tokens expiring within this window as already expired. Default 30s. */
51
61
  dataTokenSkewMs?: number;
62
+ /**
63
+ * Optional provision overlay: pick a base preset and/or layer module
64
+ * add/remove on top of it. The base module list always comes from the pinned
65
+ * `node-type-registry` preset — this only customizes it. Distributed as data
66
+ * (e.g. materialized from appstash / a pinned git ref), never as code.
67
+ */
68
+ provisionOverlay?(): HostProvisionOverlay | null | undefined | Promise<HostProvisionOverlay | null | undefined>;
52
69
  }
53
70
  export declare const DEFAULT_DATA_TOKEN_SKEW_MS = 30000;
54
71
  export declare function configureHost(host: PiToolsHost): void;
package/index.d.ts CHANGED
@@ -5,7 +5,10 @@ export declare const dbTools: ExtensionFactory;
5
5
  export declare function createDbTools(host: PiToolsHost): ExtensionFactory;
6
6
  export { type ConfirmGate, type ConfirmGateDeps, createConfirmGate } from './confirm-gate';
7
7
  export { type ModulesClient, type ProjectContext, resolveDataToken, resolveProjectContext, } from './context';
8
- export { type ActiveDataToken, configureHost, type DataAuthBroker, getHost, type HostAccount, type HostBackendConfig, type PiToolsHost, type PreviewToken, } from './host';
8
+ export { type ActiveDataToken, configureHost, type DataAuthBroker, getHost, type HostAccount, type HostBackendConfig, type HostProvisionOverlay, type PiToolsHost, type PreviewToken, } from './host';
9
+ export { loadProvisionManifest, parseProvisionManifest, PROVISION_MANIFEST_FILE, type ProvisionManifest, } from './provision-database/manifest';
10
+ export { allModulePresets, DEFAULT_PROVISION_PRESET, getModulePreset, type ModulePreset, type ProvisionModule, } from './provision-database/presets';
11
+ export { moduleKey, type ProvisionOverlay, resolveProvisionModules, } from './provision-database/resolve';
9
12
  export { toolSchema } from './tool-schema';
10
13
  export { createTemplatePreviewTables } from './tools/templates';
11
14
  export default dbTools;
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createTemplatePreviewTables = exports.toolSchema = exports.getHost = exports.configureHost = exports.resolveProjectContext = exports.resolveDataToken = exports.createConfirmGate = exports.dbTools = void 0;
3
+ exports.createTemplatePreviewTables = exports.toolSchema = exports.resolveProvisionModules = exports.moduleKey = exports.getModulePreset = exports.DEFAULT_PROVISION_PRESET = exports.allModulePresets = exports.PROVISION_MANIFEST_FILE = exports.parseProvisionManifest = exports.loadProvisionManifest = exports.getHost = exports.configureHost = exports.resolveProjectContext = exports.resolveDataToken = exports.createConfirmGate = exports.dbTools = void 0;
4
4
  exports.createDbTools = createDbTools;
5
5
  const confirm_gate_1 = require("./confirm-gate");
6
6
  const context_1 = require("./context");
@@ -53,6 +53,17 @@ Object.defineProperty(exports, "resolveProjectContext", { enumerable: true, get:
53
53
  var host_2 = require("./host");
54
54
  Object.defineProperty(exports, "configureHost", { enumerable: true, get: function () { return host_2.configureHost; } });
55
55
  Object.defineProperty(exports, "getHost", { enumerable: true, get: function () { return host_2.getHost; } });
56
+ var manifest_1 = require("./provision-database/manifest");
57
+ Object.defineProperty(exports, "loadProvisionManifest", { enumerable: true, get: function () { return manifest_1.loadProvisionManifest; } });
58
+ Object.defineProperty(exports, "parseProvisionManifest", { enumerable: true, get: function () { return manifest_1.parseProvisionManifest; } });
59
+ Object.defineProperty(exports, "PROVISION_MANIFEST_FILE", { enumerable: true, get: function () { return manifest_1.PROVISION_MANIFEST_FILE; } });
60
+ var presets_1 = require("./provision-database/presets");
61
+ Object.defineProperty(exports, "allModulePresets", { enumerable: true, get: function () { return presets_1.allModulePresets; } });
62
+ Object.defineProperty(exports, "DEFAULT_PROVISION_PRESET", { enumerable: true, get: function () { return presets_1.DEFAULT_PROVISION_PRESET; } });
63
+ Object.defineProperty(exports, "getModulePreset", { enumerable: true, get: function () { return presets_1.getModulePreset; } });
64
+ var resolve_1 = require("./provision-database/resolve");
65
+ Object.defineProperty(exports, "moduleKey", { enumerable: true, get: function () { return resolve_1.moduleKey; } });
66
+ Object.defineProperty(exports, "resolveProvisionModules", { enumerable: true, get: function () { return resolve_1.resolveProvisionModules; } });
56
67
  var tool_schema_1 = require("./tool-schema");
57
68
  Object.defineProperty(exports, "toolSchema", { enumerable: true, get: function () { return tool_schema_1.toolSchema; } });
58
69
  var templates_2 = require("./tools/templates");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentic-kit/pi",
3
- "version": "0.2.1",
3
+ "version": "0.5.0",
4
4
  "author": "Dan Lynch <pyramation@gmail.com>",
5
5
  "description": "Pi adapter for agentic-kit — the Constructive typed db tools (provision, blueprint, codegen, records, policies, templates) and confirm gate as a pi coding-agent extension",
6
6
  "main": "index.js",
@@ -29,9 +29,10 @@
29
29
  "test:watch": "jest --watch"
30
30
  },
31
31
  "dependencies": {
32
- "@agentic-kit/harness": "^0.3.1",
33
- "@constructive-io/graphql-query": "^4.5.5",
34
- "@constructive-io/sdk": "^1.5.5",
32
+ "@agentic-kit/harness": "^0.5.0",
33
+ "@constructive-io/graphql-query": "^4.8.0",
34
+ "@constructive-io/sdk": "^1.7.0",
35
+ "node-type-registry": "^1.7.0",
35
36
  "zod": "^4.4.3"
36
37
  },
37
38
  "peerDependencies": {
@@ -47,5 +48,5 @@
47
48
  "coding-agent",
48
49
  "constructive"
49
50
  ],
50
- "gitHead": "6f85eefed51d88b3a857ff36d20da44b7ebb47c9"
51
+ "gitHead": "3ba26a9dfa342feff5a2216aafea962132d51621"
51
52
  }
@@ -1,4 +1,4 @@
1
- import type { ProvisionModule } from './modules';
1
+ import type { ProvisionModule } from './presets';
2
2
  export declare function createDatabaseProvision(args: {
3
3
  endpoint: string;
4
4
  bearer: string;
@@ -0,0 +1,10 @@
1
+ import type { ProvisionOverlay } from './resolve';
2
+ export declare const PROVISION_MANIFEST_FILE = "provision.json";
3
+ export type ProvisionManifest = ProvisionOverlay;
4
+ /** Validate an untrusted value as a provision manifest, throwing on bad shape. */
5
+ export declare function parseProvisionManifest(value: unknown): ProvisionManifest;
6
+ /**
7
+ * Load `<dir>/provision.json` as an overlay, or `null` when absent. A malformed
8
+ * file throws so a broken overlay is surfaced rather than silently ignored.
9
+ */
10
+ export declare function loadProvisionManifest(dir: string): Promise<ProvisionManifest | null>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PROVISION_MANIFEST_FILE = void 0;
7
+ exports.parseProvisionManifest = parseProvisionManifest;
8
+ exports.loadProvisionManifest = loadProvisionManifest;
9
+ // `provision.json` overlay manifest.
10
+ //
11
+ // A pure-data overlay, distributed the same way skills are: written into a
12
+ // config/app directory (locally, or materialized there from appstash / a pinned
13
+ // git ref) and read at provision time. It NEVER installs or executes a package
14
+ // — the base preset is a pinned build-time dependency; this file only selects a
15
+ // preset and layers add/remove on top.
16
+ const promises_1 = require("node:fs/promises");
17
+ const node_path_1 = __importDefault(require("node:path"));
18
+ const zod_1 = require("zod");
19
+ exports.PROVISION_MANIFEST_FILE = 'provision.json';
20
+ const ModuleSchema = zod_1.z.union([
21
+ zod_1.z.string(),
22
+ zod_1.z.tuple([zod_1.z.string(), zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())]),
23
+ ]);
24
+ const ProvisionManifestSchema = zod_1.z
25
+ .object({
26
+ preset: zod_1.z.string().optional(),
27
+ add: zod_1.z.array(ModuleSchema).optional(),
28
+ remove: zod_1.z.array(zod_1.z.string()).optional(),
29
+ })
30
+ .strict();
31
+ /** Validate an untrusted value as a provision manifest, throwing on bad shape. */
32
+ function parseProvisionManifest(value) {
33
+ return ProvisionManifestSchema.parse(value);
34
+ }
35
+ /**
36
+ * Load `<dir>/provision.json` as an overlay, or `null` when absent. A malformed
37
+ * file throws so a broken overlay is surfaced rather than silently ignored.
38
+ */
39
+ async function loadProvisionManifest(dir) {
40
+ const file = node_path_1.default.join(dir, exports.PROVISION_MANIFEST_FILE);
41
+ let raw;
42
+ try {
43
+ raw = await (0, promises_1.readFile)(file, 'utf8');
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ let parsed;
49
+ try {
50
+ parsed = JSON.parse(raw);
51
+ }
52
+ catch (err) {
53
+ throw new Error(`Invalid ${exports.PROVISION_MANIFEST_FILE} at ${file}: ${err instanceof Error ? err.message : String(err)}`);
54
+ }
55
+ return parseProvisionManifest(parsed);
56
+ }
@@ -0,0 +1,4 @@
1
+ import { allModulePresets, getModulePreset, type ModulePreset } from 'node-type-registry';
2
+ export type ProvisionModule = string | [string, Record<string, unknown>];
3
+ export declare const DEFAULT_PROVISION_PRESET: string;
4
+ export { allModulePresets, getModulePreset, type ModulePreset };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getModulePreset = exports.allModulePresets = exports.DEFAULT_PROVISION_PRESET = void 0;
4
+ // Provision module presets — the canonical source of truth.
5
+ //
6
+ // The module list is NEVER hand-copied here. It is imported from the pinned
7
+ // `node-type-registry` package (the same presets the backend and dashboard use),
8
+ // so bumping that dependency is how the base module set moves. Hosts customize
9
+ // on top with an ordered overlay (see `resolve.ts`) rather than editing a list.
10
+ const node_type_registry_1 = require("node-type-registry");
11
+ Object.defineProperty(exports, "allModulePresets", { enumerable: true, get: function () { return node_type_registry_1.allModulePresets; } });
12
+ Object.defineProperty(exports, "getModulePreset", { enumerable: true, get: function () { return node_type_registry_1.getModulePreset; } });
13
+ // Default base preset. `b2b:storage` = the full multi-tenant B2B shape plus file
14
+ // storage, matching Constructive's app shape. Crucially it already includes
15
+ // `config_secrets_module` — the dependency the old hand-copied list dropped,
16
+ // which made the backend reject provisioning with `REQUIRES (config_secrets_module:app)`.
17
+ exports.DEFAULT_PROVISION_PRESET = node_type_registry_1.PresetB2bStorage.name;
@@ -0,0 +1,27 @@
1
+ import { type ProvisionModule } from './presets';
2
+ export interface ProvisionOverlay {
3
+ /**
4
+ * Base preset name to resolve from (e.g. 'b2b:storage', 'full'). The last
5
+ * layer that sets `preset` wins; if none set it, `DEFAULT_PROVISION_PRESET`
6
+ * is used.
7
+ */
8
+ preset?: string;
9
+ /**
10
+ * Modules to add. An entry whose key matches an existing module OVERRIDES it
11
+ * (e.g. re-declaring `storage_module` with app-specific options); a new key
12
+ * is appended.
13
+ */
14
+ add?: ProvisionModule[];
15
+ /**
16
+ * Module names or `name:scope` keys to drop from the base. A bare name
17
+ * removes every scope of that module; `name:scope` removes just that one.
18
+ */
19
+ remove?: string[];
20
+ }
21
+ /** Stable identity for a module entry: `name` or `name:scope` when scoped. */
22
+ export declare function moduleKey(m: ProvisionModule): string;
23
+ /**
24
+ * Resolve the effective module list from a pinned preset plus ordered overlays.
25
+ * Pure: no I/O, no host access. Later overlays win (last-write-wins by key).
26
+ */
27
+ export declare function resolveProvisionModules(layers?: ProvisionOverlay[]): ProvisionModule[];
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.moduleKey = moduleKey;
4
+ exports.resolveProvisionModules = resolveProvisionModules;
5
+ // Ordered-overlay resolution for provision modules.
6
+ //
7
+ // Mirrors the skills overlay model (`@agentic-kit/harness` skills manifest): a
8
+ // pinned base (a named preset from `node-type-registry`) is layered under zero
9
+ // or more overlays, later layers winning by module key. An empty overlay list
10
+ // resolves to the base preset unchanged.
11
+ const presets_1 = require("./presets");
12
+ function moduleName(m) {
13
+ return Array.isArray(m) ? m[0] : m;
14
+ }
15
+ function moduleScope(m) {
16
+ if (!Array.isArray(m))
17
+ return undefined;
18
+ const scope = m[1]?.scope;
19
+ return typeof scope === 'string' ? scope : undefined;
20
+ }
21
+ /** Stable identity for a module entry: `name` or `name:scope` when scoped. */
22
+ function moduleKey(m) {
23
+ const scope = moduleScope(m);
24
+ return scope ? `${moduleName(m)}:${scope}` : moduleName(m);
25
+ }
26
+ /**
27
+ * Resolve the effective module list from a pinned preset plus ordered overlays.
28
+ * Pure: no I/O, no host access. Later overlays win (last-write-wins by key).
29
+ */
30
+ function resolveProvisionModules(layers = []) {
31
+ const presetName = [...layers].reverse().find((l) => l.preset)?.preset ?? presets_1.DEFAULT_PROVISION_PRESET;
32
+ const preset = (0, presets_1.getModulePreset)(presetName);
33
+ if (!preset) {
34
+ const known = presets_1.allModulePresets.map((p) => p.name).join(', ');
35
+ throw new Error(`Unknown provision preset "${presetName}". Known presets: ${known}.`);
36
+ }
37
+ // Ordered map keyed by module identity preserves position while allowing
38
+ // overrides (same key) and appends (new key).
39
+ const resolved = new Map();
40
+ for (const m of preset.modules)
41
+ resolved.set(moduleKey(m), m);
42
+ for (const layer of layers) {
43
+ for (const target of layer.remove ?? []) {
44
+ for (const key of [...resolved.keys()]) {
45
+ if (key === target || key.split(':')[0] === target)
46
+ resolved.delete(key);
47
+ }
48
+ }
49
+ for (const m of layer.add ?? [])
50
+ resolved.set(moduleKey(m), m);
51
+ }
52
+ return [...resolved.values()];
53
+ }
@@ -15,8 +15,9 @@ const db_probe_1 = require("../db-probe");
15
15
  const create_database_provision_1 = require("../provision-database/create-database-provision");
16
16
  const credential_1 = require("../provision-database/credential");
17
17
  const env_file_1 = require("../provision-database/env-file");
18
- const modules_1 = require("../provision-database/modules");
18
+ const manifest_1 = require("../provision-database/manifest");
19
19
  const pg_fixups_1 = require("../provision-database/pg-fixups");
20
+ const resolve_1 = require("../provision-database/resolve");
20
21
  const DEFAULT_API_ENDPOINT = 'http://api.localhost:3000/graphql';
21
22
  const DEFAULT_MODULES_ENDPOINT = 'http://modules.localhost:3000/graphql';
22
23
  // The per-DB endpoints are subdomain.<domain>; the provisioning domain is the api
@@ -192,6 +193,24 @@ exports.provisionDatabaseTool = {
192
193
  ok: false,
193
194
  out: err instanceof Error ? err.message : String(err),
194
195
  }));
196
+ // Resolve the module set: pinned base preset from node-type-registry, then
197
+ // ordered overlays (host default < project-local provision.json). No list is
198
+ // hand-copied here; overlays are pure data. A bad preset/manifest fails the
199
+ // run rather than provisioning a wrong module set.
200
+ let modules;
201
+ try {
202
+ const layers = [];
203
+ const hostOverlay = await host.provisionOverlay?.();
204
+ if (hostOverlay)
205
+ layers.push(hostOverlay);
206
+ const projectOverlay = await (0, manifest_1.loadProvisionManifest)(ctx.cwd);
207
+ if (projectOverlay)
208
+ layers.push(projectOverlay);
209
+ modules = (0, resolve_1.resolveProvisionModules)(layers);
210
+ }
211
+ catch (err) {
212
+ return fail(`Cannot resolve provision modules: ${err instanceof Error ? err.message : String(err)}`);
213
+ }
195
214
  const physicalDb = process.env.CONSTRUCTIVE_DB || 'constructive';
196
215
  // Provision on the MODULES endpoint: createDatabaseProvisionModule creates
197
216
  // the database owned by ownerId, provisions the module set, and bootstraps
@@ -206,7 +225,7 @@ exports.provisionDatabaseTool = {
206
225
  databaseName,
207
226
  domain: provisionDomain(apiEndpoint),
208
227
  ownerId,
209
- modules: modules_1.DEFAULT_PROVISION_MODULES,
228
+ modules,
210
229
  })));
211
230
  }
212
231
  catch (err) {
@@ -1,2 +0,0 @@
1
- export type ProvisionModule = string | [string, Record<string, unknown>];
2
- export declare const DEFAULT_PROVISION_MODULES: ProvisionModule[];
@@ -1,40 +0,0 @@
1
- export const DEFAULT_PROVISION_MODULES = [
2
- 'users_module',
3
- 'membership_types_module',
4
- ['permissions_module', { scope: 'app' }],
5
- ['permissions_module', { scope: 'org' }],
6
- ['limits_module', { scope: 'app' }],
7
- ['limits_module', { scope: 'org' }],
8
- ['levels_module', { scope: 'app' }],
9
- ['levels_module', { scope: 'org' }],
10
- ['memberships_module', { scope: 'app' }],
11
- ['memberships_module', { scope: 'org' }],
12
- 'sessions_module',
13
- 'user_state_module',
14
- 'user_credentials_module',
15
- ['internal_secrets_module', { scope: 'app' }],
16
- 'emails_module',
17
- 'rls_module',
18
- 'user_auth_module',
19
- 'session_secrets_module',
20
- 'rate_limits_module',
21
- 'connected_accounts_module',
22
- ['identity_providers_module', { scope: 'app' }],
23
- 'webauthn_credentials_module',
24
- 'webauthn_auth_module',
25
- 'phone_numbers_module',
26
- ['profiles_module', { scope: 'app' }],
27
- ['profiles_module', { scope: 'org' }],
28
- ['hierarchy_module', { scope: 'org' }],
29
- ['invites_module', { scope: 'app' }],
30
- ['invites_module', { scope: 'org' }],
31
- // Mirrors constructive-client: api_name links storage to the same api-<slug>
32
- // endpoint the generated SDK targets (without it uploads fail with "not
33
- // enabled for this endpoint"), prefix keeps app_buckets/app_files names off
34
- // user tables, and buckets seeds the public bucket row uploads need.
35
- [
36
- 'storage_module',
37
- { scope: 'app', api_name: 'api', prefix: 'app', buckets: [{ key: 'public', is_public: true }] },
38
- ],
39
- 'devices_module',
40
- ];
@@ -1,2 +0,0 @@
1
- export type ProvisionModule = string | [string, Record<string, unknown>];
2
- export declare const DEFAULT_PROVISION_MODULES: ProvisionModule[];
@@ -1,43 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_PROVISION_MODULES = void 0;
4
- exports.DEFAULT_PROVISION_MODULES = [
5
- 'users_module',
6
- 'membership_types_module',
7
- ['permissions_module', { scope: 'app' }],
8
- ['permissions_module', { scope: 'org' }],
9
- ['limits_module', { scope: 'app' }],
10
- ['limits_module', { scope: 'org' }],
11
- ['levels_module', { scope: 'app' }],
12
- ['levels_module', { scope: 'org' }],
13
- ['memberships_module', { scope: 'app' }],
14
- ['memberships_module', { scope: 'org' }],
15
- 'sessions_module',
16
- 'user_state_module',
17
- 'user_credentials_module',
18
- ['internal_secrets_module', { scope: 'app' }],
19
- 'emails_module',
20
- 'rls_module',
21
- 'user_auth_module',
22
- 'session_secrets_module',
23
- 'rate_limits_module',
24
- 'connected_accounts_module',
25
- ['identity_providers_module', { scope: 'app' }],
26
- 'webauthn_credentials_module',
27
- 'webauthn_auth_module',
28
- 'phone_numbers_module',
29
- ['profiles_module', { scope: 'app' }],
30
- ['profiles_module', { scope: 'org' }],
31
- ['hierarchy_module', { scope: 'org' }],
32
- ['invites_module', { scope: 'app' }],
33
- ['invites_module', { scope: 'org' }],
34
- // Mirrors constructive-client: api_name links storage to the same api-<slug>
35
- // endpoint the generated SDK targets (without it uploads fail with "not
36
- // enabled for this endpoint"), prefix keeps app_buckets/app_files names off
37
- // user tables, and buckets seeds the public bucket row uploads need.
38
- [
39
- 'storage_module',
40
- { scope: 'app', api_name: 'api', prefix: 'app', buckets: [{ key: 'public', is_public: true }] },
41
- ],
42
- 'devices_module',
43
- ];