@habitat-ai/service 0.2.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 (33) hide show
  1. package/dist/client.d.ts +16 -0
  2. package/dist/client.js +16 -0
  3. package/dist/service/base.d.ts +28 -0
  4. package/dist/service/base.js +1 -0
  5. package/dist/service/contract.d.ts +10 -0
  6. package/dist/service/contract.js +5 -0
  7. package/dist/service/impl.d.ts +508 -0
  8. package/dist/service/impl.js +7 -0
  9. package/dist/service/modules/catalog/contract/catalog.d.ts +252 -0
  10. package/dist/service/modules/catalog/contract/catalog.js +13 -0
  11. package/dist/service/modules/catalog/contract/index.d.ts +3 -0
  12. package/dist/service/modules/catalog/contract/index.js +3 -0
  13. package/dist/service/modules/catalog/model/dto/catalog.d.ts +276 -0
  14. package/dist/service/modules/catalog/model/dto/catalog.js +380 -0
  15. package/dist/service/modules/catalog/model/dto/check.d.ts +104 -0
  16. package/dist/service/modules/catalog/model/dto/check.js +305 -0
  17. package/dist/service/modules/catalog/model/dto/structure.d.ts +23 -0
  18. package/dist/service/modules/catalog/model/dto/structure.js +93 -0
  19. package/dist/service/modules/catalog/model/policy/catalog.d.ts +109 -0
  20. package/dist/service/modules/catalog/model/policy/catalog.js +607 -0
  21. package/dist/service/modules/catalog/model/policy/check.d.ts +57 -0
  22. package/dist/service/modules/catalog/model/policy/check.js +207 -0
  23. package/dist/service/modules/catalog/model/policy/structure.d.ts +64 -0
  24. package/dist/service/modules/catalog/model/policy/structure.js +249 -0
  25. package/dist/service/modules/catalog/module.d.ts +521 -0
  26. package/dist/service/modules/catalog/module.js +12 -0
  27. package/dist/service/modules/catalog/router/catalog.router.d.ts +266 -0
  28. package/dist/service/modules/catalog/router/catalog.router.js +659 -0
  29. package/dist/service/modules/catalog/router.d.ts +265 -0
  30. package/dist/service/modules/catalog/router.js +3 -0
  31. package/dist/service/router.d.ts +267 -0
  32. package/dist/service/router.js +4 -0
  33. package/package.json +49 -0
@@ -0,0 +1,659 @@
1
+ import { MAX_SOURCE_INVENTORY_ENTRIES } from "@habitat-ai/resource-source-inventory";
2
+ import { Effect } from "effect";
3
+ import { parse as parseToml } from "smol-toml";
4
+ import { admitBlueprintSource, admitCompatibilityIndex, admitCompatibilityRule, admitInstanceSource, admitPolicyPackManifest, admitPolicyPackPackageJson, admitPolicyPackSelection, referencedRepositoryPaths, rejected, resolveCatalog, } from "../model/policy/catalog.js";
5
+ import { completedCheck, evaluatedApplication, evaluatedStructureApplication, extractGritProgram, failedApplication, failedStructureApplication, selectCheckApplications, } from "../model/policy/check.js";
6
+ import { admitStructureDocument, evaluateStructurePlan, isHabitatStructureApplication, makeStructureUniverse, planStructureEvaluation, structureChildObservationPaths, } from "../model/policy/structure.js";
7
+ import { module } from "../module.js";
8
+ const authorityGlobs = [
9
+ { kind: "blueprint", pattern: ".habitat/blueprints/*/blueprint.toml" },
10
+ // Effect's glob provider requires a dynamic pattern even for this exact optional file.
11
+ { kind: "index", pattern: ".habitat/{index.json}" },
12
+ { kind: "rule", pattern: ".habitat/**/rule.json" },
13
+ ];
14
+ const excludedDirectorySegments = new Set([
15
+ ".git",
16
+ ".nx",
17
+ ".turbo",
18
+ "build",
19
+ "coverage",
20
+ "dist",
21
+ "generated",
22
+ "node_modules",
23
+ "vendor",
24
+ ]);
25
+ const excludedRepositoryGlobs = [...excludedDirectorySegments].map((segment) => `**/${segment}/**`);
26
+ const MAX_MANIFEST_DIRECTORIES = 50_000;
27
+ function resolveCurrentCatalog(context) {
28
+ return Effect.gen(function* () {
29
+ const { fileSystem, path } = context;
30
+ const workspaceRoot = context.workspaceRoot;
31
+ const selectedPolicyPack = admitPolicyPackSelection(context.policyPack, path);
32
+ if (!selectedPolicyPack.ok)
33
+ return rejected(selectedPolicyPack.issues);
34
+ const selection = selectedPolicyPack.selection;
35
+ const packageSourcePath = `${selection.name}/package.json`;
36
+ const manifestSourcePath = `${selection.name}/habitat-pack.json`;
37
+ const packageRead = yield* Effect.result(fileSystem.readFileString(selection.packageJsonPath));
38
+ const manifestRead = yield* Effect.result(fileSystem.readFileString(selection.manifestPath));
39
+ const policyPackIssues = [];
40
+ if (packageRead._tag === "Failure") {
41
+ policyPackIssues.push(filesystemIssue(packageRead.failure, packageSourcePath, "read selected policy-pack package.json", "Selected policy-pack package.json does not exist."));
42
+ }
43
+ if (manifestRead._tag === "Failure") {
44
+ policyPackIssues.push(filesystemIssue(manifestRead.failure, manifestSourcePath, "read selected policy-pack manifest", "Selected policy-pack manifest does not exist."));
45
+ }
46
+ if (packageRead._tag === "Failure" || manifestRead._tag === "Failure") {
47
+ return rejected(policyPackIssues);
48
+ }
49
+ const parsedPackage = yield* Effect.result(Effect.try({ try: () => JSON.parse(packageRead.success), catch: (cause) => cause }));
50
+ const parsedManifest = yield* Effect.result(Effect.try({
51
+ try: () => JSON.parse(manifestRead.success),
52
+ catch: (cause) => cause,
53
+ }));
54
+ if (parsedPackage._tag === "Failure") {
55
+ policyPackIssues.push({
56
+ code: "authority-json-invalid",
57
+ path: packageSourcePath,
58
+ message: "Selected policy-pack package.json is not valid JSON.",
59
+ });
60
+ }
61
+ if (parsedManifest._tag === "Failure") {
62
+ policyPackIssues.push({
63
+ code: "authority-json-invalid",
64
+ path: manifestSourcePath,
65
+ message: "Selected policy-pack manifest is not valid JSON.",
66
+ });
67
+ }
68
+ if (parsedPackage._tag === "Failure" || parsedManifest._tag === "Failure") {
69
+ return rejected(policyPackIssues);
70
+ }
71
+ const admittedPackage = admitPolicyPackPackageJson(parsedPackage.success, selection.name, packageSourcePath);
72
+ const admittedManifest = admitPolicyPackManifest(parsedManifest.success, manifestSourcePath);
73
+ if (!admittedPackage.ok)
74
+ policyPackIssues.push(...admittedPackage.issues);
75
+ if (!admittedManifest.ok)
76
+ policyPackIssues.push(...admittedManifest.issues);
77
+ if (!admittedPackage.ok || !admittedManifest.ok)
78
+ return rejected(policyPackIssues);
79
+ const policyPack = {
80
+ name: admittedPackage.value.name,
81
+ version: admittedPackage.value.version,
82
+ protocolVersion: admittedManifest.value.protocolVersion,
83
+ blueprints: [],
84
+ };
85
+ if (!path.isAbsolute(workspaceRoot)) {
86
+ return rejected([
87
+ {
88
+ code: "authority-workspace-root-invalid",
89
+ path: workspaceRoot,
90
+ message: "workspaceRoot must be absolute.",
91
+ },
92
+ ]);
93
+ }
94
+ const workspaceRealPathAttempt = yield* Effect.result(fileSystem.realPath(workspaceRoot));
95
+ if (workspaceRealPathAttempt._tag === "Failure") {
96
+ return rejected([
97
+ filesystemIssue(workspaceRealPathAttempt.failure, workspaceRoot, "resolve workspaceRoot", "workspaceRoot does not exist."),
98
+ ]);
99
+ }
100
+ const workspaceRealRoot = workspaceRealPathAttempt.success;
101
+ const excludes = excludedRepositoryGlobs.map((pattern) => path.resolve(workspaceRoot, pattern));
102
+ const globResults = yield* Effect.all(authorityGlobs.map(({ kind, pattern }) => Effect.result(fileSystem.glob(path.resolve(workspaceRoot, pattern), { exclude: excludes })).pipe(Effect.map((result) => ({ kind, result })))));
103
+ const enumerationIssues = [];
104
+ const pathsByKind = {
105
+ blueprint: [],
106
+ index: [],
107
+ rule: [],
108
+ };
109
+ for (const { kind, result } of globResults) {
110
+ if (result._tag === "Failure") {
111
+ if (isNotFound(result.failure))
112
+ continue;
113
+ enumerationIssues.push(filesystemIssue(result.failure, authorityGlobs.find((candidate) => candidate.kind === kind)?.pattern ?? kind, "enumerate authority", "Authority enumeration root does not exist."));
114
+ continue;
115
+ }
116
+ for (const candidate of result.success) {
117
+ const absolutePath = path.resolve(workspaceRoot, candidate);
118
+ const relativePath = toRepositoryPath(path.relative(workspaceRoot, absolutePath), path.sep);
119
+ pathsByKind[kind].push(relativePath);
120
+ }
121
+ }
122
+ // The installed glob provider skips hidden directories, so manifests use a confined traversal.
123
+ const manifestCandidates = [];
124
+ const pendingDirectories = [{ relativePath: "", realPath: workspaceRealRoot }];
125
+ const visitedDirectories = new Set([workspaceRealRoot]);
126
+ let manifestDirectoryIndex = 0;
127
+ let manifestDirectoryBoundReached = false;
128
+ while (manifestDirectoryIndex < pendingDirectories.length && !manifestDirectoryBoundReached) {
129
+ const directory = pendingDirectories[manifestDirectoryIndex];
130
+ manifestDirectoryIndex += 1;
131
+ if (directory === undefined)
132
+ break;
133
+ const readDirectoryAttempt = yield* Effect.result(fileSystem.readDirectory(directory.realPath));
134
+ if (readDirectoryAttempt._tag === "Failure") {
135
+ if (directory.relativePath !== "" && isNotFound(readDirectoryAttempt.failure))
136
+ continue;
137
+ enumerationIssues.push(filesystemIssue(readDirectoryAttempt.failure, directory.relativePath || ".", "enumerate instance authority", "Manifest enumeration directory does not exist."));
138
+ continue;
139
+ }
140
+ for (const entry of stablePaths(readDirectoryAttempt.success)) {
141
+ if (excludedDirectorySegments.has(entry))
142
+ continue;
143
+ const relativePath = toRepositoryPath(directory.relativePath === "" ? entry : `${directory.relativePath}/${entry}`, path.sep);
144
+ const absolutePath = path.resolve(workspaceRoot, relativePath);
145
+ const statAttempt = yield* Effect.result(fileSystem.stat(absolutePath));
146
+ if (statAttempt._tag === "Failure") {
147
+ if (isNotFound(statAttempt.failure))
148
+ continue;
149
+ enumerationIssues.push(filesystemIssue(statAttempt.failure, relativePath, "inspect manifest enumeration entry", "Manifest enumeration entry does not exist."));
150
+ continue;
151
+ }
152
+ if (statAttempt.success.type === "File") {
153
+ if (entry === "habitat.toml" && !relativePath.startsWith(".habitat/blueprints/")) {
154
+ manifestCandidates.push(relativePath);
155
+ }
156
+ continue;
157
+ }
158
+ if (statAttempt.success.type !== "Directory")
159
+ continue;
160
+ const realPathAttempt = yield* Effect.result(fileSystem.realPath(absolutePath));
161
+ if (realPathAttempt._tag === "Failure") {
162
+ if (isNotFound(realPathAttempt.failure))
163
+ continue;
164
+ enumerationIssues.push(filesystemIssue(realPathAttempt.failure, relativePath, "resolve manifest enumeration directory", "Manifest enumeration directory does not exist."));
165
+ continue;
166
+ }
167
+ if (!isContained(workspaceRealRoot, realPathAttempt.success, path))
168
+ continue;
169
+ if (visitedDirectories.has(realPathAttempt.success))
170
+ continue;
171
+ if (pendingDirectories.length >= MAX_MANIFEST_DIRECTORIES) {
172
+ enumerationIssues.push({
173
+ code: "authority-resolution-failed",
174
+ path: relativePath,
175
+ message: `Manifest enumeration exceeded ${MAX_MANIFEST_DIRECTORIES} directories.`,
176
+ });
177
+ manifestDirectoryBoundReached = true;
178
+ break;
179
+ }
180
+ visitedDirectories.add(realPathAttempt.success);
181
+ pendingDirectories.push({ relativePath, realPath: realPathAttempt.success });
182
+ }
183
+ }
184
+ if (enumerationIssues.length > 0)
185
+ return rejected(enumerationIssues);
186
+ const blueprintPaths = stablePaths(pathsByKind.blueprint);
187
+ const manifestPaths = stablePaths(manifestCandidates);
188
+ const indexPaths = stablePaths(pathsByKind.index).filter((candidate) => candidate === ".habitat/index.json");
189
+ const compatibilityRulePaths = indexPaths.length === 0 ? [] : stablePaths(pathsByKind.rule);
190
+ const authorityPaths = stablePaths([
191
+ ...blueprintPaths,
192
+ ...manifestPaths,
193
+ ...indexPaths,
194
+ ...compatibilityRulePaths,
195
+ ]);
196
+ const issues = [];
197
+ const sourceText = new Map();
198
+ for (const relativePath of authorityPaths) {
199
+ const absolutePath = path.resolve(workspaceRoot, relativePath);
200
+ if (!isContained(workspaceRoot, absolutePath, path)) {
201
+ issues.push({
202
+ code: "authority-path-escape",
203
+ path: relativePath,
204
+ message: "Authority document escapes workspaceRoot.",
205
+ });
206
+ continue;
207
+ }
208
+ const realPathAttempt = yield* Effect.result(fileSystem.realPath(absolutePath));
209
+ if (realPathAttempt._tag === "Failure") {
210
+ issues.push(filesystemIssue(realPathAttempt.failure, relativePath, "resolve authority document", "Authority document does not exist."));
211
+ continue;
212
+ }
213
+ if (!isContained(workspaceRealRoot, realPathAttempt.success, path)) {
214
+ issues.push({
215
+ code: "authority-path-escape",
216
+ path: relativePath,
217
+ message: "Authority document escapes workspaceRoot through a symbolic link.",
218
+ });
219
+ continue;
220
+ }
221
+ const statAttempt = yield* Effect.result(fileSystem.stat(realPathAttempt.success));
222
+ if (statAttempt._tag === "Failure") {
223
+ issues.push(filesystemIssue(statAttempt.failure, relativePath, "inspect authority document", "Authority document does not exist."));
224
+ continue;
225
+ }
226
+ if (statAttempt.success.type !== "File") {
227
+ issues.push({
228
+ code: "authority-path-kind-mismatch",
229
+ path: relativePath,
230
+ message: "Authority document must be a regular file.",
231
+ });
232
+ continue;
233
+ }
234
+ const readAttempt = yield* Effect.result(fileSystem.readFileString(realPathAttempt.success));
235
+ if (readAttempt._tag === "Failure") {
236
+ issues.push(filesystemIssue(readAttempt.failure, relativePath, "read authority document", "Authority document does not exist."));
237
+ continue;
238
+ }
239
+ sourceText.set(relativePath, readAttempt.success);
240
+ }
241
+ if (issues.length > 0)
242
+ return rejected(issues);
243
+ const blueprints = [];
244
+ for (const relativePath of blueprintPaths) {
245
+ const text = sourceText.get(relativePath);
246
+ if (text === undefined)
247
+ continue;
248
+ const parse = Effect.try({
249
+ try: () => parseToml(text),
250
+ catch: (cause) => cause,
251
+ });
252
+ const parsed = yield* Effect.result(parse);
253
+ if (parsed._tag === "Failure") {
254
+ issues.push({
255
+ code: "authority-toml-invalid",
256
+ path: relativePath,
257
+ message: "Blueprint authority is not valid TOML.",
258
+ });
259
+ continue;
260
+ }
261
+ const admitted = admitBlueprintSource(parsed.success, relativePath);
262
+ if (admitted.ok)
263
+ blueprints.push(admitted.source);
264
+ else
265
+ issues.push(...admitted.issues);
266
+ }
267
+ const manifests = [];
268
+ for (const relativePath of manifestPaths) {
269
+ const text = sourceText.get(relativePath);
270
+ if (text === undefined)
271
+ continue;
272
+ const parse = Effect.try({
273
+ try: () => parseToml(text),
274
+ catch: (cause) => cause,
275
+ });
276
+ const parsed = yield* Effect.result(parse);
277
+ if (parsed._tag === "Failure") {
278
+ issues.push({
279
+ code: "authority-toml-invalid",
280
+ path: relativePath,
281
+ message: "Instance authority is not valid TOML.",
282
+ });
283
+ continue;
284
+ }
285
+ const admitted = admitInstanceSource(parsed.success, relativePath);
286
+ if (admitted.ok)
287
+ manifests.push(admitted.source);
288
+ else
289
+ issues.push(...admitted.issues);
290
+ }
291
+ let compatibilityIndex;
292
+ const compatibilityIndexPath = indexPaths[0];
293
+ if (compatibilityIndexPath !== undefined) {
294
+ const text = sourceText.get(compatibilityIndexPath);
295
+ if (text !== undefined) {
296
+ const parse = Effect.try({
297
+ try: () => JSON.parse(text),
298
+ catch: (cause) => cause,
299
+ });
300
+ const parsed = yield* Effect.result(parse);
301
+ if (parsed._tag === "Failure") {
302
+ issues.push({
303
+ code: "authority-json-invalid",
304
+ path: compatibilityIndexPath,
305
+ message: "Compatibility index is not valid JSON.",
306
+ });
307
+ }
308
+ else {
309
+ const admitted = admitCompatibilityIndex(parsed.success, compatibilityIndexPath);
310
+ if (admitted.ok)
311
+ compatibilityIndex = admitted.value;
312
+ else
313
+ issues.push(...admitted.issues);
314
+ }
315
+ }
316
+ }
317
+ const compatibilityRules = [];
318
+ for (const relativePath of compatibilityRulePaths) {
319
+ const text = sourceText.get(relativePath);
320
+ if (text === undefined)
321
+ continue;
322
+ const parse = Effect.try({
323
+ try: () => JSON.parse(text),
324
+ catch: (cause) => cause,
325
+ });
326
+ const parsed = yield* Effect.result(parse);
327
+ if (parsed._tag === "Failure") {
328
+ issues.push({
329
+ code: "authority-json-invalid",
330
+ path: relativePath,
331
+ message: "Compatibility rule manifest is not valid JSON.",
332
+ });
333
+ continue;
334
+ }
335
+ const admitted = admitCompatibilityRule(parsed.success, relativePath);
336
+ if (admitted.ok)
337
+ compatibilityRules.push(admitted.source);
338
+ else
339
+ issues.push(...admitted.issues);
340
+ }
341
+ if (issues.length > 0)
342
+ return rejected(issues);
343
+ const documents = {
344
+ policyPack,
345
+ blueprints,
346
+ manifests,
347
+ compatibilityIndex,
348
+ compatibilityRules,
349
+ };
350
+ const pathFacts = new Map();
351
+ for (const relativePath of referencedRepositoryPaths(documents, path)) {
352
+ const absolutePath = path.resolve(workspaceRoot, relativePath);
353
+ const statAttempt = yield* Effect.result(fileSystem.stat(absolutePath));
354
+ if (statAttempt._tag === "Failure") {
355
+ const notFound = isNotFound(statAttempt.failure);
356
+ pathFacts.set(relativePath, {
357
+ relativePath,
358
+ absolutePath,
359
+ kind: notFound ? "missing" : "other",
360
+ detail: notFound
361
+ ? `Admitted path does not exist: "${relativePath}".`
362
+ : `Unable to inspect admitted path "${relativePath}".`,
363
+ filesystemError: !notFound,
364
+ });
365
+ continue;
366
+ }
367
+ const kind = statAttempt.success.type === "Directory"
368
+ ? "directory"
369
+ : statAttempt.success.type === "File"
370
+ ? "file"
371
+ : "other";
372
+ const realPathAttempt = yield* Effect.result(fileSystem.realPath(absolutePath));
373
+ if (realPathAttempt._tag === "Failure") {
374
+ const notFound = isNotFound(realPathAttempt.failure);
375
+ pathFacts.set(relativePath, {
376
+ relativePath,
377
+ absolutePath,
378
+ kind: notFound ? "missing" : kind,
379
+ detail: notFound
380
+ ? `Admitted path does not exist: "${relativePath}".`
381
+ : `Unable to resolve admitted path "${relativePath}".`,
382
+ filesystemError: !notFound,
383
+ });
384
+ continue;
385
+ }
386
+ pathFacts.set(relativePath, {
387
+ relativePath,
388
+ absolutePath,
389
+ kind,
390
+ realPath: realPathAttempt.success,
391
+ });
392
+ }
393
+ return resolveCatalog(documents, pathFacts, workspaceRoot, workspaceRealRoot, path);
394
+ });
395
+ }
396
+ /**
397
+ * Catalog authority operations share one current-repository resolution boundary.
398
+ *
399
+ * `resolve` returns that admitted authority directly. `check` consumes the same
400
+ * resolution, selects applications, and invokes only ready host resources.
401
+ */
402
+ const resolve = module.resolve.effect(function* ({ context }) {
403
+ return yield* resolveCurrentCatalog(context);
404
+ });
405
+ const check = module.check.effect(function* ({ context, input }) {
406
+ const resolved = yield* resolveCurrentCatalog(context);
407
+ if (resolved._tag === "Rejected") {
408
+ return catalogRejected(resolved.issues);
409
+ }
410
+ const selection = selectCheckApplications(resolved.catalog, input);
411
+ if (!selection.ok) {
412
+ return selectionRejected(selection.issues);
413
+ }
414
+ const observedKinds = new Map();
415
+ const observeStructurePathKind = (relativePath) => Effect.gen(function* () {
416
+ const absolutePath = context.path.resolve(context.workspaceRoot, relativePath);
417
+ const linkAttempt = yield* Effect.result(context.fileSystem.readLink(absolutePath));
418
+ if (linkAttempt._tag === "Success") {
419
+ return { ok: true, kind: "other" };
420
+ }
421
+ if (isNotFound(linkAttempt.failure) || hasExactCauseCode(linkAttempt.failure, "ENOTDIR")) {
422
+ return { ok: true, kind: "missing" };
423
+ }
424
+ if (!hasExactCauseCode(linkAttempt.failure, "EINVAL")) {
425
+ return {
426
+ ok: false,
427
+ detail: `Unable to inspect structure path "${relativePath || "."}".`,
428
+ };
429
+ }
430
+ const statAttempt = yield* Effect.result(context.fileSystem.stat(absolutePath));
431
+ if (statAttempt._tag === "Failure") {
432
+ if (isNotFound(statAttempt.failure) || hasExactCauseCode(statAttempt.failure, "ENOTDIR")) {
433
+ return { ok: true, kind: "missing" };
434
+ }
435
+ return {
436
+ ok: false,
437
+ detail: `Unable to inspect structure path "${relativePath || "."}".`,
438
+ };
439
+ }
440
+ const kind = statAttempt.success.type === "Directory"
441
+ ? "directory"
442
+ : statAttempt.success.type === "File"
443
+ ? "file"
444
+ : "other";
445
+ return { ok: true, kind };
446
+ });
447
+ const prepareStructureObservations = (relativePaths, kinds) => Effect.gen(function* () {
448
+ for (const relativePath of relativePaths) {
449
+ let observation = observedKinds.get(relativePath);
450
+ if (observation === undefined) {
451
+ observation = yield* observeStructurePathKind(relativePath);
452
+ observedKinds.set(relativePath, observation);
453
+ }
454
+ if (!observation.ok) {
455
+ return {
456
+ kind: "failed",
457
+ detail: observation.detail,
458
+ };
459
+ }
460
+ kinds.set(relativePath, observation.kind);
461
+ }
462
+ return { kind: "ready", kinds };
463
+ });
464
+ const preparations = [];
465
+ for (const application of selection.applications) {
466
+ if (!isHabitatStructureApplication(application)) {
467
+ preparations.push({ kind: "grit", application });
468
+ continue;
469
+ }
470
+ const structureAttempt = yield* Effect.result(context.fileSystem.readFileString(application.runner.structure.absolutePath));
471
+ if (structureAttempt._tag === "Failure") {
472
+ preparations.push({
473
+ kind: "structure",
474
+ preparation: {
475
+ kind: "failed",
476
+ report: failedStructureApplication(application, "StructureReadFailed", `Unable to read structure asset "${application.runner.structure.relativePath}".`),
477
+ },
478
+ });
479
+ continue;
480
+ }
481
+ const parseAttempt = yield* Effect.result(Effect.try({ try: () => parseToml(structureAttempt.success), catch: (cause) => cause }));
482
+ if (parseAttempt._tag === "Failure") {
483
+ preparations.push({
484
+ kind: "structure",
485
+ preparation: {
486
+ kind: "failed",
487
+ report: failedStructureApplication(application, "StructureInvalid", `Invalid Habitat structure TOML in "${application.runner.structure.relativePath}".`),
488
+ },
489
+ });
490
+ continue;
491
+ }
492
+ const admission = admitStructureDocument(parseAttempt.success, application);
493
+ if (!admission.ok) {
494
+ preparations.push({
495
+ kind: "structure",
496
+ preparation: {
497
+ kind: "failed",
498
+ report: failedStructureApplication(application, "StructureInvalid", admission.detail),
499
+ },
500
+ });
501
+ continue;
502
+ }
503
+ preparations.push({
504
+ kind: "structure",
505
+ preparation: { kind: "admitted", value: admission.admitted },
506
+ });
507
+ }
508
+ const needsInventory = preparations.some((prepared) => prepared.kind === "structure" &&
509
+ prepared.preparation.kind === "admitted" &&
510
+ prepared.preparation.value.scopes.length > 0);
511
+ let inventoryPreparation = { kind: "not-required" };
512
+ if (needsInventory) {
513
+ const inventoryAttempt = yield* Effect.result(context.sourceInventory.observe({
514
+ root: context.workspaceRoot,
515
+ maxEntries: MAX_SOURCE_INVENTORY_ENTRIES,
516
+ }));
517
+ if (inventoryAttempt._tag === "Failure") {
518
+ inventoryPreparation = {
519
+ kind: "failed",
520
+ detail: `Source inventory failed (${inventoryAttempt.failure.reason}): ${inventoryAttempt.failure.detail}`,
521
+ };
522
+ }
523
+ else {
524
+ inventoryPreparation = {
525
+ kind: "ready",
526
+ universe: makeStructureUniverse(inventoryAttempt.success),
527
+ };
528
+ }
529
+ }
530
+ const reports = [];
531
+ for (const prepared of preparations) {
532
+ if (prepared.kind === "structure") {
533
+ if (prepared.preparation.kind === "failed") {
534
+ reports.push(prepared.preparation.report);
535
+ continue;
536
+ }
537
+ const admitted = prepared.preparation.value;
538
+ const application = admitted.application;
539
+ if (admitted.scopes.length === 0) {
540
+ reports.push(evaluatedStructureApplication(application, []));
541
+ continue;
542
+ }
543
+ if (inventoryPreparation.kind === "failed") {
544
+ reports.push(failedStructureApplication(application, "InventoryFailed", inventoryPreparation.detail));
545
+ continue;
546
+ }
547
+ if (inventoryPreparation.kind === "not-required") {
548
+ return yield* Effect.die(new Error("Structure inventory was not prepared for an admitted bound scope."));
549
+ }
550
+ const plan = planStructureEvaluation(admitted, inventoryPreparation.universe);
551
+ const observations = yield* Effect.gen(function* () {
552
+ const roots = yield* prepareStructureObservations(plan.rootObservationPaths, new Map());
553
+ if (roots.kind === "failed")
554
+ return roots;
555
+ return yield* prepareStructureObservations(structureChildObservationPaths(plan, roots.kinds), roots.kinds);
556
+ });
557
+ if (observations.kind === "failed") {
558
+ reports.push(failedStructureApplication(application, "StructureObservationFailed", observations.detail));
559
+ continue;
560
+ }
561
+ reports.push(evaluatedStructureApplication(application, evaluateStructurePlan(plan, observations.kinds)));
562
+ continue;
563
+ }
564
+ const application = prepared.application;
565
+ const patternAttempt = yield* Effect.result(context.fileSystem.readFileString(application.runner.pattern.absolutePath));
566
+ if (patternAttempt._tag === "Failure") {
567
+ reports.push(failedApplication(application, "PatternReadFailed", `Unable to read pattern asset "${application.runner.pattern.relativePath}".`));
568
+ continue;
569
+ }
570
+ const program = extractGritProgram(patternAttempt.success);
571
+ if (!program.ok) {
572
+ reports.push(failedApplication(application, "PatternInvalid", program.detail));
573
+ continue;
574
+ }
575
+ const subjects = resolvedSubjects(application, context.workspaceRoot, context.path);
576
+ const evaluation = yield* Effect.result(context.ruleEvaluation.evaluate({
577
+ program: program.program,
578
+ subjectPaths: subjects.map((subject) => subject.absolutePath),
579
+ }));
580
+ if (evaluation._tag === "Failure") {
581
+ reports.push(failedApplication(application, evaluation.failure.reason, evaluation.failure.detail));
582
+ continue;
583
+ }
584
+ const normalized = normalizeFindings(evaluation.success.findings, subjects, context.workspaceRoot, context.path);
585
+ reports.push(normalized.ok
586
+ ? evaluatedApplication(application, normalized.findings)
587
+ : failedApplication(application, "FindingPathInvalid", normalized.detail));
588
+ }
589
+ return completedCheck(reports);
590
+ });
591
+ /** Grouped catalog operation tree consumed by the module composition face. */
592
+ export const catalog = { resolve, check };
593
+ function catalogRejected(issues) {
594
+ return { _tag: "CatalogRejected", issues: [...issues] };
595
+ }
596
+ function selectionRejected(issues) {
597
+ return { _tag: "SelectionRejected", issues: [...issues] };
598
+ }
599
+ function filesystemIssue(error, path, action, missingMessage) {
600
+ return isNotFound(error)
601
+ ? { code: "authority-path-missing", path, message: missingMessage }
602
+ : {
603
+ code: "authority-filesystem-failed",
604
+ path,
605
+ message: `Unable to ${action}.`,
606
+ };
607
+ }
608
+ function isNotFound(error) {
609
+ return error.reason._tag === "NotFound";
610
+ }
611
+ function stablePaths(paths) {
612
+ return [...new Set(paths)].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
613
+ }
614
+ function toRepositoryPath(value, separator) {
615
+ return separator === "/" ? value : value.split(separator).join("/");
616
+ }
617
+ function isContained(root, target, path) {
618
+ const candidate = path.relative(root, target);
619
+ return (candidate === "" ||
620
+ (candidate !== ".." && !candidate.startsWith(`..${path.sep}`) && !path.isAbsolute(candidate)));
621
+ }
622
+ function hasExactCauseCode(error, code) {
623
+ const cause = "cause" in error.reason ? error.reason.cause : undefined;
624
+ return typeof cause === "object" && cause !== null && "code" in cause && cause.code === code;
625
+ }
626
+ function resolvedSubjects(application, workspaceRoot, path) {
627
+ const subjects = new Map();
628
+ for (const entry of application.runner.acquisition.entries) {
629
+ const absolutePath = path.resolve(workspaceRoot, entry.path);
630
+ subjects.set(absolutePath, { absolutePath, kind: entry.kind });
631
+ }
632
+ return [...subjects.values()].sort((left, right) => left.absolutePath < right.absolutePath ? -1 : left.absolutePath > right.absolutePath ? 1 : 0);
633
+ }
634
+ function normalizeFindings(findings, subjects, workspaceRoot, path) {
635
+ const normalized = [];
636
+ for (const finding of findings) {
637
+ if (!path.isAbsolute(finding.path)) {
638
+ return {
639
+ ok: false,
640
+ detail: `Evaluator returned a non-absolute finding path: "${finding.path}".`,
641
+ };
642
+ }
643
+ const absolutePath = path.resolve(finding.path);
644
+ if (!isContained(workspaceRoot, absolutePath, path) ||
645
+ !subjects.some((subject) => subject.kind === "file"
646
+ ? absolutePath === subject.absolutePath
647
+ : isContained(subject.absolutePath, absolutePath, path))) {
648
+ return {
649
+ ok: false,
650
+ detail: `Evaluator returned a finding outside admitted subjects: "${finding.path}".`,
651
+ };
652
+ }
653
+ normalized.push({
654
+ ...finding,
655
+ path: toRepositoryPath(path.relative(workspaceRoot, absolutePath), path.sep),
656
+ });
657
+ }
658
+ return { ok: true, findings: normalized };
659
+ }