---
name: halo-ssp-asset-write-conventions
description: Disciplined-write rules for the Halo SSP asset spine — AFUUID idempotency and per-type conventions, framework-agnostic Implementation Statement Title/AFUUID, spine write order with the reconciler-owned steps, and the Set Parameter write sequence. Platform write mechanics live in the vendored halo-platform corpus. Load before composing any write against an SSP-spine asset type or system_information.
---

# SSP asset write conventions

## References

| Reference | Type | Location | When to Use |
|---|---|---|---|
| `write-semantics.md` | File | `reference-docs/halo-platform/write-semantics.md` | **Platform write mechanics these rules build on** — merge-on-omit (§ 2), framework-CF two-pass + CFBaseline + site-scope (§ 5), new-type gotchas (§ 6), lookups (§ 3), verify + citation (§ 11) |
| `halo-ssp-asset-model` | Skill | `plugins/grcitsm-analyst/skills/halo-ssp-asset-model/` | What the fields ARE — schema + AFUUID conventions |
| `ssp-capability-seed-list` | Skill | `plugins/grcitsm-analyst/skills/ssp-capability-seed-list/` | Seeded Capabilities + starter Component mappings |
| `grcitsm` | Skill | `plugins/grcitsm-analyst/skills/grcitsm/` | Router to the broader catalog doctrine |
| `halo-bulk-update` | Skill | `plugins/grcitsm-analyst/skills/halo-bulk-update/` | Orchestration when a write run exceeds ~20 rows |
| `ssp-brand-voice` | Skill | `plugins/grcitsm-analyst/skills/ssp-brand-voice/` | Practitioner voice for Title/Description narrative |
| `ssp-spine-reference.md` | File | `reference-docs/ssp-spine-reference.md` | **Relationship ownership map** — six reconcilers, their driver fields, the three hand-wired edges |
| `ssp-write-surfaces.md` | File | `reference-docs/ssp-write-surfaces.md` | Per-surface write chains + payload examples, incl. the Set Parameter sequence |
| `halo-ssp-asset-schemas.md` | File | `reference-docs/halo-ssp-asset-schemas.md` | Per-type field schemas + IS lookup value sets and OSCAL crosswalk |

The disciplined-write rules for the SSP asset spine. Every rule is anchored to a verified Halo failure mode. Builds on [[halo-ssp-asset-model]] (what the fields ARE) by codifying how every write happens.

## The rules

1. **`update_asset` merges on omit — read-merge-write only for multi-selects.** Platform rule, incl. the CFBaseline name-string doctrine and the `update_system_information` caveat: `write-semantics.md` §§ 2, 5.3.
2. **AFUUID is the idempotency key — generate once, never regenerate.** (Below.)
3. **Framework CFs are two-pass — Controls first, Requirements after.** Platform rule, incl. the Lookup-SQL mechanics and site-scope filter: `write-semantics.md` § 5.
4. **Implementation Statement Title and AFUUID are framework-agnostic.** (Below.)
5. **Write order respects the spine — reconcilers own the framework scaffold AND most of its edges.** (Below, incl. the Set Parameter sequence.)
5a. **Standing up a new asset type requires the two mandatory gotcha fixes** (dtype allow-list + two-phase baseline-then-CF write): `write-semantics.md` § 6.
6. **Lookup values match the Halo lookup table exactly.** Platform rule: `write-semantics.md` § 3. SSP-specific value sets: below.
7. **Verify every write — trust `fields[]`/`customfields[]` (or `get_asset`), never header/`display` echoes.** Platform rule: `write-semantics.md` § 11.
8. **Citation lives in Halo's native change history — no parallel provenance layer** (`write-semantics.md` § 11). What `/ssp-ingest` runs DO produce: the approved `change-plan.md` with field-level diffs and source citations; Halo's change-history timestamps cross-reference back by time proximity.

## Rule 2 — AFUUID is the idempotency key + the `inventory_number` value

Every SSP-spine asset type carries an `AFUUID` Asset Field. The convention:

- **First ingest of a new conceptual entity** generates a structured AFUUID and stores it on BOTH the asset's UUID Asset Field (via `update_asset` `fields[]`) AND `inventory_number` (top-level, on `create_asset`) — the same string, so Halo's [Inventory Number] column shows the AFUUID. **This is also what drives the rel-6 reconciler** (Rule 5).
- **Subsequent ingests** touching the same entity MUST locate the existing asset by AFUUID and update in place. Never create a duplicate. UUIDs are write-once — an unresolvable original goes through the CHANGE-PLAN merge-candidate path, never a UUID overwrite.
- **The `(input artifact → AFUUID → asset_id)` mapping lives in `outputs/ssp-ingest-{date}/uuid-map.jsonl`.**
- **Cloud Asset / Hardware Asset rows are exempt** — they keep real identifiers (hostnames, ARNs, FQDNs) as `inventory_number`.

### AFUUID conventions per asset type

| Asset type (dtype) | AFUUID shape | Example |
|---|---|---|
| Capability (178) | `CAP-<SystemID>-<Acronym>` | `CAP-ACME-IAM` |
| Component (173) | `CMPN-<NAME>` | `CMPN-AWSKMS`, `CMPN-ENTRAID` |
| Control Implementation (192) | `CI-<SystemName>-<CATALOGSHORT>` | `CI-ACME-FR5CC`, `CI-ACME-CMMCL2` |
| Implemented Requirement (190) | `IR-<SystemName>-<CATALOGSHORT>-<CONTROL>[-<ENH>]` | `IR-ACME-FR5CC-AC-02-03` |
| Implementation Statement (191) | `IS-<COMPONENT>-<ACRONYM>[-<SUB>]` | `IS-KMS-ROT`, `IS-ENTRAID-MFA` |
| Set Parameter (193) | `SP-<SystemName>-<CATALOGSHORT>-<CONTROL>[-<REQSUFFIX>]-<IDX>` | `SP-GRCITSM-FR5CC-AU-11-1` |

`<CATALOGSHORT>` resolves from `CTBaselines.CFBLShortCode` (see [[halo-ssp-asset-model]]). For Set Parameter, `<IDX>` is the 1-based ordinal of the ODP slot within its requirement row.

**Component and IS AFUUIDs are library-acronym-verbatim, and library instantiation is the default create path.** `<NAME>` on a Component is the `CTComponents.CFComponentAcronym`; the IS `<COMPONENT>-<ACRONYM>` is the `CTImplementationStatements.CFISAcronym`. Create a Component as a shell + `update_component_from_library`; source an IS payload from `lookup_implementation_statement_definition` (narrative + six pre-split framework lists verbatim). Free-authoring either type is reserved for rows with no library definition — flagged, so the library can be extended. This is what makes the reconcilers work: the acronym drives rel 6, and the framework CFs drive rel 7.

### What counts as the same conceptual entity

Per-type identity keys (what makes two rows the same entity — Capability by Title, Component by (Title, Provider), IR by (System, Catalog, Control-ID, Enhancement), IS by (Component AFUUID, intent acronym), SP by (scope, control, ODP index)): `reference-docs/halo-ssp-asset-schemas.md` § Conceptual identity per asset type.

Identity collisions on first ingest → stage as a CHANGE-PLAN "merge candidate". Never silently merge.

## Rule 4 — Implementation Statement Title AND AFUUID — framework-agnostic

An IS (dtype 191) can satisfy a Control, a KSI, a CMMC practice, or any combination via parallel parent IR relationships (rel 7). Embedding a Control/KSI ID in the Title or AFUUID locks the IS to one framework. Title and AFUUID describe **what the IS does**, never **which requirement it maps to** — attribution lives in the framework CFs and parent IR rel-7 links.

### Title format — `{Component} – {Descriptive phrase}`

`{Component}` = the Component's Title (`AWS KMS`, `Entra ID`) or a scoping phrase (`All CUI Components`) for CSP-inherited implementations spanning Components. `{Descriptive phrase}` = a short plain-English statement of what the IS does, practitioner voice per [[ssp-brand-voice]]. Separator is an en-dash with spaces (` – `), not a hyphen.

```
AWS KMS – Customer-managed keys with annual rotation
Entra ID – Conditional access enforces MFA on privileged sessions
All CUI Components – Physical access reviewed at change events
```

### AFUUID format — `IS-<COMPONENT>-<ACRONYM>[-<SUB>]`

`<COMPONENT>` = the parent Component's AFUUID stem without the `CMPN-` prefix. `<ACRONYM>` = a 3-5 char descriptive acronym for what the IS does (`ROT`, `MFA`, `ENC`, `LOG`, `RVW`) — NOT a Control or KSI ID. `[-<SUB>]` = optional 2-5 char sub-acronym when several IS rows cluster under one process (`PVA-DOC`, `PVA-CYCLE`). **No Capability segment** — the Component alone disambiguates, and a Component may parent under more than one Capability, so a Capability prefix is arbitrary by construction. When instantiating from the IS library, `<COMPONENT>-<ACRONYM>` **is** `CFISAcronym` verbatim, so the AFUUID is `IS-<CFISAcronym>`.

### Implemented Requirement AFUUID + Title

`IR-<SystemName>-<CATALOGSHORT>-<CONTROL>[-<ENH>]` (`IR-ACME-FR5CC-AC-02-03`, `IR-ACME-FR20XCC-IAM-AAM`, `IR-ACME-CMMCL2-AC-L2-3-1-5`). IR Title: `{Control-ID}: {Control Name}` (e.g. `AC-02: Account Management`).

**What the AFUUID is NOT:** not a control ID (`IS-IAM-AC-02` is wrong), not a KSI ID, not the Title, not a UUID v4 — the structure encodes scope, which serves human ops better than randomness.

## Rule 5 — Write order

**Write FIELDS, not EDGES.** The six `reconcile_ssp_*` tools own Capabilities / CIs / IRs + framework CFs AND the rel 6 / 10 / 7 / 12 / 11 links; the five relationship reconcilers also fire on Halo `Asset created` / `Asset updated`. Hand-wiring a reconciled edge is redundant where you agree with the reconciler and **churn where you don't**, since each rewrites the full `child_assets[]` array for a drifted subject. Only three edges are ever yours: **rel 8** (Component→IS — no reconciler; without it an IS hangs off its IR but off no Component, breaking Capability→Component→IS rendering), **rel 9** (Cloud Asset→Component), and **rel 12 for KSI-/CMMC-/Rule-scoped Set Parameters** (the IR→SP reconciler matches `CFAFedRAMPControls` only). Per-edge drivers, event/settle behaviour, and the fallback for a tenant missing a reconciler: `reference-docs/ssp-spine-reference.md`.

```
 1. system_information      (Site-level SSP front-matter)
 2. reconcile_ssp_assets    BULK — creates missing Capabilities + CIs (192) + IRs (190) + framework CFs
 3. Component               (create_asset → update_component_from_library)
                            ← rel 6 needs NO call: inventory_number = CMPN-<CFComponentAcronym>
                              + the library row's CFComponentCapability ARE the driver fields
 4. Implementation Statement (create_asset → update_implementation_statement_from_library where the tenant carries it [one baseline per call, site-legal baselines only, get_asset read-back + re-run on empty framework CFs], else update_asset with two-pass framework CFs; stamp CFAFedRAMPRules on FedRAMP-Rules sites; per-instance policy fields via update_asset either way)
 5. Component↔IS            set_asset_relationships (rel 8)   ← EXPLICIT: no reconciler owns this
 6. reconcile_ssp_relationships_*        BULK backfill — rel 6 / 10 / 7 / 12 / 11 (also event-driven)
 7. Inventory↔Component     set_asset_relationships (rel 9)   — when in scope, EXPLICIT
 8. rel 12 for KSI/CMMC/Rule-scoped SPs  set_asset_relationships — EXPLICIT, the reconciler skips these
 9. KB article             create_kb_article on the documentary Component from step 3 (Title must match)
```

Why this order: `create_asset` returns the `asset_id` downstream calls need; the IR→IS reconcile matches on the IS framework CFs, so those land first (step 4); per `write-semantics.md` § 2.1, never two `update_asset` calls against one asset in parallel. Step 6 is a backfill — on a healthy tenant the edges already exist by then, so **read back before concluding one is absent**. Ingest re-runs: reconcilers no-op on a settled spine; skip `create_asset` for existing AFUUIDs.

**Derived fields: set the source, not the rollup.** An IR's `AFImplementation State` is rolled up from its child ISs by `Rollup Implemented Requirement Status` on asset events, so set state on the IS leaves. Writing the IR directly is a deliberate override where no leaf can carry the value — not durable, since the next asset event recomputes it.

**Set Parameter (dtype 193):** the 5-step single-SP sequence, the create-Active note, and the bulk path live in `reference-docs/ssp-write-surfaces.md` § Set Parameter write sequence. Use the single-asset sequence only for one SP.

## Rule 6 addendum — SSP lookup value sets

Platform lookup mechanics (verbatim matching, zero-padding, array shapes, site-scope filter, status ids): `write-semantics.md` § 3. The IS-tier value sets from `asset_type_fields(191)` plus the OSCAL crosswalk: `reference-docs/halo-ssp-asset-schemas.md` § Implementation Statement. Match exactly.

## Anti-patterns (SSP-domain)

Platform write anti-patterns (multi-select clobber, one-call Controls+Requirements, echo-trusting, status strings, payload cliff, baseline+CF combined call, numeric CFBaseline id): `write-semantics.md` § 15. The SSP-domain additions:

1. **Auto-generating Capabilities to match every control family.** The seed list is functional, not control-family-aligned — [[ssp-capability-seed-list]] § Anti-patterns.
2. **Deciding a Component's Capability parentage by hand.** Rel 6 derives from the library row's `CFComponentCapability` — extend the library, don't wire the edge.
3. **Embedding Control or KSI IDs in the IS Title or AFUUID** (Rule 4). The generator groups by Control via the parent-IR walk, not Title parsing.
4. **Regenerating an existing AFUUID** (Rule 2) — write-once; unresolvable UUIDs go through the merge-candidate path.
5. **Bypassing the CHANGE-PLAN gate.** No write fires before the user approves that row.
6. **Associating a KB article with an IR or IS, with more than one Component, or with a non-documentary Component** — and letting `Component.Title` drift from the article title. An article reaches the spine only through one documentary Component (`Policy` / `Process/Procedure` / `Plan` / `Guidance` / `Standard` / `Document`) whose Title matches it exactly; the title is the only correlation key. Rationale: [[halo-ssp-asset-model]] § KB articles reach the spine through a Component.
6a. **Leaving an IS's framework CFs unpopulated, or populated with Requirements values only.** OSCAL SSP export walks IR→IS, so an IS with no rel-7 parent is orphaned from per-control grouping — but the fix is the CFs, not a hand-wired link: the rel-7 reconciler matches Controls / KSIs / Rules / CMMC and has **no Requirements branch**.
7. **Orphaning a Set Parameter.** Every SP parents to exactly one IR (rel 12) or CI (rel 11); an unparented SP won't emit into either OSCAL `set-parameters[]` placement. The reconcilers cover FedRAMP-Controls-scoped SPs only — a **KSI-, CMMC-, or Rule-scoped SP must be hand-wired**, the one place an explicit call on a reconciled relationship id is correct.
8. **Hand-creating reconciler-owned spine rows, or hand-wiring a reconciler-owned edge** (Rule 5).
9. **Routinely writing a derived field** — an IR's Implementation State (rollup-owned; override only where no IS leaf can carry it) or a ticket's framework CFs (single-writer, `grcitsm-ticketing.md`).
10. **Declaring an edge missing without a read-back** — your code not creating it is not absence (Rule 5).
