# Forward-looking schema co-located with the examples — models the
# post-ADR-034 / post-migration-0025 catalog contract so all four example
# kinds typecheck today. Replace these imports with `import "../../.ontoref/catalog/schema.ncl"`
# once migration 0025 has been applied to your project.
#
# Contract decisions are taken from .ontoref/adrs/adr-034-catalog-extensibility-beyond-rust.ncl
# decision block. Keep this file in sync if ADR-034 evolves.

let t_OpKind = [|
  'Rust,
  'NclTransform,
  'Wasm,
  'Sidecar,
|] in

let t_NclTransformOp = [| 'Set, 'Append, 'Remove, 'Merge |] in

let t_NclTransformBody = {
  target     | String,
  selector   | String,
  operation  | t_NclTransformOp,
  value_expr | String,
} in

let t_WasmBody = {
  module_path  | String,
  export_name  | String,
  capabilities | Array String,
} in

let t_SidecarBody = {
  endpoint   | String,
  auth_token | String | default = "<from .ontoref/config.ncl::ops.sidecar_token>",
  timeout_ms | Number | default = 5000,
} in

let t_ValidatorCategory = [| 'Structural, 'Contextual |] in

let t_WitnessSignature = {
  actor_id  | String,
  algorithm | String | default = "Ed25519",
} in

let t_WitnessShape = {
  payload_kind | String,
  proof_paths  | Array String | default = [],
  signature    | t_WitnessSignature,
} in

let t_EffectKind = [| 'Insert, 'Update, 'Delete |] in

let t_Effect = {
  kind       | t_EffectKind,
  entity     | String,
  attrs      | Array String,
  dimensions | Array String | default = [],
} in

let t_ConstraintBundle = {
  pre    | Array String | default = [],
  inline | Array String | default = [],
  post   | Array String | default = [],
} in

let t_ActorPolicy = {
  roles | Array String | default = [],
} in

let t_ValidationSla = [| 'Synchronous, 'Background |] in

let t_OperationDecl = {
  id              | String,
  description     | String,
  kind            | t_OpKind | default = 'Rust,
  body            | Dyn | optional,
  inputs          | { _ : String } | default = {},
  constraints     | t_ConstraintBundle | default = {},
  validation_sla  | t_ValidationSla,
  effects         | Array t_Effect | default = [],
  witness_shape   | t_WitnessShape,
  actor_policy    | t_ActorPolicy | default = {},
  render_paths    | Array String | default = [],
} in

# Ondaod-pre Hard contract — ADR-034 `non-rust-kinds-require-ondaod-pre`.
# Non-Rust ops MUST list "evaluate_ondaod" in constraints.pre. The
# contract is exposed as a SEPARATE annotation that each catalog
# declaration applies after `OperationDecl` so the discipline shows up
# explicitly at every author site, not hidden inside the record shape.
let t_OndaodPreRequired = std.contract.custom (
  fun label =>
    fun value =>
      if value.kind == 'Rust then
        'Ok value
      else if std.array.any (fun v => v == "evaluate_ondaod") value.constraints.pre then
        'Ok value
      else
        'Error {
          message = "ADR-034 `non-rust-kinds-require-ondaod-pre`: op '%{value.id}' (kind = %{std.to_string value.kind}) MUST include \"evaluate_ondaod\" in constraints.pre"
        }
) in

# Convenience builder — applies both contracts in the correct order so
# every example reads the same way at the bottom: `... | s.make_op`.
let make_op = fun data =>
  let result | t_OperationDecl | t_OndaodPreRequired = data in
  result
in

{
  OpKind            = t_OpKind,
  NclTransformBody  = t_NclTransformBody,
  WasmBody          = t_WasmBody,
  SidecarBody       = t_SidecarBody,
  WitnessShape      = t_WitnessShape,
  WitnessSignature  = t_WitnessSignature,
  Effect            = t_Effect,
  EffectKind        = t_EffectKind,
  ConstraintBundle  = t_ConstraintBundle,
  ActorPolicy       = t_ActorPolicy,
  ValidationSla     = t_ValidationSla,
  OperationDecl     = t_OperationDecl,
  OndaodPreRequired = t_OndaodPreRequired,
  make_op           = make_op,
}
