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”Context
Section titled “Context”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.”
Decision
Section titled “Decision”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.
Alternatives considered
Section titled “Alternatives considered”- 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_tagis a broad catch-all and most published knowledge does reach the bundle. The gaps are specifically a publishedq_a_pairwith an emptyscope_tagarray, and a source document that never produced aq_a_pair— both enumerated by nothing. - Keep the closure and rely on
RunSummaryto report what was skipped. Rejected on measurement: nothing is skipped, because nothing is ever considered. Unenumerated content is not rejected, not logged, and absent fromRunSummary.failed— it never becomes aConceptKey, 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.
Consequences
Section titled “Consequences”- 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_studygrains but does not close this. Coverage and resolution are orthogonal. ontology.jsondeclares 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
timestampin favour ofgenerated: { by, at }and the body# Citationstrailer in favour of frontmattersources[]. It also legitimises thecanonical://scheme: v0.2 §5.1 widensresourceto admit “a population or scope descriptor [the consumer] cannot” follow. confidence(strong|partial|no-content|needs-SME) occupies the slot v0.2 standardises asverifiedplus 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 alreadystatus: retiredwithsuperseded_by: null, and itssubstance_moved_topoints atproducer/enrich.py+test_producer_frontmatter.py(consumerlib/okf/bundle-graph.ts). Replacingconfidencechanges 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.mdis 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.mdshould exist at all is re-opened: it currently documents supersets this decision removes.
Rider (S546) — coverage, not partition
Section titled “Rider (S546) — coverage, not partition”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.