# SSP spine reconcile reference

How the SSP asset spine — Capabilities, Components, Control Implementations, Implemented Requirements, Implementation Statements, Set Parameters, and the relationships that wire them — is materialized and kept consistent in Halo via the diff-and-create reconcile pattern, exposed as six reconcile tools on the grcitsm MCP server.

**Write FIELDS, not EDGES.** The spine's shape is a function of `Site.CFBaselines`, `CFBaseline` on each asset, the framework CFs, the Component `inventory_number` convention, Set Parameter Scope, and (on the ticket side) ticket category. Get those driver fields right and the tenant derives the graph on Halo asset events, with no MCP call. The only relationship calls a skill should ever make are **rel 8** (Component→IS), **rel 9** (Cloud Asset→Component), and **non-FedRAMP-Controls rel 12** — see § Edges nothing reconciles.

## The pattern

The reconcile pattern follows one shape end to end: **read the spec, query existing state, diff, create only what is missing.** Every reconcile is fully idempotent — a re-run on stable state finds an empty diff and writes nothing.

Spec sources (what SHOULD exist):

- `CTCapabilities` custom table (15 rows — see the `ssp-capability-seed-list` skill) — drives Capability creation per Site
- `Site.CFBaselines` CSV — drives Control Implementation creation per Site × Baseline
- `CTControlBaselines` catalog — drives Implemented Requirement creation per CI × Control
- The framework CFs on IRs and ISs (`CFAFedRAMPControls` / `CFACMMCControls` / `CFAFedRAMPKSIs`) — drive IR→IS relationship matching

State source (what DOES exist): Halo's `Device` (assets) + `DeviceChild` (relationships) tables.

## The six reconcile tools

Run them in this order. Each takes no parameters and sweeps every Site.

**The five relationship reconcilers are also event-triggered** on `Asset created` + `Asset updated`, so on a tenant carrying them the edges settle within seconds of the driver fields landing — the MCP tools are the on-demand/backfill path, not the only path. They compute desired-vs-existing and correct drift on every asset event, rewriting the full `child_assets[]` array for any subject with drift. **An out-of-band edge on a reconciled (subject, relationship) pair is therefore at risk of being swept** — never hand-wire an edge a reconciler owns.

### 1. `reconcile_ssp_assets`

Creates any missing SSP framework assets across every Site:

- **Capabilities** — `CTCapabilities × Sites (with non-empty CFBaselines)` diffed against existing Capability assets (dtype 178 per the standard GRCITSM tenant schema) → creates per-Site Capability assets with AFUUID `CAP-{SystemID}-{Acronym}`.
- **Control Implementations** — `Site.CFBaselines × baseline catalog` diffed against existing CI assets (dtype 192) → creates per-Site per-Baseline CI assets with AFUUID `CI-{SystemID}-{CatalogShort}` (short code resolved from `CTBaselines.CFBLShortCode`, e.g. `FR5CC`). Sets `CFBaseline` (CF id 503) at creation.
- **Implemented Requirements** — `CIs × CTControlBaselines` diffed against existing IR assets (dtype 190) → creates per-CI per-Control IR assets with AFUUID `IR-{SystemID}-{BaselineSlug}-{ControlID}`. Sets BOTH `CFBaseline` (503) AND the framework Controls CF matching the control's shape (FIid 382 FedRAMP Controls / 383 CMMC / 384 KSIs) at creation — no post-create framework-CF backfill is needed.

It sets identity fields (UUID, Title, Baseline) on every created asset. It does NOT wire relationships and does NOT create Implementation Statements (those come from ingest/interview flows, not from the catalog spec).

### 1a. `reconcile_ssp_relationships_cap_component`

Wires the Capability → Component hop (relationship 6, `Capability Has Components`). **The edge is derived from the Component LIBRARY, not from anything an ingest run decides.** Two driver fields:

- The Component's `inventory_number` must be exactly `CMPN-` + `CTComponents.CFComponentAcronym` — which is the standing AFUUID convention (`[[halo-ssp-asset-write-conventions]]` Rule 2), so a library-instantiated Component complies by construction.
- The matching library row must carry `CFComponentCapability` (the Capability slug).

A Component whose `inventory_number` drifts off the `CMPN-<CFComponentAcronym>` shape, or whose library row has no `CFComponentCapability`, will not attach — that is the diagnostic for a missing rel-6 link, not a reason to hand-wire one. Extending the library's `CFComponentCapability` is the fix; free-authored Components with no library row are the one case needing another route (raise them as a library-extension candidate per Rule 2).

Live-confirmed 2026-08-09 on Dev: `create_asset(Component)` + `update_component_from_library` produced the Capability→Component edge with no relationship call.

### 2. `reconcile_ssp_relationships_ci_ir`

Wires the CI → IR hop (relationship 10, `Control Implementation Has Implemented Requirements`): links each Control Implementation to every Implemented Requirement in the same Site and baseline, and removes links that no longer belong. The match is **structural** — `Device.dsite` + `CFBaseline` equality — never AFUUID/Title string parsing. Baseline-scoped and control-shape-agnostic: covers FedRAMP Controls, KSIs, FedRAMP Rules, and CMMC alike. Run AFTER `reconcile_ssp_assets`.

### 3. `reconcile_ssp_relationships_ir_is`

Wires the IR → IS hop (relationship 7, `Implemented Requirement Has Implementation Statements`): links each Implemented Requirement to every Implementation Statement in the same Site and baseline whose framework CF list CONTAINS the IR's value — a CSV-intersection match run independently across **four** families (UNION ALL), one branch per CF:

| CF | Field id | Family |
|---|---|---|
| `CFAFedRAMPControls` | 382 | FedRAMP Rev5 controls |
| `CFACMMCControls` | 383 | CMMC practices |
| `CFAFedRAMPKSIs` | 384 | FedRAMP 20x KSIs |
| `CFAFedRAMPRules` | 597 | FedRAMP Rules |

**The Requirements CFs are NOT matched** — `CFAFedRAMPRequirements` (399) and `CFACMMCRequirements` (400) have no branch. An IS carrying only Requirements values will never auto-link, however well-formed it looks. Run AFTER the other two.

A link only forms when **both sides** carry the matching framework custom field — an Implementation Statement missing its control / KSI / Rule / CMMC value will not attach. That makes populating the IS framework CFs (per the two-pass rule in the `halo-ssp-asset-write-conventions` skill) a precondition for relationship reconciliation, and a missing rel-7 link is the diagnostic signal for an unpopulated, Requirements-only, or out-of-baseline framework CF — **once the edge has had a chance to settle** (see § Verifying an edge before calling it absent).

Live-confirmed 2026-08-09 on Dev: `update_implementation_statement_from_library` writing `KSI-IAM-AAM` produced the rel-7 edge to `IR-DEMO-FR20XCC-KSI-IAM-AAM` with no relationship call.

### 4. `reconcile_ssp_relationships_ir_sp`

Wires the IR → SP hop (relationship 12, per-control organization-defined parameters): links each Implemented Requirement to every Set Parameter (dtype 193, AFUUID prefix `SP-`) in the same Site and baseline that has **Scope = Control** and shares a FedRAMP control.

**FedRAMP Controls ONLY.** Unlike rel 7, this reconciler has a single match branch — `CFAFedRAMPControls`. There is no KSI, CMMC, or Rules branch. A Set Parameter scoped to a KSI, a CMMC practice, or a FedRAMP Rule will **never** auto-link and MUST be hand-wired with `set_asset_relationships`. This is the one relationship carve-out where an explicit call on a reconciled relationship id is correct — and it is safe precisely because the reconciler never removes links (add-and-fix-drift only, below). Adds missing links and fixes drifted relationship types only — it **never removes existing links** (an IR's IS links are preserved). Run AFTER the Set Parameter assets are created with their `CFBaseline` + FedRAMP Controls CF set (two-phase per the `halo-ssp-asset-write-conventions` skill — a combined write silently drops the control, which then blocks the link from forming).

### 5. `reconcile_ssp_relationships_ci_sp`

Wires the CI → SP hop (relationship 11, system-wide organization-defined parameters): links each Control Implementation to every Set Parameter in the same Site and baseline with **Scope = System**. Same add-and-fix-drift-only semantics — never removes existing links. Per-control SPs (Scope = Control) attach to their Implemented Requirement via reconciler 4 instead; most ODPs are control-specific, so the system-wide set is often empty. Run AFTER the system-wide Set Parameter assets exist with `CFBaseline` set.

Scope routing is exclusive: a Set Parameter attaches to exactly one parent hop — `Scope = Control` → IR (rel 12), `Scope = System` → CI (rel 11). On the bulk/ingest path these two reconcilers own the SP parent links — never hand-wire them (single-shot `set_asset_relationships` is acceptable only for a one-off SP outside an ingest run, per [[halo-ssp-asset-write-conventions]]).

## Edges nothing reconciles — wire these explicitly

Verified by scanning every deployed runbook for `AS relationship_id`: only 6, 7, 10, 11 and 12 appear. Everything else is the writer's job.

| Edge | Why it needs an explicit call |
|---|---|
| **rel 8** Component → Implementation Statement | No reconciler. An IS created without an explicit rel-8 call ends up hanging off its Implemented Requirement (rel 7, automatic) but off **no Component** — which silently breaks any SSP rendering that walks Capability → Component → IS. |
| **rel 9** Cloud Asset → Component | No reconciler. |
| **rel 12** for KSI- / CMMC- / Rule-scoped Set Parameters | The IR→SP reconciler matches `CFAFedRAMPControls` only (§ 4). |

## Verifying an edge before calling it absent

The reconcilers fire on Halo asset events and settle within seconds. **Do NOT conclude an edge is missing merely because your own code did not create it** — after a spine write, read back with `get_asset` (`parent_assets[]` / `child_assets[]`) and give the event a moment to land before diagnosing anything as absent. A premature read is the most common source of a false "missing rel-7 link" report, and the fix for a genuinely missing edge is almost always a driver field, never a hand-wired relationship.

## Diff semantics

All five reconciles use the same SQL shape under the hood:

```sql
WITH DesiredX AS (
  -- the spec: what SHOULD exist
),
ExistingX AS (
  -- Halo state: what DOES exist
),
SubjectsWithMissing AS (
  SELECT DISTINCT dx.subject_id
  FROM DesiredX dx
  LEFT JOIN ExistingX ex
    ON ex.subject_id = dx.subject_id
   AND ex.target_id  = dx.target_id
  WHERE ex.subject_id IS NULL
)
SELECT ... FROM SubjectsWithMissing
```

Two `DeviceChild` quirks shape how relationship diffs work:

- **`DeviceChild.DCID` is the PARENT asset id and `DCCID` is the CHILD** — the column names suggest the opposite of their meaning. True parent/child rows are those with `dcissibling = 0 OR IS NULL`.
- **Halo stores `dcrelationship = 0`** in `DeviceChild` even when the write specified a typed relationship — the typed relationship lives only in the API-side derived view, not the row. Relationship diffs therefore match on **(subject, target) only, never on relationship_id**.

Relationship matching uses **only first-class structured fields** (Site, Baseline, framework CFs, FKs, catalog tables) — never substring/LIKE parsing over `name`, `title`, AFUUID, or `inventory_number`. If the structured field isn't populated, backfill it first.

Relationship writes are full-set per subject: the complete relationship set for each subject is written in one shot, preserving out-of-band relationships while replacing the reconciled set.

## Idempotency

- A re-run on stable state returns an empty diff at every step — nothing is created, nothing is touched.
- Run the asset reconcile after anything that changes the spec: baselines added/removed on a Site, a control-catalog refresh, or Implementation Statement library changes.
- Run the relationship reconciles after the asset reconcile, and after any bulk IS create/update that changed framework CF values.
- The reconcile tools cover Capability / CI / IR creation and the Capability→Component (rel 6), CI→IR (rel 10), IR→IS (rel 7), IR→SP (rel 12) and CI→SP (rel 11) wiring. What the ingest/interview flows still write: `system_information`, Components, Implementation Statements themselves, and the rel 8 / rel 9 / non-FedRAMP-Controls-rel-12 links per the write order in the `halo-ssp-asset-write-conventions` skill.
- **Fallback for a tenant missing a reconciler.** All six reconcilers are expected on every tenant; where one is absent it should be deployed. Until it is, the corresponding edge falls back to an explicit `set_asset_relationships` call at its write-order position — full child list per parent, since relationship writes are full-set per subject. Check availability from the session tool catalog (or `get_runbooks`) rather than assuming: a reconciler present but `Active: No` / `Enabled For Instances: No` does not run.
- **Rollups are reconciler-owned too.** `Rollup Implemented Requirement Status` derives an IR's `AFImplementation State` from its child Implementation Statements on asset events. Set Implementation State on the IS leaves; write it on the IR only where no leaf can carry the value, and treat that as transient — the next asset event recomputes it.

## Pair with

- The `halo-ssp-asset-model` skill — the asset-spine schema (dtype 178/173/190/191/192) and relationship restriction allow-list.
- The `halo-ssp-asset-write-conventions` skill — write-time field shape conventions for SSP asset types.
- The `ssp-capability-seed-list` skill — the canonical 15 Capabilities behind the Capabilities iteration.
- `halo-platform/write-semantics.md` (this folder) — full-replace semantics and lookup-value rules for any manual writes alongside the reconcilers.
