# SSP run state conventions

Every long-running SSP flow (`/ssp-ingest`, `/ssp-interview`) maintains an ongoing state file layout under `outputs/{flow}-{date}-{run-id}/`. Any agent in the pipeline can read the state, resume from the last known good point, and append/update its own slice. **No agent silently advances without persisting state.** This page defines the schema, layout, and update cadence so every agent treats the working folder identically.

`/ssp-generate` is single-shot read-only and exempt from these conventions — see § Exemptions below.

## When this convention applies

- Any agent or skill that runs as part of `/ssp-ingest` or `/ssp-interview`.
- Any long-running multi-phase SSP workflow added later.

## Run folder layout

```
outputs/{flow}-{YYYY-MM-DD}-{run-id}/
  state.json                                    # always present; updated atomically after every state transition
  interview-notes.md                            # /ssp-interview only — running Q+A log
  classification.jsonl                          # /ssp-ingest only — Phase 1 output
  evidence.jsonl                                # /ssp-ingest only — Phase 2 output
  change-plan-fragment-{phase}.jsonl            # per-phase staged CHANGE-PLAN rows
  change-plan.md                                # current polished plan (overwritten each outer-loop iteration)
  change-plan.jsonl                             # machine-readable form the Phase-6 compose step consumes
  review-log.md                                 # append-only AUTO-FIX audit across all reviewer passes
  blockers.md                                   # present only if internal review loop halts on BLOCKERs
  user-iterations.md                            # per outer-loop iteration: user feedback + mutations + review outcome
  manifests/*.csv                               # structural projection compiled in Phase 6 (Phase-3 emit on /ssp-ingest; re-projected at Phase 6 if edits touched structure)
  state/actions-*.jsonl                         # Phase 6 — compiled (actions-01) + composed (actions-02) action files
  state/progress.jsonl                          # Phase 6 — append-only apply ledger (halo-action-pipeline; the resume surface)
  uuid-map.jsonl                                # AFUUID ↔ asset_id assignments, projected from the ledger at end of run
```

Where `{flow}` is `ssp-ingest` or `ssp-interview` and `{run-id}` is a per-day sequential ID (e.g. `r01`, `r02`).

## `state.json` schema

The canonical pointer file. Single JSON object, updated atomically (write to `state.json.tmp`, `mv state.json.tmp state.json`). Schema:

```json
{
  "run_id": "ssp-interview-2026-01-15-r01",
  "flow": "ssp-interview",
  "site_id": 12,
  "client_name": "ACME Corp",
  "system_name": "ACME",
  "baselines": ["FedRAMP Rev5 Class C", "FedRAMP 20x Class C"],
  "current_phase": 1,
  "current_phase_name": "Interview",
  "current_round": 3,
  "current_round_name": "Authorization Boundary",
  "status": "awaiting_user_response",
  "last_update": "2026-01-15T21:14:22Z",
  "last_agent": "ssp-interviewer",
  "open_questions": [
    "multi-tenant_vs_single_tenant",
    "entra_connect_what_syncs",
    "defender_for_cloud_sensor_scope"
  ],
  "locked_rounds": [1, 2],
  "pending_rounds": [4, 5],
  "next_action": "Collect user response on 3 open questions, then revise Authorization Boundary to v2.",
  "fragment_files": [
    "change-plan-fragment-phase-0.jsonl"
  ]
}
```

### Required keys

- `run_id` — folder name (without `outputs/` prefix).
- `flow` — `ssp-ingest` or `ssp-interview`.
- `site_id` — target Site (integer).
- `current_phase` — integer (0-6).
- `current_phase_name` — human-readable.
- `status` — one of: `running`, `awaiting_user_response`, `paused`, `halted_on_blocker`, `awaiting_approval`, `composing`, `composed`, `writing`, `completed`, `aborted`.
- `last_update` — ISO 8601 UTC timestamp.
- `last_agent` — which agent last wrote state (one of: `ssp-curator`, `ssp-interviewer`, `ssp-reviewer`, `ssp-writer`, `main`).
- `next_action` — one-sentence description of what should happen next, so any reader (human or agent) can resume.

### Optional keys

- `client_name`, `system_name` — for human-readability.
- `baselines` — array of in-scope baselines (from `Site.CFBaselines`).
- `current_round`, `current_round_name` — for interview / multi-round phases.
- `open_questions` — array of unresolved interview questions (slugs the agent can reference).
- `locked_rounds` / `pending_rounds` — round-level progress tracking.
- `fragment_files` — list of `change-plan-fragment-*.jsonl` files emitted so far.
- `blockers_count` — integer; present when `status: halted_on_blocker`.
- `iteration` — outer-loop iteration number when `status: awaiting_approval`.

## Update cadence

Every agent updates `state.json` AND its relevant slice file at these trigger points:

| Agent | Trigger | Files updated |
|---|---|---|
| `ssp-interviewer` | After every user Q+A | `state.json` (status, last_update, current_round, open_questions); `interview-notes.md` (append Q+A); `change-plan-fragment-{phase}.jsonl` (append/update rows derived from response) |
| `ssp-curator` | After classification of each input file | `state.json` (last_update, current_phase); `classification.jsonl` (append) |
| `ssp-curator` | After extraction of each evidence statement | `state.json` (last_update); `evidence.jsonl` (append) |
| `ssp-curator` | After reconciliation completes | `state.json` (current_phase=3 done, status=running); `change-plan.md` (full write); `change-plan.jsonl` (full write) |
| `ssp-reviewer` | After every AUTO-FIX | `review-log.md` (append: row, field, original, reviewed, rule cited) |
| `ssp-reviewer` | After internal-loop terminates | `state.json` (status: `awaiting_approval` OR `halted_on_blocker`); `change-plan.md` overwrite (polished); `change-plan.jsonl` overwrite; `blockers.md` if halted |
| `main` (Phase 5 outer loop) | After every user iteration | `state.json` (iteration++, status, last_update); `user-iterations.md` (append: user feedback + mutations applied + review outcome) |
| `ssp-writer` | Compose start / sealed / failed | `state.json` (status: `composing` → `composed` with next_action `halo-apply`, or `aborted` + failure marker); `state/actions-02.jsonl` (write once, immutable) |
| `main` (Phase 6 apply) | During `/grcitsm-analyst:halo-apply` | `state/progress.jsonl` (executors append start/done/fail events — the write ledger; owned by the halo-action-pipeline, not this convention) |
| `main` (Phase 6 verify pass) | After apply + reconcilers complete | `state.json` (status: `completed`); `uuid-map.jsonl` (projected from the ledger's done events) |
| `main` (Phase 6) | On apply/verify failure | `state.json` (status: `aborted`, error field); halt — resume re-runs halo-apply with doneIds from the ledger |

**Atomic writes:** every `state.json` update writes to `state.json.tmp` first, then `mv state.json.tmp state.json`. Same for any file that gets fully rewritten (`change-plan.md`, `change-plan.jsonl`). Append-only files (`*.jsonl`, `interview-notes.md`, `review-log.md`, `user-iterations.md`) append directly with newline framing.

## Resume discipline

Any agent invoked against an existing run folder MUST:

1. **Read `state.json`** first.
2. **Validate the run is recoverable** — check `status`. If `completed` or `aborted`, refuse to re-run without explicit user direction.
3. **Read the relevant slice files** for its phase (e.g. the Phase-6 compose step reads `change-plan.jsonl` + the manifests; the apply reads `state/actions-*.jsonl` + `state/progress.jsonl`).
4. **Skip already-completed work** — the apply resumes from the `state/progress.jsonl` ledger (`doneIds`); a sealed `actions-*.jsonl` is never recomposed; reviewer doesn't re-review iterations the user has already accepted; etc.
5. **Re-validate Halo state** before resuming any write — the live state may have drifted since the prior run halted.

If `state.json` is missing in a folder that contains other state files, treat as corrupted — halt and ask the user.

## Per-flow expected sequence

### /ssp-ingest

```
state.json status transitions:
  running                   (ssp-curator Phase 1-3)
  → running                 (ssp-reviewer Phase 4 internal loop)
  → awaiting_approval       (Phase 5 polished draft rendered to user, iteration=1)
  → running                 (user gave edits; agents applying; reviewer re-runs)
  → awaiting_approval       (iteration=2 rendered)
  → ... (loop until user approves)
  → composing               (Phase 6: compile + ssp-writer compose)
  → writing                 (Phase 6: /grcitsm-analyst:halo-apply + reconcilers)
  → completed               (apply + verify pass succeeded)
  | halted_on_blocker       (review-time halt — terminal until user re-runs)
  | aborted                 (compose/apply/verify failure OR user cancel)
```

### /ssp-interview

Same state transitions as `/ssp-ingest`, with Phase 1 = interview (not classify+extract+reconcile). `interview-notes.md` is the per-Q+A log; `change-plan-fragment-{phase}.jsonl` rows accumulate as the interview progresses.

## Exemptions

- `/ssp-generate` (read-only Path B) — no state file. Output is the final markdown SSP(s) + `gaps.md`. Single-shot; nothing to resume.
- The `ssp-author` agent — same. No state.json.
- Standalone reviewer audits (running `ssp-reviewer` against an existing `change-plan.md` outside the pipeline) — no state.json; emits standalone review-log.md + blockers.md only.

## Anti-patterns

1. **Don't advance the pipeline phase without updating `state.json`.** A reader should never see `current_phase: 3` while the actual work is mid-Phase-4.
2. **Don't overwrite append-only files.** `interview-notes.md`, `review-log.md`, `user-iterations.md`, `*.jsonl` files are append-only across the entire run.
3. **Don't write `change-plan.md` mid-loop.** Only the reviewer writes the polished plan (after internal loop terminates). Curator writes the draft; interviewer writes fragments; main rewrites after user edits.
4. **Don't skip atomic writes for `state.json`.** A reader catching a partial write will fail to parse.
5. **Don't read state files from a previous run unless explicit resume.** A new run starts a new folder with a new run-id; cross-run state borrowing is forbidden.
6. **Don't bury the next action.** `state.json.next_action` must always be human-readable and self-contained — "Collect user response on 3 open questions, then revise Authorization Boundary to v2" is good; "Continue" is bad.
7. **Don't let `state.json` and slice files diverge.** If `state.json` says iteration=2 but `user-iterations.md` only has iteration=1, the state is inconsistent — halt and surface.

## Cross-references

- The `ssp-ingest` skill — pipeline definition (Phase 1-6).
- The `ssp-interview` skill — interview-driven counterpart.
- The `ssp-review-standards` skill — internal/outer loop discipline that drives `review-log.md` and `user-iterations.md`.
- The `ssp-curator`, `ssp-interviewer`, `ssp-reviewer`, and `ssp-writer` agents — per-phase state-file responsibilities as tabled above.
