@habitat-ai/service 0.2.4 → 0.3.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.
@@ -599,21 +599,22 @@ const check = module.check.effect(function* ({ context, input }) {
599
599
  };
600
600
  }
601
601
  }
602
- const reports = [];
603
- for (const prepared of preparations) {
602
+ const reports = new Array(preparations.length);
603
+ const readyGritEvaluations = [];
604
+ for (const [applicationIndex, prepared] of preparations.entries()) {
604
605
  if (prepared.kind === "structure") {
605
606
  if (prepared.preparation.kind === "failed") {
606
- reports.push(prepared.preparation.report);
607
+ reports[applicationIndex] = prepared.preparation.report;
607
608
  continue;
608
609
  }
609
610
  const admitted = prepared.preparation.value;
610
611
  const application = admitted.application;
611
612
  if (admitted.scopes.length === 0) {
612
- reports.push(evaluatedStructureApplication(application, []));
613
+ reports[applicationIndex] = evaluatedStructureApplication(application, []);
613
614
  continue;
614
615
  }
615
616
  if (inventoryPreparation.kind === "failed") {
616
- reports.push(failedStructureApplication(application, "InventoryFailed", inventoryPreparation.detail));
617
+ reports[applicationIndex] = failedStructureApplication(application, "InventoryFailed", inventoryPreparation.detail);
617
618
  continue;
618
619
  }
619
620
  if (inventoryPreparation.kind === "not-required") {
@@ -627,50 +628,87 @@ const check = module.check.effect(function* ({ context, input }) {
627
628
  return yield* prepareStructureObservations(structureChildObservationPaths(plan, roots.kinds), roots.kinds);
628
629
  });
629
630
  if (observations.kind === "failed") {
630
- reports.push(failedStructureApplication(application, "StructureObservationFailed", observations.detail));
631
+ reports[applicationIndex] = failedStructureApplication(application, "StructureObservationFailed", observations.detail);
631
632
  continue;
632
633
  }
633
- reports.push(evaluatedStructureApplication(application, evaluateStructurePlan(plan, observations.kinds)));
634
+ reports[applicationIndex] = evaluatedStructureApplication(application, evaluateStructurePlan(plan, observations.kinds));
634
635
  continue;
635
636
  }
636
637
  const application = prepared.application;
637
638
  const patternAttempt = yield* Effect.result(context.fileSystem.readFileString(application.runner.pattern.absolutePath));
638
639
  if (patternAttempt._tag === "Failure") {
639
- reports.push(failedApplication(application, "PatternReadFailed", `Unable to read pattern asset "${application.runner.pattern.relativePath}".`));
640
+ reports[applicationIndex] = failedApplication(application, "PatternReadFailed", `Unable to read pattern asset "${application.runner.pattern.relativePath}".`);
640
641
  continue;
641
642
  }
642
643
  const program = extractGritProgram(patternAttempt.success);
643
644
  if (!program.ok) {
644
- reports.push(failedApplication(application, "PatternInvalid", program.detail));
645
+ reports[applicationIndex] = failedApplication(application, "PatternInvalid", program.detail);
645
646
  continue;
646
647
  }
647
648
  const subjectPreparation = yield* prepareGritSubjects(application, context.workspaceRoot, context.fileSystem, context.path);
648
649
  if (subjectPreparation.kind === "failed") {
649
- reports.push(failedApplication(application, "SetupFailed", subjectPreparation.detail));
650
+ reports[applicationIndex] = failedApplication(application, "SetupFailed", subjectPreparation.detail);
650
651
  continue;
651
652
  }
652
653
  if (subjectPreparation.kind === "not-applicable") {
653
654
  if (!isCompatibilityRule(application)) {
654
655
  return yield* Effect.die(new Error("Version-three Grit applications cannot be not applicable."));
655
656
  }
656
- reports.push(notApplicableApplication(application));
657
+ reports[applicationIndex] = notApplicableApplication(application);
657
658
  continue;
658
659
  }
659
660
  const subjects = subjectPreparation.subjects;
660
- const evaluation = yield* Effect.result(context.ruleEvaluation.evaluate({
661
+ readyGritEvaluations.push({
662
+ applicationIndex,
663
+ application,
664
+ programId: `application-${applicationIndex}`,
661
665
  program: program.program,
666
+ subjects,
662
667
  subjectPaths: subjects.map((subject) => subject.absolutePath),
668
+ });
669
+ }
670
+ for (const evaluations of groupGritEvaluations(readyGritEvaluations)) {
671
+ const first = evaluations[0];
672
+ if (first === undefined) {
673
+ return yield* Effect.die(new Error("A Grit evaluation group must not be empty."));
674
+ }
675
+ const evaluation = yield* Effect.result(context.ruleEvaluation.evaluate({
676
+ programs: evaluations.map(({ programId, program }) => ({ id: programId, program })),
677
+ subjectPaths: first.subjectPaths,
663
678
  }));
664
679
  if (evaluation._tag === "Failure") {
665
- reports.push(failedApplication(application, evaluation.failure.reason, evaluation.failure.detail));
680
+ for (const prepared of evaluations) {
681
+ reports[prepared.applicationIndex] = failedApplication(prepared.application, evaluation.failure.reason, evaluation.failure.detail);
682
+ }
666
683
  continue;
667
684
  }
668
- const normalized = normalizeFindings(evaluation.success.findings, subjects, context.workspaceRoot, context.path);
669
- reports.push(normalized.ok
670
- ? evaluatedApplication(application, normalized.findings)
671
- : failedApplication(application, "FindingPathInvalid", normalized.detail));
685
+ const resultMismatch = evaluationResultMismatch(evaluations, evaluation.success.results);
686
+ if (resultMismatch !== undefined) {
687
+ for (const prepared of evaluations) {
688
+ reports[prepared.applicationIndex] = failedApplication(prepared.application, "InvalidOutput", resultMismatch);
689
+ }
690
+ continue;
691
+ }
692
+ const results = indexEvaluationResults(evaluation.success.results);
693
+ for (const prepared of evaluations) {
694
+ const result = results.get(prepared.programId);
695
+ if (result === undefined) {
696
+ return yield* Effect.die(new Error(`Validated evaluation result '${prepared.programId}' is absent.`));
697
+ }
698
+ const normalized = normalizeFindings(result.findings, prepared.subjects, context.workspaceRoot, context.path);
699
+ reports[prepared.applicationIndex] = normalized.ok
700
+ ? evaluatedApplication(prepared.application, normalized.findings)
701
+ : failedApplication(prepared.application, "FindingPathInvalid", normalized.detail);
702
+ }
703
+ }
704
+ const completedReports = [];
705
+ for (const [applicationIndex, report] of reports.entries()) {
706
+ if (report === undefined) {
707
+ return yield* Effect.die(new Error(`Habitat check produced no report for application ${applicationIndex}.`));
708
+ }
709
+ completedReports.push(report);
672
710
  }
673
- return completedCheck(reports);
711
+ return completedCheck(completedReports);
674
712
  });
675
713
  /** Grouped catalog operation tree consumed by the module composition face. */
676
714
  export const catalog = { resolve, check };
@@ -707,6 +745,34 @@ function hasExactCauseCode(error, code) {
707
745
  const cause = "cause" in error.reason ? error.reason.cause : undefined;
708
746
  return typeof cause === "object" && cause !== null && "code" in cause && cause.code === code;
709
747
  }
748
+ function groupGritEvaluations(evaluations) {
749
+ const groups = new Map();
750
+ for (const evaluation of evaluations) {
751
+ const key = JSON.stringify(evaluation.subjectPaths);
752
+ const group = groups.get(key);
753
+ if (group === undefined)
754
+ groups.set(key, [evaluation]);
755
+ else
756
+ group.push(evaluation);
757
+ }
758
+ return [...groups.values()];
759
+ }
760
+ function evaluationResultMismatch(evaluations, results) {
761
+ if (results.length !== evaluations.length) {
762
+ return `Rule evaluator returned ${results.length} program results for ${evaluations.length} requested programs.`;
763
+ }
764
+ const uniqueIds = new Set(results.map(({ programId }) => programId));
765
+ if (uniqueIds.size !== results.length) {
766
+ return "Rule evaluator returned duplicate program result identities.";
767
+ }
768
+ const unexpected = results.find((result, index) => result.programId !== evaluations[index]?.programId);
769
+ return unexpected === undefined
770
+ ? undefined
771
+ : "Rule evaluator did not preserve requested program result order and identity.";
772
+ }
773
+ function indexEvaluationResults(results) {
774
+ return new Map(results.map((result) => [result.programId, result]));
775
+ }
710
776
  function prepareGritSubjects(application, workspaceRoot, fileSystem, path) {
711
777
  if (!isCompatibilityRule(application)) {
712
778
  return Effect.succeed({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@habitat-ai/service",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,8 +24,8 @@
24
24
  "test": "bun test test"
25
25
  },
26
26
  "dependencies": {
27
- "@habitat-ai/resource-rule-evaluation": "0.2.4",
28
- "@habitat-ai/resource-source-inventory": "0.2.4",
27
+ "@habitat-ai/resource-rule-evaluation": "0.3.0",
28
+ "@habitat-ai/resource-source-inventory": "0.3.0",
29
29
  "@opentelemetry/api": "^1.9.0",
30
30
  "@orpc/contract": "2.0.0-beta.20",
31
31
  "@orpc/experimental-effect": "2.0.0-beta.20",