102 lines
7.6 KiB
Text
102 lines
7.6 KiB
Text
|
|
let d = import "defaults.ncl" in
|
||
|
|
|
||
|
|
d.make_adr {
|
||
|
|
id = "adr-016",
|
||
|
|
title = "Component Lift-Out Pattern: Four-Criterion Gate for Standalone Extraction",
|
||
|
|
status = 'Accepted,
|
||
|
|
date = "2026-05-01",
|
||
|
|
|
||
|
|
context = "Ontoref itself was extracted from stratumiops (ADR-001). The same pattern recurred in the provisioning project: buildkit-launcher (build substrate) and backup-manager (backup orchestration) grew inside provisioning but serve a broader class of consumers — vapora, workspace infras, CI pipelines, and third-party projects. No formal criterion existed for deciding when a component is ready to become a standalone peer project. Without a criterion, the decision is arbitrary and either premature (decomposition overhead before consumer plurality) or delayed (host coupling accretes, extraction becomes expensive).",
|
||
|
|
|
||
|
|
decision = "A component is extracted as a standalone peer project when it passes all four criteria: (1) Orthogonal concern — the component's core domain is not the host project's core domain; (2) Consumer plurality — at least two distinct callers exist or are immediately planned; (3) Release cadence divergence — the component's evolution is not gated by the host project's release cycle; (4) Config path-agnostic — the component can receive its config from any caller without importing host infrastructure (workspace crates, host config loaders, host schema registries). The extracted project registers in ontoref before any code moves. The host project retains extension-side artifacts (schemas, defaults, component declarations) that allow workspace infras to declare directives for the extracted tool.",
|
||
|
|
|
||
|
|
rationale = [
|
||
|
|
{
|
||
|
|
claim = "Orthogonality is the primary criterion",
|
||
|
|
detail = "A build substrate (lian-build) and a backup orchestrator (cloudatasave) do not belong to provisioning's core domain (workspace lifecycle management). Provisioning calls them; it does not own their concerns. Orthogonality is the structural signal that extraction is correct, not premature.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
claim = "Consumer plurality prevents premature extraction",
|
||
|
|
detail = "A component with a single caller has no demonstrated need for independent lifecycle. Two or more distinct callers (e.g. provisioning + vapora, or provisioning + a workspace CI pipeline) prove the component's value is general, not host-specific. ADR-001 precedent: ontoref serves stratumiops, typedialog, vapora, kogral.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
claim = "Config path-agnostic is the coupling criterion",
|
||
|
|
detail = "If a component imports the host's config loader, schema registry, or workspace crates to function, it cannot be independently deployed or tested. Severing this import at extraction time is not simplification — it is restoration of the correct dependency direction. The extracted project has its own config infrastructure; callers pass directives in the extracted project's own NCL vocabulary.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
claim = "Ontoref registration before code move establishes ontological identity",
|
||
|
|
detail = "Creating the .ontology/ and ADRs in the new project before any code moves ensures the project exists as a declared concept before it exists as an implementation. This prevents the common failure mode where a project's identity is derived post-hoc from accumulated code rather than declared from design.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
claim = "Host retains extension-side artifacts",
|
||
|
|
detail = "The host project (provisioning) retains the extension schemas, defaults, and component declarations that allow workspace infras to configure the extracted tool. This is not coupling — it is the host's side of the protocol contract. The extracted project defines its capabilities in its own ontology; provisioning extensions expose those capabilities to workspace consumers.",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
|
||
|
|
consequences = {
|
||
|
|
positive = [
|
||
|
|
"Decisions to extract are justified, not arbitrary — four criteria provide a checkable gate",
|
||
|
|
"Extracted projects are immediately usable by non-provisioning callers (vapora, CI, workspace infras)",
|
||
|
|
"Host projects remain focused on their core domain; extension schemas handle the integration surface",
|
||
|
|
"The pattern is self-documenting: ADR-001 (ontoref from stratumiops) and ADR-016 instances (lian-build, cloudatasave from provisioning) form a corpus of precedent",
|
||
|
|
"Ontoref registration before code move ensures the project has identity before it has implementation",
|
||
|
|
],
|
||
|
|
negative = [
|
||
|
|
"Extraction requires a deliberate severing of host infrastructure imports — this is work that must be done correctly, not deferred",
|
||
|
|
"Two registration surfaces per project: the extracted project's own .ontoref/ and the host's extension schemas",
|
||
|
|
"Consumer projects (workspace infras) must import from the extracted project's schema vocabulary, not from the host's workspace schema",
|
||
|
|
],
|
||
|
|
},
|
||
|
|
|
||
|
|
alternatives_considered = [
|
||
|
|
{
|
||
|
|
option = "Extract only when a second consumer exists and requests it",
|
||
|
|
why_rejected = "Reactive extraction means coupling has already accreted. By the time a second consumer requests the component, host infrastructure imports are deep. Proactive evaluation at the four-criterion gate is cheaper than reactive untangling.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
option = "Keep all tools inside provisioning as internal workspace crates, expose via provisioning CLI",
|
||
|
|
why_rejected = "This routes all callers through provisioning's release cycle and binary. vapora and workspace CI pipelines cannot use the tool without depending on provisioning. The four-criterion test exists precisely to identify when this constraint becomes incorrect.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
option = "Publish extracted crates to crates.io immediately",
|
||
|
|
why_rejected = "Published crates require stable API contracts before the consumer relationship is proven. Path-based standalone project references allow simultaneous development of the extracted project and its consumers without publication ceremony.",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
|
||
|
|
constraints = [
|
||
|
|
{
|
||
|
|
id = "ontology-before-code",
|
||
|
|
claim = "An extracted project must have .ontology/core.ncl, .ontology/state.ncl, and adrs/adr-001 committed before any code from the host is moved",
|
||
|
|
scope = "any project applying the lift-out pattern",
|
||
|
|
severity = 'Hard,
|
||
|
|
check = {
|
||
|
|
tag = 'FileExists,
|
||
|
|
path = ".ontology/core.ncl",
|
||
|
|
present = true,
|
||
|
|
},
|
||
|
|
rationale = "Ontological identity precedes implementation. A project that exists only as code has no declared purpose, axioms, or constraints — the extraction is indistinguishable from a rename.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id = "no-host-infrastructure-import",
|
||
|
|
claim = "The extracted project must not import workspace crates, config loaders, or schema registries from the host project",
|
||
|
|
scope = "extracted project source code",
|
||
|
|
severity = 'Hard,
|
||
|
|
check = {
|
||
|
|
tag = 'Grep,
|
||
|
|
pattern = "platform_config|stratum-config|provisioning-config",
|
||
|
|
paths = ["src/", "crates/"],
|
||
|
|
must_be_empty = true,
|
||
|
|
},
|
||
|
|
rationale = "Importing host infrastructure is the definition of host coupling. If the import cannot be severed, the component fails criterion 4 and must not be extracted until the coupling is resolved.",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
|
||
|
|
related_adrs = ["adr-001-protocol-as-standalone-project"],
|
||
|
|
|
||
|
|
ontology_check = {
|
||
|
|
decision_string = "components are extracted as standalone peer projects when they pass a four-criterion gate; ontoref registration precedes code move; host retains extension-side schemas",
|
||
|
|
invariants_at_risk = ["protocol-not-runtime", "self-describing"],
|
||
|
|
verdict = 'Safe,
|
||
|
|
},
|
||
|
|
}
|