@ingestron/core 0.12.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.
Files changed (84) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +56 -0
  4. package/SECURITY.md +44 -0
  5. package/THIRD_PARTY_NOTICES.md +693 -0
  6. package/dist/assets/schemas/ODCS-LICENSE +201 -0
  7. package/dist/assets/schemas/odcs-3.1.0.json +2929 -0
  8. package/dist/assets/schemas/provenance.json +16 -0
  9. package/dist/compiler-fingerprint.json +84 -0
  10. package/dist/core/authoring.d.ts +55 -0
  11. package/dist/core/authoring.js +315 -0
  12. package/dist/core/config.d.ts +34 -0
  13. package/dist/core/config.js +233 -0
  14. package/dist/core/connections.d.ts +19 -0
  15. package/dist/core/connections.js +203 -0
  16. package/dist/core/connector-package.d.ts +37 -0
  17. package/dist/core/connector-package.js +43 -0
  18. package/dist/core/contracts.d.ts +8 -0
  19. package/dist/core/contracts.js +45 -0
  20. package/dist/core/deliveries.d.ts +16 -0
  21. package/dist/core/deliveries.js +28 -0
  22. package/dist/core/delivery-exports.d.ts +20 -0
  23. package/dist/core/delivery-exports.js +119 -0
  24. package/dist/core/development.d.ts +50 -0
  25. package/dist/core/development.js +94 -0
  26. package/dist/core/ecosystem-authoring.d.ts +13 -0
  27. package/dist/core/ecosystem-authoring.js +82 -0
  28. package/dist/core/errors.d.ts +12 -0
  29. package/dist/core/errors.js +43 -0
  30. package/dist/core/execution.d.ts +28 -0
  31. package/dist/core/execution.js +297 -0
  32. package/dist/core/extension-packs.d.ts +8 -0
  33. package/dist/core/extension-packs.js +34 -0
  34. package/dist/core/fingerprint.d.ts +1 -0
  35. package/dist/core/fingerprint.js +16 -0
  36. package/dist/core/generate.d.ts +16 -0
  37. package/dist/core/generate.js +198 -0
  38. package/dist/core/installation.d.ts +3 -0
  39. package/dist/core/installation.js +14 -0
  40. package/dist/core/installed-plugins.d.ts +41 -0
  41. package/dist/core/installed-plugins.js +92 -0
  42. package/dist/core/model-pack-schema.d.ts +46 -0
  43. package/dist/core/model-pack-schema.js +81 -0
  44. package/dist/core/model-packs.d.ts +4 -0
  45. package/dist/core/model-packs.js +33 -0
  46. package/dist/core/operations.d.ts +346 -0
  47. package/dist/core/operations.js +760 -0
  48. package/dist/core/package-references.d.ts +36 -0
  49. package/dist/core/package-references.js +103 -0
  50. package/dist/core/packages.d.ts +340 -0
  51. package/dist/core/packages.js +579 -0
  52. package/dist/core/planner.d.ts +114 -0
  53. package/dist/core/planner.js +821 -0
  54. package/dist/core/project-build.d.ts +13 -0
  55. package/dist/core/project-build.js +266 -0
  56. package/dist/core/provider-commands.d.ts +83 -0
  57. package/dist/core/provider-commands.js +252 -0
  58. package/dist/core/provider-starter.d.ts +5 -0
  59. package/dist/core/provider-starter.js +155 -0
  60. package/dist/core/report-pack-schema.d.ts +13 -0
  61. package/dist/core/report-pack-schema.js +15 -0
  62. package/dist/core/schema.d.ts +257 -0
  63. package/dist/core/schema.js +126 -0
  64. package/dist/core/workflows.d.ts +91 -0
  65. package/dist/core/workflows.js +438 -0
  66. package/dist/plugins/authoring.d.ts +88 -0
  67. package/dist/plugins/authoring.js +34 -0
  68. package/dist/plugins/compatibility.d.ts +23 -0
  69. package/dist/plugins/compatibility.js +55 -0
  70. package/dist/plugins/generation.d.ts +5 -0
  71. package/dist/plugins/generation.js +105 -0
  72. package/dist/plugins/provider-renderer.d.ts +7 -0
  73. package/dist/plugins/provider-renderer.js +37 -0
  74. package/dist/plugins/provider-worker.d.ts +1 -0
  75. package/dist/plugins/provider-worker.js +74 -0
  76. package/dist/sdk/adapter.d.ts +15 -0
  77. package/dist/sdk/adapter.js +15 -0
  78. package/dist/sdk/index.d.ts +4 -0
  79. package/dist/sdk/index.js +3 -0
  80. package/dist/sdk/schemas.d.ts +34 -0
  81. package/dist/sdk/schemas.js +74 -0
  82. package/dist/version.d.ts +5 -0
  83. package/dist/version.js +7 -0
  84. package/package.json +72 -0
@@ -0,0 +1,13 @@
1
+ export declare function buildProject(root: string, environment: string, args: any): {
2
+ files: number;
3
+ digest: string;
4
+ directory: any;
5
+ nodes: number;
6
+ packages: any[];
7
+ scope: {
8
+ projectBuild: boolean;
9
+ flow?: any;
10
+ provider?: any;
11
+ };
12
+ evidence: string;
13
+ };
@@ -0,0 +1,266 @@
1
+ /** One build path: native compilation and connector preparation feed provider assembly. */
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, relative, resolve } from "node:path";
4
+ import { validateProviderFiles } from "../plugins/generation.js";
5
+ import { inspectProject, planProject } from "./planner.js";
6
+ import { canonical, check, digest, isMap } from "./errors.js";
7
+ import { canonicalPackageReference, fence, packageYaml, providerPackageSchema, resolvePackage, } from "./packages.js";
8
+ import { checkProviderCompatibility } from "../plugins/compatibility.js";
9
+ import { prepareConnection } from "./connections.js";
10
+ import { runProviderCommand } from "./provider-commands.js";
11
+ import { deliveryExports } from "./delivery-exports.js";
12
+ import { renderProject, validateGeneratedFiles, writeOutput, } from "./generate.js";
13
+ import { compilerFingerprint } from "./fingerprint.js";
14
+ import { version } from "../version.js";
15
+ export function buildProject(root, environment, args) {
16
+ const { project, flows, reader, profile } = inspectProject(root, environment);
17
+ check(!(args.provider && args.flow), "SELECT", "Choose provider or flow, not both");
18
+ check(!args.table && !args.step, "SELECT", "Project packages contain complete flows; select flow, not individual tables or steps");
19
+ if (args.provider)
20
+ check(project.providers.configurations[args.provider], "PROVIDER", `Unknown provider configuration ${args.provider}`);
21
+ const owner = (f) => f.provider ?? project.defaults.provider ?? "";
22
+ const wanted = new Set();
23
+ const active = new Set();
24
+ const include = (id) => {
25
+ check(!active.has(id), "CYCLE", `Flow dependency cycle at ${id}`);
26
+ if (wanted.has(id))
27
+ return;
28
+ const flow = flows.find((f) => f.id === id);
29
+ check(flow, "SELECT", `Unknown flow ${id}`);
30
+ active.add(id);
31
+ for (const r of Object.values(flow.requires)) {
32
+ const parts = r.dataset.split(".");
33
+ check(parts[0] === project.id && parts.length >= 3, "DATASET", `Unknown project dataset ${r.dataset}`);
34
+ include(parts[1]);
35
+ }
36
+ active.delete(id);
37
+ wanted.add(id);
38
+ };
39
+ flows
40
+ .filter((f) => args.flow
41
+ ? f.id === args.flow
42
+ : args.provider
43
+ ? owner(f) === args.provider
44
+ : true)
45
+ .forEach((f) => include(f.id));
46
+ if (args.flow)
47
+ include(args.flow);
48
+ check(wanted.size, "INPUT", "No buildable flows; add a flow and contract before building");
49
+ // The existing planner owns native standards, resources, handovers and dependency validation.
50
+ const targetNames = new Set(flows.filter((f) => wanted.has(f.id)).map(owner));
51
+ const candidates = new Set(flows
52
+ .filter((f) => !f.ingestion?.connection && targetNames.has(owner(f)))
53
+ .map((f) => f.id));
54
+ const addNativeParents = (id, visiting = new Set()) => {
55
+ check(!visiting.has(id), "CYCLE", `Flow dependency cycle at ${id}`);
56
+ visiting.add(id);
57
+ const f = flows.find((f) => f.id === id);
58
+ for (const r of Object.values(f.requires)) {
59
+ const parent = r.dataset.split(".")[1];
60
+ const producer = flows.find((f) => f.id === parent);
61
+ check(producer && !producer.ingestion?.connection, "DATASET", "Connector-to-native dataset handovers require an explicit supported publication contract");
62
+ candidates.add(parent);
63
+ addNativeParents(parent, new Set(visiting));
64
+ }
65
+ };
66
+ [...candidates].forEach((id) => addNativeParents(id));
67
+ const nativePlan = planProject(root, environment, {
68
+ nativeOnly: true,
69
+ flowIds: [...candidates],
70
+ delivery: true,
71
+ projectBuild: true,
72
+ });
73
+ // A partial build must include every flow sharing an owned native resource.
74
+ let changed = true;
75
+ while (changed) {
76
+ const before = wanted.size;
77
+ const claims = new Set(nativePlan.nodes
78
+ .filter((n) => wanted.has(n.flow))
79
+ .flatMap((n) => (n.resources ?? []).map((r) => canonical([n.platform, r.scope, r.kind, r.name]))));
80
+ for (const n of nativePlan.nodes)
81
+ if ((n.resources ?? []).some((r) => claims.has(canonical([n.platform, r.scope, r.kind, r.name]))))
82
+ include(n.flow);
83
+ for (const f of flows.filter((f) => wanted.has(f.id) && f.export))
84
+ flows
85
+ .filter((p) => p.export === f.export && owner(p) === owner(f))
86
+ .forEach((p) => include(p.id));
87
+ changed = before !== wanted.size;
88
+ }
89
+ const nativeBody = {
90
+ ...nativePlan,
91
+ nodes: nativePlan.nodes.filter((n) => wanted.has(n.flow)),
92
+ flows: nativePlan.flows.filter((f) => wanted.has(f.id)),
93
+ };
94
+ const { digest: _old, ...body } = nativeBody;
95
+ const selectedNative = { ...body, digest: digest(canonical(body)) };
96
+ const nativeGroups = deliveryExports(selectedNative);
97
+ const configurations = [
98
+ ...new Set(flows.filter((f) => wanted.has(f.id)).map(owner)),
99
+ ].sort();
100
+ check(!(args.flow ||
101
+ configurations.some((configuration) => flows.filter((f) => owner(f) === configuration && wanted.has(f.id))
102
+ .length !== flows.filter((f) => owner(f) === configuration).length)) || !nativeGroups.some((g) => g.imports.length), "HANDOVER", "Partial cross-target native handovers require path rebinding; build the complete project instead");
103
+ const deploymentRoots = new Set();
104
+ const files = {};
105
+ const packages = [];
106
+ const add = (path, content) => {
107
+ check(!Object.hasOwn(files, path), "OWNER", `Duplicate assembled file ${path}`);
108
+ check(typeof content === "string" &&
109
+ !path.startsWith("/") &&
110
+ !path.includes("\\") &&
111
+ path.split("/").every((p) => p && p !== "." && p !== ".."), "PATH", "Unsafe assembled file path");
112
+ check(path !== "ingestron-manifest.json" && path !== "ingestron-project.json", "OWNER", "Provider cannot replace host build manifests");
113
+ files[path] = content;
114
+ };
115
+ for (const configuration of configurations) {
116
+ const selected = flows.filter((f) => wanted.has(f.id) && owner(f) === configuration);
117
+ const config = project.providers.configurations[configuration];
118
+ check(config, "PROVIDER", `Unknown provider configuration ${configuration}`);
119
+ const pkg = project.providers.packages[config.package];
120
+ check(pkg, "PACKAGE", `Missing execution package ${config.package}`);
121
+ const reference = canonicalPackageReference(`${pkg.source}@${pkg.version}`);
122
+ const locked = pkg.source.startsWith(".")
123
+ ? { file: reader.path(pkg.source), root, entry: undefined }
124
+ : resolvePackage(root, reference);
125
+ const manifest = providerPackageSchema.parse(packageYaml(locked.file));
126
+ checkProviderCompatibility(manifest);
127
+ const partial = !!args.flow ||
128
+ selected.length !==
129
+ flows.filter((f) => owner(f) === configuration).length;
130
+ const scope = {
131
+ partial,
132
+ id: partial
133
+ ? "subset_" +
134
+ digest(canonical(selected.map((f) => f.id).sort())).slice(0, 12)
135
+ : "full",
136
+ flows: selected.map((f) => f.id).sort(),
137
+ };
138
+ const identity = { project: project.id, environment, configuration, scope };
139
+ const native = nativeGroups.find((g) => g.id === configuration);
140
+ const nativeFiles = native ? renderProject(root, native.plan) : {};
141
+ const connections = selected
142
+ .filter((f) => f.ingestion?.connection)
143
+ .map((f) => {
144
+ check(manifest.projectAssembly, "CAPABILITY", `${configuration}: install a provider with project assembly support`);
145
+ const prepared = prepareConnection(root, environment, f.id, false, undefined, (execution) => {
146
+ const configured = runProviderCommand(root, environment, {
147
+ configuration,
148
+ command: manifest.projectAssembly.command,
149
+ input: {
150
+ phase: "configure",
151
+ ...identity,
152
+ flow: f.id,
153
+ execution,
154
+ binding: profile.bindings[config.binding],
155
+ options: config.options,
156
+ },
157
+ });
158
+ check(isMap(configured.result) && isMap(configured.result.execution), "PROVIDER", "Invalid project execution configuration");
159
+ return configured.result.execution;
160
+ });
161
+ return {
162
+ flow: f.id,
163
+ artifacts: prepared.result.artifacts,
164
+ inputDigest: prepared.inputDigest,
165
+ };
166
+ });
167
+ let artifacts = nativeFiles;
168
+ let details = {
169
+ resources: native?.resources ?? [],
170
+ execution: "not-run",
171
+ };
172
+ if (manifest.projectAssembly) {
173
+ const assembled = runProviderCommand(root, environment, {
174
+ configuration,
175
+ command: manifest.projectAssembly.command,
176
+ input: {
177
+ phase: "assemble",
178
+ ...identity,
179
+ binding: profile.bindings[config.binding],
180
+ options: config.options,
181
+ nativeFiles,
182
+ connections,
183
+ dependencies: native?.imports ?? [],
184
+ },
185
+ });
186
+ check(assembled.result?.apiVersion === "ingestron.project-package/v1" &&
187
+ isMap(assembled.result.artifacts), "PROVIDER", "Invalid assembled project package");
188
+ artifacts = assembled.result.artifacts;
189
+ details = assembled.result.details;
190
+ }
191
+ else
192
+ check(!partial, "CAPABILITY", `${configuration}: partial project builds require project assembly support`);
193
+ if (manifest.lifecycle) {
194
+ const asset = (path) => reader.text(fence(locked.root, relative(locked.root, resolve(dirname(locked.file), path))));
195
+ validateProviderFiles(asset(manifest.lifecycle.module), Object.fromEntries(Object.entries(manifest.outputSchemas ?? {}).map(([key, path]) => [
196
+ key,
197
+ asset(path),
198
+ ])), artifacts);
199
+ }
200
+ if (details?.workspaceRoot) {
201
+ const rootIdentity = canonical([
202
+ manifest.platform,
203
+ profile.bindings[config.binding]?.host,
204
+ details.workspaceRoot,
205
+ ]);
206
+ check(!deploymentRoots.has(rootIdentity), "OWNER", "Provider configurations cannot share a deployment root");
207
+ deploymentRoots.add(rootIdentity);
208
+ }
209
+ const directory = configurations.length === 1 ? "" : `providers/${configuration}/`;
210
+ for (const [name, content] of Object.entries(artifacts))
211
+ add(directory + name, content);
212
+ packages.push({
213
+ configuration,
214
+ platform: manifest.platform,
215
+ directory: directory || ".",
216
+ reference,
217
+ commit: locked.entry?.commit,
218
+ scope,
219
+ details,
220
+ needs: native?.needs ?? [],
221
+ imports: native?.imports ?? [],
222
+ });
223
+ }
224
+ check(Object.keys(files).length <= 10000 &&
225
+ Buffer.byteLength(JSON.stringify(files)) <= 10 * 1024 * 1024, "LIMIT", "Project output exceeds 10000 files or 10 MiB");
226
+ const selection = {
227
+ projectBuild: true,
228
+ ...(args.flow ? { flow: args.flow } : {}),
229
+ ...(args.provider ? { provider: args.provider } : {}),
230
+ };
231
+ const metadata = {
232
+ apiVersion: "ingestron.project-build/v1",
233
+ project: project.id,
234
+ environment,
235
+ compilerVersion: version,
236
+ compilerDigest: compilerFingerprint(),
237
+ selection,
238
+ complete: !args.flow && !args.provider,
239
+ requestedFlows: args.flow ? [args.flow] : undefined,
240
+ includedFlows: [...wanted].sort(),
241
+ packages,
242
+ deployment: { executed: false, omissionMeansDeletion: false },
243
+ files: Object.fromEntries(Object.entries(files).map(([n, t]) => [n, digest(t)])),
244
+ sourceFiles: { ...reader.files, ...nativePlan.files },
245
+ packageLockSha256: existsSync(fence(root, "packages.lock.yaml"))
246
+ ? digest(readFileSync(fence(root, "packages.lock.yaml")))
247
+ : undefined,
248
+ };
249
+ files["ingestron-project.json"] = JSON.stringify(metadata, null, 2) + "\n";
250
+ validateGeneratedFiles(files);
251
+ const output = writeOutput(fence(root, args.out), {
252
+ project: project.id,
253
+ environment,
254
+ selection,
255
+ digest: digest(canonical(metadata)),
256
+ }, files, args.ownership);
257
+ return {
258
+ ...output,
259
+ directory: args.out,
260
+ nodes: selectedNative.nodes.length +
261
+ flows.filter((f) => wanted.has(f.id) && f.ingestion?.connection).length,
262
+ packages: packages.map((p) => p.configuration),
263
+ scope: selection,
264
+ evidence: "Project assets generated locally; no deployment or source execution",
265
+ };
266
+ }
@@ -0,0 +1,83 @@
1
+ export declare function validateSchemaShape(schema: unknown): void;
2
+ export declare function providerCommands(root: string, environment: string, configuration: string): {
3
+ apiVersion: string;
4
+ configuration: string;
5
+ environment: string;
6
+ provider: string;
7
+ version: string;
8
+ reference: string;
9
+ commit: string;
10
+ execution: "offline-json";
11
+ commands: {
12
+ name: string;
13
+ description: string;
14
+ reportPack?: boolean | undefined;
15
+ examples?: string[] | undefined;
16
+ resultSchema?: Record<string, unknown> | undefined;
17
+ inputSchema: Record<string, unknown>;
18
+ }[];
19
+ };
20
+ export declare function runProviderCommand(root: string, environment: string, args: {
21
+ configuration: string;
22
+ command: string;
23
+ input: Record<string, unknown>;
24
+ }): {
25
+ project: string;
26
+ environment: string;
27
+ configuration: string;
28
+ provider: string;
29
+ version: string;
30
+ commit: string;
31
+ apiVersion: string;
32
+ command: string;
33
+ execution: string;
34
+ inputDigest: string;
35
+ reportLock?: Record<string, unknown> | undefined;
36
+ result: import("../plugins/provider-renderer.js").ProviderAssets;
37
+ };
38
+ /** Core names are reserved even when a compatibility command is hidden. */
39
+ export declare const reservedCommandNamespaces: Set<string>;
40
+ /** Data-only discovery; resolvePackage verifies locked bytes but never runs the guest. */
41
+ export declare function pluginCommands(root: string, environment: string): {
42
+ apiVersion: string;
43
+ bindings: {
44
+ apiVersion: string;
45
+ configuration: string;
46
+ environment: string;
47
+ provider: string;
48
+ version: string;
49
+ reference: string;
50
+ commit: string;
51
+ execution: "offline-json";
52
+ commands: {
53
+ name: string;
54
+ description: string;
55
+ reportPack?: boolean | undefined;
56
+ examples?: string[] | undefined;
57
+ resultSchema?: Record<string, unknown> | undefined;
58
+ inputSchema: Record<string, unknown>;
59
+ }[];
60
+ namespace: string;
61
+ effects: "offline-json";
62
+ availability: "implemented";
63
+ }[];
64
+ };
65
+ export declare function runPluginCommand(root: string, environment: string, args: {
66
+ namespace: string;
67
+ target?: string;
68
+ command: string;
69
+ input: Record<string, unknown>;
70
+ }): {
71
+ project: string;
72
+ environment: string;
73
+ configuration: string;
74
+ provider: string;
75
+ version: string;
76
+ commit: string;
77
+ apiVersion: string;
78
+ command: string;
79
+ execution: string;
80
+ inputDigest: string;
81
+ reportLock?: Record<string, unknown> | undefined;
82
+ result: import("../plugins/provider-renderer.js").ProviderAssets;
83
+ };
@@ -0,0 +1,252 @@
1
+ import { reportPackSchema } from "./report-pack-schema.js";
2
+ import { validateModelPack } from "./model-pack-schema.js";
3
+ /** Provider-owned offline operations. No target access, shell, or filesystem guest APIs. */
4
+ import { readFileSync } from "node:fs";
5
+ import { dirname, relative, resolve } from "node:path";
6
+ import { Ajv2020 } from "ajv/dist/2020.js";
7
+ import { Configuration } from "./config.js";
8
+ import { projectSchema } from "./schema.js";
9
+ import { canonical, check, digest } from "./errors.js";
10
+ import { canonicalPackageReference, fence, packageYaml, providerPackageSchema, resolvePackage, } from "./packages.js";
11
+ import { executeProvider } from "../plugins/provider-renderer.js";
12
+ import { checkProviderCompatibility } from "../plugins/compatibility.js";
13
+ // No references, regular expressions or schema code: validation itself must be bounded.
14
+ export function validateSchemaShape(schema) {
15
+ let nodes = 0;
16
+ const allowed = new Set([
17
+ "type",
18
+ "oneOf",
19
+ "properties",
20
+ "items",
21
+ "required",
22
+ "additionalProperties",
23
+ "enum",
24
+ "minItems",
25
+ "maxItems",
26
+ "minLength",
27
+ "maxLength",
28
+ "minimum",
29
+ "maximum",
30
+ ]);
31
+ const walk = (value, depth = 0) => {
32
+ check(++nodes <= 1000 &&
33
+ depth <= 20 &&
34
+ value &&
35
+ typeof value === "object" &&
36
+ !Array.isArray(value), "PACKAGE", "Command schema exceeds supported bounds");
37
+ for (const key of Object.keys(value))
38
+ check(allowed.has(key), "PACKAGE", `Unsupported command schema keyword ${key}`);
39
+ if (value.properties)
40
+ for (const child of Object.values(value.properties))
41
+ walk(child, depth + 1);
42
+ if (value.items)
43
+ walk(value.items, depth + 1);
44
+ if (value.oneOf !== undefined) {
45
+ check(Array.isArray(value.oneOf) &&
46
+ value.oneOf.length >= 2 &&
47
+ value.oneOf.length <= 4, "PACKAGE", "oneOf requires 2–4 bounded alternatives");
48
+ for (const child of value.oneOf)
49
+ walk(child, depth + 1);
50
+ }
51
+ if (value.additionalProperties &&
52
+ typeof value.additionalProperties === "object")
53
+ walk(value.additionalProperties, depth + 1);
54
+ check(value.additionalProperties === undefined ||
55
+ (value.additionalProperties &&
56
+ typeof value.additionalProperties === "object") ||
57
+ typeof value.additionalProperties === "boolean", "PACKAGE", "additionalProperties must be boolean");
58
+ };
59
+ check(JSON.stringify(schema).length <= 64000, "PACKAGE", "Command schema exceeds 64 KB");
60
+ walk(schema);
61
+ }
62
+ function selected(root, environment, configuration) {
63
+ const reader = new Configuration(root);
64
+ const project = reader.parse(projectSchema, reader.load());
65
+ check(project.environments[environment], "ENVIRONMENT", `Unknown environment ${environment}`);
66
+ const configured = project.providers.configurations[configuration];
67
+ check(configured, "PROVIDER", `Unknown provider configuration ${configuration}`);
68
+ const pkg = project.providers.packages[configured.package];
69
+ check(pkg && !pkg.source.startsWith("builtin:") && !pkg.source.startsWith("."), "PACKAGE", "Provider commands require an installed locked provider");
70
+ const reference = canonicalPackageReference(`${pkg.source}@${pkg.version}`);
71
+ const source = resolvePackage(reader.root, reference);
72
+ const manifest = providerPackageSchema.parse(packageYaml(source.file));
73
+ checkProviderCompatibility(manifest);
74
+ check(manifest.commands, "PROVIDER", "This provider version does not declare commands");
75
+ return {
76
+ reader,
77
+ project,
78
+ source,
79
+ manifest,
80
+ reference,
81
+ configuration,
82
+ environment,
83
+ };
84
+ }
85
+ export function providerCommands(root, environment, configuration) {
86
+ const s = selected(root, environment, configuration);
87
+ return {
88
+ apiVersion: "ingestron.provider-command-catalogue/v1",
89
+ configuration,
90
+ environment,
91
+ provider: s.manifest.id,
92
+ version: s.manifest.version,
93
+ reference: s.reference,
94
+ commit: s.source.entry.commit,
95
+ execution: s.manifest.commands.execution,
96
+ commands: s.manifest.commands.definitions,
97
+ };
98
+ }
99
+ export function runProviderCommand(root, environment, args) {
100
+ const s = selected(root, environment, args.configuration), commands = s.manifest.commands;
101
+ const definition = commands.definitions.find((c) => c.name === args.command);
102
+ check(definition, "PROVIDER", `Unknown provider command ${args.command}; inspect provider_commands for ${args.configuration}`);
103
+ check(Buffer.byteLength(JSON.stringify(args.input)) <=
104
+ (s.manifest.projectAssembly?.command === args.command
105
+ ? 7_000_000
106
+ : 2_000_000), "LIMIT", "Command input exceeds its bounded transport limit");
107
+ const ajv = new Ajv2020({
108
+ strict: true,
109
+ allErrors: false,
110
+ validateFormats: false,
111
+ });
112
+ validateSchemaShape(definition.inputSchema);
113
+ const validate = ajv.compile(definition.inputSchema);
114
+ check(validate(args.input), "INPUT", `Invalid provider command input: ${ajv.errorsText(validate.errors)}`);
115
+ const module = fence(s.source.root, relative(s.source.root, resolve(dirname(s.source.file), commands.module)));
116
+ check(Object.hasOwn(s.source.entry.files, relative(s.source.root, module)), "PACKAGE", "Command module is not in the locked inventory");
117
+ let input = args.input;
118
+ let reportLock;
119
+ if (definition.reportPack) {
120
+ check(typeof input.reportPack === "string", "REPORT_PACK", "Select an exact installed report pack");
121
+ const packSource = resolvePackage(s.reader.root, input.reportPack);
122
+ const pack = reportPackSchema.parse(packageYaml(packSource.file));
123
+ check(pack.platform === s.manifest.platform, "REPORT_PACK", "Report pack/platform mismatch");
124
+ const modelSource = resolvePackage(s.reader.root, pack.model);
125
+ const model = validateModelPack(packageYaml(modelSource.file));
126
+ reportLock = {
127
+ reference: input.reportPack,
128
+ commit: packSource.entry.commit,
129
+ model: pack.model,
130
+ modelCommit: modelSource.entry.commit,
131
+ };
132
+ input = { ...input, reportPack: pack, modelPack: model, reportLock };
133
+ }
134
+ const request = {
135
+ apiVersion: "ingestron.provider-command-request/v1",
136
+ command: args.command,
137
+ context: {
138
+ project: s.project.id,
139
+ environment,
140
+ configuration: args.configuration,
141
+ provider: s.manifest.id,
142
+ version: s.manifest.version,
143
+ commit: s.source.entry.commit,
144
+ },
145
+ input,
146
+ };
147
+ const result = executeProvider(readFileSync(module, "utf8"), request, false, "command");
148
+ if (definition.resultSchema) {
149
+ validateSchemaShape(definition.resultSchema);
150
+ const validateResult = new Ajv2020({ strict: true }).compile(definition.resultSchema);
151
+ check(validateResult(result), "PROVIDER_COMMAND", "Provider result does not match its declared schema");
152
+ }
153
+ return {
154
+ apiVersion: "ingestron.provider-command-result/v1",
155
+ ...request.context,
156
+ command: args.command,
157
+ execution: "offline-json",
158
+ inputDigest: digest(canonical(input)),
159
+ ...(reportLock ? { reportLock } : {}),
160
+ result,
161
+ };
162
+ }
163
+ /** Core names are reserved even when a compatibility command is hidden. */
164
+ export const reservedCommandNamespaces = new Set([
165
+ "init",
166
+ "check",
167
+ "plan",
168
+ "build",
169
+ "generate",
170
+ "validate",
171
+ "doctor",
172
+ "schema",
173
+ "source",
174
+ "contract",
175
+ "flow",
176
+ "step",
177
+ "table",
178
+ "dataset",
179
+ "config",
180
+ "environment",
181
+ "environments",
182
+ "plugin",
183
+ "plugins",
184
+ "provider",
185
+ "providers",
186
+ "packages",
187
+ "execution",
188
+ "runtime",
189
+ "run",
190
+ "deploy",
191
+ "advanced",
192
+ "mcp",
193
+ "help",
194
+ "version",
195
+ "connection",
196
+ "connections",
197
+ "activity",
198
+ "activities",
199
+ "standards",
200
+ "resolve",
201
+ "diff",
202
+ "validate-output",
203
+ ]);
204
+ /** Data-only discovery; resolvePackage verifies locked bytes but never runs the guest. */
205
+ export function pluginCommands(root, environment) {
206
+ const reader = new Configuration(root);
207
+ const project = reader.parse(projectSchema, reader.load());
208
+ check(project.environments[environment], "ENVIRONMENT", `Unknown environment ${environment}`);
209
+ const bindings = [];
210
+ const owners = new Map();
211
+ for (const [configuration, binding] of Object.entries(project.providers.configurations)) {
212
+ const pkg = project.providers.packages[binding.package];
213
+ if (!pkg || pkg.source.startsWith("builtin:") || pkg.source.startsWith("."))
214
+ continue;
215
+ const reference = canonicalPackageReference(`${pkg.source}@${pkg.version}`);
216
+ const source = resolvePackage(reader.root, reference);
217
+ const manifest = providerPackageSchema.parse(packageYaml(source.file));
218
+ if (!manifest.commands)
219
+ continue;
220
+ const catalogue = providerCommands(root, environment, configuration);
221
+ const namespace = manifest.commands.namespace ?? manifest.id;
222
+ check(/^[a-z][a-z0-9-]*$/.test(namespace) &&
223
+ !reservedCommandNamespaces.has(namespace), "COMMAND_NAMESPACE", `Invalid or reserved plugin namespace ${namespace}`);
224
+ check(!owners.has(namespace) || owners.get(namespace) === reference, "COMMAND_NAMESPACE", `Multiple packages claim namespace ${namespace}`);
225
+ owners.set(namespace, reference);
226
+ const names = catalogue.commands.map((c) => c.name);
227
+ check(!names.some((name) => names.some((other) => other.startsWith(name + " "))), "COMMAND_NAMESPACE", `Command path cannot also be a command group in ${namespace}`);
228
+ for (const command of catalogue.commands) {
229
+ validateSchemaShape(command.inputSchema);
230
+ if (command.resultSchema)
231
+ validateSchemaShape(command.resultSchema);
232
+ }
233
+ bindings.push({
234
+ ...catalogue,
235
+ namespace,
236
+ effects: "offline-json",
237
+ availability: "implemented",
238
+ });
239
+ }
240
+ return { apiVersion: "ingestron.plugin-commands/v1", bindings };
241
+ }
242
+ export function runPluginCommand(root, environment, args) {
243
+ const bindings = pluginCommands(root, environment).bindings.filter((binding) => binding.namespace === args.namespace &&
244
+ (!args.target || binding.configuration === args.target));
245
+ check(bindings.length > 0, "COMMAND_NAMESPACE", "No installed plugin binding matches namespace and target");
246
+ check(bindings.length === 1, "COMMAND_TARGET", "Multiple configurations match; select target explicitly");
247
+ return runProviderCommand(root, environment, {
248
+ configuration: bindings[0].configuration,
249
+ command: args.command,
250
+ input: args.input,
251
+ });
252
+ }
@@ -0,0 +1,5 @@
1
+ /** A complete offline example, deliberately without a platform execution adapter. */
2
+ export declare function scaffoldProvider(root: string, args: {
3
+ id: string;
4
+ out: string;
5
+ }): import("./authoring.js").Proposal;