# domains/framework/ontology/schema.ncl
# Shared reflection mode schema for Library, Service, and Tool projects.
# Defines the DAG-validated mode contract: steps, dependencies, guards,
# converge strategy. Projects import via:
#   let s = import "framework/ontology/schema.ncl" in

let _Dependency = {
  step      | String,
  kind      | [| 'Always, 'OnSuccess, 'OnFailure |] | default = 'Always,
  condition | String | optional,
} in

let _OnError = {
  strategy   | [| 'Stop, 'Continue, 'Retry, 'Fallback, 'Branch |],
  target     | String | optional,
  on_success | String | optional,
  max        | Number | default = 3,
  backoff_s  | Number | default = 5,
} in

let _Guard = {
  id       | String,
  cmd      | String,
  reason   | String,
  severity | [| 'Block, 'Warn |] | default = 'Block,
} in

let _Converge = {
  condition      | String,
  max_iterations | Number                        | default = 3,
  strategy       | [| 'RetryFailed, 'RetryAll |] | default = 'RetryFailed,
} in

let _ActionStep = fun ActionContract => {
  id         | String,
  action     | ActionContract,
  depends_on | Array _Dependency | default = [],
  cmd        | String | optional,
  actor      | [| 'Human, 'Agent, 'Both |] | default = 'Both,
  on_error   | _OnError | default = { strategy = 'Stop },
  verify     | String | optional,
  note       | String | optional,
} in

let _ResolutionStrategy = [| 'Override, 'Delegate, 'Merge, 'Compose |] in

let _ModeBase = fun ActionContract => {
  id             | String,
  trigger        | String,
  preconditions  | Array String | default = [],
  guards         | Array _Guard | default = [],
  steps          | Array (_ActionStep ActionContract),
  postconditions | Array String | default = [],
  converge       | _Converge | optional,
  strategy       | _ResolutionStrategy | optional,
  extends        | Array String | optional,
} in

let _Mode = fun ActionContract =>
  std.contract.custom (fun label value =>
    let validated = value | (_ModeBase ActionContract) in
    let steps     = validated.steps in
    let ids       = steps |> std.array.map (fun s => s.id) in

    let _after_unique = ids |> std.array.fold_left (fun acc id =>
      if std.record.has_field id acc.seen then
        std.contract.blame_with_message
          "Mode '%{validated.id}': duplicate step id '%{id}'"
          label
      else
        { seen = acc.seen & { "%{id}" = true }, ok = true }
    ) { seen = {}, ok = true } in

    let bad_refs = steps |> std.array.flat_map (fun step =>
      step.depends_on
        |> std.array.filter (fun dep =>
            !(ids |> std.array.any (fun i => i == dep.step))
          )
        |> std.array.map (fun dep =>
            "step '%{step.id}' depends_on unknown '%{dep.step}'"
          )
    ) in

    if std.array.length bad_refs > 0 then
      std.contract.blame_with_message
        "Mode '%{validated.id}' has invalid depends_on: %{std.string.join ", " bad_refs}"
        label
    else
      'Ok validated
  )
in

{
  Dependency         = _Dependency,
  OnError            = _OnError,
  Guard              = _Guard,
  Converge           = _Converge,
  ActionStep         = _ActionStep,
  Mode               = _Mode,
  ResolutionStrategy = _ResolutionStrategy,
}
