PLAN — ID-427 producer-inversion subtasks
PLAN — ID-427 producer inversion
Section titled “PLAN — ID-427 producer inversion”Proposed subtasks for TECH.md. Decision cites are §N in that document;
measurement cites are M*/C* in RESEARCH.md.
Test strategy throughout is behaviour-first (docs/reference/testing/test-philosophy.md
§1): assert on what the emitted bundle and the run log say, never on which private helper
was called. A test whose title reads “routes through _list_residual_concepts” has failed
criterion 1 before it runs; the title to write is “a published document with no answers
appears in the bundle”.
External interfaces
Section titled “External interfaces”| Interface | Owner | Gates |
|---|---|---|
v0.2 frontmatter (generated, sources[], footnotes, no top-level resource:) | id-426 | {427.10} — the residual template emits into that shape. Sequencing only; land id-426 first or coordinate on frontmatter.py. |
status: draft for no-content concepts | id-428 | Nothing. {427.10} emits confidence: no-content; id-428 maps it later. |
Index emission + theme retirement, shared file bundle_writer.py | id-429 | {427.8}, {427.9}, {427.11} touch the same module. Not logically blocking; merge-order coordination required. |
Parser acceptance of the new log.md line | id-439 | {427.9} verifies parse-log.ts rather than assuming it (TECH §7). |
scope_tag publication gate | id-422 | Nothing. {427.10}‘s hole-1 branch is defensive by ruling and stays until id-422 lands (TQ-4). |
Wave rule (DR-060, TECH §5): exactly one version= bump on enrich_concept, applied
in {427.7} and not re-bumped by any later subtask in this wave.
{427.4} — Neutral Source protocol module · free now · closes id-362 F1 leg 1
Section titled “{427.4} — Neutral Source protocol module · free now · closes id-362 F1 leg 1”Move ConceptKey, ConceptRaw and the Source protocol out of l_records.py into
scripts/cocoindex_pipeline/sources/base.py. enrich.py:246 imports from there;
repo_docs.py:234-242’s duplicate protocol declaration deletes (M14). No behaviour
change, no field change, no gate change — a pure re-home so the two Sources stop being
parallel implementations of one idea.
Test strategy. Existing suites pass byte-unchanged (that is the assertion for a
re-home). One new test: both LRecordsSource and RepoDocsSource satisfy
isinstance(x, Source) — the runtime-checkable protocol is the public contract id-362 F1
asks for, so asserting it is behaviour, not structure.
{427.5} — type becomes a shape-validated label · depends {427.4}
Section titled “{427.5} — type becomes a shape-validated label · depends {427.4}”Delete ALLOWED_CONCEPT_TYPES, CONCEPT_TYPES + its __post_init__ membership branch,
_CLASS_CONCEPT_TYPES, _permit_overlay_concept_types and its contextvar,
SYSTEM_BASELINE_CONCEPT_TYPES, EffectiveOntology.base_for_class and the
concept_types field. check_type_membership → check_type_shape (TECH §2.8).
RepoConceptKey’s membership check becomes the same shape check. write_bundle’s
internal_dev fail-loud at gate entry deletes with the register it guarded.
Closes AC 2.
Test strategy (test_producer_validator.py, test_l_records_source.py,
test_producer_bundle_writer.py):
- a concept typed
procurement_policy— a value no registry has ever held — is written to the bundle and appears in the run summary; Q A Pair!,x,a_very_long_five_word_type_labeland""are each rejected with a shape error naming what is wrong, and the error text names no permitted set;q_a_pairis still refused unconditionally (BI-3), including viaConceptKey;- a
system_baselinerun writes a concept typeddocument— the uniformity ruling (PI-7) stated as behaviour; - an
internal_devrun reaches the write loop instead of raising at gate entry.
{427.6} — Facet vocabulary retires; the three bends resolve as types · depends {427.5}
Section titled “{427.6} — Facet vocabulary retires; the three bends resolve as types · depends {427.5}”Delete RECOGNISED_FACET_TAGS, FACET_TAG_ALIASES, canonical_facet_tag,
normalise_facet_tags. web_pass.py emits type="reference" and drops
_REFERENCE_CONCEPT_TAG. Delete TS CONCEPT_TYPE_VALUES; extend KNOWN_TYPES with
document, questionnaire_response, answer_set, reference.
Closes AC 3. Note for the implementer: policy/capability have no emitter to
re-point (M2) — this subtask is a registry deletion for them, and going looking for an
emission change will waste a session.
Test strategy:
- a Pass-2 reference concept is emitted with
type: referenceand noreferencetag; - a concept carrying
metricanddatasettags is written and validated — free-form facet tags survive without a registry; - a concept carrying an arbitrary tag (
encryption) is still accepted (unchanged behaviour, asserted because its guard is being deleted); - TS: an unmapped type falls back to the default badge token (already true — assert it
holds after
CONCEPT_TYPE_VALUESis gone).
{427.7} — Grain registry + dispatch unification · depends {427.5} · the structural subtask
Section titled “{427.7} — Grain registry + dispatch unification · depends {427.5} · the structural subtask”Introduce GrainSpec / GrainEnumeration / Coverage; add ConceptKey.grain; convert the
six preferred grains + the feeder grain to registry entries declaring directory and
type_label; replace all three concept_type dispatchers (TECH §1). _source_documents_for_key
takes patterns. Feeder config gains directory (TECH §2.7). Apply the wave’s single
version= bump.
Also lands the reserved-name slug guard (TECH §2.6, id-429 IA-3 — net-new, created
by this task and owed to id-429):
mint_concept_slug suffixes index/log with -concept; _validate_concept_feeder_schema
rejects them as directory names.
Test strategy — the assertion that proves the structure, not the refactor:
- a fake grain registered in a test appears in the emitted bundle with no edit to any dispatcher. This is the property whose absence caused the inversion; it is the one test that must exist.
- each existing grain still returns the same rows for the same key (a read-parity test per grain, asserting on returned row ids — the public return value, not the SQL);
- a
scope_tagliterally namedindexproducestopics/index-concept.md, and the directory’s ownindex.mdis untouched; - a feeder config declaring
"directory": "log"is rejected at read time; - relabelling a grain’s
type_labelchanges the emittedtypeand does not move the file (PI-5, asserted directly).
{427.8} — Won-bid identity collapse · depends {427.7} · coordinate id-429
Section titled “{427.8} — Won-bid identity collapse · depends {427.7} · coordinate id-429”The won-bid grain declares directory="case-studies/won-bid". Delete
_won_bid_case_study_redirect, bundle_write_path, bundle_write_path_for_key; flow_def’s
BI-28 map and the embed lookup key on rel_path.
Test strategy:
- a won-bid concept is written at
case-studies/won-bid/<slug>.md— unchanged physical path, asserted against the shipped showcase layout; - a named-client and a won-bid case study with the same buyer slug both write, neither overwriting the other (the collision the redirect existed to prevent);
- a BI-9 cross-link to a won-bid concept resolves to the path the reader can open;
- BI-28 provenance still attributes the won-bid proposal (guards the
rel_pathre-key).
{427.9} — Corpus census · depends {427.7} · coordinate id-429, verify id-439
Section titled “{427.9} — Corpus census · depends {427.7} · coordinate id-429, verify id-439”Every grain returns Coverage; Source.census() on both implementations;
RunSummary.census; the unconditional Considered line and the conditional Unrouted line
in _render_run_bullets; is_no_op accounts for unrouted (TECH §2.11).
Verify, do not assume: read lib/okf/parse-log.ts and confirm the new bullet does not
break log parsing; if it does, that is an id-439 hand-off, not a silent revert (TECH §7).
Closes AC 4.
Test strategy:
- a run that changes nothing still writes a
Consideredline naming both unit counts — “a no-op run still says what it considered”; - with a grain stubbed to cover nothing, the run reports a non-zero
Unroutedcount and is not a no-op, even though every file on disk is byte-identical; - a fully covered corpus reports
considered == routedfor both unit kinds; - no uuid appears anywhere in
log.md(BI-10 asserted directly on the artefact).
{427.10} — The residual grain · depends {427.9} · sequence after id-426 · the headline AC
Section titled “{427.10} — The residual grain · depends {427.9} · sequence after id-426 · the headline AC”The three-step attribution cascade, the three directories, the deterministic
render_undistilled_draft template with its contains_record_pointer title guard, and
confidence: no-content (TECH §2.1–§2.3).
Closes AC 1.
Test strategy — this is the acceptance test the task is judged on, and it is written as a corpus fixture, not a unit test:
A corpus deliberately containing both measured holes — (a) a published
source_documentthat produced noq_a_pair, and (b) a publishedq_a_pairwith an emptyscope_tagarray — is run end-to-end, and both records are present in the emitted bundle, each citing its own record, with the run census reporting zero unrouted.
Plus:
- a published pair whose only lineage is
source_form_instance_idlands inquestionnaire-responses/; one with neither lineage lands in the singleunattributed-answers/concept; that concept is absent when the corpus has no such pair (no empty placeholder); - an undistilled document’s concept body contains the escalation sentence and no model-generated prose — assert the agent loop was not entered by asserting the emitted body equals the template render for that record (behaviour: the page says only what the records support);
- a residual concept for a document whose filename embeds a uuid still writes, with the neutral fallback title;
- two documents with identical filenames produce two distinct concepts (the unconditional
-<uuid[:8]>suffix), and neither trips the write-path collision guard; - a residual document that does have published pairs drafts through Pass-1 normally —
the
drafts_viaswitch stated as behaviour.
{427.11} — Ontology artefacts overlay-only · depends {427.5} · executes DR-027 (S546)
Section titled “{427.11} — Ontology artefacts overlay-only · depends {427.5} · executes DR-027 (S546)”write_ontology_artefact emits {"overlay": …}; _base_ontology_snapshot deletes;
EffectiveOntology keeps two dimensions; iri_projection drops the concept_types row and
its docstring counts (TECH §2.10). _OVERLAY_DIMENSIONS keeps all three keys.
Test strategy:
- the emitted
ontology.jsonhas nobasekey and still carriesoverlay: nullfor a bundle with no overlay; - a client bundle with an
ontology-overlay.jsondeclaringconcept_typesstill validates, still echoes those terms intooverlay, and no longer gates any write on them; - a non-
client_businessbundle with a stray overlay still hard-rejects (DR-054/DR-079 gate unchanged — asserted because its neighbour is being deleted); SUPERSEDED by TQ-1’s S546 ruling, executed by {427.14} — the base entity/relationship projection retires too, socontext.jsonldcarries entity and relationship terms only;context.jsonldcarries CLIENT-OVERLAY terms only and a bundle with no overlay emits{"@context": {}}. This bullet described {427.11}‘s scope correctly at authoring time and was already contradicted by the ruling when {427.11} landed; {427.11} executed theontology.jsonhalf only and carried the rest to {427.14}. Left struck rather than deleted so {427.13}‘s disposition-table walk sees the supersession rather than a silently edited requirement;bundle-graph.ts’s overlay read still produces its styling signal against the new payload. Widened by {427.14}: that read is now the ONLY on-disk source for BOTH union-graph signals — the bundle-class shape channel and the re-sourced border channel (overlay.concept_types->typeDeclaration).
{427.12} — id-358 rename · depends {427.7}
Section titled “{427.12} — id-358 rename · depends {427.7}”ConceptKey.workspace_id → form_instance_id; ProposedChange.source_workspace_id →
source_form_instance_id; sync_bundle(source_workspace_ids=) → source_form_instance_ids=;
flow_def’s map follows. The BI-28 slot stays.
Closes id-358. Sequenced after {427.7} so the two ConceptKey field changes ride one
fingerprint invalidation, not two.
Test strategy (test_producer_bi28_bidoutcome_proposal.py): a won-bid draft proposal is
still stamped with its form instance’s id and is still never unattributed. The rename is
proved by the provenance surviving, not by grepping for the old name.
{427.13} — Audit delivery + artefact sweep · depends all · closes AC 5, AC 6
Section titled “{427.13} — Audit delivery + artefact sweep · depends all · closes AC 5, AC 6”Walk TECH §3’s disposition table and mark each row landed; withdraw superset 1 from the
showcase CONFORMANCE.md (hand-authored, producer-preserved — a manual edit); correct
producer docstring §-cites that name the withdrawn taxonomy; sweep specs/id-132-* for
DR-141 propagation (carried open from the substrate); record the DR-141 coverage-not-
partition rider recommendation (TECH §2.1) and the four TQ-* UNDECIDABLEs into the task
file for the owner.
Test strategy: none executable. The gate is the table itself — every row changed,
kept, deleted or UNDECIDABLE with its question, which is what AC 5 asks for. A row
left blank is the failure condition.
Dependency graph
Section titled “Dependency graph”{427.4} ──▶ {427.5} ──┬──▶ {427.6} ├──▶ {427.7} ──┬──▶ {427.8} │ ├──▶ {427.9} ──▶ {427.10} [after id-426] │ └──▶ {427.12} └──▶ {427.11} all ──▶ {427.13}Independently landable and free now: {427.4}. Free after {427.5}: {427.6}, {427.11}. The two that carry the task’s meaning: {427.7} (a grain is one registry entry) and {427.10} (both holes land in the bundle). If the wave has to stop early, stop after {427.10} — {427.11}/{427.12}/{427.13} are honest tidy-up; {427.10} is the acceptance.