Enrich a CISO Assistant framework YAML by linking each assessable requirement to reference control URNs from the central doc-pol library (CISO Assistant Key Reference Controls). Produces a reviewable xlsx and patches the framework YAML in place. Use when the user asks to "add reference controls to framework X", "link doc-pol controls to requirements", "wire up applied-control suggestions for framework Y", or wants to contribute a reference-control-enriched framework to backend/library/libraries/.
Enrich a CISO Assistant framework YAML by linking each assessable requirement to reference control URNs from the central doc-pol library (CISO Assistant Key Reference Controls). Produces a reviewable xlsx and patches the framework YAML in place. Use when the user asks to "add reference controls to framework X", "link doc-pol controls to requirements", "wire up applied-control suggestions for framework Y", or wants to contribute a reference-control-enriched framework to backend/library/libraries/.
Reference Controls Enricher
What this skill does
For a given framework YAML, walk every assessable requirement and propose 1–5 reference control URNs from the central doc-pol library (the CISO Assistant Key Reference Controls). Outputs:
a reviewable xlsx per framework
a YAML patch applied in place (adds URNs to each requirement's reference_controls field, adds doc-pol to dependencies)
a coverage report
Claude is the reasoner — no embedders, no local models. The pipeline is pure stdlib + pyyaml (+ optional openpyxl for xlsx).
When NOT to use this skill:
For frameworks with >500 assessable items: the conversation context fills up. Chunk the run across sessions or adapt tools/mapping_builder/map_v2.py for offline use.
For frameworks that already reference doc-pol:* URNs on most requirements: run coverage_report.py first; if coverage is already good, don't rewrite.
How to use the helpers
Stdlib + pyyaml scripts under .claude/skills/reference-controls-enricher/scripts/.
One JSON object per line. target_urns is the list of doc-pol URNs to link. Empty list → requirement is consciously left unlinked.
{"source_urn":"urn:intuitem:risk:req_node:<fw>:<ref>","target_urns":["urn:intuitem:risk:function:doc-pol:pol.access","urn:intuitem:risk:function:doc-pol:tech.mfa"],"confidence":8,"rationale":"Access control obligation with explicit MFA — maps to access policy + MFA capability."}
Persist /tmp/<framework>_verdicts.jsonl and append after each section so progress is recoverable if the conversation gets interrupted.
Always read source_urn from the parsed framework JSON's items[].urn — do NOT construct it from ref_id. Ref_ids may use mixed case (e.g. 2.A.1) but the URN suffix is always lowercase (e.g. 2.a.1). Constructing URNs by string concatenation is a known bug trap.
Parsed framework shape (important)
parse_framework.py emits a flatitems list, each item carrying section_urn, section_ref_id, section_name — items are not nested inside sections[].items. Iterate parsed["items"], not parsed["sections"][i]["items"]. This differs from common heuristic expectations and has bitten at least one implementation mid-session.
Workflow
Step 1 — confirm the request
Confirm with the user:
Target framework YAML path (the one being enriched)
Whether the framework already has a doc-pol dependency — if yes, existing reference_controls on requirements must be preserved (this skill appends, never replaces)
Whether they want the skill to apply in place or emit a review-only pass
Grep the framework for urn:intuitem:risk:function:doc-pol: to check prior linkage. Report coverage before touching anything.
Read both JSONs. Note n_assessable for the framework and the doc-pol prefix breakdown (doc / pol / proc / tech / phys / train).
Size gate: if n_assessable > 500, stop and recommend a chunked or offline approach.
Step 3 — build a section → prefix-family affinity map (DO NOT SKIP)
Read the sections array from the parsed framework (typically 5–30 entries). For each section, reason about which doc-pol prefixes are most likely relevant. doc-pol prefixes group by control-type:
10: obvious / near-identical semantic match (e.g. "implement the PSSI" → pol.main). Use sparingly — reserved for the ~15% of cases with zero interpretation.
8–9: clean semantic match — this is the norm and should be where most verdicts land.
7: plausible, some interpretation or scope trimming required.
6: mitigation / exception patterns — recognise the pattern "when X is not possible, entity implements risk-reduction measures". These legitimately map to proc.exception_mgmt + the parent topic at confidence 6. Don't chase these to higher confidence — they're correctly low.
0–5: weak — rare; only include if the requirement is thin and this is the best available.
Single-URN verdicts are fine when many requirements are sub-clauses of the same parent topic (e.g. ten pol.access sub-clauses in an IAM objective). Don't force artificial expansion to meet an imagined 2-URN minimum.
Four-plus-URN verdicts must be justified explicitly — e.g. a requirement listing "at minimum: crypto policy, access policy, audit policy, account policy" genuinely deserves 4 URNs. Default 4+ linkages are usually over-linking; 2–3 is the sweet spot.
Prefer strong high-level policy URNs (e.g. pol.access) over too-specific technicals when the requirement is broadly worded. Prefer specific tech URNs (e.g. tech.mfa) when the requirement explicitly mentions a capability. Don't over-link — 3 crisp URNs beat 8 soft ones.
Affinity is a starting heuristic, not a fence — if a requirement obviously needs a control from a different prefix family, pull it.
Append verdicts to /tmp/<name>_verdicts.jsonl section-by-section so progress is recoverable.
Verdicts with confidence < 5 — borderline calls to confirm or drop
Requirements with zero target URNs — either a true gap (add to doc-pol backlog) or a miss (reconsider)
Requirements with >5 URNs — likely over-linking
Apply the user's edits to the running jsonl before moving to Step 6.
Slug usage as a sanity signal. Check the most-cited doc-pol slugs — they should match the framework's character. Example: for a French NIS2-style framework (RECYF), pol.access, pol.network, proc.pam dominating is expected. For a privacy framework, expect pol.privacy, proc.dpia, doc.ropa at the top. If the top slugs don't match the framework's theme, something's off (too shallow, or affinity was wrong).
Also check the unused slugs: they should fall in out-of-scope domains relative to the framework. A security framework using 0 of the privacy/AI/OT slugs is normal. If a supposedly broad framework leaves 70%+ of slugs unused, reconsider affinity.
Expect "Successfully stored library". If the library fails to load, inspect the stderr and revert via git checkout -- backend/library/libraries/<framework>.yaml.
Step 8 — final summary, no auto-PR
Report:
Enrichment coverage % (from coverage_report.py)
Number of URNs added, number of requirements touched
Path to the review xlsx
Path to the verdicts jsonl (for replay / audit)
Suggested commit message
Do NOT auto-commit or auto-push. The user confirms.
Common mistakes to avoid
Over-linking. 3 tight URNs beat 8 loose ones. The suggestions UI exposes all linked controls to the auditor — noise hurts.
Constructing source URNs from ref_id. Case mismatches (2.A.1 vs 2.a.1) break resolution silently. Always use the urn field from the parsed framework JSON.
Iterating sections[].items.parse_framework.py emits a flat items list. Iterate parsed["items"] and group by section_ref_id if you need section structure.
Missing the affinity step. Without pre-filtering by prefix family, you'll waste context reading irrelevant controls for each requirement.
Replacing existing reference_controls.apply_enrichment.py only appends, but if a future script or hand-edit clobbers the list, audit evidence gets orphaned.
Forgetting the dependency line.apply_enrichment.py adds it automatically when any URN is added, but if you hand-edit the verdicts file, the library will still fail to load on import with URN-resolution errors.
Ignoring zero-URN requirements. A truly uncovered requirement tells you either: (1) the framework is asking for something doc-pol doesn't have (flag for doc-pol extension), (2) the requirement is too abstract to link (leave empty by design), or (3) you missed a match (revisit).
Re-applying after editing verdicts.apply_enrichment.py only appends and dedups — it cannot remove URNs from a prior apply. To change verdicts after an apply, git checkout -- <framework.yaml> first, then re-run with the updated jsonl.
Chasing confidence 10 for mitigation cases. Requirements phrased "when X is not possible, implement mitigating measures" correctly map to proc.exception_mgmt + the parent topic at confidence 6. Don't force them higher — the lower score accurately signals the exception nature.