@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,579 @@
1
+ import { connectorPackageSchema } from "./connector-package.js";
2
+ import { reportPackSchema } from "./report-pack-schema.js";
3
+ import { validateModelPack } from "./model-pack-schema.js";
4
+ /** Explicit Git installation. Generation only reads integrity-checked cached text. */
5
+ import { execFileSync } from "node:child_process";
6
+ import { readFileSync, writeFileSync, readdirSync, mkdirSync, mkdtempSync, rmSync, existsSync, realpathSync, lstatSync, renameSync, openSync, closeSync, } from "node:fs";
7
+ import { resolve, relative, dirname, isAbsolute } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+ import { parseDocument, stringify } from "yaml";
10
+ import { z } from "zod";
11
+ import { check, digest, canonical, Problem } from "./errors.js";
12
+ import { gitTags, taggedVersions, packageGitEnvironment, } from "./package-references.js";
13
+ const platform = z.string().regex(/^[a-z][a-z0-9-]*$/);
14
+ export const externalActivitySchema = z
15
+ .object({
16
+ apiVersion: z.literal("ingestron.activity/v1"),
17
+ id: z.string().regex(/^[a-z][a-z0-9-]*$/),
18
+ version: z.string().regex(/^(?:0|[1-9]\d*)\.\d+\.\d+$/),
19
+ description: z.string(),
20
+ platform,
21
+ input: z.enum(["source", "files", "relation", "committed"]),
22
+ output: z.enum(["files", "relation", "committed"]),
23
+ effect: z.enum(["read", "stage", "commit", "control"]),
24
+ capabilities: z.array(z.string()).default([]),
25
+ optionsSchema: z.record(z.string(), z.unknown()),
26
+ generator: z
27
+ .object({
28
+ kind: z.enum(["native-notebook", "native-pipeline", "native-project"]),
29
+ artifactKind: platform.optional(),
30
+ execution: platform.optional(),
31
+ persistence: z.enum(["capture", "ephemeral", "durable"]).optional(),
32
+ runtime: z.string().optional(),
33
+ code: z.string().optional(),
34
+ scope: z.enum(["table", "collection"]).default("table"),
35
+ modes: z
36
+ .array(z.enum(["table", "collection"]))
37
+ .min(1)
38
+ .default(["table"]),
39
+ groupBy: z
40
+ .array(z.string().regex(/^[A-Za-z_]\w*(\.[A-Za-z_]\w*)*$/))
41
+ .default([]),
42
+ dependencyOptions: z
43
+ .record(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/), z.string().regex(/^[A-Za-z_]\w*(\.[A-Za-z_]\w*)*$/))
44
+ .default({}),
45
+ entrypoint: z
46
+ .string()
47
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/)
48
+ .default("run"),
49
+ })
50
+ .strict()
51
+ .optional(),
52
+ requirements: z
53
+ .object({
54
+ execution: z.string().min(1),
55
+ features: z.array(z.string()).default([]),
56
+ sourceKinds: z.array(z.string()).optional(),
57
+ evidence: z.literal("offline"),
58
+ })
59
+ .strict()
60
+ .optional(),
61
+ templates: z
62
+ .array(z
63
+ .object({
64
+ source: z.string(),
65
+ output: z.string(),
66
+ format: z.enum(["python", "sql", "json", "yaml", "text"]),
67
+ })
68
+ .strict())
69
+ .default([]),
70
+ dependencies: z
71
+ .record(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/), z.string().regex(/^\d+\.\d+\.\d+[A-Za-z0-9.+-]*$/))
72
+ .default({}),
73
+ })
74
+ .strict();
75
+ export const providerPackageSchema = z
76
+ .object({
77
+ apiVersion: z.literal("ingestron.provider/v1"),
78
+ id: z.string(),
79
+ version: z.string().regex(/^(?:0|[1-9]\d*)\.\d+\.\d+$/),
80
+ platform,
81
+ activities: z.record(z.string(), z.string()),
82
+ capabilities: z
83
+ .object({
84
+ artifactKinds: z.array(platform).min(1),
85
+ delivery: z.boolean().default(false),
86
+ handovers: z.array(z.enum(["files", "relation"])).default([]),
87
+ packContracts: z
88
+ .record(platform, z
89
+ .object({
90
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
91
+ activities: z.array(platform).min(1),
92
+ })
93
+ .strict())
94
+ .default({}),
95
+ })
96
+ .strict()
97
+ .optional(),
98
+ projectAssembly: z
99
+ .object({
100
+ apiVersion: z.literal("ingestron.project-assembly/v1"),
101
+ command: z.string().min(1),
102
+ })
103
+ .strict()
104
+ .optional(),
105
+ connectorRuntimes: z
106
+ .record(z.string(), z
107
+ .object({
108
+ selectionSchema: z.record(z.string(), z.unknown()),
109
+ executionSchema: z.record(z.string(), z.unknown()),
110
+ prepareCommand: z.string(),
111
+ contractsCommand: z.string().optional(),
112
+ executionPlatforms: z.array(z.string()).min(1),
113
+ })
114
+ .strict())
115
+ .optional(),
116
+ configurationSchema: z
117
+ .union([z.string(), z.record(z.string(), z.unknown())])
118
+ .optional(),
119
+ standards: z
120
+ .array(z
121
+ .object({
122
+ id: z.string(),
123
+ description: z.string(),
124
+ sourceKinds: z.array(z.string()).optional(),
125
+ dataFlow: z.enum(["forbidden", "required"]).optional(),
126
+ })
127
+ .strict())
128
+ .optional(),
129
+ compatibility: z
130
+ .object({
131
+ plan: z.literal("ingestron.plan/v1"),
132
+ requiredFeatures: z.array(platform).default([]),
133
+ minimumCli: z.string().regex(/^\d+\.\d+\.\d+$/),
134
+ })
135
+ .strict()
136
+ .optional(),
137
+ outputSchemas: z.record(z.string(), z.string()).optional(),
138
+ lifecycle: z
139
+ .object({
140
+ apiVersion: z.literal("ingestron.provider-lifecycle/v1"),
141
+ module: z.string(),
142
+ })
143
+ .strict()
144
+ .optional(),
145
+ planner: z
146
+ .object({
147
+ apiVersion: z.literal("ingestron.provider-planning/v1"),
148
+ module: z.string(),
149
+ })
150
+ .strict()
151
+ .optional(),
152
+ commands: z
153
+ .object({
154
+ apiVersion: z.literal("ingestron.provider-commands/v1"),
155
+ namespace: z
156
+ .string()
157
+ .regex(/^[a-z][a-z0-9-]*$/)
158
+ .optional(),
159
+ execution: z.literal("offline-json"),
160
+ module: z.string(),
161
+ definitions: z
162
+ .array(z
163
+ .object({
164
+ name: z.string().regex(/^[a-z][a-z0-9-]*( [a-z][a-z0-9-]*)*$/),
165
+ description: z.string().min(1).max(500),
166
+ reportPack: z.boolean().optional(),
167
+ examples: z.array(z.string().max(500)).max(10).optional(),
168
+ resultSchema: z.record(z.string(), z.unknown()).optional(),
169
+ inputSchema: z.record(z.string(), z.unknown()),
170
+ })
171
+ .strict())
172
+ .min(1)
173
+ .max(50)
174
+ .refine((items) => new Set(items.map((i) => i.name)).size === items.length, "Duplicate provider command"),
175
+ })
176
+ .strict()
177
+ .optional(),
178
+ execution: z
179
+ .object({
180
+ apiVersion: z.literal("ingestron.execution/v1"),
181
+ transport: z.string().min(1),
182
+ entryPoint: z.string().min(1),
183
+ actions: z
184
+ .array(z.enum(["prepare", "discover", "review", "approve", "run"]))
185
+ .min(1),
186
+ status: z.literal("receipt"),
187
+ retry: z.literal("same-run-id"),
188
+ cancellation: z.literal("interrupt"),
189
+ })
190
+ .strict()
191
+ .optional(),
192
+ renderer: z
193
+ .object({
194
+ apiVersion: z.literal("ingestron.provider-generation/v1"),
195
+ module: z.string(),
196
+ })
197
+ .strict()
198
+ .optional(),
199
+ })
200
+ .strict();
201
+ /** Data-only presets: no modules, nested dependencies or renderer replacement. */
202
+ export const extensionPackSchema = z
203
+ .object({
204
+ apiVersion: z.literal("ingestron.extension-pack/v1"),
205
+ id: platform,
206
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
207
+ description: z.string().min(1),
208
+ extends: z
209
+ .object({
210
+ provider: z.string().min(1),
211
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
212
+ platform,
213
+ contract: platform,
214
+ contractVersion: z.string().regex(/^\d+\.\d+\.\d+$/),
215
+ })
216
+ .strict(),
217
+ activities: z
218
+ .record(platform, z
219
+ .object({
220
+ uses: platform,
221
+ with: z.record(z.string(), z.unknown()).default({}),
222
+ })
223
+ .strict())
224
+ .refine((value) => Object.keys(value).length > 0, "Pack must export activities"),
225
+ })
226
+ .strict();
227
+ const lockSchema = z
228
+ .object({
229
+ apiVersion: z.literal("ingestron.packages-lock/v1"),
230
+ packages: z.record(z.string(), z
231
+ .object({
232
+ repository: z.string(),
233
+ path: z.string(),
234
+ commit: z.string().regex(/^[a-f0-9]{40}$/),
235
+ files: z.record(z.string(), z.string().regex(/^[a-f0-9]{64}$/)),
236
+ })
237
+ .strict()),
238
+ })
239
+ .strict();
240
+ export function fence(root, name) {
241
+ check(!isAbsolute(name) &&
242
+ !name.includes("\\") &&
243
+ name.split("/").every((p) => p && ![".", ".."].includes(p)), "PATH", `Unsafe package path ${name}`);
244
+ const target = resolve(root, name);
245
+ let current = root;
246
+ for (const part of name.split("/")) {
247
+ current = resolve(current, part);
248
+ check(!existsSync(current) || !lstatSync(current).isSymbolicLink(), "PATH", "Package symlinks are forbidden");
249
+ }
250
+ return target;
251
+ }
252
+ export function packageYaml(file) {
253
+ const text = readFileSync(file, "utf8");
254
+ check(Buffer.byteLength(text) < 2 * 1024 * 1024, "LIMIT", "Package YAML exceeds 2 MiB");
255
+ const doc = parseDocument(text, { uniqueKeys: true });
256
+ check(!doc.errors.length, "YAML", doc.errors[0]?.message ?? "Invalid package YAML", file);
257
+ const value = doc.toJS({ maxAliasCount: 20 });
258
+ const walk = (v, depth = 0) => {
259
+ check(depth <= 64, "LIMIT", "Package structure exceeds 64 levels");
260
+ if (v && typeof v === "object")
261
+ for (const [k, c] of Object.entries(v)) {
262
+ check(!["__proto__", "constructor", "prototype"].includes(k), "KEY", "Unsafe package key");
263
+ walk(c, depth + 1);
264
+ }
265
+ };
266
+ walk(value);
267
+ return value;
268
+ }
269
+ /** Stable convenience names; locks always retain the explicit repository and path. */
270
+ export function canonicalPackageReference(reference) {
271
+ reference = reference.replace(/@v((?:0|[1-9]\d*)\.\d+\.\d+)$/, "@$1");
272
+ const short = /^([\w.-]+(?:\/[\w.-]+)?)@([^@]+)$/.exec(reference);
273
+ if (!short)
274
+ return reference;
275
+ const repository = short[1];
276
+ check(repository.includes("/"), "PACKAGE", "Unknown plugin name; use an explicit owner/repository@version");
277
+ return `${repository}/plugin/provider.yaml@${short[2]}`;
278
+ }
279
+ const parseReference = (reference) => {
280
+ check(!reference.endsWith("@latest"), "PACKAGE", "@latest is not supported: packages must be pinned to an exact semantic version or commit. Use plugin_versions with an explicit repository to inspect available versions.");
281
+ const m = /^([\w.-]+\/[\w.-]+)\/(.+\.ya?ml)@(v1|(?:0|[1-9]\d*)\.\d+\.\d+|[a-f0-9]{40})$/.exec(reference);
282
+ check(m && !m[1].includes(".."), "PACKAGE", "Use owner/repository@1.2.3 or owner/repository/path/manifest.yaml@commit; legacy v1 tags are also accepted");
283
+ fence("/package", m[2]);
284
+ return { repository: m[1], path: m[2], ref: m[3] };
285
+ };
286
+ const inventory = (root, dir = root) => readdirSync(dir, { withFileTypes: true })
287
+ .flatMap((e) => {
288
+ check(!e.isSymbolicLink(), "PATH", "Package symlinks are forbidden");
289
+ return e.isDirectory()
290
+ ? inventory(root, resolve(dir, e.name))
291
+ : [relative(root, resolve(dir, e.name))];
292
+ })
293
+ .sort();
294
+ export function packageLock(root) {
295
+ const file = resolve(root, "packages.lock.yaml");
296
+ check(!existsSync(file) || !lstatSync(file).isSymbolicLink(), "PATH", "Package lock cannot be a symlink");
297
+ return existsSync(file)
298
+ ? lockSchema.parse(packageYaml(file))
299
+ : {
300
+ apiVersion: "ingestron.packages-lock/v1",
301
+ packages: {},
302
+ };
303
+ }
304
+ export function resolvePackage(root, reference) {
305
+ reference = canonicalPackageReference(reference);
306
+ const parsed = parseReference(reference), entry = packageLock(root).packages[reference];
307
+ check(entry &&
308
+ entry.repository === parsed.repository &&
309
+ entry.path === parsed.path, "PACKAGE", `Install locked package ${reference} using packages_install`);
310
+ if (/^[a-f0-9]{40}$/.test(parsed.ref))
311
+ check(entry.commit === parsed.ref, "PACKAGE", "Lock commit differs from selected reference");
312
+ const cache = fence(root, `.ingestron/packages/${digest(entry.repository)}/${entry.commit}`);
313
+ check(existsSync(cache), "PACKAGE", `Package cache missing for ${reference}`);
314
+ check(canonical(inventory(cache)) === canonical(Object.keys(entry.files).sort()), "PACKAGE", "Package file inventory changed");
315
+ for (const [file, hash] of Object.entries(entry.files))
316
+ check(digest(readFileSync(fence(cache, file))) === hash, "PACKAGE", `Package integrity failed: ${file}`);
317
+ return { file: fence(cache, entry.path), root: cache, entry };
318
+ }
319
+ /** Derived information lives outside the integrity-checked source cache. */
320
+ export function savePluginInformation(root, reference) {
321
+ const source = resolvePackage(root, reference);
322
+ const manifest = packageYaml(source.file);
323
+ const connector = manifest.apiVersion === "ingestron.connector/v1"
324
+ ? connectorPackageSchema.parse(manifest)
325
+ : undefined;
326
+ const documents = Object.keys(source.entry.files)
327
+ .filter((name) => name === connector?.upstream.licenceFile ||
328
+ /(?:^|\/)(?:licen[cs]e|copying|notice|copyright|third[-_]party[-_]notices?)(?:[._-].*)?$/i.test(name))
329
+ .sort()
330
+ .map((path) => ({
331
+ path,
332
+ sha256: source.entry.files[path],
333
+ text: readFileSync(fence(source.root, path), "utf8"),
334
+ }));
335
+ let declaredLicence = null;
336
+ if (source.entry.files["package.json"]) {
337
+ try {
338
+ const metadata = JSON.parse(readFileSync(fence(source.root, "package.json"), "utf8"));
339
+ if (typeof metadata.license === "string")
340
+ declaredLicence = metadata.license;
341
+ }
342
+ catch {
343
+ /* Optional upstream metadata is not a licence determination. */
344
+ }
345
+ }
346
+ const file = `.ingestron/plugin-info/${digest(reference)}.json`;
347
+ const target = fence(root, file);
348
+ mkdirSync(dirname(target), { recursive: true });
349
+ const information = {
350
+ apiVersion: "ingestron.plugin-information/v1",
351
+ reference,
352
+ id: manifest.id,
353
+ version: manifest.version,
354
+ kind: manifest.apiVersion,
355
+ repository: source.entry.repository,
356
+ commit: source.entry.commit,
357
+ manifest: source.entry.path,
358
+ sourceDocumentation: `https://github.com/${source.entry.repository}/tree/${source.entry.commit}`,
359
+ ...(connector
360
+ ? {
361
+ upstream: connector.upstream,
362
+ execution: connector.execution,
363
+ upstreamDocumentation: connector.documentation,
364
+ }
365
+ : {}),
366
+ declaredLicence,
367
+ licenceDocuments: documents,
368
+ licenceReview: "Not assessed: retained upstream evidence is not permission or a complete dependency licence audit. Review the selected connector and its dependencies before customer-operated use or redistribution.",
369
+ };
370
+ const temporary = fence(root, `${file}.${process.pid}.tmp`);
371
+ writeFileSync(temporary, JSON.stringify(information, null, 2) + "\n", {
372
+ flag: "wx",
373
+ });
374
+ try {
375
+ renameSync(temporary, target);
376
+ }
377
+ finally {
378
+ rmSync(temporary, { force: true });
379
+ }
380
+ return file;
381
+ }
382
+ export function installPackage(root, reference, options = {}) {
383
+ root = realpathSync(root);
384
+ reference = canonicalPackageReference(reference);
385
+ const parsed = parseReference(reference), lock = packageLock(root);
386
+ const previous = lock.packages[reference];
387
+ check(!(options.update && options.frozen), "PACKAGE", "Frozen install cannot update packages");
388
+ check(!options.frozen || previous, "PACKAGE", "Frozen install requires an existing lock entry");
389
+ if (previous &&
390
+ !options.update &&
391
+ existsSync(resolve(root, `.ingestron/packages/${digest(previous.repository)}/${previous.commit}`)))
392
+ return {
393
+ reference,
394
+ ...resolvePackage(root, reference).entry,
395
+ cached: true,
396
+ informationFile: savePluginInformation(root, reference),
397
+ };
398
+ const control = fence(root, ".ingestron");
399
+ mkdirSync(control, { recursive: true });
400
+ const guard = resolve(control, "package-install.lock");
401
+ let fd;
402
+ try {
403
+ fd = openSync(guard, "wx");
404
+ }
405
+ catch {
406
+ throw new Problem("CONFLICT", "Another package install is running");
407
+ }
408
+ const temp = mkdtempSync(resolve(tmpdir(), "ingestron-package-"));
409
+ try {
410
+ const git = (args, cwd) => execFileSync("git", args, {
411
+ cwd,
412
+ encoding: "utf8",
413
+ timeout: 60000,
414
+ maxBuffer: 12 * 1024 * 1024,
415
+ env: packageGitEnvironment(),
416
+ stdio: ["ignore", "pipe", "pipe"],
417
+ });
418
+ const repo = options.fromGit
419
+ ? realpathSync(options.fromGit)
420
+ : resolve(temp, "git"), requested = previous && !options.update ? previous.commit : parsed.ref;
421
+ let gitRef = requested;
422
+ if (/^(?:0|[1-9]\d*)\.\d+\.\d+$/.test(requested)) {
423
+ const tag = taggedVersions(gitTags(parsed.repository, options.fromGit ? repo : undefined), options.tagPrefix).find((v) => v.version === requested);
424
+ check(tag, "PACKAGE", `No version ${requested} found; use plugin versions ${parsed.repository}`);
425
+ check(!tag.ambiguous, "PACKAGE", `Tags ${requested} and v${requested} point to different commits; select an explicit commit instead`);
426
+ gitRef = tag.commit;
427
+ }
428
+ if (!options.fromGit) {
429
+ mkdirSync(repo);
430
+ git(["init", "--quiet"], repo);
431
+ git([
432
+ "-c",
433
+ "core.hooksPath=/dev/null",
434
+ "-c",
435
+ "credential.helper=!gh auth git-credential",
436
+ "fetch",
437
+ "--quiet",
438
+ "--depth=1",
439
+ `https://github.com/${parsed.repository}.git`,
440
+ gitRef,
441
+ ], repo);
442
+ }
443
+ const commit = git([
444
+ "rev-parse",
445
+ "--verify",
446
+ `${options.fromGit ? gitRef : "FETCH_HEAD"}^{commit}`,
447
+ ], repo).trim();
448
+ check(/^[a-f0-9]{40}$/.test(commit), "PACKAGE", "Invalid Git commit");
449
+ if (/^[a-f0-9]{40}$/.test(gitRef))
450
+ check(commit === gitRef, "PACKAGE", "Fetched commit mismatch");
451
+ const rows = git(["ls-tree", "-r", "-l", "-z", commit], repo)
452
+ .split("\0")
453
+ .filter(Boolean);
454
+ check(rows.length <= 1024, "LIMIT", "Package exceeds 1024 files");
455
+ const staged = resolve(temp, "source");
456
+ mkdirSync(staged);
457
+ const files = {};
458
+ let total = 0;
459
+ for (const row of rows) {
460
+ const m = /^(\d+) (\w+) ([a-f0-9]+)\s+(\d+)\t(.+)$/.exec(row);
461
+ check(m && ["100644", "100755"].includes(m[1]) && m[2] === "blob", "PACKAGE", "Symlinks/submodules are forbidden");
462
+ const size = +m[4];
463
+ total += size;
464
+ check(size <= 2 * 1024 * 1024 && total <= 10 * 1024 * 1024, "LIMIT", "Package exceeds text limits");
465
+ const text = git(["cat-file", "blob", m[3]], repo);
466
+ check(!text.includes("\0") &&
467
+ !text.includes("\uFFFD") &&
468
+ Buffer.byteLength(text) === size, "PACKAGE", "Packages contain UTF-8 text only");
469
+ const dest = fence(staged, m[5]);
470
+ mkdirSync(dirname(dest), { recursive: true });
471
+ writeFileSync(dest, text);
472
+ files[m[5]] = digest(text);
473
+ }
474
+ const manifest = packageYaml(fence(staged, parsed.path));
475
+ if (manifest.apiVersion === "ingestron.connector/v1") {
476
+ const connector = connectorPackageSchema.parse(manifest);
477
+ check(connector.upstream.licenceStatus === "evidenced", "LICENCE", "Connector licence evidence is missing or conflicting; installation held");
478
+ check(connector.connector.endsWith("@" + connector.upstream.version), "PACKAGE", "Connector and upstream version differ");
479
+ const licenceFile = fence(staged, connector.upstream.licenceFile);
480
+ check(existsSync(licenceFile) &&
481
+ readFileSync(licenceFile, "utf8").trim().length > 0, "LICENCE", "Connector licence file is missing or empty");
482
+ }
483
+ else if (manifest.apiVersion === "ingestron.activity/v1")
484
+ externalActivitySchema.parse(manifest);
485
+ else if (manifest.apiVersion === "ingestron.provider/v1")
486
+ providerPackageSchema.parse(manifest);
487
+ else if (manifest.apiVersion === "ingestron.extension-pack/v3")
488
+ reportPackSchema.parse(manifest);
489
+ else if (manifest.apiVersion === "ingestron.extension-pack/v2")
490
+ validateModelPack(manifest);
491
+ else if (manifest.apiVersion === "ingestron.extension-pack/v1")
492
+ extensionPackSchema.parse(manifest);
493
+ else
494
+ throw new Problem("PACKAGE", "Only activity, provider and extension-pack packages are supported");
495
+ if (/^(?:0|[1-9]\d*)\.\d+\.\d+$/.test(parsed.ref))
496
+ check(manifest.version === parsed.ref, "PACKAGE", "Manifest version differs from selected exact version");
497
+ const entry = {
498
+ repository: parsed.repository,
499
+ path: parsed.path,
500
+ commit,
501
+ files,
502
+ }, dest = fence(root, `.ingestron/packages/${digest(parsed.repository)}/${commit}`);
503
+ mkdirSync(dirname(dest), { recursive: true });
504
+ if (!existsSync(dest)) {
505
+ const staging = dest + `-${process.pid}`;
506
+ mkdirSync(staging);
507
+ try {
508
+ for (const name of Object.keys(files)) {
509
+ const target = fence(staging, name);
510
+ mkdirSync(dirname(target), { recursive: true });
511
+ writeFileSync(target, readFileSync(fence(staged, name)));
512
+ }
513
+ renameSync(staging, dest);
514
+ }
515
+ finally {
516
+ rmSync(staging, { recursive: true, force: true });
517
+ }
518
+ }
519
+ check(canonical(inventory(dest)) === canonical(Object.keys(files).sort()), "PACKAGE", "Existing cached inventory differs");
520
+ for (const [name, hash] of Object.entries(files))
521
+ check(digest(readFileSync(fence(dest, name))) === hash, "PACKAGE", `Existing package cache is modified: ${name}`);
522
+ lock.packages[reference] = entry;
523
+ const temporary = resolve(root, `.packages-${process.pid}.yaml`);
524
+ writeFileSync(temporary, stringify(lock), { flag: "wx" });
525
+ try {
526
+ renameSync(temporary, resolve(root, "packages.lock.yaml"));
527
+ }
528
+ finally {
529
+ rmSync(temporary, { force: true });
530
+ }
531
+ resolvePackage(root, reference);
532
+ return {
533
+ reference,
534
+ ...entry,
535
+ cached: false,
536
+ informationFile: savePluginInformation(root, reference),
537
+ };
538
+ }
539
+ finally {
540
+ closeSync(fd);
541
+ rmSync(guard, { force: true });
542
+ rmSync(temp, { recursive: true, force: true });
543
+ }
544
+ }
545
+ export function installedProviders(root) {
546
+ return Object.keys(packageLock(root).packages).flatMap((reference) => {
547
+ const resolved = resolvePackage(root, reference);
548
+ const manifest = packageYaml(resolved.file);
549
+ if (manifest.apiVersion !== "ingestron.provider/v1")
550
+ return [];
551
+ return [
552
+ {
553
+ ...providerPackageSchema.parse(manifest),
554
+ reference,
555
+ commit: resolved.entry.commit,
556
+ evidence: "offline",
557
+ },
558
+ ];
559
+ });
560
+ }
561
+ export function installedActivities(root) {
562
+ return installedProviders(root).flatMap((provider) => {
563
+ const source = resolvePackage(root, provider.reference);
564
+ return Object.entries(provider.activities).map(([id, path]) => {
565
+ const file = fence(source.root, relative(source.root, resolve(dirname(source.file), path)));
566
+ const manifest = externalActivitySchema.parse(packageYaml(file));
567
+ check(manifest.id === id && manifest.platform === provider.platform, "PACKAGE", "Activity export identity or platform differs from provider");
568
+ return {
569
+ id,
570
+ provider: provider.reference,
571
+ version: manifest.version,
572
+ description: manifest.description,
573
+ platforms: [manifest.platform],
574
+ evidence: "offline",
575
+ implementations: [manifest],
576
+ };
577
+ });
578
+ });
579
+ }
@@ -0,0 +1,114 @@
1
+ import { Configuration } from "./config.js";
2
+ import { type Plan } from "./schema.js";
3
+ export interface Select {
4
+ nativeOnly?: boolean;
5
+ flowIds?: string[];
6
+ projectBuild?: boolean;
7
+ flow?: string;
8
+ table?: string;
9
+ step?: string;
10
+ draft?: boolean;
11
+ delivery?: boolean;
12
+ environmentVariables?: Record<string, string | undefined>;
13
+ }
14
+ export declare function inspectProject(root: string, environment?: string, options?: Select): {
15
+ reader: Configuration;
16
+ project: {
17
+ apiVersion: "ingestron.project/v1";
18
+ id: string;
19
+ description?: string | undefined;
20
+ defaults: {
21
+ provider?: string | undefined;
22
+ naming: {
23
+ table?: string | undefined;
24
+ job?: string | undefined;
25
+ };
26
+ with: Record<string, unknown>;
27
+ };
28
+ modelPacks: Record<string, {
29
+ source: string;
30
+ version: string;
31
+ }>;
32
+ connections: Record<string, {
33
+ package: string;
34
+ connector?: string | undefined;
35
+ sourceId: string;
36
+ tenantId: string;
37
+ binding?: string | undefined;
38
+ settings: Record<string, unknown>;
39
+ }>;
40
+ flows: unknown[];
41
+ providers: {
42
+ packages: Record<string, {
43
+ source: string;
44
+ version: string;
45
+ }>;
46
+ packs: Record<string, {
47
+ source: string;
48
+ version: string;
49
+ }>;
50
+ configurations: {
51
+ [k: string]: {
52
+ package: string;
53
+ binding: string;
54
+ packs: string[];
55
+ options: any;
56
+ };
57
+ };
58
+ };
59
+ environments: {
60
+ [environment]: any;
61
+ };
62
+ };
63
+ flows: {
64
+ apiVersion: "ingestron.flow/v1";
65
+ kind: "ingestion" | "transformation";
66
+ id: string;
67
+ description?: string | undefined;
68
+ domain?: string | undefined;
69
+ export?: string | undefined;
70
+ product?: string | undefined;
71
+ provider?: string | undefined;
72
+ defaults: {
73
+ source: Record<string, unknown>;
74
+ with: Record<string, unknown>;
75
+ };
76
+ ingestion?: Record<string, unknown> | undefined;
77
+ tables?: Record<string, {
78
+ source: Record<string, unknown>;
79
+ contract: Record<string, unknown>;
80
+ ingestion?: Record<string, unknown> | undefined;
81
+ steps: Record<string, {
82
+ with: Record<string, unknown>;
83
+ }>;
84
+ }> | undefined;
85
+ steps?: {
86
+ id: string;
87
+ uses: string;
88
+ provider?: string | undefined;
89
+ with: Record<string, unknown>;
90
+ select?: string[] | undefined;
91
+ }[] | undefined;
92
+ requires: Record<string, {
93
+ dataset: string;
94
+ handover?: "files" | "relation" | undefined;
95
+ select: {
96
+ mode: "same-run";
97
+ };
98
+ }>;
99
+ publishes: Record<string, {
100
+ from: string;
101
+ contract: Record<string, unknown>;
102
+ location?: Record<string, unknown> | undefined;
103
+ }>;
104
+ }[];
105
+ profile: any;
106
+ context: {
107
+ env: string;
108
+ project: {
109
+ id: string;
110
+ };
111
+ values: any;
112
+ };
113
+ };
114
+ export declare function planProject(root: string, environment?: string, options?: Select): Plan;