ID-132 SPEC — theme_config: OKF bundle index.md theme map (proposal, bl-463) [SUPERSEDED S547]
ID-132 SPEC — theme_config: OKF bundle index.md theme map
Section titled “ID-132 SPEC — theme_config: OKF bundle index.md theme map”Kind: Spec proposal — a ratifiable proposal for WHERE the index.md theme→concept map
lives and a concrete starter map. It does not implement; it gives the owner what is needed
to ratify config-home + starter map, riding the already-landed theme_config seam in
bundle_writer.build_index_themes.
Authored: 15/07/2026 · placed post-conformance-wave. Source: copied verbatim from the
session scratchpad okf-theme-config-proposal.md.
Ratification status (2026-07-15)
Section titled “Ratification status (2026-07-15)”- Option A — OWNER-RATIFIED. The theme map lives in a client-owned theme-map JSON at the
bundle root, sibling of
ontology-overlay.json(§2 Option A below). Ratified 2026-07-15 (recorded bl-463). - §5 item 5 (deploy-seed policy) — FOLDED into
{45.10}(journaled there): whether the deploy drops a starter map at first-client mint sits on the{45.10}seeding boundary. - §5 item 7 (SSCM duplicate) — already OWNED by
{45.11}(S465, the pre-{45.9}gate): the near-duplicate SSCM concepts are a data/producer question tracked there, not a theme-map question. - Items 2/3/4/6 — RATIFIED 2026-07-15 (post OKF-alignment check, recorded bl-463):
item 2 file name =
index-themes.json; item 3 Platform map = §3 as recommended (incl. the won-bid → Bids & Tendering and GreenCycle → Products calls); item 4 section order ratified in principle (§3 ordering stands; reorder is a cheap config edit); item 6 first-client themes noted — deferred to the client-bundle mint, ratified against that bundle’s real concepts.
OKF-alignment check (pre-ratification, upstream knowledge-catalog @ d44368c). The SPEC
prescribes no theme model: index.md is optional, MAY be auto-generated, consumers MAY
synthesize their own, and it never gates writes (§3/§6/§9 — a fixed taxonomy is an explicit
non-goal). The upstream reference_agent groups index sections mechanically by frontmatter
type (bundle/index.py) with per-directory recursive indexes — no configured map exists
upstream. Content that doesn’t naturally fit is never discarded: it mints into references/
as first-class concepts (web-ingestion prompt) or is an explicit skip; the index only reflects
what exists. Our theme_config is structurally gate-free (every concept appears exactly once;
unclaimed concepts land under the unthemed heading; the map is consulted only at index render),
so the editorial map cannot block or force-align concept writes — organic growth is preserved.
Open recommendation (bl-463): the no-map DEFAULT should group by concept type (mirroring
upstream) instead of one flat Other bucket, keeping index-themes.json as the optional
editorial override; per-client maps are generated at onboarding (corpus-derived draft per §4’s
method → client/owner ratification → {45.10} deploy-seed).
Pin caveat. Body file:line pins were captured at canonical 855118df (pre-conformance-wave);
commit 9a795c5e shifted producer line numbers — verify pins before implementation (the plumbing
seams themselves are unchanged).
Purpose. The concept producer regenerates a bundle-root index.md as a
themes → concepts progressive-disclosure outline. The first Platform producer run
passed no theme map, so all 18 published concepts render under a single ## Other
heading. This document gives the owner everything needed to ratify (a) where the theme
map should live and (b) a concrete starter map, plus a preliminary sketch for the first
client bundle.
All file:line citations are against scripts/cocoindex_pipeline/producer/ in the
canonical repo unless stated otherwise.
1. Current plumbing — how the index is themed today (and why it isn’t)
Section titled “1. Current plumbing — how the index is themed today (and why it isn’t)”build_index_themes(theme_config, concepts, *, unthemed_heading="Other")
(bundle_writer.py:418-470) is the pure-Python bridge from a caller-supplied
theme_config: [(heading, [rel_path, ...]), ...] to the renderable IndexTheme list
that regenerate_indexes (bundle_writer.py:391-415) turns into index.md. Its
contract: every concept appears under exactly one theme; any concept no
theme_config entry claims falls into a trailing unthemed_heading bucket
(bundle_writer.py:455-469); a theme_config entry naming a rel_path not present in
concepts is silently skipped rather than inventing an entry. So the mechanism is
already safe against a stale/incomplete map — it just needs one supplied.
The theme_config value threads cleanly through the whole chain, defaulting to empty
at every hop:
| Hop | Site | What it passes |
|---|---|---|
write_bundle param | bundle_writer.py:731 | theme_config: Sequence[...] = () (default empty) |
write_bundle → builder | bundle_writer.py:844-847 | build_index_themes(theme_config, written) then declare_file(bundle_dir/index.md, ...) |
run_producer_flow param | flow_def.py:339 | theme_config: Sequence[...] = () (default empty) |
run_producer_flow → write_bundle | flow_def.py:403-408 | theme_config=theme_config |
default_producer_entry_point → flow | trigger.py:120 | run_producer_flow(pool=..., bundle_dir=..., **flow_kwargs) |
| Real invocation (post-walk) | flow.py:4768-4773 | trigger_producer_post_walk(run_op_id, deltas, pool=..., re_target=..., repo_path=...) — no theme_config |
Conclusion. The pipe is fully built; nothing fills it. The production trigger in
flow.py passes only pool / re_target / repo_path, so theme_config inherits its
() default end-to-end. build_index_themes(()) claims nothing, so all 18 concepts land
in the trailing Other bucket — which is exactly what the published
canonical-okf-showcase/index.md (pre-rename canonical-okf-platform) shows (one ## Other heading over all 18 links). This
is a missing configuration source, not a code bug.
The precedent surface: how per-client config (client_overlay) is supplied
Section titled “The precedent surface: how per-client config (client_overlay) is supplied”The {132.34} client ontology overlay is the pattern theme_config should copy:
- A client-authored file at the bundle repo root.
OVERLAY_FILENAME = "ontology-overlay.json"(bundle_writer.py:170).read_client_overlay(bundle_dir)(bundle_writer.py:633-664) readsbundle_dir / OVERLAY_FILENAME, validates it, and returns an OV-6 provenance-wrapped mapping (source,sha256, dimensions) orNonewhen absent (absence is not an error). - Read at runtime, threaded in by
write_bundle.write_bundlecomposesoverlay = client_ontology_overlay if ... is not None else read_client_overlay(bundle_dir)(bundle_writer.py:780-784), then hands it towrite_ontology_artefact(bundle_dir, client_overlay=overlay)(bundle_writer.py:848, def at667-696). The explicit kwarg is only a test/escape-hatch override; the real source is the file in the bundle. - Producer never writes it (DR-016 client-ownership).
OVERLAY_FILENAMEis in_RESERVED_BUNDLE_FILENAMES(bundle_writer.py:171), so cocoindex’s orphan-delete reconciliation leaves it alone (S464 rider R1, noted atbundle_writer.py:646). The producer only ever reads it. - Single-clone model (DR-055).
bundle_dir == repo_path == OKF_BUNDLE_DIR(flow.pycomment ~4758). The producer auto-resolvesbundle_dirfrom theOKF_BUNDLE_DIRenv var (flow_def.py:197-215). Config that lives in the bundle therefore needs no extra wiring to be found. - Bundle self-carries its effective config (DR-027).
write_ontology_artefactships the materialised ontology into the bundle so a consumer needs no other repo.
This is the surface theme_config should ride: a sibling client-owned file at the
bundle root, read at runtime, never producer-written.
2. Config-home options — and the recommendation
Section titled “2. Config-home options — and the recommendation”The theme map is editorial, client-owned data about the client’s own bundle (BI-5:
the theme→concept mapping is “an owner’s call”). That fact drives the decision more than
any mechanical concern. Weighed against client-ownership (DR-016), portability,
producer determinism, and the {45.10} deploy-time seeding boundary:
Option A — a bundle-root client-authored file (RECOMMENDED).
A new sibling of ontology-overlay.json, e.g. index-themes.json, at the bundle repo
root. Add a read_theme_config(bundle_dir) reader mirroring read_client_overlay, add
the filename to _RESERVED_BUNDLE_FILENAMES (bundle_writer.py:171), and feed its result
into the existing theme_config= seam (write_bundle at bundle_writer.py:780-ish,
right where read_client_overlay is already called).
- Client-ownership (DR-016): the map lives where the client edits it — in their own bundle repo, exactly like the overlay. ✔
- Portability: travels with the repo (DR-027 self-carrying posture; DR-055 single-clone means it’s already on disk where the producer looks). ✔
- Producer determinism: a theme edit re-shapes
index.mdonly — no concept bodies change, no re-embed, no new commits beyond the regenerated index. Cheapest possible change surface. ✔ - Cost: one small reader + one line in the reserved-filenames set + one line at the
write_bundlecompose site. Mirrors code that already exists.
Option B — producer-side per-client config (env/config in the platform deploy).
The theme_config kwarg already threads all the way down, so the plumbing would work if
the platform supplied it at flow.py:4768. But this puts the client’s editorial map on
the platform’s deploy surface, splitting client config across two homes (overlay in
the bundle, themes in the platform) and violating the DR-016 posture that the client owns
their bundle’s shape. Rejected as the primary home.
Option C — env var (OKF_THEME_CONFIG as JSON).
Env is for deploy-time wiring (OKF_BUNDLE_DIR), not structured editorial data. A
~17-theme × N-concept map is too large and too frequently edited for an env var, and it’s
not client-editable. Rejected.
Recommendation: Option A. Co-locate the theme map with the overlay it is a sibling of, on the same client-owned surface, read the same way. It keeps DR-016, DR-055, and DR-027 intact and is producer-deterministic.
Interaction with the {45.10} deploy-time seeding boundary. Because the theme map is
client-editorial (not producer-generated), it sits on the client side of the seeding
boundary — the same side as the overlay. Concretely: the deploy seed can drop a starter
index-themes.json (the §3 draft below) when the bundle repo is first minted, so the
very first producer run yields a themed index.md instead of all-Other; from then on
the client owns and edits it. The producer never regenerates or overwrites it (reserved
filename). This keeps deploy-time seeding to a one-time scaffold and leaves steady-state
ownership with the client.
File format. theme_config is an ordered [(heading, [rel_path, ...]), ...];
section order in index.md follows config order. JSON has no tuples, so the readable
client-editable shape is an ordered array of objects; the reader converts to the tuple
form build_index_themes expects:
[ { "heading": "Company Overview", "concepts": ["company/overview.md"] }, { "heading": "Products & Service Offerings", "concepts": ["products/stocksense-ordering-portal.md", "..."] }]3. DRAFT Platform-bundle theme map (all 18 concepts, no leftovers)
Section titled “3. DRAFT Platform-bundle theme map (all 18 concepts, no leftovers)”Themes are business-domain (bid-writer’s navigation), derived from concept
frontmatter/tags plus the corpus signals (the Platform seed corpus carries a capability
statement → company, procurement forms → tendering, methodology/sector-intel →
services), and cross-checked against the first client’s real “BID RESPONSE TOPIC INDEX”
row vocabulary. Order below is the intended index.md section order (who we are → what we
sell → what we’re accredited for → how we operate → proof).
As a Python literal for theme_config (also the shape the JSON reader would produce):
theme_config = [ ("Company Overview", [ "company/overview.md", ]), ("Products & Service Offerings", [ "products/stocksense-ordering-portal.md", "products/rapidrestock-managed-print-service.md", "products/greencycle-furniture-programme.md", ]), ("Accreditations & Standards", [ "certifications/iso-9001.md", "certifications/facilities-assurance-standard-fas-2100.md", "certifications/sscm.md", "certifications/sustainable-supply-chain-mark-sscm.md", ]), ("Quality Management", [ "topics/quality-management.md", ]), ("Data Protection & Information Governance", [ "topics/data-protection.md", ]), ("Sustainability & Social Value", [ "topics/social-value.md", ]), ("Bids & Tendering", [ "topics/procurement--tender-evaluation.md", "case-studies/won-bid/northgate-borough-council.md", ]), ("Client Case Studies", [ "case-studies/corvedale-academies-trust.md", "case-studies/northgate-borough-council.md", "case-studies/ridgeway-commercial-services-ltd.md", "case-studies/st-aldhelm-s-nhs-foundation-trust.md", "case-studies/wyndale-metropolitan-borough-council.md", ]),]Coverage check: 1 + 3 + 4 + 1 + 1 + 1 + 2 + 5 = 18 concepts, zero left to Other.
Rationale / non-obvious calls:
- Concepts are claimed exactly once (
build_index_themesinvariant), so cross-cutting concepts get one editorial slot.greencycle-furniture-programmeis tagged sustainability/circular-economy/social-value but is aproduct— placed under Products (it is a sellable programme); its social-value story is reachable via the Sustainability theme’s own concept. - The won-bid case study (
case-studies/won-bid/…) is placed under Bids & Tendering alongside the tender-evaluation topic — it is the “proof we win tenders” evidence a bid-writer wants there, and it keeps the two Northgate concepts from sitting side-by-side under one heading. The five non-won engagements stay under Client Case Studies. This mirrors the bundle’s ownwon-bid/subdirectory split (README). - Quality Management and Data Protection are kept as their own single-concept themes because a bid-writer navigates by those exact topics (the client’s real bid index has distinct rows for both). If the owner prefers fewer headings, the cheapest merge is folding Quality Management into Accreditations & Standards (ISO 9001 is the shared anchor) — a one-line edit.
- Observation (out of scope, flag only):
certifications/sscm.mdandcertifications/sustainable-supply-chain-mark-sscm.mdare near-duplicate concepts (same SSCM accreditation). Both are mapped here; whether one should be removed is a data/producer question, not a theme-map question.
4. Preliminary first-client theme sketch (headings only)
Section titled “4. Preliminary first-client theme sketch (headings only)”No client bundle exists yet (no concepts minted), so this is candidate headings, not a concept map. The first client corpus already contains its own “BID RESPONSE TOPIC INDEX” (in the corpus’s deduplicated bid-library markdown) — i.e. the client has, in effect, already authored the ~17-theme business-domain classification this feature is built for. These candidate themes are lifted from that index (they are generic bid-topic categories, not client-identifying):
| Candidate theme | One-line rationale |
|---|---|
| Company Overview & Credentials | Anchor “who we are” row; every bid opens here. |
| Products & Services | The client’s service/digital-product catalogue (corpus 02-services-and-digital-products). |
| Data Protection & GDPR | Distinct, high-frequency bid topic; corpus has a dedicated section. |
| Security & Information Governance | ISMS / governance answers, separate from data-protection specifics. |
| Penetration Testing & Vulnerability Management | Recurrent technical-assurance bid question. |
| Encryption & Data Security | Encryption-at-rest/in-transit answers. |
| Business Continuity & Disaster Recovery | Standard resilience bid section. |
| Development & Change Management | SDLC / change-control assurance. |
| Support & Implementation | Onboarding, SLAs, implementation model (corpus bid-library “Implementation & Support”). |
| Insurance & Commercial | Insurances, pricing, commercial terms. |
| Corporate Social Responsibility & Social Value | CSR / social-value row (corpus 06-company-values-and-ethos). |
| Sub-Contractors & Supply Chain | Supply-chain and sub-contractor disclosures. |
| Staffing & People | Team structure, key people, vetting (corpus 05-team-structure-and-key-people). |
| Secure Disposal & Data Sanitisation | End-of-life data handling. |
| Audit & Monitoring | Logging, audit trails, compliance monitoring (corpus 07-compliance-governance-and-certifications). |
That is ~15 headings — squarely the “~17 business-domain themes” BI-5 anticipates, and materially broader than the Platform seed’s 8. It confirms the design premise: theme count and shape are per-client, so the map must be a per-bundle client-owned config, not a producer constant.
5. What remains the owner’s call (ratification list)
Section titled “5. What remains the owner’s call (ratification list)”- Config home — ratify Option A (a client-owned
index-themes.jsonat the bundle root, sibling ofontology-overlay.json) vs Option B/C. (Recommendation: A.) - The file name —
index-themes.json?theme-config.json? (Needs to be added to_RESERVED_BUNDLE_FILENAMES.) - Platform theme headings + membership — ratify the §3 map, including the specific calls: won-bid → Bids & Tendering; GreenCycle → Products; keep vs merge the Quality Management and Data Protection singletons.
- Section order — ratify the §3 ordering (or reorder).
- Deploy-seed policy — should the deploy drop the §3 starter map so the first client
producer run is themed out of the box, or is an all-
Otherfirst index acceptable until the client authors their own? ({45.10}boundary decision.) - First-client themes — the §4 sketch is for planning only; the actual client theme map is ratified when/if a client bundle is minted, against that bundle’s real concepts.
- SSCM duplicate — a separate data/producer decision, flagged here for visibility.