# bond.ncl — contract for the ontoref BOND: a typed relation between projects
# (and their domains), distinct from a `link` (the generic `ln`/Link node
# reference). Frozen semantics live in .ontoref/ontology/glossary.ncl
# (terms: bond, ontoref-node, ontoref-plane); this file types them.
#
# Session 2026-07-17 (rustelo instantiation map + domain-model brief).
# Frame is native ontoref (nodes/edges/axes/planes). DDD Context-Map is borrowed
# ONLY as the dependency-edge type enum; Hexagonal only intra-domain.
#
# ondaod:
#  - formalization-vs-adoption — the type is formal; membership cardinality is
#    UNCAPPED and a bond is declarable WITHOUT ontoref managing the project (the
#    carrier BondSet lives in .domains-ontoref/<domain>/, orthogonal to the
#    manifest's scalar repo_kind).
#  - enforcement-vs-emergence — `dep_kind` seeds the 6 DDD kinds already witnessed;
#    the protocol supplies the mechanism, never the number. No cardinality cap.
#  - ontology-vs-reflection — the TYPES live here (ontology); the `bond` verb,
#    forms and FSM execution live in reflection. `linkage_transitions` is the
#    canonical data the verb consults, not behaviour.
#  - plane-habitability — the mutual/Partnership dependency bond IS a habitable
#    plane (a 2-cycle), never a hierarchy. The `mutual` flag marks it.

# ── Axis ─────────────────────────────────────────────────────────────────────
# Governance = the project is an ontoref-node in a domain's DAG (acyclic).
# Dependency = the project inhabits an ontoref-plane with peer nodes (cycles OK).
let bond_axis_type = [| 'Governance, 'Dependency |] in

# ── Dependency edge kind — DDD Context-Map (applies when axis = 'Dependency) ──
# Seeded with the 6 kinds witnessed in the constellation; the enum grows by
# evidence (enforcement-vs-emergence), never by speculation.
let dep_kind_type = [|
  'CustomerSupplier,
  'Partnership,
  'OpenHostService,
  'Conformist,
  'SharedKernel,
  'AnticorruptionLayer,
|] in

# ── Governance resolution (applies when axis = 'Governance) ───────────────────
# How a domain (level N) resolves against its governed nodes (level N+1).
# ADR-018 (level hierarchy + mode resolution), ADR-045 (recursive level chains).
let resolution_type = [| 'Delegate, 'Override, 'Compose |] in

# ── Linkage state — the FSM a bonded project moves through ────────────────────
# 'None    — not bonded (default).
# 'Linked  — bonded on the DEPENDENCY axis (inhabits a plane; see `plane`).
# 'Node    — bonded on the GOVERNANCE axis (a governed node in a domain's DAG).
# 'Managed — ontoref governs the project's development (full project management).
# The `linked(plane)` case of the brief = state 'Linked + a populated `plane`
# field (bare-tag enums carry no payload — matches core.ncl style).
let linkage_state_type = [| 'None, 'Linked, 'Node, 'Managed |] in

let linkage_transition_type = [| 'Onboard, 'Offboard, 'Update |] in

let linkage_transition = {
  from | linkage_state_type,
  via  | linkage_transition_type,
  to   | linkage_state_type,
  note | String | default = "",
} in

# Canonical FSM the `bond init|update|drop` verb consults (data, not behaviour).
# Onboard climbs (None→Linked/Node→Managed); Update is a self-loop (retype the
# same-tier bond); Offboard descends (Managed→Node, then →None).
let default_linkage_transitions | Array linkage_transition = [
  { from = 'None,    via = 'Onboard,  to = 'Linked,  note = "declare a dependency bond — inhabit a plane" },
  { from = 'None,    via = 'Onboard,  to = 'Node,    note = "declare a governance bond — become a governed node" },
  { from = 'Linked,  via = 'Onboard,  to = 'Managed, note = "escalate to full ontoref management" },
  { from = 'Node,    via = 'Onboard,  to = 'Managed, note = "escalate to full ontoref management" },
  { from = 'Linked,  via = 'Update,   to = 'Linked,  note = "retype dep_kind / mutual / capabilities" },
  { from = 'Node,    via = 'Update,   to = 'Node,    note = "retype resolution / capabilities" },
  { from = 'Managed, via = 'Update,   to = 'Managed, note = "retune the managed bond" },
  { from = 'Managed, via = 'Offboard, to = 'Node,    note = "demote from management to a governance node" },
  { from = 'Node,    via = 'Offboard, to = 'None,    note = "drop the governance bond" },
  { from = 'Linked,  via = 'Offboard, to = 'None,    note = "drop the dependency bond" },
] in

# ── The bond record ───────────────────────────────────────────────────────────
# `from`/`to` name the DOMAIN ROOTS of the two endpoints — a bond resolves
# against their definitions/attributes/states/capabilities, NEVER on the edge
# (glossary::bond). The edge is thin: `capabilities` are reference ids into a
# domain root's catalog, not inline definitions (ontology-vs-reflection).
#
# Field applicability by axis (not enforceable in bare Nickel — stated, and the
# `bond validate` verb checks it):
#   axis = 'Governance → `resolution` meaningful; `dep_kind`/`mutual`/`plane` unused.
#   axis = 'Dependency → `dep_kind`/`mutual`/`plane` meaningful; `resolution` unused.
let bond_type = {
  id           | String,        # stable slug, e.g. "rustelo-app-to-rustelo"
  from         | String,        # this project's domain root (the bonding endpoint)
  to           | String,        # the target domain root
  axis         | bond_axis_type,
  state        | linkage_state_type | default = 'None,
  # Dependency-axis fields
  dep_kind     | dep_kind_type      | default = 'CustomerSupplier,
  mutual       | Bool               | default = false,
  plane        | String             | default = "",  # which ontoref-plane (when 'Linked)
  # Governance-axis field
  resolution   | resolution_type    | default = 'Delegate,
  # Reference ids into the endpoints' domain-root catalogs (never inline defs).
  capabilities | Array String       | default = [],
  note         | String             | default = "",
} in

# ── Carrier — what a bonded project declares on disk ──────────────────────────
# One file per domain folder: .domains-ontoref/<domain>/bond.ncl : BondSet.
# Multi-domain membership = multiple <domain>/ subfolders, each a BondSet — never
# derived from the manifest's single `repo_kind` (formalization-vs-adoption: the
# cardinality is uncapped and lives outside project management).
let bond_set_type = {
  domain | String,             # the domain these bonds target (matches folder name)
  bonds  | Array bond_type,
} in

{
  BondAxis          = bond_axis_type,
  DepKind           = dep_kind_type,
  Resolution        = resolution_type,
  LinkageState      = linkage_state_type,
  LinkageTransition = linkage_transition,
  LinkageTransitionKind = linkage_transition_type,
  Bond              = bond_type,
  BondSet           = bond_set_type,

  # Canonical FSM, exported for the `bond` verb and forms to consult.
  linkage_transitions = default_linkage_transitions,
}
