# Grounding + Identity Contract (`R-GND-*`)

The cross-cutting backbone. Every claim in every artifact of A and B is grounded here, and every node carries a stable identity here. Operation laws (`laws.md`) reference these by id; they never restate them.

This is the contract that makes the guardrails *trustworthy*: it is what lets a human — or the KG ingest — audit any single claim back to a real source, and it is the discipline that keeps the LLM's fluent domain knowledge from silently manufacturing facts.

---

## Identity — the dual-ID scheme

Every LO, component, and authored node carries **two** ids:

- **Local id** — chapter-stable, sequential, always present. Independent of KG state.
  - LO: `LO-G<grade>C<chap>-NN` · Component: `LC-G<grade>C<chap>-NNN` · Chapter: `G<grade>C<chap>` · Journey: `<chapter>J<pos>`
- **Canonical id** — the KG anchor. One of four resolutions:

| Resolution | Canonical id | Meaning |
|---|---|---|
| **KG-resolved** | the exact KG LO id (e.g. `merdeka-math-…`) | first choice, always — reuse the KG string verbatim |
| **Bridged** | the KG LO id + `sister_curriculum_anchors{}` | cross-curriculum equivalence via a KG cluster/bridge edge |
| **External-only** | a real external standard code (e.g. `CBSE.6.AL.A.4`) | the KG has no node; an external standard captures it |
| **Proposed** | a proposed KG node, `state: ai_proposed`, `kg_write_candidate: true` | a genuine gap — authored through the KG's `create_lo` / `bulk_create_*` path, human-gated. **Replaces the old `CRX.*` scheme.** |

The local id is used in the chapter's internal navigation; the canonical id in all cross-references, KG lookups, and ingest. The proposed-vs-resolved distinction is a **hard signal** — never blur a proposed node into a resolved one (`R-GND-01`).

---

## The laws

### R-GND-01 · No invented ids &nbsp;`[HARD]`
**Law.** Every reference to a KG entity — LO id, misconception id, VIR id, interactive/activity URL, convention id, false-friend id, curriculum standard code — must resolve to a **real KG node or tag**. When none matches, the reference is `null` and the claim is flagged (`R-GND-02`); a genuinely-needed new entity is authored as a **proposed** node, never asserted as resolved.
**Detect.** Any canonical id / URL not present in the pulled KG evidence; any proposed node lacking `kg_write_candidate: true`.
**Seam.** `kg` (resolve against real nodes) + `compile` (cross-check every id against the evidence bundle).
**Why.** The cardinal failure mode of a fluent LLM is a plausible fabricated identifier. URLs and misconception ids especially: they *look* right and are load-bearing downstream.

### R-GND-02 · Gap does not mean exclude &nbsp;`[HARD]`
**Law.** Missing KG coverage never means dropping the content. Author it, set the KG reference `null`, flag `kg_gap: true` with a reason, and surface it for KG enrichment.
**Detect.** Content silently absent where the textbook/curriculum demands it; a `null` reference without a `kg_gap` flag.
**Seam.** `prompt` + `compile` (null-without-flag is a violation).
**Why.** Silent omission hides a real curriculum need and starves the KG-enrichment loop. Null must be *explicit*, never *silent*.

### R-GND-03 · Gap over fabrication &nbsp;`[HARD]`
**Law.** When a claim cannot be grounded in a KG node or a source page, default to `gap::<reason>` rather than a fabricated claim. Gaps are honest; fabrications are dishonest.
**Seam.** `prompt` + `compile` (a `synthesised::low` claim with no reasoning trace is treated as a candidate fabrication).
**Why.** This is the line that lets the kit *trust* the model's domain fluency elsewhere: fluency is welcome for synthesis and gap-identification, forbidden for grounding.

### R-GND-04 · Reuse before invent &nbsp;`[HARD]`
**Law.** Before authoring *anything* new — an LO-component, a screen, a visual model, or a whole applet — search the KG for what it has **already extracted** on the same LO and **reuse or adapt** it. Two reuse surfaces:
1. **Components / substrate** — existing `lo_components`, misconceptions, VIRs, IAS patterns tagged to the LO. Statement-equivalent match (canonical form: stripped + lower-cased; near-paraphrases count) → reuse (`reuse::<lo>#<node>`).
2. **Built assets** — the KG's *extracted lesson/asset corpus*, spanning every asset kind: **lesson slides, images, applets, mcp-simulations, videos**. Discovery + alignment come from the KG (`kg_lesson_full` → per-lesson `lesson_brief`, `slides_brief`, `asset_briefs` with `content_moments` / `pedagogy` / `assessment_inference`; `kg_list_ai_tags` → per-asset `asset_kind`, `slide_position`, `lo_id`, evidence excerpt, confidence). When an extracted asset of any kind already teaches this LO, **reuse or adapt it** (`kg_tag::<asset>::<lo>`) rather than authoring a net-new one.
   - **KG discovers, CMS runs.** The KG holds the *extract + LO alignment*; the *runnable bytes* (deck, image, applet bundle, mcp-sim, video) live in the CMS. Reuse is a two-hop: KG says which asset teaches the LO → resolve its bytes from the CMS at build time. Provenance carries both: `kg_tag::<asset>::<lo>` (why it's a match) + the CMS asset id.

Mint new only when the KG has no reusable asset/component, marking it `kg_write_candidate: true`.
**Detect.** A newly-authored component/screen/applet whose intent matches an existing KG component or extracted asset on the same LO, with no `reuse::`/`kg_tag::` provenance.
**KG.** `kg_lesson_full`, `kg_list_ai_tags`, `kg_lo_neighborhood`.
**Seam.** `prompt` (the search is instructed) + `kg` (equivalence check at ingest).
**Grounding.** grounding-discipline (convergence, not sprawl) — reuse-first keeps the built corpus converging and stops the KG accreting duplicates. Instantiated for the blueprint by `R-BLU-22`.

### R-GND-05 · Every enumerated claim carries provenance &nbsp;`[HARD]`
**Law.** Every entry in these lists carries a `_provenance` tag (below) with a **reason**: `surfaced_lo_codes`, `curriculum_alignment`, `prerequisite_chain`, `misconceptions`, `visual_models`, `interactive_landscape`, `false_friends`, `conventions_flags`, `tropes`, `cultural_anchors`, per-journey `lo_codes`, `lo_components`, `visual_treatments`, `ias_recommendations`.
**Detect.** Any listed entry without a `_provenance`, or with a tag of an unknown type.
**Seam.** `compile`.

### R-GND-06 · Delivery-language purity &nbsp;`[HARD]`
**Law.** Never author native-language student-facing tokens into LOs, component statements, or specs. Native-language parentheticals in KG source text are **localization references only** — strip them from generated content (e.g. drop `and pangkat` from an authored LO). The delivery language is `lever:content_scope.language`.
**Detect.** Non-target-language tokens in any generated `statement` / `lo_text` / spec field.
**Seam.** `compile` (a cleanse pass) + `prompt`.
**Why.** Prevents source-language pollution from leaking into student-facing output — a subtle, common LLM leak when the KG source is bilingual.

---

## The tag vocabulary

Every `_provenance` value is one of these types (compose with `+` when a claim has multiple sources).

| Tag | Meaning | Use when |
|---|---|---|
| `kg_node::<type>::<id>::<reason>` | direct KG node reference | a KG node is the source. `type` ∈ LO / misconception / vir / interactive / ias / convention / false_friend / curriculum. `reason` ∈ `fk_from_lo` / `keyword_match` / `lo_overlap` / `strand_breadth` / `term_match` / `neighbourhood` |
| `kg_tag::<asset>::<lo>` | KG asset↔LO alignment tag | grounding an asset/interactive/VIR to an LO via `kg_list_ai_tags` |
| `reuse::<lo>#<component>` | reused approved component | `source_lo_code` is non-null after a statement-equality match (`R-GND-04`) |
| `kg_source::<doc_id>::p<page>` | KG source-document citation | the source is a textbook page (`R-CHP-01`); always cite the page |
| `kg_bridge::<cluster_or_edge_id>` | cross-curriculum bridge | a curriculum alignment translates via a KG cluster/bridge edge |
| `synthesised::<low\|medium\|high>` | judgment-derived | no KG node or source page grounds it directly. **high** = corroborated by both KG-adjacent nodes AND a source page; **medium** = one source + clear reasoning; **low** = pure domain judgment, flagged for KG-write review |
| `gap::<reason>` | explicit gap | no claim can be grounded. Reasons: `kg_scope_ceiling`, `kg_misconceptions_thin`, `kg_interactive_thin`, `kg_ff_absent`, `iks_rows_absent`, … |

**Compound example:** `synthesised::high + kg_node::convention::103` · `synthesised::high + kg_source::G6C2::p13_teachers_note`

## The never-synthesize list

These four may **never** be `synthesised::*` — they are `kg_node`/`kg_tag`/external-code only, or `null` + gap:

1. **Interactive / activity URLs** — `kg_node::interactive::<id>` or `null` + `kg_status: no_kg_row_matched`. Never recalled from training.
2. **Misconception ids on a journey** — real KG ids; synthesised misconceptions use a `synth_misc_*` string id, never a fake integer.
3. **Reused-component references** (`{master}#{node}`) — must trace to a real KG component node.
4. **External standard codes** — real codes from the KG curriculum nodes only.

## The visible-badge promise

The renderer surfaces every `_provenance` as a coloured badge so a reader can audit any single claim to its named source. Badges:

| Badge | Meaning |
|---|---|
| `KG` (green) | KG-resolved node |
| `PROPOSED` (purple) | proposed KG node — `kg_write_candidate` |
| `EXTERNAL` (orange) | external standard (e.g. CBSE) with no KG node |
| `SYN` (yellow) | synthesised (misconception / false-friend / convention proposed) |
| `SRC` (blue) | source-document (textbook) citation |
| `GAP` (red) | uncovered gap / no-node candidate |

A badge is a contract: clicking `kg_node::misconception::70` must land on node 70 in the evidence with the matching description; clicking `kg_source::G6C2::p13` must land on that page carrying what was cited. When the promise can't be kept, use `gap::` or `synthesised::` — but never lie about provenance.
