provisioning/schemas/lib/op.ncl

114 lines
4.9 KiB
Text
Raw Normal View History

# schemas/lib/op.ncl — Op (Operation) governance contracts
#
# An Op is the atomic unit of workspace state management — it records intent,
# authorization, execution artifacts, and state transitions as a DAG node,
# enabling audit, rollback, and concurrent agent control.
#
# Storage (per op):
# ops/{id}/op.json — runtime instance record (JSON, not NCL)
# ops/{id}/pre.json — pre-execution state snapshot
# ops/{id}/post.json — post-execution state snapshot (absent on failure)
# .ops-archive/ — restic repo (S3 backend): logs + bundles, encrypted
#
# Identity:
# actor.nid = Radicle Node ID (rad self --nid). Falls back to "local:{user}".
# Op ID = {nid-short}:{uuid} — globally attributable, DAG-safe
#
# DAG semantics:
# Each Op is a node. lineage.parent_op is the incoming edge.
# Rollback is a new forward Op — lineage.rollback_of points to the Op being undone.
# The DAG remains acyclic; rollback is a forward move restoring an earlier snapshot.
let _OpActor = {
nid | String | doc "Radicle Node ID (rad self --nid) or 'local:{user}' fallback",
identity | String | doc "Human-readable label — username or agent name",
source | [| 'cli, 'agent, 'api |] | default = 'cli,
} in
let _Constraint = {
kind | [| 'backup, 'restore, 'health_check, 'dry_run_check, 'concurrent_lock |],
resource | String | doc "Component name or volume/resource identifier",
scope | [| 'direct, 'indirect |] | default = 'direct,
params | { .. } | default = {},
} in
let _RecoveryAction = {
kind | [| 'backup, 'restore, 'health_check, 'dry_run_check, 'concurrent_lock |],
resource | String,
from | [| 'pre_backup, 'last_known_good |] | default = 'pre_backup,
params | { .. } | default = {},
} in
let _OpConstraints = {
pre | Array _Constraint | doc "Gates evaluated before execution starts" | default = [],
on_failure | Array _RecoveryAction | doc "Recovery actions if op fails" | default = [],
} in
let _OpSnapshots = {
pre | String | doc "Relative path to pre.json from workspace root",
post | String | optional | doc "Relative path to post.json — absent if op failed before completion",
} in
let _OpArtifacts = {
archive_snapshot | String | optional | doc "Snapshot ID in the configured archive backend (restic/kopia)",
bundles | Array String | doc "Bundle tar.gz paths within the archive snapshot" | default = [],
} in
let _OpLineage = {
parent_op | String | optional | doc "Op ID this state was derived from (incoming DAG edge)",
rollback_of | String | optional | doc "If this op is a rollback, the ID of the op it undoes",
} in
{
OpActor = _OpActor,
Constraint = _Constraint,
RecoveryAction = _RecoveryAction,
OpConstraints = _OpConstraints,
OpSnapshots = _OpSnapshots,
OpArtifacts = _OpArtifacts,
OpLineage = _OpLineage,
# Enum type exports — use these as field annotation values in workspace NCL
OpSource = [| 'cli, 'agent, 'api |],
OpOperation = [| 'install, 'update, 'delete, 'rollback, 'dry_run |],
OpStatus = [| 'pending, 'running, 'constraint_failed, 'recovering, 'success, 'failed, 'rolled_back, 'cancelled |],
Op = {
id | String | doc "Op ID: {nid-short}:{uuid}",
actor | _OpActor,
intent | String | doc "Human description of why this op is needed",
workspace | String | doc "Workspace name from config/provisioning.ncl",
component | String | doc "Component being operated on",
operation | [| 'install, 'update, 'delete, 'rollback, 'dry_run |],
targets | Array String | doc "Server hostnames targeted by this op",
constraints | _OpConstraints | default = { pre = [], on_failure = [] },
snapshots | _OpSnapshots,
artifacts | _OpArtifacts | default = { bundles = [] },
lineage | _OpLineage | default = {},
status | [| 'pending, 'running, 'constraint_failed, 'recovering, 'success, 'failed, 'rolled_back, 'cancelled |] | default = 'pending,
jj_change | String | optional | doc "jj change ID created for this op",
radicle_rid | String | optional | doc "Radicle Repository ID after rad sync — globally unique RID of this workspace",
started_at | String | doc "ISO 8601 UTC timestamp",
ended_at | String | optional,
},
# Workspace-level ops configuration — added under `ops` in config/provisioning.ncl
OpsConfig = {
archive = {
backend | [| 's3, 'local |] | default = 's3,
tool | [| 'restic, 'kopia |] | doc "Backup provider — must have a matching entry in extensions/providers/backup/" | default = 'restic,
endpoint | String | optional | doc "S3-compatible endpoint URL",
bucket | String | optional,
prefix | String | default = "ops",
},
retention = {
keep_last | Number | default = 50,
keep_monthly | Number | default = 12,
keep_yearly | Number | default = 3,
},
},
}