@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,760 @@
1
+ import { executionSchemas, performExecution } from "./execution.js";
2
+ import { buildProject } from "./project-build.js";
3
+ import { prepareConnection } from "./connections.js";
4
+ import { reportPackSchema } from "./report-pack-schema.js";
5
+ import { modelPackSchema } from "./model-pack-schema.js";
6
+ import { deliveryIndexSchema } from "./deliveries.js";
7
+ import { existsSync, realpathSync } from "node:fs";
8
+ import { resolve } from "node:path";
9
+ import { z } from "zod";
10
+ import { check, Problem, canonical } from "./errors.js";
11
+ import { Configuration } from "./config.js";
12
+ import { projectSchema, flowSchema, stepSchema, environmentSchema, } from "./schema.js";
13
+ import { inspectProject, planProject } from "./planner.js";
14
+ import { renderProject, writeOutput, validateOutput } from "./generate.js";
15
+ import { contractColumns, validateContract } from "./contracts.js";
16
+ import { packageLock, installedProviders, installedActivities, installPackage, externalActivitySchema, providerPackageSchema, extensionPackSchema, fence, } from "./packages.js";
17
+ import { providerCommands, runProviderCommand, pluginCommands, runPluginCommand, } from "./provider-commands.js";
18
+ import * as development from "./development.js";
19
+ import { scaffoldProvider } from "./provider-starter.js";
20
+ import * as ecosystemAuthor from "./ecosystem-authoring.js";
21
+ import * as author from "./authoring.js";
22
+ import * as workflows from "./workflows.js";
23
+ import { providerVersions, friendlyReference } from "./package-references.js";
24
+ import { browseProviders, pluginDetails } from "./installed-plugins.js";
25
+ export const operationSchemas = {
26
+ ...executionSchemas,
27
+ provider_scaffold: z
28
+ .object({ id: workflows.identifier, out: z.string() })
29
+ .strict(),
30
+ pack_configure: z
31
+ .object({
32
+ reference: z.string(),
33
+ name: workflows.identifier,
34
+ provider: workflows.identifier.optional(),
35
+ })
36
+ .strict(),
37
+ flow_connect: z
38
+ .object({
39
+ id: workflows.identifier,
40
+ input: workflows.identifier,
41
+ dataset: z.string(),
42
+ handover: z.enum(["files", "relation"]),
43
+ table: workflows.identifier.optional(),
44
+ })
45
+ .strict(),
46
+ flow_export: z
47
+ .object({ id: workflows.identifier, group: workflows.identifier })
48
+ .strict(),
49
+ provider_check: z.object({ delivery: z.boolean().default(false) }).strict(),
50
+ plugin_show: z.object({ id: z.string().min(1).max(200) }).strict(),
51
+ plugin_browse: z
52
+ .object({
53
+ engine: z.string().min(1).optional(),
54
+ installable: z.boolean().optional(),
55
+ query: z.string().max(128).optional(),
56
+ kind: z
57
+ .enum([
58
+ "provider",
59
+ "connector",
60
+ "model-pack",
61
+ "activity-pack",
62
+ "report-pack",
63
+ ])
64
+ .optional(),
65
+ })
66
+ .strict(),
67
+ plugin_versions: z
68
+ .object({
69
+ provider: z.string(),
70
+ fromGit: z.string().optional(),
71
+ tagPrefix: z.string().optional(),
72
+ })
73
+ .strict(),
74
+ source_list: z.object({}).strict(),
75
+ source_show: z.object({ id: workflows.identifier }).strict(),
76
+ source_add: workflows.sourceSchema.omit({
77
+ apiVersion: true,
78
+ environments: true,
79
+ }),
80
+ source_configure: z
81
+ .object({
82
+ id: workflows.identifier,
83
+ patch: z.record(z.string(), z.unknown()),
84
+ })
85
+ .strict(),
86
+ source_prepare: z
87
+ .object({
88
+ id: workflows.identifier,
89
+ input: z.string(),
90
+ out: z.string().optional(),
91
+ })
92
+ .strict(),
93
+ source_import: z
94
+ .object({ id: workflows.identifier, metadata: z.string() })
95
+ .strict(),
96
+ plugin_configure: z
97
+ .object({
98
+ reference: z.string(),
99
+ name: workflows.identifier.optional(),
100
+ update: z.boolean().optional(),
101
+ })
102
+ .strict(),
103
+ contract_list: z.object({}).strict(),
104
+ contract_show: z.object({ id: z.string() }).strict(),
105
+ contract_check: z
106
+ .object({ id: z.string(), native: z.boolean().default(false) })
107
+ .strict(),
108
+ contract_create: z
109
+ .object({
110
+ id: workflows.identifier,
111
+ source: workflows.identifier.optional(),
112
+ metadata: z.string().optional(),
113
+ entity: z.string().optional(),
114
+ out: z.string().optional(),
115
+ provider: z.string().optional(),
116
+ })
117
+ .strict(),
118
+ flow_create: z
119
+ .object({
120
+ id: workflows.identifier,
121
+ kind: z.enum(["ingestion", "transformation"]).default("ingestion"),
122
+ standard: z.string().optional(),
123
+ sourceKind: z.string().optional(),
124
+ format: z.string().optional(),
125
+ sourceBinding: workflows.identifier.optional(),
126
+ inputDataset: z.string().optional(),
127
+ provider: z.string().optional(),
128
+ source: workflows.identifier.optional(),
129
+ contract: z.string().optional(),
130
+ })
131
+ .strict(),
132
+ flow_show: z.object({ id: workflows.identifier }).strict(),
133
+ build: z
134
+ .object({
135
+ provider: z.string().optional(),
136
+ delivery: z.boolean().default(false),
137
+ flow: z.string().optional(),
138
+ table: z.string().optional(),
139
+ step: z.string().optional(),
140
+ out: z.string().default("build/generated"),
141
+ ownership: z.enum(["managed", "team"]).default("managed"),
142
+ })
143
+ .strict(),
144
+ environments: z.object({}).strict(),
145
+ environment_add: z.object({ name: z.string(), from: z.string() }).strict(),
146
+ datasets: z.object({}).strict(),
147
+ tables: z.object({}).strict(),
148
+ config_explain: z.object({ path: z.string() }).strict(),
149
+ diff: z.object({ plan: z.string() }).strict(),
150
+ inspect: z.object({}).strict(),
151
+ schema: z
152
+ .object({
153
+ name: z.enum([
154
+ "project",
155
+ "source",
156
+ "flow",
157
+ "delivery-index",
158
+ "step",
159
+ "environment",
160
+ "activity",
161
+ "provider",
162
+ "extension-pack",
163
+ ]),
164
+ })
165
+ .strict(),
166
+ read: z.object({ path: z.string() }).strict(),
167
+ validate: z
168
+ .object({
169
+ mode: z.enum(["draft", "strict"]).default("strict"),
170
+ flow: z.string().optional(),
171
+ table: z.string().optional(),
172
+ step: z.string().optional(),
173
+ })
174
+ .strict(),
175
+ resolve: z.object({}).strict(),
176
+ plan: z
177
+ .object({
178
+ flow: z.string().optional(),
179
+ table: z.string().optional(),
180
+ step: z.string().optional(),
181
+ })
182
+ .strict(),
183
+ generate: z
184
+ .object({
185
+ plan: z.string(),
186
+ out: z.string(),
187
+ ownership: z.enum(["managed", "team"]).default("managed"),
188
+ })
189
+ .strict(),
190
+ validate_output: z.object({ directory: z.string() }).strict(),
191
+ catalogue: z
192
+ .object({
193
+ kind: z.enum(["activities", "standards", "providers"]),
194
+ id: z.string().optional(),
195
+ })
196
+ .strict(),
197
+ initialise: z
198
+ .object({
199
+ id: z.string(),
200
+ provider: z.string().optional(),
201
+ environments: z.array(z.string()).optional(),
202
+ })
203
+ .strict(),
204
+ activity_inspect: z.object({ path: z.string() }).strict(),
205
+ contract_draft: z
206
+ .object({ metadata: z.string(), id: z.string(), out: z.string() })
207
+ .strict(),
208
+ contract_validate: z.object({ path: z.string() }).strict(),
209
+ flow_configure: z
210
+ .object({ flow: z.string(), patch: z.record(z.string(), z.unknown()) })
211
+ .strict(),
212
+ step_add: z
213
+ .object({
214
+ flow: z.string(),
215
+ step: stepSchema,
216
+ after: z.string().optional(),
217
+ })
218
+ .strict(),
219
+ flow_add: z
220
+ .object({
221
+ id: z.string(),
222
+ kind: z.enum(["ingestion", "transformation"]),
223
+ standard: z.string().optional(),
224
+ sourceKind: z.string().optional(),
225
+ provider: z.string().optional(),
226
+ format: z.string().min(1).max(32).optional(),
227
+ inputDataset: z.string().optional(),
228
+ sourceBinding: z
229
+ .string()
230
+ .regex(/^[A-Za-z_][A-Za-z0-9_-]*$/)
231
+ .optional(),
232
+ })
233
+ .strict(),
234
+ table_add: z
235
+ .object({
236
+ id: z.string(),
237
+ flow: z.string(),
238
+ contract: z.string(),
239
+ })
240
+ .strict(),
241
+ table_configure: z
242
+ .object({
243
+ id: z.string(),
244
+ flow: z.string(),
245
+ patch: z.record(z.string(), z.unknown()),
246
+ })
247
+ .strict(),
248
+ dataset_add: z
249
+ .object({
250
+ id: z.string(),
251
+ flow: z.string(),
252
+ contract: z.string(),
253
+ from: z.string(),
254
+ })
255
+ .strict(),
256
+ config_set: z.object({ path: z.string(), value: z.unknown() }).strict(),
257
+ edit: z.object({ files: z.record(z.string(), z.string()) }).strict(),
258
+ apply: z.object({ proposal: z.unknown() }).strict(),
259
+ packages_install: z
260
+ .object({
261
+ reference: z.string(),
262
+ fromGit: z.string().optional(),
263
+ update: z.boolean().optional(),
264
+ frozen: z.boolean().optional(),
265
+ tagPrefix: z.string().optional(),
266
+ })
267
+ .strict(),
268
+ provider_migrate: z.object({ package: z.string(), to: z.string() }).strict(),
269
+ provider_export: z.object({ result: z.string(), out: z.string() }).strict(),
270
+ connection_prepare: z
271
+ .object({
272
+ flow: z.string(),
273
+ validateOnly: z.boolean().default(false),
274
+ review: z.string().optional(),
275
+ })
276
+ .strict(),
277
+ plugin_commands: z.object({}).strict(),
278
+ plugin_command: z
279
+ .object({
280
+ namespace: z.string(),
281
+ target: z.string().optional(),
282
+ command: z.string(),
283
+ input: z.record(z.string(), z.unknown()).default({}),
284
+ })
285
+ .strict(),
286
+ provider_commands: z.object({ configuration: z.string() }).strict(),
287
+ provider_command: z
288
+ .object({
289
+ configuration: z.string(),
290
+ command: z.string(),
291
+ input: z.record(z.string(), z.unknown()).default({}),
292
+ })
293
+ .strict(),
294
+ packages_list: z.object({}).strict(),
295
+ doctor: z.object({}).strict(),
296
+ };
297
+ export function execute(context, operation, request = {}) {
298
+ try {
299
+ check(Object.hasOwn(operationSchemas, operation), "OPERATION", "Unknown operation");
300
+ const parsed = operationSchemas[operation].safeParse(request);
301
+ check(parsed.success, "SCHEMA", parsed.error?.issues
302
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
303
+ .join("; ") ?? "Invalid request");
304
+ const args = parsed.data, root = existsSync(context.root)
305
+ ? realpathSync(context.root)
306
+ : resolve(context.root), environment = context.environment ?? "dev";
307
+ const write = () => check(context.allowWrite, "PERMISSION", "This operation requires local-write access");
308
+ let result;
309
+ switch (operation) {
310
+ case "runtime_prepare":
311
+ case "run":
312
+ case "run_status":
313
+ throw new Problem("EXECUTION", "Use executeAsync for execution operations");
314
+ case "provider_scaffold":
315
+ result = scaffoldProvider(root, args);
316
+ break;
317
+ case "pack_configure":
318
+ result = ecosystemAuthor.configurePack(root, args);
319
+ break;
320
+ case "flow_connect":
321
+ case "flow_export":
322
+ result = ecosystemAuthor.configureFlow(root, args);
323
+ break;
324
+ case "provider_check": {
325
+ const plan = planProject(root, environment, {
326
+ delivery: args.delivery,
327
+ });
328
+ const first = renderProject(root, plan), second = renderProject(root, planProject(root, environment, { delivery: args.delivery }));
329
+ check(canonical(first) === canonical(second), "DETERMINISM", "Provider output differs between repeated builds");
330
+ result = {
331
+ evidence: "offline",
332
+ execution: "not-run",
333
+ planDigest: plan.digest,
334
+ files: Object.keys(first).length,
335
+ deterministic: true,
336
+ };
337
+ break;
338
+ }
339
+ case "plugin_show":
340
+ result = pluginDetails(root, args.id);
341
+ break;
342
+ case "plugin_browse":
343
+ result = browseProviders(root, args.query, args.kind, args);
344
+ break;
345
+ case "plugin_versions":
346
+ check(context.allowNetwork || args.fromGit, "PERMISSION", "Provider version discovery requires allowNetwork in the operation context");
347
+ if (args.fromGit && !context.allowNetwork)
348
+ args.fromGit = fence(root, args.fromGit);
349
+ result = providerVersions(args.provider, args.fromGit, args.tagPrefix);
350
+ break;
351
+ case "source_list":
352
+ result = workflows.sourceList(root, environment);
353
+ break;
354
+ case "source_show":
355
+ result = workflows.sourceShow(root, args.id, environment);
356
+ break;
357
+ case "source_add":
358
+ result = workflows.sourceAdd(root, args);
359
+ break;
360
+ case "source_configure":
361
+ result = workflows.sourceConfigure(root, args);
362
+ break;
363
+ case "source_prepare":
364
+ result = workflows.sourcePrepare(root, environment, args);
365
+ break;
366
+ case "source_import":
367
+ result = workflows.sourceImport(root, environment, args);
368
+ break;
369
+ case "plugin_configure":
370
+ result = workflows.pluginConfigure(root, args);
371
+ break;
372
+ case "contract_list":
373
+ result = workflows.contractList(root);
374
+ break;
375
+ case "contract_show":
376
+ result = new Configuration(root).load(workflows.contractPath(root, args.id));
377
+ validateContract(result);
378
+ break;
379
+ case "contract_check": {
380
+ const path = workflows.contractPath(root, args.id), contract = new Configuration(root).load(path);
381
+ validateContract(contract, path);
382
+ result = {
383
+ path,
384
+ valid: true,
385
+ status: contract.status,
386
+ ...(args.native ? { columns: contractColumns(contract, path) } : {}),
387
+ evidence: "Offline ODCS validation; business review and source execution are separate",
388
+ };
389
+ break;
390
+ }
391
+ case "contract_create":
392
+ result = workflows.draftContract(root, environment, args);
393
+ break;
394
+ case "flow_create":
395
+ result = workflows.flowCreate(root, environment, args);
396
+ break;
397
+ case "flow_show": {
398
+ const state = inspectProject(root, environment, { draft: true });
399
+ result = state.flows.find((f) => f.id === args.id);
400
+ check(result, "SELECT", `Unknown flow ${args.id}`);
401
+ break;
402
+ }
403
+ case "build": {
404
+ write();
405
+ result = buildProject(root, environment, args);
406
+ break;
407
+ }
408
+ case "datasets":
409
+ result = planProject(root, environment).datasets;
410
+ break;
411
+ case "environment_add":
412
+ result = author.addEnvironment(root, args);
413
+ break;
414
+ case "environments":
415
+ result = {
416
+ selected: environment,
417
+ available: Object.keys(new Configuration(root).load().environments ?? {}),
418
+ };
419
+ break;
420
+ case "config_explain": {
421
+ const reader = new Configuration(root);
422
+ const raw = reader.load();
423
+ const profile = raw.environments[environment];
424
+ check(profile, "ENVIRONMENT", `Unknown environment ${environment}`);
425
+ const keys = args.path.split(".");
426
+ check(["values", "bindings"].includes(keys[0]), "CONFIG", "Explain a values.* or bindings.* path");
427
+ let value = profile, owner = profile;
428
+ for (const key of keys) {
429
+ check(value &&
430
+ typeof value === "object" &&
431
+ Object.hasOwn(value, key) &&
432
+ !["__proto__", "prototype", "constructor"].includes(key), "CONFIG", `Unknown field ${args.path}`);
433
+ owner = value;
434
+ value = value[key];
435
+ }
436
+ result = {
437
+ path: args.path,
438
+ environment,
439
+ declared: value,
440
+ resolved: reader.values(value, {
441
+ env: environment,
442
+ project: { id: raw.id },
443
+ values: profile.values,
444
+ }, { draft: true }),
445
+ source: reader.locations.get(owner),
446
+ pending: reader.pending,
447
+ };
448
+ break;
449
+ }
450
+ case "diff": {
451
+ const previous = JSON.parse(new Configuration(root).text(args.plan)), current = planProject(root, environment, previous.selection);
452
+ check(previous.apiVersion === "ingestron.plan/v1", "VERSION", "Diff requires a current saved plan");
453
+ const before = new Map(previous.nodes.map((n) => [n.id, canonical(n)])), after = new Map(current.nodes.map((n) => [n.id, canonical(n)]));
454
+ result = {
455
+ added: [...after.keys()].filter((id) => !before.has(id)),
456
+ removed: [...before.keys()].filter((id) => !after.has(id)),
457
+ changed: [...after.keys()].filter((id) => before.has(id) && before.get(id) !== after.get(id)),
458
+ configurationChanged: canonical(previous.bindings) !== canonical(current.bindings),
459
+ sourceChanged: canonical(previous.files) !== canonical(current.files),
460
+ beforeDigest: previous.digest,
461
+ afterDigest: current.digest,
462
+ evidence: "Local plans only; no platform drift lookup",
463
+ };
464
+ break;
465
+ }
466
+ case "tables": {
467
+ const state = inspectProject(root, environment, { draft: true });
468
+ result = state.flows
469
+ .filter((flow) => flow.kind === "ingestion")
470
+ .map((flow) => ({
471
+ flow: flow.id,
472
+ tables: Object.keys(flow.tables ?? {}),
473
+ }));
474
+ break;
475
+ }
476
+ case "inspect": {
477
+ const state = inspectProject(root, environment, { draft: true });
478
+ result = {
479
+ project: state.project.id,
480
+ environment,
481
+ flows: state.flows.map((f) => ({
482
+ id: f.id,
483
+ kind: f.kind,
484
+ tables: Object.keys(f.tables ?? {}),
485
+ inputs: f.requires,
486
+ outputs: Object.keys(f.publishes),
487
+ })),
488
+ providers: state.project.providers,
489
+ pending: state.reader.pending,
490
+ };
491
+ break;
492
+ }
493
+ case "schema":
494
+ result = z.toJSONSchema({
495
+ project: projectSchema,
496
+ source: workflows.sourceSchema,
497
+ flow: flowSchema,
498
+ "delivery-index": deliveryIndexSchema,
499
+ step: stepSchema,
500
+ environment: environmentSchema,
501
+ activity: externalActivitySchema,
502
+ provider: providerPackageSchema,
503
+ "extension-pack": z.union([
504
+ extensionPackSchema,
505
+ modelPackSchema,
506
+ reportPackSchema,
507
+ ]),
508
+ }[args.name]);
509
+ break;
510
+ case "read":
511
+ check(/\.(ya?ml|json|sql|py|md)$/.test(args.path) &&
512
+ !args.path.split("/").some((p) => p.startsWith(".")), "PATH", "Read only project configuration and source files");
513
+ result = {
514
+ path: args.path,
515
+ content: new Configuration(root).text(args.path),
516
+ };
517
+ break;
518
+ case "validate": {
519
+ if (args.mode === "draft") {
520
+ check(!args.flow && !args.table && !args.step, "OPTION", "Draft validation checks the whole project; use strict mode for flow/table/step selection");
521
+ const state = inspectProject(root, environment, { draft: true });
522
+ result = {
523
+ mode: "draft",
524
+ pending: state.reader.pending,
525
+ flows: state.flows.length,
526
+ };
527
+ }
528
+ else {
529
+ const state = inspectProject(root, environment, { draft: true });
530
+ const selected = state.flows.filter((f) => !args.flow || f.id === args.flow);
531
+ check(selected.length > 0, "FLOW", "Unknown flow");
532
+ const connected = selected.filter((f) => f.ingestion?.connection);
533
+ if (connected.length) {
534
+ check(!args.table && !args.step, "CONNECTION", "Validate connector flows as a whole");
535
+ const checks = connected.map((f) => prepareConnection(root, environment, f.id, true));
536
+ for (const flow of selected.filter((f) => !f.ingestion?.connection))
537
+ planProject(root, environment, { ...args, flow: flow.id });
538
+ result = {
539
+ mode: "strict",
540
+ evidence: "offline",
541
+ connections: checks,
542
+ };
543
+ break;
544
+ }
545
+ const plan = planProject(root, environment, args);
546
+ result = {
547
+ mode: "strict",
548
+ nodes: plan.nodes.length,
549
+ digest: plan.digest,
550
+ evidence: "offline",
551
+ };
552
+ }
553
+ break;
554
+ }
555
+ case "resolve": {
556
+ const state = inspectProject(root, environment, { draft: true });
557
+ result = {
558
+ project: state.project,
559
+ flows: state.flows,
560
+ pending: state.reader.pending,
561
+ sources: state.reader.files,
562
+ };
563
+ break;
564
+ }
565
+ case "plan":
566
+ result = planProject(root, environment, args);
567
+ break;
568
+ case "generate": {
569
+ write();
570
+ const plan = JSON.parse(new Configuration(root).text(args.plan));
571
+ check(plan.environment === environment, "ENVIRONMENT", `Saved plan is for ${plan.environment}, not selected ${environment}`);
572
+ result = writeOutput(fence(root, args.out), plan, renderProject(root, plan), args.ownership);
573
+ break;
574
+ }
575
+ case "validate_output":
576
+ result = validateOutput(fence(root, args.directory));
577
+ break;
578
+ case "catalogue": {
579
+ const installed = installedProviders(root);
580
+ const entries = args.kind === "standards"
581
+ ? installed.flatMap((p) => (p.standards ?? []).map((s) => ({
582
+ ...s,
583
+ provider: p.reference,
584
+ version: p.version,
585
+ evidence: "offline",
586
+ })))
587
+ : args.kind === "activities"
588
+ ? installedActivities(root)
589
+ : installed;
590
+ result = args.id
591
+ ? entries.filter((e) => e.id.replace(/@v1$/, "") === args.id.replace(/@v1$/, ""))
592
+ : entries;
593
+ check(!args.id || result.length, "SELECT", `Unknown ${args.kind} entry ${args.id}`);
594
+ break;
595
+ }
596
+ case "initialise":
597
+ result = author.initialise(root, args);
598
+ break;
599
+ case "activity_inspect":
600
+ result = development.activityInspect(root, args.path);
601
+ break;
602
+ case "contract_draft":
603
+ result = development.contractDraft(root, args.metadata, args.id, args.out);
604
+ break;
605
+ case "contract_validate":
606
+ result = {
607
+ columns: contractColumns(new Configuration(root).load(args.path)),
608
+ evidence: "offline",
609
+ };
610
+ break;
611
+ case "flow_configure":
612
+ result = author.configureFlow(root, args.flow, args.patch);
613
+ break;
614
+ case "step_add":
615
+ result = author.addStep(root, args.flow, args.step, args.after);
616
+ break;
617
+ case "flow_add":
618
+ result = author.addFlow(root, args);
619
+ break;
620
+ case "table_add":
621
+ result = author.addTable(root, args);
622
+ break;
623
+ case "table_configure":
624
+ result = author.configureTable(root, args.flow, args.id, args.patch);
625
+ break;
626
+ case "dataset_add":
627
+ result = author.addDataset(root, args);
628
+ break;
629
+ case "config_set":
630
+ result = author.editValue(root, environment, args.path, args.value);
631
+ break;
632
+ case "edit": {
633
+ for (const file of Object.keys(args.files))
634
+ check(/\.(yaml|yml|json|sql|py|md)$/.test(file), "PATH", "Only configuration, source and Markdown files may be edited");
635
+ result = author.preview(root, args.files);
636
+ break;
637
+ }
638
+ case "apply":
639
+ write();
640
+ result = author.apply(root, args.proposal);
641
+ break;
642
+ case "packages_install":
643
+ write();
644
+ check(context.allowNetwork || args.fromGit, "PERMISSION", "Package fetch requires explicit package-network access");
645
+ if (args.fromGit && !context.allowNetwork)
646
+ args.fromGit = fence(root, args.fromGit);
647
+ result = installPackage(root, args.reference, args);
648
+ result.displayReference = friendlyReference(result.reference);
649
+ break;
650
+ case "provider_migrate":
651
+ result = author.migrateProvider(root, args);
652
+ break;
653
+ case "provider_export":
654
+ result = development.exportProviderArtifacts(root, args.result, args.out);
655
+ break;
656
+ case "connection_prepare":
657
+ result = prepareConnection(root, context.environment ?? "dev", args.flow, args.validateOnly, args.review);
658
+ break;
659
+ case "plugin_commands":
660
+ result = pluginCommands(root, environment);
661
+ break;
662
+ case "plugin_command":
663
+ result = runPluginCommand(root, environment, args);
664
+ break;
665
+ case "provider_commands":
666
+ result = providerCommands(root, context.environment ?? "dev", args.configuration);
667
+ break;
668
+ case "provider_command":
669
+ result = runProviderCommand(root, context.environment ?? "dev", args);
670
+ break;
671
+ case "packages_list":
672
+ result = packageLock(root);
673
+ result = {
674
+ ...result,
675
+ entries: Object.entries(result.packages).map(([reference, entry]) => ({
676
+ reference: friendlyReference(reference),
677
+ canonicalReference: reference,
678
+ commit: entry.commit,
679
+ })),
680
+ };
681
+ break;
682
+ case "doctor": {
683
+ const state = inspectProject(root, environment, { draft: true });
684
+ result = {
685
+ node: process.version,
686
+ project: state.project.id,
687
+ environment,
688
+ pending: state.reader.pending,
689
+ platformAccess: "Not checked; compiler is offline",
690
+ packages: Object.keys(packageLock(root).packages),
691
+ next: state.reader.pending.length
692
+ ? "Supply missing environment inputs or create a config_set proposal"
693
+ : "Use validate and build for selected flows",
694
+ };
695
+ break;
696
+ }
697
+ }
698
+ return {
699
+ apiVersion: "ingestron.operation/v1",
700
+ operation,
701
+ ok: true,
702
+ result,
703
+ diagnostics: [],
704
+ };
705
+ }
706
+ catch (error) {
707
+ const e = error instanceof Problem
708
+ ? error
709
+ : error instanceof z.ZodError
710
+ ? new Problem("SCHEMA", error.issues
711
+ .map((i) => `${i.path.join(".") || "input"}: ${i.message}`)
712
+ .join("; "))
713
+ : new Problem("INTERNAL", error instanceof Error ? error.message : "Operation failed");
714
+ return {
715
+ apiVersion: "ingestron.operation/v1",
716
+ operation,
717
+ ok: false,
718
+ diagnostics: [
719
+ {
720
+ code: e.code,
721
+ message: e.message,
722
+ file: e.file,
723
+ pointer: e.pointer,
724
+ hint: e.hint,
725
+ },
726
+ ],
727
+ };
728
+ }
729
+ }
730
+ /** Shared CLI/MCP entry point; ordinary compiler operations remain synchronous/offline. */
731
+ export async function executeAsync(context, operation, request = {}) {
732
+ if (!Object.hasOwn(executionSchemas, operation))
733
+ return execute(context, operation, request);
734
+ try {
735
+ const name = operation;
736
+ const parsed = executionSchemas[name].safeParse(request);
737
+ check(parsed.success, "SCHEMA", parsed.error?.issues
738
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
739
+ .join("; ") ?? "Invalid execution request");
740
+ const result = await performExecution(context, name, parsed.data);
741
+ return {
742
+ apiVersion: "ingestron.operation/v1",
743
+ operation,
744
+ ok: true,
745
+ result,
746
+ diagnostics: [],
747
+ };
748
+ }
749
+ catch (error) {
750
+ const e = error instanceof Problem
751
+ ? error
752
+ : new Problem("EXECUTION", "Execution preflight failed; check generated files and runtime preparation");
753
+ return {
754
+ apiVersion: "ingestron.operation/v1",
755
+ operation,
756
+ ok: false,
757
+ diagnostics: [{ code: e.code, message: e.message }],
758
+ };
759
+ }
760
+ }