Generalized validation invalidation and imperative extension

Type: ../types/design-proposal.md · Tags: type-system, kb-maintenance

ADR 050 shipped the small common execution model: validation is artifact-anchored, one library-owned run caches parsed artifacts and collection indexes, and the CLI only resolves targets and presents results. This proposal retains the two questions that implementation deliberately did not answer: when explicit invalidation selectors should become a general dependency mechanism, and how a collection-local type could own a deterministic dereferencing check.

Current state (as of 2026-07-13)

ValidationRun evaluates ordinary per-artifact checks, tag-README marks, collection-scoped orphan detection, and collection structure through one result surface. It parses target artifacts once, builds one shared tag index per consulted collection, and builds the authored-link graph once for a collection target. The base → imperative type rules → schema sequence remains unchanged.

Target expansion is related but deliberately separate. impacted_marked_tag_readmes is still an explicit inverse selector: changing a tagged note pulls the corresponding marked tag-README into the run. There is no generic dependency vocabulary, old/new-state comparison, deletion handling, or schema-definition fan-out.

A type's intra-document deterministic rules remain authorable as JSON Schema. Its semantic rules remain authorable in the type-conformance review criterion. A collection-local type still cannot author a deterministic rule that dereferences other artifacts. The one current mark-shaped rule, tag-readme, is a framework type with framework Python enforcement.

The remaining problems

Incremental invalidation has one worked selector

Evaluation inputs and invalidation dependencies are different. A check may cheaply read a whole collection index while only tag membership changes can alter its result. Deriving a precise inverse from a coarse declaration such as "reads the collection" would over-invalidate; deriving it from current state alone misses removed tags, deleted files, and prior referents.

Schema checks expose the same future issue: their result depends on the note, type spec, schema, and referenced-schema closure, but editing a definition does not currently target every applicable note. This is a real dependency; it is not yet evidence that a generic engine is cheaper than another explicit selector.

Local imperative ownership has no worked case

JSON Schema cannot dereference. If a future collection-local type declares a mark—a cached value recomputable from other artifacts—review cannot safely enforce it: a derived copy of recomputable truth must be checked or absent. Yet allowing arbitrary KB-side code would turn an inspectable data substrate into an execution surface.

The extension gap is therefore narrower than "types need plugins": a local type may eventually need one constrained, deterministic, dereferencing primitive. No current local type does.

Deferred options

A. General dependency keys and inverse selection

A check could declare artifact, path-existence, frontmatter-field, tag-membership, or definition-closure dependencies, with the run deriving impacted anchors.

For: definition edits and cross-artifact changes become first-class invalidators; repeated selectors may share indexes and old/new-state handling.

Against: this is an incremental build engine. It needs deletion semantics, previous state, dependency closure, and a conservative fallback. One selector does not establish the right vocabulary.

B. A declarative mark primitive

A type spec could declare a constrained recomputation such as tag-members(index_key) plus a comparison such as every-member-linked.

For: a local type could own a machine-checked mark without arbitrary code, and the primitive could expose precise invalidation keys.

Against: it is a new language designed from one framework example. Languages grow, and a premature primitive would make the example's accidental shape permanent.

C. KB-side code hooks — rejected

Arbitrary hooks provide maximal reach but violate the commitment that the KB is inspectable data rather than executable code. No current demand justifies reopening that substrate choice.

D. Collection-owned deterministic checks — no present demand

Collections own text contracts and type menus, not frontmatter semantics. Intra-document mechanical variation belongs in a local type and schema; semantic variation belongs in review. A future heterogeneous collection relation may challenge this boundary, but only after an honest artifact anchor and type owner fail.

Forces

  • A schema cannot dereference. This inherited limit keeps an imperative path necessary.
  • A mark must be machine-checked or absent. Review cannot substitute for deterministic recomputation.
  • Evaluation reads do not determine precise invalidation. Old state and deletions matter to inverse selection.
  • The KB is data, not code. Extension should remain constrained and inspectable.
  • YAGNI. The run context removed current execution duplication; neither remaining mechanism has more than one worked case.

Adoption criteria

A general invalidation model is ready for its own ADR only after at least two unlike explicit selectors expose a stable shared vocabulary. Its prototype must handle old and new state, deletions, path-existence changes, and definition closures, or be explicitly conservative where it cannot.

A declarative imperative primitive is ready only when a collection-local type needs a mark that neither its schema nor its review criterion can safely enforce. The primitive must be designed from that case together with tag-readme, not from tag-readme alone.

Collection ownership remains closed until a mechanical relation is inherently collection-wide after attempting an artifact anchor and type owner.


Relevant Notes: