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).
129 lines
4.5 KiB
Text
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.
|
|
"%,
|
|
}
|