Factored dependency pairs for review freshness

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

An accepted review is a build product and its inputs are prerequisites — make-like staleness detection. Review freshness realizes this for exactly two inputs per pair: note text and criterion text. Type-conformance pairs (ADR 038) showed the cheap way to admit a new dependency: do not widen one pair's input set to N — factor each dependency into its own two-input pair where the dependency document is the gate side. This proposal holds the not-yet-adopted remainder of that direction.

Current state (as of 2026-08-25)

  • Type-conformance pairs are shipped (ADR 038): the selector derives (note, type_spec) pairs from note frontmatter, the type spec is the gate snapshot, and a type edit stales its cohort via the existing criterion-changed reason.
  • COLLECTION.md-as-gate is shipped (ADR 041): the selector derives (note, collection_md) pairs from note location under the collection/collection/{path} lens, again with no storage change.
  • Source-as-gate was shipped and then removed by the ADR 073 revision. It factored each direct ingest link into a separate pair, but its criterion was an interpreted Claims ledger. Direct source alignment now belongs to the standard semantic/grounding-alignment pair, which reads retained quotes or a declared snapshot through the note's links. The factoring pattern remains proven for type and collection dependencies; this proposal retains only cohort-scoped acknowledgement and the N-ary fallback.
  • Freshness compares two hashes per pair against accepted_note_hash / accepted_criterion_hash (ADR 032). The snapshot table is role-neutral — keyed by path and content hash — so any repo document can sit on the criterion side.
  • Acknowledgement (commonplace-ack-review) re-pins the current note and gate snapshots while carrying forward completed review evidence. It is per-note: acking a cohort of N notes after one shared-gate edit takes N invocations (one per note, batching only across gates).
  • The selector emits JSON consumed by job creation; there is no cohort-scoped ack surface fed from selector output.
  • No judgment has been identified that irreducibly reads three or more texts in one prompt; no N-ary input-set table exists.
  • A fuller design for a general lineage model — lineage targets, append-only events, per-event input versions, typed resolvers — is in flight in the workshop layer at kb/work/lineage-mechanisms/general-lineage-refresh-state-design.md (cited by path, not linked, per the no-workshop-links convention); factoring-into-pairs narrows how much of it review freshness will ever need.

The design: one pair per dependency edge

Each new review dependency becomes its own (note_path, dependency_path) pair with the dependency document as the gate:

  • COLLECTION.md-as-gateadopted by ADR 041: a note's conformance to its collection's text contract. One pair per note, criterion side the collection's COLLECTION.md. Each factored pair reuses the entire freshness/ack/warn stack unchanged, exactly as type-conformance pairs do. Like the type spec, COLLECTION.md is not written as a Failure mode / Test procedure, so it needs a mechanical wrapper (or an authored review section in the dependency document, which the hash then sees).

The N-ary input-set design — freshness baseline pinning a variable set of (input_key, role, resolver, accepted_version) records — remains the fallback for a judgment that irreducibly reads three or more texts in one prompt. No such judgment is identified yet, which is exactly why the input-set table stays unbuilt.

Cohort-scoped ack

The force that factored pairs sharpen: cohort blast radius. A COLLECTION.md edit stales a whole collection; a type edit stales a whole type cohort — same shape as a wide gate edit. The answer is a cohort-scoped ack surface — an improvement to the existing ack command (by type, by gate, or fed from selector JSON) rather than new freshness semantics. The operator judges one gate-side diff and acknowledges the cohort in one decision instead of N per-note invocations.

Forces that keep the remainder at planning

  • Review cost. Every factored dependency adds a pair per note; the corpus-times-dependencies product is real. The retired source case showed how a multi-link artifact can multiply pairs even when one standard semantic gate already owns the judgment.
  • No cohort-ack demand signal yet. No type or collection edit has yet staled more pairs than per-note acking comfortably clears.

Free choices

  • Wrapper prompt vs authored review section. Same trade as for type specs, and the freshness boundary weighs the same way: criteria in the dependency document are hashed, wrapper text is not.
  • Cohort-ack input. By gate path, by type, or by piping selector JSON into the ack command; selector JSON is the most general and adds no new selection logic.

Adoption criteria

  • Adopt cohort-scoped ack when the first real type or collection edit stales more pairs than per-note acking comfortably clears. COLLECTION.md-as-gate raises the odds: one contract edit stales a whole collection.
  • Adopt the N-ary input-set model only if a judgment appears that genuinely needs a third text in one prompt; the default answer to a new dependency is a new factored pair, not a wider input set.

Relevant Notes: