The Control Catalog as Data
Last reviewed
IntermediateWhat you'll learn
~18 min- Structure a compliance program as a three-layer system: universal catalog, per-system config, per-run outputs
- Design a per-control schema whose required fields force assessor-grade answers
- Explain why an enum for sampling strategy beats a free-text field - and generalize the principle
From binder to repository
Meridian’s vendor seat, week one: the receiving system’s ISSO asks how Meridian will demonstrate its controls. The reflex answer — a spreadsheet with a row per control and a Status column — is where compliance programs go to rot. This module teaches the alternative that the strongest reference programs actually run: the control catalog as a schema-gated data repository, where every downstream artifact (statements, collectors, assessments, exports) is generated or validated from one source of truth.
The architecture that makes it work is a three-layer separation, and the layers earn their boundaries:
LAYER 1: UNIVERSAL CATALOG one file per control - environment-agnostic (the product) by construction: CI LINT FORBIDS system identifiers here. This layer is reusable across every system you will ever authorize.
LAYER 2: PER-SYSTEM CONFIG what THIS system inherits, tailors, scopes - (the instantiation) the only layer that knows deployment names, subscriptions, boundaries.
LAYER 3: PER-RUN OUTPUTS dated, immutable results of each collection (the record) and assessment run (Module 3).The payoff arrives with the second authorization: it’s a sibling directory in layer 2, not a fork of the whole program. The reference shop that runs this pattern maintains one catalog of 84 technical controls across 10 families serving multiple system packages — and the lint rule keeping system IDs out of layer 1 is what makes that possible. It’s the same discipline as the DevSecOps Foundations training’s template libraries: the reusable thing stays parameterized, and CI enforces the boundary.
One YAML per control — and the fields that do the work
Each control is one file, schema-validated on every merge. The shape (paraphrased teaching version):
# controls/AC-2.yaml - one control, one file, schema-gatedcontrol: AC-2family: access-controlbaseline: moderate # baseline membership is a FIELD, not a foldercomponentImplementations: # OSCAL-shaped on purpose (Lesson 2.3) - component: identity-platform status: implemented origination: hybrid # system | inherited | hybrid roles: [platform-team, isso] statement: > (the human-authored implementation narrative - Lesson 2.2)scope: boundary: > # REQUIRED: what this control claims to cover. # Phrased GENERICALLY here (layer 1) - layer 2 binds real names/IDs: all workforce identities within the authorization boundary; break-glass accounts excluded per documented exceptionsampling: strategy: full-population # ENUM: full-population | time-window | risk-basedevidence: # the CONTRACT for Module 3's collectors automated: [identity-export, access-review-log] operator: [quarterly-review-signoff] query: [kql/ac-2-stale-accounts.kql]Three design choices here are the lesson:
scope.boundary is mandatory. The most common assessment failure isn’t a missing control — it’s a control whose claimed coverage was never stated, so the assessor discovers the exclusions live. Forcing the boundary into the schema means every control answers “covering what, exactly?” before any assessor asks.
sampling.strategy is an enum, not a string. Free text invites “we sample appropriately” — the hand-wave that dissolves under 800-53A procedures. An enum forbids the hand-wave: you either assessed the full population, a defined time window, or a documented risk-based subset. There is no fourth value, so there is no vague answer. This is the generalizable principle: wherever assessors probe, replace prose with a closed vocabulary.
evidence is a contract, not a description. Naming the automated feeds, operator artifacts, and queries per control is what lets Module 3’s collectors be driven by the catalog — and what makes a control with no evidence entry visibly unverifiable at merge time instead of at assessment time.
Prompt first: generate the skeleton, argue with the schema
Read our current compliance spreadsheet (attached) and draft thethree-layer skeleton: one YAML per control following the schema inschema/control.schema.json. For every row where the spreadsheetcannot fill a REQUIRED field - scope.boundary missing, samplingunstated, no evidence source named - do NOT invent content: emit thefile with a FIXME block naming who must answer (ISSO vs platform teamvs inherited-from-provider) and what question they must answer.Then summarize: how many controls arrived complete, and the top fivequestions the spreadsheet never answered.The FIXME discipline matters more than the generation. An AI CLI will cheerfully fabricate a plausible scope.boundary — and a fabricated boundary is worse than a missing one, because it reads as decided. The schema’s job is to surface the questions; humans answer them (next lesson takes over from there).
All three design choices are inert unless the schema runs in CI: a control file that fails validation fails the merge, exactly like a failing test. That single wiring decision is what turns the catalog from documentation into a system - it is the same move the DevSecOps Foundations training makes with pipeline templates (Templates Are Code), applied to compliance. If your catalog can drift from its schema, you have the binder again, just in YAML.
Meridian's team proposes simplifying the schema: drop the sampling.strategy enum for a free-text 'assessmentApproach' field, since teams complained the three values did not fit every case. What does this lesson predict, and what is the better fix?
Key takeaway
Structure the program as three layers — universal catalog (lint-forbidden system IDs), per-system config, per-run outputs — so the second authorization is a sibling directory, not a fork. One schema-gated YAML per control, with the schema doing assessor work: mandatory scope boundaries, closed-vocabulary sampling, evidence as a named contract. Generate skeletons with your AI CLI, but let FIXMEs surface what no tool should invent. Next: the statements humans still write — and how to make them survive assessment.