# schemas/lib/dag/contracts.ncl — DAG domain type contracts # # Two distinct DAG layers: # 1. Capability layer — ExtensionCapability/ExtensionDependency (extension metadata) # 2. Composition layer — WorkspaceComposition (inter-formula ordering) # 3. Resolution layer — ResolutionPolicy (capability → extension mapping) # # Pattern: separate let bindings with _ prefix, same as formula.ncl. # No self-references, no let rec — each binding is in scope for subsequent ones. # --------------------------------------------------------------------------- # Capability layer # --------------------------------------------------------------------------- let _capability_kind = [| 'Required, 'Optional, 'ConflictsWith |] in let _ExtensionCapability = { id | String, version | String, interface | String, } in let _ExtensionDependency = { capability | String, kind | _capability_kind, min_version | String | optional, } in # --------------------------------------------------------------------------- # Composition layer — inter-formula DAG # Distinct from the intra-formula DAG in formula.ncl (per-server task ordering). # WorkspaceComposition declares execution ordering between formulas. # --------------------------------------------------------------------------- let _composition_condition = [| 'Completed, 'Healthy, 'Running |] in let _FormulaDep = { formula_id | String, condition | _composition_condition, } in let _HealthGate = { check_cmd | String, expect | String, timeout_ms | Number, retries | Number, check_server | String | optional, } in let _FormulaCompositionEntry = { formula_id | String, depends_on | Array _FormulaDep | default = [], parallel | Bool | default = false, health_gate | _HealthGate | optional, } in # Base shape — used as first step inside the custom contract (same pattern as _FormulaBase # in formula.ncl) so missing-field errors surface before cross-field validation runs. let _WorkspaceCompositionBase = { formulas | Array _FormulaCompositionEntry, } in # Custom contract: validates referential integrity across formula entries. # - At least one formula must have depends_on = [] (root node) # - All depends_on[].formula_id must reference a declared formula_id let _WorkspaceComposition = std.contract.custom (fun label value => let base = value | _WorkspaceCompositionBase in let ids = base.formulas |> std.array.map (fun e => e.formula_id) in let has_root = base.formulas |> std.array.any (fun e => e.depends_on == []) in let bad_deps = base.formulas |> std.array.flat_map (fun e => e.depends_on |> std.array.filter (fun d => !(ids |> std.array.any (fun id => id == d.formula_id)) ) |> std.array.map (fun d => "formula '%{e.formula_id}' depends_on unknown '%{d.formula_id}'" ) ) in if !has_root then std.contract.blame_with_message "WorkspaceComposition: at least one formula must have depends_on = []" label else if (std.array.length bad_deps) > 0 then std.contract.blame_with_message "WorkspaceComposition: invalid depends_on references: %{std.string.join ", " bad_deps}" label else 'Ok base ) in # --------------------------------------------------------------------------- # Resolution layer — capability → concrete extension mapping # --------------------------------------------------------------------------- let _resolution_strategy = [| 'Strict, 'BestEffort |] in let _ResolutionEntry = { capability_id | String, extension_name | String, } in let _ResolutionPolicy = { strategy | _resolution_strategy, overrides | Array _ResolutionEntry | default = [], allow_optional_gaps | Bool, } in # --------------------------------------------------------------------------- # Exports # --------------------------------------------------------------------------- { CapabilityKind = _capability_kind, ExtensionCapability = _ExtensionCapability, ExtensionDependency = _ExtensionDependency, CompositionCondition = _composition_condition, FormulaDep = _FormulaDep, HealthGate = _HealthGate, FormulaCompositionEntry = _FormulaCompositionEntry, WorkspaceComposition = _WorkspaceComposition, ResolutionStrategy = _resolution_strategy, ResolutionEntry = _ResolutionEntry, ResolutionPolicy = _ResolutionPolicy, }