Skip to content

DR-141: The OKF producer enumerates over the corpus, and concept type is a label rather than a gate

DR-141 — The OKF producer enumerates over the corpus, and concept type is a label rather than a gate

Section titled “DR-141 — The OKF producer enumerates over the corpus, and concept type is a label rather than a gate”

producer/sources/l_records.py list_concepts() is six hard-coded SELECT DISTINCT grains — topic over scope_tag, product and certification over entity_mentions.canonical_name, company, named-client case_study, won-bid case_study — and there is no seventh, catch-all grain. ALLOWED_CONCEPT_TYPES is a frozenset of five values enforced at the write gate.

Read against the upstream specification this is legal: OKF §4.1 says type values are not registered centrally and consumers MUST tolerate unknown ones, but it places no constraint on how few a producer emits. DR-019 ratified the closure on exactly that reading, as “spec-legal producer-side strictness”.

The owner rejected the framing, and the codebase supports them. OKF lists “defining a fixed taxonomy of concept types” under §1 Non-goals precisely so a knowledge base can grow organically; the consumer-tolerance rule exists to make that safe, and “producers SHOULD pick values that are descriptive and self-explanatory” is per-concept naming guidance, not a licence to fix a vocabulary in advance. CONFORMANCE.md names our deviation in the spec’s own vocabulary — “a closed, validator-enforced type taxonomy”.

And the closure was already bending the model. validator.py’s comments record three occasions where a genuinely new type was needed and was expressed as a topic plus a facet tag instead: reference is “a topic + this tag, never a sixth type”; policy and capability are “both topic-concept facets, no new type”; methodology — “a retired won-bid content_type” — was folded onto playbook. A parallel facet vocabulary was invented to say what the type vocabulary would not admit. The concept types were derived from content_type, which DR-050 already rules must never be overloaded as a grain key.

Owner, S545:

“My actual concern is limiting a producer to a set of types, which unless I’ve misunderstood, would to some degree be a ‘taxonomy’, when one of the underlying values of OKF is enabling a knowledge base to grow organically.”

Enumeration is corpus-complete by construction, and type labels a concept rather than deciding whether it may exist. Every published source_document and every published q_a_pair lands in exactly one concept; the six grains remain as preferred routing and a residual grain catches whatever they miss.

DR-019’s divergence 1 — the closed, validator-enforced type taxonomy — is withdrawn. DR-019’s remaining posture (a documented, deliberate superset of upstream OKF) stands, and its version target moves to v0.2: upstream shipped v0.2 on 2026-07-24 and DR-019’s own latest amendment, dated 2026-07-26, still reads v0.1.

  • Add more concept types. Rejected — it repeats the mistake at a larger N. The measured hole is not “five types is too few”: topic ← DISTINCT scope_tag is a broad catch-all and most published knowledge does reach the bundle. The gaps are specifically a published q_a_pair with an empty scope_tag array, and a source document that never produced a q_a_pair — both enumerated by nothing.
  • Keep the closure and rely on RunSummary to report what was skipped. Rejected on measurement: nothing is skipped, because nothing is ever considered. Unenumerated content is not rejected, not logged, and absent from RunSummary.failed — it never becomes a ConceptKey, so no downstream reader learns it existed.
  • Open the type vocabulary but leave enumeration as-is. Rejected: the inversion is the enumeration, not the vocabulary. A producer with unlimited types and six grains produces exactly the same bundle.
  • The load-bearing failure this fixes is the negative answer. The procurement outcome turns on a user being able to trust “we do not know this — escalate to an SME” over “I could not find it”. That requires the bundle to be a faithful projection of the corpus; today it cannot distinguish absent-because-unknown from absent-because-unrouted.
  • Hoisting entity resolution (DR-140) sharpens the product / certification / case_study grains but does not close this. Coverage and resolution are orthogonal.
  • ontology.json declares the closed vocabulary concepts were validated against; what it declares changes when the vocabulary opens. Its purpose is re-opened for review, and DR-027, DR-054 and DR-082 are in scope for amendment. Note OKF’s opening paragraph — “there is no schema registry, no central authority, and no required tooling” — reaches the same self-describing goal by the opposite means.
  • Adopting v0.2 retires timestamp in favour of generated: { by, at } and the body # Citations trailer in favour of frontmatter sources[]. It also legitimises the canonical:// scheme: v0.2 §5.1 widens resource to admit “a population or scope descriptor [the consumer] cannot” follow.
  • confidence (strong|partial|no-content|needs-SME) occupies the slot v0.2 standardises as verified plus derived tiers, making our trust signal unreadable to a generic OKF consumer. It is retired in favour of the standard field. The register action is a tombstone amendment, not a supersession: DR-081a is already status: retired with superseded_by: null, and its substance_moved_to points at producer/enrich.py + test_producer_frontmatter.py (consumer lib/okf/bundle-graph.ts). Replacing confidence changes that code and makes the pointer stale, so the tombstone is amended; a new decision may still be wanted for the v0.2 trust slot, but it supersedes nothing.
  • A per-subdirectory index.md is adopted as a design decision, not a conformance fix — §8 says an index MAY appear in any directory and §11 forbids rejecting a bundle for missing ones. The defect is that our single root index is organised by theme while the directories are organised by type, so the one index we emit describes a different axis to the directory it sits in, defeating §8’s stated purpose of progressive disclosure.
  • Whether a bundle CONFORMANCE.md should exist at all is re-opened: it currently documents supersets this decision removes.

The Decision’s “every published unit lands in exactly one concept” is amended to at least one (coverage), not exactly one (partition). Measured S546 (id-427 RESEARCH C4): a published pair carrying a scope_tag whose parent document also matches a product/case-study pattern already lands in two concepts today via _SQL_QA_BY_SOURCE_DOCS_OR_ENTITY — the partition reading was never true, and enforcing it would delete legitimate evidence attachments. The guarantee DR-141 exists for is coverage; multiplicity above one is evidence reuse, not a defect. Ruling delegated by the owner to Coordinator staff-engineer judgment, S546.

RATIFIED by the owner, S551 — and why it needed a second pass

Section titled “RATIFIED by the owner, S551 — and why it needed a second pass”

S546 recorded this rider on delegated judgment, not owner ratification. {427.13} (S550) declined to treat that as settled, on the ground that amending a register entry’s Decision text is the owner’s to take, and re-recorded it for ratification with a stronger supporting measurement. Owner, S551: “Ratify DR-141.” The rider above is now owner-ratified, and the Decision’s “exactly one” reads at least one.

The supporting measurement, which the S546 rider did not carry (TECH §2.1, {427.13}): coverage cannot be computed by a standalone SQL predicate. The product / certification / case_study grains select documents by data-dependent %<entity canonical_name>% ILIKE patterns, so “the set the preferred grains reach” is not expressible as a static predicate — which is why every grain declares what it covers and the residual grain takes the complement, rather than a single query asserting the invariant. A partition invariant would additionally require proving disjointness between grains, which those same ILIKE patterns make undecidable in general. So partition is not merely untrue today; it is not checkable even in principle by the mechanism that would have to check it.

First execution against a real corpus, S551. The closing producer run reported considered 11 source_documents / 25 q_a_pairs, routed 11 / 25, zero unrouted — the coverage guarantee holding on real data rather than on RESEARCH C4’s static reasoning. Note the limit of that evidence: routed is reported as a union, so the run confirms coverage and says nothing either way about multiplicity. The overlap half of the rider still rests on S546’s measurement, not on this run.