ontoref/reflection/migrations/0016-registry-credential-vault.ncl
Jesús Pérez 82a358f18d
Some checks failed
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (push) Has been cancelled
feat: #[onto_mcp_tool] catalog, OCI credential vault layer, validate ADR-018 mode hierarchy
ontoref-derive: #[onto_mcp_tool] attribute macro registers MCP tool unit-structs in
  the catalog at link time via inventory::submit!; annotated item is emitted unchanged,
  ToolBase/AsyncTool impls stay on the struct. All 34 tools migrated from manual wiring
  (net +5: ontoref_list_projects, ontoref_search, ontoref_describe,
  ontoref_list_ontology_extensions, ontoref_get_ontology_extension).

  validate modes (ADR-018): reads level_hierarchy from workflow.ncl and checks every
  .ncl mode for level declared, strategy declared, delegate chain coherent, compose
  extends valid. mode resolve <id> shows which hierarchy level handles a mode and why.
  --self-test generates synthetic fixtures in a temp dir for CI smoke-testing.

  validate run-cargo: two-step Cargo.toml resolution — workspace layout first
  (crates/<check.crate>/Cargo.toml), single-crate fallback by package name or repo
  basename. Lets the same ADR constraint shape apply to workspace and single-crate repos.

  ontology/schemas/manifest.ncl: registry_topology_type contract — multi-registry
  coordination, push targets, participant scopes, per-namespace capability.

  reflection/requirements/base.ncl: oras ≥1.2.0, cosign ≥2.0.0, sops ≥3.9.0, age
  ≥1.1.0, restic declared as Hard/Soft requirements with version_min, check_cmd, and
  install_hint (ADR-017 toolchain surface).

  ADR-019: per-file recipient routing for tenant isolation without multi-vault. Schema
  additions: sops.recipient_groups + sops.recipient_rules in ontoref-project.ncl.
  secrets-bootstrap generates .sops.yaml from project.ncl in declarative mode. Three
  new secrets-audit checks: recipient-routing-coherent, recipient-routing-coverage,
  no-multi-vault. Adoption templates: single-team/, multi-tenant/, agent-first/.
  Integration templates: domain-producer/, mode-producer/, mode-consumer/.

  UI: project_picker surfaces registry badge (⟳ participant) and vault badge
  (⛁ vault_id · N, green=declarative / amber=legacy) per project card. Expanded panel
  adds collapsible Registry section with namespace, endpoint, and push/pull capability.
  manage.html gains Runtime Services card — MCP and GraphQL toggleable without restart
  via HTMX POST /ui/manage/services/{service}/toggle.

  describe.nu: capabilities JSON includes registry_topology and vault_state per project.
  sync.nu: drift check extended to detect //! absence on newly registered crates.
  qa.ncl: six entries — credential-vault-best-practice (layered data-flow diagram),
  credential-vault-templates (paths A/B/C), credential-vault-troubleshooting (15 named
  errors), integration-what-and-why (ADR-042 OCI federation), integration-how-to-implement,
  integration-troubleshooting.

  on+re: core.ncl + manifest.ncl updated to reflect OCI, MCP, and mode-hierarchy nodes.
  Deleted stale presentation assets (2026-02 slides + voice notes).
2026-05-12 04:46:15 +01:00

129 lines
4.5 KiB
Text

{
id = "0016",
slug = "registry-credential-vault",
description = "Add sops vault config to project.ncl and credential_sops fields to registry entries (ADR-017). Enables per-project multi-recipient age-encrypted credential vaults stored as OCI artifacts in ZOT, with isolated DOCKER_CONFIG for every oras invocation.",
check = {
tag = "Grep",
paths = [".ontoref/project.ncl"],
pattern = "sops",
},
instructions = m%"
## Registry credential vault (migration 0016)
Implements ADR-017: per-project sops multi-recipient vaults stored as OCI artifacts
in ZOT. Credentials are never in env vars, never in ambient docker config, never
plaintext on disk. Every oras invocation runs with an isolated DOCKER_CONFIG.
### Reference material
- **Helper contract**: `reflection/modules/secrets.nu` header docstring — defines
`resolve-vault-access` (Layer 0), `resolve-registry-credential` (Layer 2),
`assert-actor-authorized`, plus the 13 named errors callers must handle.
- **Working example**: ontoref's own `.ontoref/project.ncl` and
`.ontology/manifest.ncl` — copy this shape into your project.
- **CLI surface**: `ore secrets {bootstrap|sync|push|open|close|status|audit}` and
`ore vault {status|snapshots|check}` (P2 dispatcher wireup).
### Step 1 — Set the global master key path (one-time per developer)
In `~/.config/ontoref/config.ncl`:
vault = {
master_key_path = "/path/to/your/.kage",
backend = 'restic,
},
This becomes the default for all projects. A project may override it in its own
`.ontoref/project.ncl::sops.master_key_path` if it requires a different key.
### Step 2 — Add the sops block to .ontoref/project.ncl
Inside the `make_project { ... }` call:
sops = {
enabled = true,
vault_id = "<your-project-slug>",
vault_backend = 'restic,
registry_endpoint = "<your-zot-host>",
actor_key_bindings = {
developer = "developer",
ci = "cdci",
agent = "ontoref",
admin = "admin",
},
# master_key_path absent → resolves from ~/.config/ontoref/config.ncl::vault.master_key_path
},
### Step 3 — Add registry_provides + credential_sops to .ontology/manifest.ncl
registry_provides = m.make_registry_provides {
participant = "<your-project-slug>",
registries = m.make_registries_config {
default = "primary",
registries = [
m.make_registry_entry {
id = "primary",
endpoint = "<your-zot-host>",
role = 'primary,
tls = true,
namespaces = {
own = ["domains/<your-slug>/", "modes/<your-slug>/"],
prefixes = ["domains/<your-slug>/", "modes/<your-slug>/"],
},
credential_sops = "registry/ro.sops.yaml", # RO — relative to src-vault root
credential_sops_rw = "registry/rw.sops.yaml", # RW — relative to src-vault root
},
],
},
},
NOTE: `credential_sops` paths are relative to the src-vault root
(`~/.config/ontoref/vaults/<vault_id>/src-vault/`), NOT to the project root.
### Step 4 — Declare uses_registry in domains/modes that call oras
Any domain or mode NCL file that pushes or pulls artifacts must add:
uses_registry = "<registry-entry-id>", # id from make_registry_entry
Enables impact analysis on `ore secrets close` to find affected services.
### Step 5 — Bootstrap the vault (admin only, one-time per project)
export SOPS_AGE_RECIPIENTS="age1<developer>,age1<ci>,age1<admin>,..."
ore secrets bootstrap
This creates `~/.config/ontoref/vaults/<vault_id>/access.sops.yaml`, initializes
the local restic/kopia repo, and (next step) pushes the first src-vault artifact.
### Step 6 — Push the bootstrapped vault to ZOT
export COSIGN_KEY_PATH=/path/to/cosign.key # required by ADR-017
ore secrets push
The artifact is signed with cosign — unsigned vault pushes are rejected by the
`src-vault-cosign-signed` hard constraint.
### Step 7 — Non-admin actors: sync the vault
ore secrets sync
Pulls `src-vault/<vault_id>:latest` from ZOT and decrypts access.sops.yaml.
### Step 8 — Verify ADR-017 constraints
ore secrets audit
Runs all checks: bootstrap-credentials, no-credential-env, multi-recipient
mandatory. Failures here block downstream operations.
### Step 9 — Verify visible state
ore secrets status # vault dir + last access entry + master_key resolution
ore vault status # backend + repo presence + last snapshot
Both should report a healthy state with no errors.
"%,
}