# Oikos policy seed — risk classes, approval rules, autonomy settings.
#
# Bootstraps risk_classes / approval_rules / autonomy_settings (migration
# 005). After ingest the DB is authoritative; runtime policy changes go
# through the dual-control meta-approval flow (plan S3) and are exported
# back here via `GET /api/v1/export`.
#
# Adapted from legacy oikos/policy.yaml (2026-07-07):
#   - `commands:`/`mcp_tools:` maps are gone — read endpoints and MCP read
#     tools are read_only by construction and never classified; mutating
#     API calls classify via approval_rules below.
#   - `actions:` map became approval_rules keyed on (entity_type, action);
#     entity types may be abstract (rule inherits down the hierarchy,
#     most-specific match wins: scope_entity > concrete type > ancestor).
#   - `service_overrides:` became scope_entity rules.
#   - `lifecycle_overrides:` became autonomy_settings keys read by the
#     classifier.

version: 1

risk_classes:
  read_only:
    description: Observes state; cannot change anything.
    approval_required: none
    autonomy_allowed: true
  reversible_low:
    description: >-
      Changes runtime state in a way a single follow-up command undoes
      (restart, cache clear, sync pull). No config or data changes.
    approval_required: none          # still subject to global.auto_act + rules below
    autonomy_allowed: true
  config_mutation:
    description: >-
      Changes tracked configuration or deployed software: repo edit + push,
      deploy trigger, Caddy/Gitea/app config, package upgrades. Reversible
      via git, but affects other consumers.
    approval_required: operator      # Matrix ✅/❌ (single-use HMAC token)
    autonomy_allowed: false
  destructive:
    description: >-
      Destroys or irreversibly alters data/entities: container destroy,
      disk format, DB wipe, secret rotation, client revocation.
    approval_required: operator_confirmed
    autonomy_allowed: false

approval_rules:
  # Signal kinds are looked up as `action` by internal/policy/classify.go, and
  # an unmatched kind silently falls back to reversible_low/operator. Declare
  # `unmonitored` so its routing is intentional: it reports a coverage gap and
  # there is nothing to remediate automatically — closing it means an operator
  # adding a check_def, which is its own deliberate change.
  - {entity_type: entity, action: unmonitored, risk_class: read_only, autonomy_level: auto}

  # Backup freshness signals. Reporting-only for the same reason: the fix for a
  # stale or missing backup is a deliberate human change (re-run the job, fix
  # the mount, correct the path), never something to auto-remediate. Declared
  # so the routing is intentional rather than the reversible_low/operator
  # fallback an unmatched kind would otherwise get.
  - {entity_type: backup-target, action: backup-stale, risk_class: read_only, autonomy_level: auto}
  - {entity_type: backup-target, action: backup-missing, risk_class: read_only, autonomy_level: auto}
  - {entity_type: backup-target, action: backup-misconfigured, risk_class: read_only, autonomy_level: auto}
  - {entity_type: backup-target, action: backup-unreachable, risk_class: read_only, autonomy_level: auto}

  # ── Generic rules on (possibly abstract) types ──
  - {entity_type: service, action: restart, risk_class: reversible_low, autonomy_level: auto}
  - {entity_type: service, action: cache-clear, risk_class: reversible_low, autonomy_level: auto}
  - {entity_type: service, action: sync-pull, risk_class: reversible_low, autonomy_level: auto}
  - {entity_type: service, action: db-wipe, risk_class: destructive, autonomy_level: never}
  - {entity_type: compose-stack, action: restart, risk_class: reversible_low, autonomy_level: auto}
  - {entity_type: docker-container, action: restart, risk_class: reversible_low, autonomy_level: auto}
  - {entity_type: machine, action: apt-upgrade, risk_class: config_mutation, autonomy_level: escalate}
  - {entity_type: machine, action: reboot, risk_class: config_mutation, autonomy_level: escalate}
  - {entity_type: machine, action: format-disk, risk_class: destructive, autonomy_level: never}
  - {entity_type: config-repo, action: edit, risk_class: config_mutation, autonomy_level: escalate}
  - {entity_type: deploy-pipeline, action: trigger, risk_class: config_mutation, autonomy_level: escalate}
  - {entity_type: lxc, action: create, risk_class: config_mutation, autonomy_level: escalate}
  - {entity_type: lxc, action: migrate, risk_class: config_mutation, autonomy_level: escalate}
  - {entity_type: lxc, action: restart, risk_class: reversible_low, autonomy_level: auto}
  - {entity_type: lxc, action: destroy, risk_class: destructive, autonomy_level: never}
  - {entity_type: vm, action: restart, risk_class: reversible_low, autonomy_level: auto}
  - {entity_type: vm, action: destroy, risk_class: destructive, autonomy_level: never}
  - {entity_type: dns-record, action: change, risk_class: config_mutation, autonomy_level: escalate}
  - {entity_type: ingress-route, action: change, risk_class: config_mutation, autonomy_level: escalate}
  - {entity_type: storage-pool, action: change, risk_class: destructive, autonomy_level: never}
  - {entity_type: secret, action: rotate, risk_class: destructive, autonomy_level: never}
  - {entity_type: key, action: revoke, risk_class: destructive, autonomy_level: never}

  # ── Governance objects (the OS's own levers — always operator-gated) ──
  - {entity_type: pattern, action: activate, risk_class: config_mutation, autonomy_level: never}
  - {entity_type: skill, action: activate, risk_class: config_mutation, autonomy_level: never}

  # ── Per-entity overrides (wide blast radius) ──
  - {entity_type: service, action: restart, scope_entity: "service:caddy",
     risk_class: config_mutation, autonomy_level: escalate}
     # everything *.hubris.network rides on caddy
  - {entity_type: service, action: restart, scope_entity: "service:dns",
     risk_class: config_mutation, autonomy_level: escalate}
     # LAN-wide resolver
  - {entity_type: service, action: restart, scope_entity: "service:authentik",
     risk_class: config_mutation, autonomy_level: escalate}
     # SSO provider — restart locks logins fleet-wide

autonomy_settings:
  # Global kill-switch. Cold start = off: the agent escalates everything
  # until patterns validate and the operator raises this (plan: trust is earned).
  global.auto_act: "off"           # off | reversible_low

  # Per-entity hard blocks (checked even when global.auto_act is on)
  never_auto_act.service:caddy: "true"
  never_auto_act.service:dns: "true"
  never_auto_act.service:authentik: "true"
  never_auto_act.host:hubris: "true"
  never_auto_act.host:strong: "true"

  # Lifecycle-state classifier overrides (from legacy lifecycle_overrides)
  lifecycle_override.provisioning.config_mutation: reversible_low
    # no dependents yet — config changes are cheap
  lifecycle_override.deprecated.refuse: new-inbound-edges
  lifecycle_override.destroyed.refuse: all
    # any action targeting a destroyed entity raises a drift signal instead
