@elevasis/core 0.37.0 → 0.38.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.
@@ -29,7 +29,8 @@ import type {
29
29
  ResourceRelationships,
30
30
  ExternalResourceDefinition,
31
31
  HumanCheckpointDefinition,
32
- ResourceType
32
+ ResourceType,
33
+ ApiInterfaceConformanceGap
33
34
  } from './types'
34
35
 
35
36
  // ============================================================================
@@ -612,6 +613,64 @@ function addSystemInterfaceIssue(
612
613
  })
613
614
  }
614
615
 
616
+ /**
617
+ * Detects systems that are API-backed (have resources with non-empty ontology
618
+ * bindings) but have not declared `apiInterface` in the Organization Model.
619
+ *
620
+ * Broad-by-design — tighten via test-driven counterexample.
621
+ * See _execution-plan.mdx Decision 1.
622
+ *
623
+ * A system is flagged when:
624
+ * (a) `system.apiInterface === undefined`, AND
625
+ * (b) at least one resource in `organizationModel.resources` has
626
+ * `resource.systemPath` matching the system path AND
627
+ * `resource.ontology` non-empty (has `actions` and/or `writes` defined).
628
+ *
629
+ * Returns `[]` when `organizationModel` is undefined or no gaps are found.
630
+ * The structured return value drives both CLI warnings and scaffold fill-mode
631
+ * input payloads without any transformation on the caller side.
632
+ */
633
+ export function detectMissingApiInterfaceDeclarations(
634
+ orgName: string,
635
+ organizationModel: OrganizationModel | undefined
636
+ ): ApiInterfaceConformanceGap[] {
637
+ if (organizationModel === undefined) return []
638
+
639
+ // Build a map of system path → resource IDs for resources with non-empty ontology.
640
+ const apiBoundResourcesBySystemPath = new Map<string, string[]>()
641
+ for (const resource of Object.values(organizationModel.resources ?? {})) {
642
+ const ontology = resource.ontology
643
+ if (ontology === undefined) continue
644
+ const hasOntologyBindings =
645
+ (ontology.actions !== undefined && ontology.actions.length > 0) ||
646
+ (ontology.writes !== undefined && ontology.writes.length > 0)
647
+ if (!hasOntologyBindings) continue
648
+ const existing = apiBoundResourcesBySystemPath.get(resource.systemPath)
649
+ if (existing) {
650
+ existing.push(resource.id)
651
+ } else {
652
+ apiBoundResourcesBySystemPath.set(resource.systemPath, [resource.id])
653
+ }
654
+ }
655
+
656
+ if (apiBoundResourcesBySystemPath.size === 0) return []
657
+
658
+ const gaps: ApiInterfaceConformanceGap[] = []
659
+
660
+ for (const { path, system } of listAllSystems(organizationModel)) {
661
+ if (system.apiInterface !== undefined) continue
662
+ const resourceIds = apiBoundResourcesBySystemPath.get(path)
663
+ if (!resourceIds || resourceIds.length === 0) continue
664
+ gaps.push({
665
+ systemPath: path,
666
+ resourceIds,
667
+ message: `[${orgName}] System '${path}' has ${resourceIds.length} API-backed resource(s) (${resourceIds.join(', ')}) but no apiInterface declaration.`
668
+ })
669
+ }
670
+
671
+ return gaps
672
+ }
673
+
615
674
  /**
616
675
  * Validates every explicitly declared API System Interface by deriving
617
676
  * readiness from its scoped resources and ontology bindings.