let d = import "adr-defaults.ncl" in d.make_adr { id = "adr-009", title = "Manifest Self-Interrogation Layer — Three Semantic Axes", status = 'Accepted, date = "2026-03-26", context = "The manifest.ncl schema described structural facts (layers, modes, consumption modes, tools, config surface) but had no typed layer for self-interrogation: agents and operators could not query why a capability exists, what it needs to run, or what external dependencies have a documented blast radius. The existing tools[] field covered only dev tooling (install_method, version) — no prod/dev classification, no services, no environment variables, no infrastructure dependencies. Practice/node descriptions in core.ncl carry architectural meaning (invariant=true, ADR-backed) but are not the right home for operational, audience-facing descriptions of what a project offers. Capabilities, requirements, and dependency blast-radius analysis are three orthogonal concerns that needed typed, queryable homes in the manifest schema.", decision = "Three new typed arrays are added to manifest_type: capabilities[] (capability_type), requirements[] (requirement_type), and critical_deps[] (critical_dep_type). These are semantically distinct layers: capabilities answer 'what does this project do, why does it exist, and how does it work' with explicit cross-references to ontology node IDs and ADR IDs; requirements classify prerequisites by env_target_type ('Production | 'Development | 'Both) and requirement_kind_type ('Tool | 'Service | 'EnvVar | 'Infrastructure); critical_deps document blast radius — what breaks when an external dependency disappears or breaks its contract — distinct from requirements because the concern is runtime failure impact, not startup prerequisites. A description | String | default = '' field is also added to manifest_type, fixing a pre-existing bug where collect-identity in describe.nu read manifest.description? (always null) instead of a field that existed. describe requirements is added as a new subcommand; describe capabilities is extended to render manifest capabilities; describe guides output gains capabilities/requirements/critical_deps keys so agents on cold start receive full self-interrogation context.", rationale = [ { claim = "Capabilities are operationally distinct from Practice nodes in core.ncl", detail = "Practice nodes are architectural artifacts: they carry invariant=true, are protected by Hard ADR constraints, and describe structural patterns that must never be violated. A 'capability' is operational and audience-facing — it answers what a project offers, why it was built, and how to find the relevant code. Merging these into core.ncl would inflate the invariant-protected graph with non-architectural facts. The manifest is the right home: it is per-project, optional, and already carries operational metadata (layers, modes, config surface).", }, { claim = "requirements[] supercedes tools[] with a principled classification axis", detail = "The legacy tools[] field (name, install_method, version, required) modeled only dev tooling. Production deployments need SurrealDB; CI needs the Rust nightly toolchain; the daemon needs NATS_STREAMS_CONFIG in certain topologies. env_target_type ('Production | 'Development | 'Both) and requirement_kind_type ('Tool | 'Service | 'EnvVar | 'Infrastructure) make these distinctions queryable. The tools[] field is kept for backward compatibility but requirements[] is the complete model.", }, { claim = "critical_deps[] separates blast-radius documentation from prerequisites", detail = "A requirement says 'you need X to run'. A critical dep says 'X failing at runtime breaks these capabilities in these ways'. inventory (crates.io) not present as a requirement — ontoref compiles without it. But if inventory's API breaks, GET /api/catalog goes silent and ontoref_api_catalog becomes blind. This distinction matters for operational runbooks and for agents reasoning about degraded-mode operation. failure_impact is required (no default) because undocumented blast radius defeats the purpose of the type.", }, ], consequences = { positive = [ "describe guides output now includes capabilities, requirements, and critical_deps — agents on cold start receive complete self-interrogation context without extra tool calls", "describe requirements new subcommand answers 'what does this project need?' for both developer setup and production deployment", "capabilities.nodes[] and capabilities.adrs[] create explicit bidirectional cross-references between the manifest and the ontology DAG", "env_target_type makes prod vs dev prerequisite separation queryable — CI can verify only its subset of requirements", "critical_dep mitigation field enables agents to reason about build flags (--no-default-features) and fallback paths when a dep is unavailable", "Bug fixed: collect-identity in describe.nu was reading manifest.kind? (always null) instead of manifest.repo_kind?; adding description | String | default = '' fixes the identity description gap", ], negative = [ "Consumer projects must populate capabilities/requirements/critical_deps manually — there is no automatic extraction; an empty manifest still validates (all fields default = [])", "capabilities.nodes[] cross-references must be kept in sync with core.ncl node IDs manually — no DAG consistency check at nickel export time", "Three new array fields increase manifest.ncl verbosity; projects with complex capability/dependency landscapes will have large manifest files", ], }, alternatives_considered = [ { option = "Extend tools[] with env and kind fields", why_rejected = "tools[] is semantically 'dev tooling required to build'. Extending it to cover SurrealDB (a production service) or ONTOREF_ADMIN_TOKEN_FILE (an env var) would violate the field's implied meaning. A type named tool_requirement_type that has kind = 'Service is confusing. Separate types with clear names are preferable to an overloaded catch-all.", }, { option = "Add capability descriptions to Practice nodes in core.ncl", why_rejected = "Practice nodes are architectural: invariant=true nodes are ADR-protected and represent constraints future contributors must follow. Adding 'what does this offer to end users' to the invariant graph would force every capability description through the ADR lifecycle. capabilities[] is per-project, evolves freely, and belongs to the manifest (the operational layer), not the ontology (the architectural layer).", }, { option = "Merge critical_deps into requirements with an is_critical flag", why_rejected = "The concern is different: requirements are prerequisites (the system cannot start without them). critical_deps are runtime load-bearing with a documented blast radius. A requirement that is optional (required = false) can still be a critical dep. The is_critical flag on requirement_type would blur this distinction and make failure_impact logically optional (not required for non-critical items) — creating a type that is partially applicable based on a flag, which is a code smell in typed schemas.", }, ], constraints = [ { id = "capabilities-nodes-are-ontology-ids", claim = "capability_type.nodes[] entries must reference valid node IDs declared in .ontology/core.ncl", scope = "ontology/schemas/manifest.ncl (capability_type definition)", severity = 'Soft, check = { tag = 'Grep, pattern = "nodes.*=.*\\[", paths = [".ontology/manifest.ncl"], must_be_empty = false }, rationale = "Cross-references to non-existent node IDs produce silent broken links in the graph UI and describe output. Soft because nickel export cannot verify cross-file ID consistency; responsibility lies with the author.", }, { id = "failure-impact-required-in-critical-deps", claim = "critical_dep_type.failure_impact must be a non-empty string — undocumented blast radius defeats the purpose of the type", scope = "ontology/schemas/manifest.ncl (critical_dep_type definition)", severity = 'Hard, check = { tag = 'Grep, pattern = "failure_impact.*=.*\"\"", paths = [".ontology/manifest.ncl"], must_be_empty = true }, rationale = "A critical dep entry with no failure_impact is indistinguishable from a regular requirement. The type's value is entirely in the blast-radius documentation.", }, ], related_adrs = ["adr-001", "adr-009"], ontology_check = { decision_string = "manifest_type gains three typed self-interrogation arrays (capabilities, requirements, critical_deps) with orthogonal semantic axes; describe.nu gains describe requirements and extend describe guides; collect-identity bug fixed", invariants_at_risk = ["dag-formalized", "self-describing"], verdict = 'Safe, }, }