---
name: demo-build
description: Build the stored GRCITSM demo system into any Halo tenant — package #1 of the unified system-package format, built exclusively through the system-package driver (validate → stamp-lookups → check → compile per family → halo-apply → verify). Ships the demo package (Cloud Assets, library Components/Implementation Statements, and the operational ticket set — Issues, Deviations, Change Requests, UARs, Alerts/Incidents, Project + Tasks, Validations + runs); demo_compile.py is the emission engine under the driver, not an entry point. Tenant-agnostic: symbolic keys only, tenant ids resolved at compile from regenerated lookups. Load when asked to build, rebuild, reset, or replay demo data, or to stand a demo system up in a fresh or wiped tenant.
---

# demo-build — the stored demo system, replayable into any tenant

> **The scaffold is package #1 of the unified system-package format** (design ruling D3,
> 2026-08-10), and the `system-package` driver is **THE path** —
> `validate` → regen lookups → `stamp-lookups` → `check` → `compile --only <family>` → apply →
> verify. `demo_compile.py` is the emission engine underneath, not an entry point.

## References

| Reference | Type | Location | When to Use |
|---|---|---|---|
| `demo_compile.py` | File | `skills/demo-build/scripts/demo_compile.py` | The EMISSION ENGINE under the `system-package` driver (not an entry point). Pure and offline — never calls MCP |
| `demo_scaffold_schema.py` | File | `skills/demo-build/scripts/demo_scaffold_schema.py` | Column contract, verbatim lookup values, and `BOOLEAN_CFS` — the single source of truth `check` and `compile` share |
| Scaffold contract | File | `skills/demo-build/scaffold/README.md` | Full column contract per CSV, the `@file.md` narrative convention, and the lookups-regeneration table |
| `halo-action-pipeline` | Skill | `skills/halo-action-pipeline/` | The action-file schema this compiler targets, plus guards, `$ref` resolution, and resume |
| `ssp-csv-compile` | Skill | `skills/ssp-csv-compile/` | The `ssp` family's emission engine under the same driver — deliberately not reimplemented here |
| `system-package` | Skill | `skills/system-package/` | THE driver: validate · stamp-lookups · check · compile · diff · capture · session-open · render · approve |

Cross-reference in the body: `[[grcitsm]]` (tool routing and create→update chains) and `[[halo-bulk-update]]` (six-phase bulk discipline).

The demo dataset lives here as flat CSV, not in whichever tenant last ran a build. **The CSVs are the demo data; the tenant is a projection of them.** That is what makes "reset the demo" a replay rather than a re-improvisation.

`demo_compile.py` is pure and offline — it NEVER calls MCP. Tenant-derived inputs arrive as files an agent produced upstream, the same discipline as `ssp_compile.py`. Every write reaches Halo through `/grcitsm-analyst:halo-apply`.

## Layout

```
skills/demo-build/
  scripts/
    demo_compile.py          compiler: init / check / compile
    demo_scaffold_schema.py  column contract + verbatim lookup values + BOOLEAN_CFS
    vendor_ksi_checks.py     regenerates validations.csv from a steampipe-compliance-mod checkout
    generate_validation_runs.py  re-stamps the run week deterministically (--week-ending, --seed)
  scaffold/
    package.json             declares the six families + target tenant (package #1, D3/D6)
    run-config.csv           client/site/agent/SLA/baseline BY NAME
    system/                  System Information, ports & protocols, client details
    narratives/*.md          long prose, pulled into a cell via @file.md
    assets/cloud-assets.csv  Cloud Assets (type 188)
    ssp/                     library Components + Implementation Statements
    tickets/*.csv            Issues, VDs, CRs, UARs, Alerts/Incidents, Projects, Validations
    lookups/                 SHIPPED EMPTY (headers only) — see below
```

**`lookups/` ships with headers and no rows, on purpose.** It is the scaffold's only tenant-specific surface. Populating it in the plugin would hardcode one tenant's ids into a shared artifact and silently mis-target every other instance. Regenerate it against the target tenant before every build:

| File | Regenerate from |
|---|---|
| `id-map.csv` | `client_details`, `agents`, `statuses`, `slas_and_priorities`, `users` |
| `asset-field-map.csv` | `asset_type_fields` (unparameterized — filter locally). CANONICAL: `assettype_id,field_name,field_id,prefix`, field_name UNPREFIXED, covering type 188 AND the spine types; the driver derives each engine's variant |
| `site-field-map.csv`, `site-lookup-values.csv` | `site_fields` |
| `baseline-catalog.csv` | `baseline_catalog` |
| `component-definition-map.csv` | OFFLINE — `library_component_key` in `ssp/components.csv` IS the numeric CTComponents id: emit `library_component_key,definition_id` as an identity map |
| `is-definition-map.csv` | `lookup_implementation_statement_definition` per `library_is_key` acronym in `ssp/is-manifest.csv` → `library_is_key,definition_id` ([Row ID]). REQUIRED for the ssp family — check/compile hard-fail on library rows without both maps |
| **stamp** | after regenerating, `package.py stamp-lookups --package <pkg> --prefix <prefix>` writes `lookups_stamp` into `package.json` — it refuses header-only lookups or a name mismatch, and `check` refuses to run without it (the D5 anchor of the three-way tenant assertion) |
| `snapshot.csv` | `asset_summary` / `ticket_summary` — existing keys → ids, incl. the L2 validation ticket ids keyed by KSI family |

**Scope the `users` lookup to the run's client and site.** The default requester is the site's
`Is General User: Yes` account, and its display name ("General User") repeats across sites and
clients — an unscoped name→id lookup silently picks another site's account. Filter `users` by the
run-config `client_name` + `site_name`, then write that one row to `id-map.csv` as `kind=user`.

Work on a COPY of `scaffold/` outside the plugin directory. Plugin dirs under `~/.claude/plugins/` are re-cloned by `/plugin` operations, so anything written there is disposable.

## Three rules that make it portable

1. **Symbolic keys, never tenant ids.** Every row has a `key`; `parent_key` / `asset_keys` / `target_issue_key` reference other rows by it. No ticket or asset id appears in any CSV.
2. **Names, not ids, for tenant objects.** Agent, status, SLA, priority, category, requester are the strings a human reads; `id-map.csv` resolves them at compile.
3. **Structural data in cells, prose in files.** Anything multi-paragraph lives in `narratives/` behind `@file.md`.

## Running a build — the system-package pipeline is THE path

```bash
SK="$CLAUDE_PLUGIN_ROOT/skills"
PKG=outputs/demo-rebuild-<date>/package    # your copy; never edit in the plugin dir
cp -R "$SK/demo-build/scaffold/." "$PKG/"
DRV="python3 $SK/system-package/scripts/package.py"

$DRV validate --package "$PKG"                             # offline, before any tenant contact
# regenerate lookups/ against the resolved tenant (table above), then:
$DRV stamp-lookups --package "$PKG" --prefix <prefix>      # refuses header-only/wrong-tenant lookups
$DRV check   --package "$PKG" --witness <run>/state/tenant-witness.json
$DRV compile --package "$PKG" --out <run>/state --only <family>   # per family, in order
```

`demo_compile.py` is the **emission engine underneath the driver, not an entry point** — the
driver normalizes the conventions (one `--out` directory, one canonical field-map, staging for
partial packages) and its direct CLI remains only for the engine's own tests.

**`check` is not a formality.** Halo does not error on an unknown lookup value — it silently drops
it. `check` validates every cell against the verbatim allowed-value lists and is the only thing
standing between a typo and a field that looks written but isn't. It also resolves snapshot keys,
so it fails when an earlier step's ids are missing.

### Family order (one `compile --only` + one `/grcitsm-analyst:halo-apply` each)

| Family | Notes |
|---|---|
| `system` | System Information + ports/protocols — the system definition, written first. THEN `reconcile_ssp_assets` (it reads `Site.CFBaselines`) + `reconcile_ssp_relationships_ci_ir`. **Never hand-create spine assets** — AFUUID mismatches duplicate on the next reconcile |
| `ssp` | library Components / Implementation Statements + rel 8; rel 6 fires on asset events; `reconcile_ssp_relationships_ir_is` after IS rows land |
| `cloud-assets` | create → fields → rel 9 to Component. THEN **refresh `snapshot.csv` with the returned asset ids** — `asset_ids_json` / `relationships_json` are serialized strings a `$ref` cannot reach into, so later families recompile against concrete ids |
| `tickets` | the operational set: create → site → requester → agent → **category alone** → CFs → SLA → assets → parent. Category rides its own call BEFORE the CF payload — framework CFs auto-derive only on category change, and the CF call omits category so the derive is not re-fired |
| `validations` + `validation-runs` *(opted in per build)* | 226 per-check tickets + 1,582 run rows ≈ 2,000 apply actions, so `/grcitsm-analyst:demo-build` asks first. **Skip = don't compile/apply these two families** — the package still carries them and a later run adds them to the same build. Precondition when opted in: run the tenant's `create_validation_tickets` sweep (zero-param, idempotent; a relay timeout completes server-side), wait ≥90s for the framework-CF derive, then snapshot each L2 id as `ticket,<KSI id>,<ticket id>` — `validations.csv` parents to KSI families it does not author, so compile hard-fails without them |

## Before you apply: pin the tenant

`halo-apply` refuses to lease anything without a tenant witness, and that refusal is the last line of defence against building into the wrong instance. In the run folder's `state/`:

- `tenant-prefix` — the resolved MCP prefix, e.g. `mcp__claude_ai_GRCITSM_Dev__`
- `tenant-witness.json` — `{"client_id": N, "client_name": "..."}` read from `<prefix>CF_client_details`

**A witness is necessary but not sufficient.** Instances cloned from each other carry the same `client_id` AND the same client name, so the witness passes on either. When several GRCITSM connectors are live and there is no `.claude/grcitsm-tenant.json` pin, the target is ambiguous — ask, never guess. Discriminate by content: read `asset_summary` and check whether the site's existing assets carry this scaffold's naming.

## Every ticket lands on New

No ticket CSV has a `status` column; `check` errors on one. The human drives every stage transition — UAR approval, deviation adjudication and the cascade it fires, change approval, issue remediation and POA&M close-out, and the Evaluation → IIR → OIR → FIR → AAR incident workflow.

This is the point, not a limitation: the process *is* the demo and the approval is the evidence. A seeded end-state would be a record of work that never happened. Evaluation-tier fields (PAIN, detected/started at, detection source) are fine to seed; `check` warns on human-produced fields like `CFLessonsLearned` that contradict a New ticket.

Expect the tenant's own event runbooks to create tickets you did not author — a VER-TFR-IRI escalation off an Issue, for instance. Cascade output is correct behavior, not duplication; count it separately when verifying.

## Resetting an existing demo

MCP cannot hard-delete a ticket, so reset is two moves:

1. Delete the target site's tickets and assets in the Halo UI. The destructive step stays human and manual.
2. Re-apply the step files. Every create carries a guard, so a partial wipe is safe: surviving rows are skipped, missing ones recreated.

**A guard hit against data you meant to wipe means the wipe was incomplete** — not that the row should be skipped. Verify the site reads empty before a full rebuild, or you get a half-rebuild that looks clean.

## Verifying

The ledger is a resume aid, not evidence. `done` counts have been fabricated by executors, and Halo accepts wrong-shaped payloads with HTTP success while discarding the content. Verify against the tenant: per-type counts from `asset_summary` / `ticket_summary` scoped to the target site, plus `get_asset` / `get_ticket` read-backs on a sample confirming fields are POPULATED, not merely that rows exist. Report divergence rather than success.

## Typed custom fields

Most CFs are strings. `BOOLEAN_CFS` in `demo_scaffold_schema.py` lists those Halo stores as a real boolean — a CSV cell holds `Yes`/`No` and compile coerces it to a JSON bool. Halo rejects the entire field call on a type mismatch (`expects boolean, got string 'No'`), losing every CF in that payload, so an undeclared boolean CF is expensive. Add a name only once the live tenant has proven the field is a boolean; a lookup CF wrongly listed there would be mangled instead.

## Scope

In scope: Cloud Assets, library-sourced Components and Implementation Statements, and the operational ticket set.

Out of scope by design: the reconciler-owned SSP spine (Capabilities 178, Control Implementations 192, Implemented Requirements 190, Set Parameters 193) and the relationship reconcilers — materialized by `reconcile_ssp_assets` / `reconcile_ssp_relationships_*`. Also out of scope: the Client record (no MCP write path; compile emits `manual-setup.md` for the UI) and System POCs (established by Halo Agent Role assignment, not a Site write — read with `system_pocs`).

Two further surfaces are owned elsewhere and are NOT built from this scaffold: **KB articles** (the governing documents, plus both directions of the KB↔Component link) and the **KSI Data Request tree** (the `create_ksi_data_requests` scaffolder — deployed on no connector as of 2026-08-10, so treat it as unavailable rather than merely elsewhere). Both belong to the companion `demo-smoke-test` skill — an internal test playbook that composes this skill and is not included in the client marketplace. Where that skill is unavailable, treat a request for either as out of scope rather than a gap to improvise here.

**The KSI Data Request tree is not the KSI validation tree — do not merge them.** The validation tree is in scope here, but only from L3 down: the 46 per-KSI **L2 validation tickets are created by the tenant's own `create_validation_tickets` sweep**, and this scaffold supplies the per-check L3 children (`tickets/validations.csv`, one row per compliance-mod check) plus their run rows (`tickets/validation-runs.csv`). Run the sweep and snapshot its ids before compiling step 02.

## Cross-references

- `[[halo-action-pipeline]]` — the plan→apply engine; action schema, guards, resume
- `[[ssp-csv-compile]]` — compiles the `ssp/` library manifests; deliberately not reimplemented here
- `[[grcitsm]]` — tool routing and create→update chains
- `[[halo-bulk-update]]` — six-phase bulk discipline
- `scaffold/README.md` — the column contract in full
