# ssp-review-standards — procedures, finding catalogs, and scope detail

Detail companion to `skills/ssp-review-standards/SKILL.md` (the two-tier severity model, gate rules, and reviewer anti-patterns). This file carries the finding catalogs, the loop procedures verbatim, the full-plan scope checklists, and the presentation conventions.

## Tier 1 — BLOCKER catalog

| Example | Why it's a BLOCKER |
|---|---|
| Implementation Statement narrative describes AU (audit) behavior but the IS is tagged with control AC-02 | Wrong control mapping — would create a falsely-attributed IS |
| Lookup value for a static-lookup field not in Halo's `[Allowed Values]`, no obvious correct match | Halo silently drops the write; the asset ends up partially populated. NOT a BLOCKER for dynamic-lookup fields (framework CFs, baselines): their `[Allowed Values]` snapshot can be stale — validate against the per-family catalog tool (`baseline_catalog` `[Baseline Name]`, active and inactive rows alike, for baselines) per write-semantics § 3.11 — a value absent from the whole live catalog is still a BLOCKER, but a name matching an inactive baseline is NOT (prefer active baselines for new writes; never strip an existing value whose baseline has since gone inactive) |
| Origination is `Organization` / Role is `Self`, but the narrative says "Microsoft manages this" | Self-contradicting metadata; would render incorrectly in the generated SSP |
| `set_asset_relationships` would link two asset types not in the allow-list (e.g. Capability → IS directly) | Halo refuses the write; reveals a structural error upstream |
| Required field missing (e.g. IS has no Implementation State, or Component has no Title, or IR has no Title) | Halo will fail or accept a malformed asset |
| Implementation Statement narrative has no source citation in the run's evidence.jsonl | Hallucinated statement — no source to audit |
| IS Title embeds a Control / KSI ID OR doesn't follow `{Component} – {Descriptive phrase}` format with en-dash separator | Framework attribution leaks into identifiers; the IS can satisfy a Control AND a KSI AND a CMMC practice via parallel IR rel-7 parents so identifiers must be framework-agnostic |
| IS AFUUID embeds a Control / KSI ID or a Capability segment, OR doesn't follow `IS-<COMPONENT>-<ACRONYM>` shape with 3-5 char descriptive acronym | Same — framework attribution belongs in the framework CFs and parent IR links, not the IS AFUUID |
| IR Title doesn't follow `{Control-ID}: {Control Name}` OR IR AFUUID doesn't follow `IR-<SystemName>-<CATALOGSHORT>-<CONTROL>[-<ENH>]` | The per-control row's identifiers must be catalog+control-scoped to support OSCAL SSP grouping at export time |
| Framework CFs include Requirements without their parent Controls (would fail the Controls-filter at write) | Requirements value would be silently dropped by Halo — see [[ssp]] § Framework CF two-pass |
| Capability proposed by Title that already exists in this Site (case-insensitive match) | Would create a duplicate Capability |
| Cross-IS contradiction: two IS rows for the same `(Component, intent)` with different Implementation States | Identity collision; only one should exist per the AFUUID rule |
| KB article associated with an Implemented Requirement or an Implementation Statement | Fabricates a second path into the spine; control coverage gets asserted twice and the two drift with no reconciler owning the conflict — [[halo-ssp-asset-model]] § KB articles reach the spine through a Component |
| KB article associated with more than one Component, or with none | An article's place in the SSP is exactly one Component's place; zero leaves the document unreachable from any control walk |
| KB article's Component has a `Component Type` outside `Policy` / `Process/Procedure` / `Plan` / `Guidance` / `Standard` / `Document` | Documentation hung off implementing technology; the modeling error is upstream and must be fixed there, not worked around by attaching anyway |
| `Component.Title` does not exactly match the title of the KB article associated with it | The title is the only correlation key between the two — there is no foreign key, so a mismatch silently breaks correlation with nothing failing loudly |

## Tier 2 — AUTO-FIX catalog

| Example | Auto-fix |
|---|---|
| Control ID not zero-padded (`AC-2` instead of `AC-02`) | Zero-pad |
| Comma-separated multi-value lookup has spaces (`AC-02, AC-03`) | Strip spaces (`AC-02,AC-03`) |
| Component name uses non-canonical form (`HaloPSA` vs the Component Title `HaloITSM`) | Canonicalize to the Component Title verbatim |
| Vendor name doesn't match the Provider field on the Component (`Microsoft` vs `Microsoft - GCC High`) | Canonicalize to the Provider field |
| Narrative uses em-dash (—) | Replace with semicolon, comma, colon, or period as context warrants |
| Filler word from the cut-list (`fundamentally`, `leverage`, `comprehensive`) | Cut per [[ssp-brand-voice]] word list |
| Passive voice in Implementation Statement (`Logs are generated by Defender`) | Convert to active voice (`Defender generates logs`) |
| Future tense / aspirational (`will enforce`, `is designed to`) | Convert to present-tense active |
| Hedging (`may help`, `could potentially`) | Cut the hedge; state the fact |
| Rhetorical scaffold (`not only X but also Y`, `not just X, it's Y`) | Rewrite as a direct statement (`X and Y`, or state Y with its consequence) per [[ssp-brand-voice]] AI-tell section |
| And-chain: narrative joins 3+ clauses with `and` | Split into two sentences (or a semicolon pair) while staying inside the 150-300 char budget |
| Narrative value doesn't match Halo's verbatim lookup string | Match Halo's verbatim string — including Halo-side typos, which must be matched verbatim (typo catalog + current value sets: `reference-docs/halo-platform/write-semantics.md` §3.4) |
| Hyphen separator in IR Title instead of en-dash | Replace `-` with ` – ` in the Title's separator positions |
| `Azure AD` reference in a Microsoft-Entra-ID context | Canonicalize to `Entra ID` |
| Component/IS CREATE row missing its `library:` binding annotation | Resolve mechanically from the AFUUID acronym via the definition-library summaries; annotate `library: <acronym/row id>` or `library: off-library` (off-library is a flagged warning surfaced in Reviewer Notes, never a halt) |
| Component/IS CREATE row hand-composes content its bound library definition already provides (paraphrased narrative, re-split framework IDs) | Replace with the library values verbatim (Statement text, six pre-split framework lists); site-specific tailoring survives only as an explicit reviewed deviation on the row |

## The internal review loop (procedure)

```
1. INITIAL REVIEW
   Read the reconciled CHANGE-PLAN.
   For each row, evaluate against full-plan scope (narratives + structural fields).
   Classify findings into BLOCKER / AUTO-FIX.

2. AUTO-FIX PASS
   Apply every AUTO-FIX finding silently to the CHANGE-PLAN rows.
   Log each fix to outputs/ssp-ingest-{date}-{run-id}/review-log.md
   (original → reviewed, finding category, rule citation).

3. RE-REVIEW
   Run the review again against the fixed plan.
   This catches downstream effects (e.g. canonicalizing 'Azure AD' → 'Entra ID' in 12 IRs
   may surface a new BLOCKER if it conflicts with a Component reference elsewhere).

4. TERMINATION CONDITION
   Loop terminates when BOTH:
     - Zero BLOCKERs in the current pass
     - Zero new AUTO-FIX changes in the current pass

5. MAX-ITERATIONS GUARD
   Hard cap: 5 review passes. If the loop hasn't terminated after 5 passes,
   halt with a 'review oscillating' diagnostic — fix the underlying rule
   conflict before re-running.

6. POST-LOOP DECISION
   If BLOCKERs remain → HALT THE ENTIRE RUN (no writes execute).
     Emit outputs/ssp-ingest-{date}-{run-id}/blockers.md listing every BLOCKER
     with row reference, rule cited, and what the user must do to resolve.
   If zero BLOCKERs → render the polished draft to the user (Phase 4 approval).
```

## The outer user-edit loop (procedure)

```
USER-ITERATION N:

  A. RENDER DRAFT
     Show the polished CHANGE-PLAN to the user in chat.
     Include the Reviewer Notes summary (what the internal loop changed).

  B. USER RESPONSE
     User responds in chat. Three possible response shapes:

     - "approve" / "yes" / "apply" → exit outer loop, proceed to Phase 5 (writer)
     - "no" / "cancel" → exit outer loop, run aborts with no writes
     - Edit feedback in any natural shape — examples:
         "tighten the Defender XDR statement on row 31"
         "row 5's Origination should be Inherited, not Organization"
         "split the IAM statement into two IRs covering AC-02 and AC-03 separately"
         "the Capability description for Logging is too generic — say it covers SIEM + audit aggregation"
         "drop row 73"
         "Microsoft - GCC High everywhere, not just some rows"
         "Entra ID, not Azure AD"

  C. APPLY USER EDITS (only if edit feedback)
     Translate the user's natural-language feedback into concrete CHANGE-PLAN row mutations:
       - text replacements in narrative fields
       - structural field changes (Origination, Role, Implementation State, Type, Provider)
       - row deletions, additions, or splits
       - cross-row canonicalizations
     Apply the mutations to the plan.

  D. RE-RUN INTERNAL REVIEW LOOP
     The modified plan goes through the internal review loop again (steps 1-6 above).
     This validates that the user's edits didn't introduce BLOCKERs (e.g. user asked to
     change a control mapping that's now technically wrong) and re-applies any AUTO-FIX
     that the user's edits warrant.

  E. CONTINUE
     If internal loop terminates clean → goto A with iteration N+1.
     If internal loop halts on BLOCKERs introduced by user edits → surface the BLOCKERs
       to the user with an explanation; user can revise their edits or accept the BLOCKER
       as a row drop.
```

**Outer-loop telemetry:** capture each iteration's diff in `outputs/ssp-ingest-{date}-{run-id}/user-iterations.md`:

```
## Iteration 1 (2026-05-25T14:32:01Z)
User feedback: "tighten the Defender XDR statement on row 31"
Mutations applied: row 31 Implementation Statement shortened 320 → 165 chars
Internal review: clean (0 BLOCKERs, 1 AUTO-FIX — passive→active on the rewritten text)

## Iteration 2 (2026-05-25T14:35:18Z)
User feedback: "Microsoft - GCC High everywhere, not just some rows"
Mutations applied: 8 rows canonicalized Provider value
Internal review: clean (0 BLOCKERs, 0 AUTO-FIX)
```

This gives the user a record of what changed across iterations without bloating the CHANGE-PLAN itself.

## Full-plan scope checklists

The reviewer inspects **every field** in **every CHANGE-PLAN row**. No field is out of scope.

### Narrative fields

- Implementation Statement (IS) `Implementation Statement` (field id 229) — voice, accuracy, length, tense, factual alignment with the cited evidence
- Implemented Requirement (IR) `Description` — voice, factual accuracy, optional (most narrative lives on child IS rows)
- Component `Description` — voice, factual accuracy, 60-100 word target
- Component `Purpose` — voice, security framing, 40-80 word target
- Capability `Description` — voice, functional scope clarity, 1-2 sentences
- system_information `General System Description` / `Authorization Boundary Description` / `Network Architecture Description` / `Data Flow Description` — voice, separation between blocks (no copy-paste duplication), concrete architectural detail
- IS `Validation Name` / `Validation Process` / `Validation Pass/Fail Criteria` / `Validation Test` — voice, completeness

### Structural fields

- IS `Title` — `{Component} – {Descriptive phrase}` framework-agnostic format with en-dash separator; no Control / KSI IDs
- IS `AFUUID` — `IS-<COMPONENT>-<ACRONYM>` with 3-5 char descriptive acronym; no Capability segment, no Control / KSI IDs. Legacy `IR-` prefix tolerated on existing data.
- IR `Title` — `{Control-ID}: {Control Name}` (e.g. `AC-02: Account Management`)
- IR `AFUUID` — `IR-<SystemName>-<CATALOGSHORT>-<CONTROL>[-<ENH>]` (e.g. `IR-ACME-FR5CC-AC-02`, `IR-ACME-FR20XCC-IAM-AAM`). Legacy `CI-` prefix tolerated on existing data.
- IS `Control Origination` — matches the narrative (`Inherited` if narrative says CSP handles it; `Organization` if in-house). Validate-W array shape required.
- IS `Responsible Role` — pairs correctly with Origination (Inherited↔CSP; Organization↔Self in most cases)
- IS `Implementation State` — matches the narrative (`Implemented` only if the statement asserts current operation; `Partial` if explicit partial; `Not Applicable` for `N/A`-pattern statements). Status-vocabulary semantics: `reference-docs/halo-platform/ssp-narrative-doctrine.md` §3.
- Component `Component Type` — matches the Halo lookup (`System`, `Service`, `Software`, `Hardware`, `Policy`, `Plan`, `Document`, etc.)
- Component `CMMC Asset Type` — matches the Halo lookup (`SPA`, `CUI`, `SPD`, `CRMA`, `OOS`)
- Framework CFs — Controls listed before/separately from Requirements; control IDs zero-padded and comma-separated without spaces; multi-select uses array shape not CSV blob
- Relationships — every proposed link conforms to the allow-list (Capability→Component rel 6; Component→IS rel 8; IR→IS rel 7; Cloud Asset→Component rel 9)
- AFUUID — present on every CREATE row; preserved verbatim on every UPDATE row

### Cross-row consistency

- Same Component referred to consistently across IS rows (always `Entra ID`, never mix `Entra ID` / `Microsoft Entra` / `Azure AD`)
- Same control mapping pattern across IS rows that share the same parent IR (if `AC-02` is `Inherited` on one IS, it should usually be `Inherited` on others — variance is allowed but flagged as informational)
- No two CREATE rows propose Capabilities / Components / IRs / IS rows with the same canonical identity
- Multi-pass framework-CF writes on IS rows are correctly split (Controls in one call, Requirements in a follow-up call)
- Every IS with framework CFs populated has at least one parent IR via rel 7 (one IR per catalog the IS satisfies)

## What the user sees at each outer-loop iteration

The user does NOT see verbose `original → reviewed` diffs in the CHANGE-PLAN. The plan they see is the FINAL polished draft for the current outer-loop iteration. Brief Reviewer Notes summarize what the internal loop changed in this iteration.

```markdown
# SSP Ingest CHANGE-PLAN (iteration 1)
**Run:** 2026-05-25-r42
**Site:** <site-id> (ACME Corp / ACME)
**Inputs:** 8 files (3 policies, 4 transcripts, 1 prior SSP)

## Reviewer Notes (this iteration)

Internal review converged in 3 passes. Auto-fixes applied across 18 rows.

- **Narrative polish:** 14 statements tightened (filler cuts, passive→active, hedging removed)
- **Terminology:** 6 IS rows canonicalized `Azure AD` → `Entra ID` to match Component Title
- **Format:** 4 narratives had em-dash → semicolon; 1 IS Title separator hyphen → en-dash
- **Technical:** 8 control IDs zero-padded; 2 multi-value lookups had spaces stripped
- **BLOCKER count:** 0 (internal loop terminated clean)

Full audit at `outputs/ssp-ingest-2026-05-25-r42/review-log.md`.
Per-iteration history at `outputs/ssp-ingest-2026-05-25-r42/user-iterations.md`.

## Summary
(... standard CHANGE-PLAN sections — system_information, Capabilities, Components, Implemented Requirements, Implementation Statements, relationships, PROPOSED-NEW ...)

---

**Approve and write to Halo? Or describe edits you want, and I'll re-render.**
```

The user responds with either approval, cancellation, or natural-language edit feedback. If edits, the outer loop applies them, re-runs the internal review, and re-renders for iteration 2 (and so on) until the user approves.

## Citation convention

When flagging or fixing a finding, cite the rule **category** rather than the verbatim rule text:

- `[brand-voice cut-list]` — filler word removed
- `[brand-voice anti-pattern]` — hedge or hype removed
- `[brand-voice keep-list]` — technical descriptor preserved (e.g. didn't strip "automatically")
- `[brand-voice voice]` — passive→active or future→present-tense
- `[brand-voice format]` — em-dash, hashtag, or formatting fix
- `[brand-voice preserve]` — verified control ID / framework term / lookup value preserved verbatim
- `[ssp-asset-write-conventions]` — framework-agnostic Implementation Statement Title + AFUUID format, framework-CF ordering, AFUUID idempotency, write order
- `[technical-accuracy]` — control mapping correctness, lookup-value match, Origination↔Role consistency
- `[cross-row consistency]` — naming canonicalization across IS rows, no-duplicate identity

This pattern lets the writer learn the rule, not just see the one-time fix.

## Pattern flagging vs per-instance flagging

If the same finding appears across many rows, surface it once as a **pattern** in the Reviewer Notes:

> "**Pattern:** 12 IS rows used `Azure AD`; all canonicalized to `Entra ID` to match Component Title."

Rather than:

> "Row 3: Azure AD → Entra ID. Row 5: Azure AD → Entra ID. Row 7: Azure AD → Entra ID. ..." (×12)

The per-row audit lives in `review-log.md` for traceability; the CHANGE-PLAN stays readable.
