---
name: system-package
description: The unified system package — ONE on-disk format for demo data, document ingest, interview capture, and live-tenant capture, with one driver (package.py) that validates offline, checks against regenerated lookups, compiles per family through the proven compilers, diffs against captured tenant state, renders the CHANGE-PLAN view, and manages content-hash approvals. Load when creating, validating, compiling, diffing, reviewing, or writing back a system package, or when a producer (demo-build, ssp-ingest, ssp-interview, tenant capture) needs the package contract.
---

# system-package — one package format, many producers, one pipeline

Design of record: the unified-system-package design rulings, D1–D13, locked 2026-08-10 (restated inline below). The package is
a **session-scoped working copy** of system state: author it, ingest into it, interview into it, or
capture it from a live tenant via MCP — then refine on disk, write the delta back, and archive it.
Within its session the package is the source of truth and the CHANGE-PLAN is a rendered view of it
(D1); **between sessions the TENANT is the durable source of truth** (D11) — many users edit a
tenant by hand, a package is a checkout, and no process may "re-apply the package" to revert
manual tenant work.

## When NOT to use this skill (D12)

One-off and small-N updates never touch this pipeline. Operational ticket/asset work routes to the
existing ops surface (`halo-ops-agent` ≤5 rows direct; planner → action file → `halo-apply` for
bulk) with tenant-truth read-modify-write. A small-N SSP-spine *content* edit (one narrative fix,
one Control Origination) goes DIRECT — read the asset, edit, read back — under the existing
reviewer chain. This pipeline is for **multi-row or structural** system-definition work: new
components, document re-ingest, demo rebuilds, migrations. The next `capture` picks direct edits up.

## References

| Reference | Type | Location | When to Use |
|---|---|---|---|
| `package.py` | File | `skills/system-package/scripts/package.py` | The driver: validate · stamp-lookups · check · compile · diff · capture · session-open · render · dossier · approve |
| `package_schema.py` | File | `skills/system-package/scripts/package_schema.py` | Family registry, package.json contract, row content-hash |
| `test_package.py` | File | `skills/system-package/scripts/test_package.py` | Offline regression suite (golden cases = the 2026-08-10 run's real failures) |
| `demo-build` | Skill | `skills/demo-build/` | Package #1: the checked-in demo system + its per-family emission (`demo_compile.py`) |
| `ssp-csv-compile` | Skill | `skills/ssp-csv-compile/` | The ssp family's emission (`ssp_compile.py`) — reconciler exclusions, library-first, narrative gate |
| `halo-action-pipeline` | Skill | `skills/halo-action-pipeline/` | Applies whatever compile emits; guards, ledger, tenant witness, fabrication audit |

## The pipeline

```
author | ingest | interview | capture        (any producer; packages MERGE — D8)
  → validate      offline, ZERO tenant calls
  → regen lookups tenant reads only, then `stamp-lookups` (refuses header-only or wrong-tenant lookups)
  → check         three-way tenant assertion + the family compilers' own checks
  → render        CHANGE-PLAN view → edits go to the PACKAGE → re-render (D1)
  → per family, dependency order:
      compile --only <family> → /grcitsm-analyst:halo-apply → refresh snapshot
      (reconcilers at their fixed positions: reconcile_ssp_assets AFTER system lands)
  → verify        tenant-side counts + populated fields, never ledger counts
```

**Write-backs (updating rows that already exist on the tenant) additionally require
`compile --write-back <diff-dir>`** — a `diff` run against the CURRENT tenant within the last
30 minutes, with any drift acknowledged in `drift-ack.json` (D11: the tenant is multi-writer, so
the drift gate must reflect the tenant *now*, not at capture time). Omit `--write-back` only for
greenfield / fresh-tenant builds where nothing being compiled exists yet.

## Multi-user concurrency (D13)

Many users — separate repos, AI and manual, bulk and individual — share one tenant. Event creates
(guard search = identity) and upserts are concurrency-safe by construction; write-backs over
disjoint rows interleave cleanly (untouched rows are no-ops). The contested unit is **the same
stateful row in the same window**, guarded in layers:

- **L3 — per-row expectation guards** (enforced): `diff` writes `expectations.json` (each
  tenant-present row's at-diff field values); `compile --write-back` embeds them as `expect` on
  that row's update/fromlib actions; the executor asserts them with one pre-write read and fails
  the row `CONFLICT` on mismatch → re-diff. Field values, never `last_modified` (reconcilers churn
  timestamps). Coverage: spine Title / Implementation State, system-information (the SI_MAP
  fields incl. all four narratives), cloud assets (the CA_MAP 1:1 fields via
  `asset_summary` + `active_inventory`), PPS Encryption.
- **L4 — session visibility** (tooled): `package.py session-open --package <pkg> --out <dir>
  --operator <who>` composes the session **Change Request (change type Routine Recurring)**
  through the standard ticket chain — apply it via `/grcitsm-analyst:halo-apply`, close the CR at
  verify through the ops path. `diff --sessions <file>` (a saved JSON of open session CRs) surfaces
  overlapping open sessions in the report before you apply anything. The ticket is the audit trail.
- **L5 — topology** (doctrine): structural work uses ONE shared package repo — branches, PR,
  reviewer chain, write-backs only from merged main. Per-user repos are for operational work,
  which checks nothing out (D12).

## Families and their semantics

| Family | Semantics | Compiled by | Key |
|---|---|---|---|
| `system` (system-information, ports-protocols, client-details) | stateful | demo_compile step 00 | field / PPS natural key |
| `ssp` (components, rel6, is-manifest, odp-parameters) | stateful | ssp_compile | AFUUID |
| `cloud-assets` | stateful | demo_compile step 01 | row key / inventory_number |
| `tickets` (issues, VDs, CRs, UARs, incidents, projects, tasks) | **event** | demo_compile step 02 | row key + guard |
| `validations` | **event** | demo_compile step 03 | row key + guard |
| `validation-runs` | upsert | demo_compile step 04 | run key |

Producers record `provenance/<family>.jsonl` — one line per authored row `{family, key, source, ts, row_hash}` — so every row stays attributable to the document, interview turn, or tenant read that produced it (D8).

Stateful families diff and re-write. **Event families are never diffed into existence** — append-only
guarded creates. Packages are **partial by declaration** (D6): `package.json` `families` lists what
the package carries; `validate` ignores everything undeclared.

## Rules the driver enforces

1. **Three-way tenant assertion** (check/compile): `package.json target == lookups_stamp == witness`,
   by client/site NAME. Each pair catches a distinct wrong-tenant failure. No stamp → no check.
2. **`--out` is always a directory.** The old `demo_compile` (dir) vs `ssp_compile` (file) split is
   normalized inside the driver.
3. **ONE canonical `lookups/asset-field-map.csv`** — `assettype_id,field_name,field_id,prefix`,
   field_name UNPREFIXED. The driver derives each compiler's variant; never hand-maintain two.
4. **Asset statuses are their own id-map kind** (`asset_status`). The names collide with ticket
   statuses (`Rejected` = ticket 19 / asset 10); the flat kind is a warned legacy fallback.
5. **`lookups/pps-snapshot.csv`** maps (service,port,protocol,purpose) → `port_protocol_id` so
   step 00 UPDATEs existing PPS rows. Without it every row INSERTs — safe ONLY on an empty table
   (a populated one duplicates silently; measured 36-from-17, 2026-08-10).
6. **Diff is reconcile-shaped** (D7): create missing, update drifted, NEVER delete — a tenant row
   absent from the package is a REPORT line. Drift default is **package wins**, gated by one batch
   drift report (D10), and untouched rows compile as no-ops.
7. **Approvals live in `approvals.jsonl`**, keyed by row key + CONTENT HASH (D9): editing an
   approved row invalidates its approval by construction; `render` and `check` surface it.

## Capture (tenant → package)

The capturing agent saves RAW tool outputs into a tenant-state dir — `spine.json`
(`query_ssp_spine`), `system_information.json`, `ports_protocols.json`, `asset_summary.json` +
`active_inventory.json` (cloud assets), and optional per-asset `assets/<asset_id>.json`
(`get_asset` dumps) for ssp enrichment. Then:

- `package.py capture --package <pkg> --tenant-state <dir>` builds/refreshes package rows.
  FAITHFUL for system (full narratives land in `narratives/` behind `@file.md`), PPS, and cloud
  assets; **skeleton-honest for ssp**: spine.json carries no narratives or framework CFs, so an
  ssp row is captured ONLY where a `get_asset` dump supplies the content — everything else lands
  in `capture-report.md` as "needs enrichment", never fabricated. Existing rows keep every column
  capture cannot see (`component_key`, `library_flag`); captured columns refresh; nothing deletes.
- `package.py diff --package <pkg> --tenant-state <dir> --out <dir>` classifies every stateful row.

Event families are captured read-only for context; a write-back never re-emits an existing event
(the guard search is the identity test).

## The ingest acceptance loop (`dossier`)

`package.py dossier --package <pkg> --out <file.md>` renders the package as the prose system
specification a client would hand us — narratives verbatim, inventories, the operational record as
stories. It is a RENDERED VIEW (D1): regenerate after edits, never hand-maintain, and it is NOT a
build path — builds replay the package deterministically. Its purpose is the acceptance test for
the ingest producer: `dossier(package) → /grcitsm-analyst:ssp-ingest → package′ →
diff(package, package′)` measures ingest fidelity against a known answer key. The demo package is
the standing fixture for this loop.

## Worked example (package #1 — the demo scaffold)

```bash
SK="$CLAUDE_PLUGIN_ROOT/skills"
PKG=outputs/<run>/package          # your working copy; never edit the plugin's copy
cp -R "$SK/demo-build/scaffold/." "$PKG/"

python3 "$SK/system-package/scripts/package.py" validate --package "$PKG"
# ... regenerate lookups/ against the target tenant, write lookups_stamp ...
python3 "$SK/system-package/scripts/package.py" check    --package "$PKG" --witness state/tenant-witness.json
python3 "$SK/system-package/scripts/package.py" compile  --package "$PKG" --out state/ --only system
# apply via /grcitsm-analyst:halo-apply, refresh snapshot, next family
```

Regression: `python3 scripts/test_package.py` (offline). The compile path is additionally proven by
golden-diff — recompiling the 2026-08-10 run's package reproduces that run's action files
byte-identically.
