Accepted (decided 2026-09-13; implemented 2026-09-13). The knowledge reconcile implementation, its crash-recoverable atomic save, the execution of 0028, and the related documentation updates are all done.
The current path for creating Knowledge from an AI or script requires the caller to pass a KnowledgeDraft through knowledge validate and knowledge apply, then run identity migrate. Creating a new Requirement together with a Feature that references it additionally requires an earlier interactive operation or a temporary display-ID reference because the Requirement UID does not yet exist.
This path requires the caller to understand Knowledge persistence order, UID issuance timing, the distinction between display IDs and UIDs, and the ordering of apply and migrate. If migration is omitted or processing stops after a successful apply, UID-less Knowledge or a display ID in requirement_uids can become visible in the canonical storage area. That is inconsistent with 0013’s invariant that ordinary commands do not introduce new UID-less Knowledge in UID mode.
AI authoring should declare which Knowledge is desired, rather than prescribe its persistence procedure. markharness must reconcile that declaration against the current repository state and complete UID issuance, reference resolution, validation, and atomic persistence itself.
knowledge reconcile the standard AI-authoring pathIntroduce this non-interactive command:
markharness knowledge reconcile <intent-file> [--check] [--json] [--dir <path>]
Its input is called a Knowledge Intent. A Knowledge Intent is an authoring declaration, not the storage format under .markharness/knowledge/. It does not require callers to provide UIDs for new elements, storage paths, identity events, or migration steps.
For one Knowledge Intent, knowledge reconcile performs the following as one operation:
Canonical storage is not changed before validation completes. A failure before the logical commit point converges to the old state; a failure after that point rolls forward idempotently to the committed new state. Ordinary commands never observe UID-less elements, unresolved references, or a one-sided update of Knowledge and identity events.
New elements in the same Intent refer to each other through document-local key values that are never persisted. For example, a Feature records its Requirement relationships in contributes_to using Requirement keys. Existing Requirements are referenced by UID; a display ID alone never selects an existing Requirement.
format: markharness/knowledge-intent/v1
mode: merge
requirements:
- key: req_todo
id: todo
source: native
label: TODO management
axis: [functional]
features:
- key: feature_todo
id: todo-management
contributes_to: [req_todo]
label: TODO management
axis: [functional]
behaviors:
- key: behavior_add
id: add-todo
description: Add a TODO
scenarios:
- id: empty-title
description: An empty title cannot be added
phases:
- steps:
- action: Attempt to add an empty title
results:
- No TODO is added
key and contributes_to are authoring representations and are not persisted. In the canonical Feature, requirement_uids remains the sole source of truth for contributes-to relationships. No new persistent relationship type or generic graph is introduced.
New elements omit UID. Only markharness issues UIDs.
A change to an existing element’s content or display ID must identify that element by UID in the Knowledge Intent. Existing identity is never inferred or inherited from similar content or a matching display ID alone. This preserves 0017’s distinction between revision and creation and 0021’s identity rule for reintroduced elements.
Resolution follows these rules:
| Intent specification | Current state | Result |
|---|---|---|
| No UID; no matching kind, scope, and ID | New | Issue a UID and create |
| No UID; matching kind, scope, and ID exists and normalized content is exactly equal | Existing | Report unchanged |
| No UID; matching kind, scope, and ID exists but content differs | Existing | Stop with ambiguous_identity and require UID |
| UID supplied; UID and kind agree, and current scope matches Intent scope | Existing | Compare content, then update or report unchanged |
| UID supplied; only the display ID differs | Existing | Process as an explicit rename |
| Scenario UID supplied under a different Feature or Behavior in the Intent | Existing | Process as an explicit reparent while preserving UID |
| UID is unknown, kind differs, or scope conflicts for an element other than Scenario | Inconsistent | Stop fail-closed |
Changing Scenario content or explicitly reparenting it by UID preserves its Scenario UID and recomputes Case revision from effective content. Reparenting occurs only when an existing Scenario UID is placed under a different Feature or Behavior in the Intent. A same-named Scenario without UID never implies a move. A Scenario newly produced by a split, merge, or copy receives a new UID, and reconciliation never infers a split or merge from content similarity.
mergeThe initial version supports only mode: merge. It creates or updates elements named in the Intent and leaves every omitted existing element unchanged. Omission is never interpreted as an instruction to delete or retire.
An exact mode that retires omitted elements is not implemented until target scope, authorization and confirmation, and effects on derived Scenarios and TestCases are decided separately. --delete and --allow-retire are likewise outside this ADR’s initial implementation scope.
Knowledge Intent may reference only registered Axes. Creating or deleting Axes from an Intent would mix Knowledge and Axis ownership, so the initial version does not do it. An unknown Axis stops with unknown_axis.
For an existing element selected by UID, only fields present in the Intent change. Omitted scalars, value collections, and child Knowledge elements retain their current values. A supplied scalar replaces its value. An attempt to clear a required value with null or an empty value is rejected according to that field’s domain rules.
Collections have two distinct categories:
axis, contributes_to, procedures, phases, steps, and results. Supplying one replaces the whole collection. An empty collection explicitly clears it and differs from omission. The replacement must still satisfy that field’s required, non-empty, and reference rules.Omitting a Feature’s contributes_to preserves its current requirement_uids; supplying it replaces the whole collection with the set resolved from document-local keys or Requirement UIDs. This expresses both addition and removal of relationships without separate commands.
Naming child Knowledge elements of an existing element patches or creates only those children. It never deletes siblings omitted under the same parent. procedures and phases are values owned by their Behavior or Scenario rather than independently UID-bearing Knowledge elements, so they follow value-collection replacement instead of this child-element rule.
To advance an external Requirement’s pinned reference to the current .sdoc blob, a UID-selected Requirement explicitly supplies source_revision: current. The Reconciliation Module resolves and persists the blob OID addressed by source_locator; it stops fail-closed for a native Requirement, a missing locator, or an unresolvable Git state. current is an Intent-only instruction value and is never persisted in canonical Knowledge.
--check and execution--check uses the same implementation as execution for parsing, resolution, validation, and mutation planning, but performs no writes. It succeeds when no change is required; when changes are required, it returns a dedicated machine-detectable exit code and the plan.
Execution rechecks current state immediately before commit and stops with a stale-plan result when state changed after --check or during planning. A --check result is not treated as authorization for a later write.
The same repository state and Intent produce the same mutation plan except for concrete UID values. Plans represent new UIDs as temporary tokens and assign them within the committing execution. When the same UID-less Intent is rerun after success, an element whose kind, scope, display ID, and normalized content are exactly equal is unchanged. An update with content differences or a rename must include the UID obtained from the first success result or a current machine-readable snapshot.
On success, --json returns at least created, updated, and unchanged, plus each element’s kind, UID, display ID, and changed paths. A failure returns a stable code, the location in the Intent, a message, and a remediation where possible. Rust type names and internal error strings are not part of the external contract.
The initial diagnostics include at least:
invalid_formatduplicate_keyduplicate_uidunknown_local_referenceunknown_axisambiguous_identityunknown_uidconflicting_scopeconflicting_existing_valueinvalid_procedure_referenceinvalid_source_revisionstale_planinvariant_violationidentity migrate part of authoringWhen knowledge reconcile succeeds, every canonical Knowledge element it created has a UID and every UID reference is resolved. A subsequent identity migrate is not part of its success contract.
identity migrate remains available for migration or explicit repair of existing or manually introduced data. AI-facing documentation uses knowledge reconcile as the standard path and does not prescribe a procedural sequence of multiple commands.
Existing authoring commands whose responsibilities overlap knowledge reconcile are removed according to 0028, after this ADR’s Reconciliation Module and replacement Interface are complete. No backward compatibility with the old paths is provided.
requirement_uids stores only Requirement UIDs, never display IDs or Intent keys.docs/knowledge-from-code.ai.md to make knowledge reconcile the standard workflow when implementation is complete.All of the following functional-readiness conditions must hold before beginning the removals in 0028. This section is an intermediate gate for starting ADR 0028, not the acceptance conditions for ADR 0027 as a whole.
contributes_to expresses both addition and removal of Requirement relationships.source_revision: current on an external Requirement performs the same blob-OID validation as existing requirement repin.--check, machine-readable outcomes, an Intent template, and crash-recoverable atomic persistence work.This ADR is implemented completely when all of the following hold:
knowledge reconcile supports creating a complete new Knowledge graph, recognizing unchanged existing elements, UID-selected updates and renames, multi-element operations, --check, machine-readable outcomes, an Intent template, and crash-recoverable atomic persistence.knowledge reconcile as the standard authoring path.identity migrate once after knowledge apply: this is a small change from the current implementation, but temporarily persists a display ID in requirement_uids and exposes UID-less canonical Knowledge and command ordering to the caller.knowledge apply: it could complete in one operation, but it would mix the current one-chain, create-or-reuse Draft contract with a declaration of the desired state of multiple elements. An explicit authoring-intent seam is preferable to silently changing the meaning of an existing command, and the duplicated old Interface is removed after migration completes.