Scenario Packs

A scenario pack is declarative content plus the tooling, docs, and evidence for a known-good reference implementation. It is not the range engine, scoreboard, portal, class-management UI, or telemetry product.

This repository is the authoritative home for that format, and is subordinate to ACES core (aces-sdl): it exists to make authoring and shipping ACES scenarios easier and defines no extensions to ACES semantics (ADR 0009). It ships the layout contract, template, and tooling as package data, so an author validates against the same version they build against. Packs live in their own catalog repositories and consume this package; this repo does not host packs.

Required Shape

Every new pack starts under scenarios/<name>/ in a catalog repo and follows the layout contract bundled in the package (contract/pack-layout.md):

  • pack.yaml for identity, provenance, status, and optional-layer inventory.

  • docs/provenance-ledger.yaml (required) — the machine-readable source, licensing, content-safety, publication-review, and customer-overlay contract, referenced from pack.yaml via provenance_ledger:.

  • pack.compatibility.yaml when a pack needs a validated product/commercial compatibility projection over runtime profiles, delivery bundles, platform features, artifact boundaries, operator surfaces, and validation gates.

  • sdl/ for the scenario start state and structured scenario definition.

  • assets/ for custom files, planted content, host assets, and briefing assets.

  • flags/, challenges/, and ctfd/ as an all-or-nothing flag layer.

  • build/, tests/, and docs/walkthroughs/ as the reference triangle.

  • profiles/ for delivery and audience bundles when the scenario has them.

  • docs/golden-readiness-checklist.md for milestone planning and final review.

SDL Validation (through ACES)

sdl/ holds the scenario start state as one or more <name>.sdl.yaml documents authored in [ACES SDL][aces-sdl]. aces-pack-validate parses every direct sdl/*.sdl.yaml through ACES (aces_sdl.parse_sdl_file, with full semantic validation) and fails on a pack that ships no start-state document or any document ACES rejects. SDL is validated through ACES — no SDL schema is restated here, and the local JSON-Schema subset validator never touches SDL.

aces-sdl is therefore a hard, exactly-pinned runtime dependency (the exact version lives once in pyproject.toml) rather than an optional one: the gate is fail-closed, so an optional coupling that skipped SDL when ACES was absent would leave every pack’s most important content unchecked. The exact pin is deliberate while ACES SDL contracts are stability: draft; advancing it requires compatibility tests. This raises the package’s Python floor to match ACES (>=3.11). See ADR 0011.

The validator also enforces the one canonical pack→SDL link: each flags/placement.yaml host must resolve to a real Scenario.nodes id in at least one validated SDL document in the pack (the union across a pack’s full and reduced start-state variants). Only flags[].host is checked; no SDL reference is inferred from prose, filenames, or arbitrary keys.

Single-Pack Consumer Validation

aces-pack-validate is the author-CI surface for a catalog checkout. A consumer ingesting one pack uses the public library boundary instead:

from aces_scenario_packs import PackValidationLimits, validate_pack

result = validate_pack(
    staged_pack,
    limits=PackValidationLimits(max_metadata_bytes=512 * 1024),
)

ValidationResult.ok is true when errors is empty. Each error is a bounded, body-free code with at most a canonical pack-relative filename and field path. The function is silent and does not read environment configuration, invoke Git or subprocesses, execute pack code, log, write caches, or mutate process globals.

The consumer contract checks:

  • pack.yaml and its name, title, and version identity fields, including agreement between the pack name and staging-directory name;

  • the required referenced provenance ledger against the packaged schema, matching pack name, all-true content-safety attestations, and required review gates;

  • the referenced compatibility manifest, when present, against the packaged schema; and

  • every direct sdl/*.sdl.yaml document through the pinned ACES parser.

The boundary inventory and reads are descriptor-anchored and no-follow. Non-canonical paths, escaping pointers, symlinks, hardlinks, special files, duplicate YAML keys, invalid UTF-8, and resource-limit violations fail closed. Callers must acquire and immutably stage the directory before validation, then promote those same bytes.

ACES 0.20 does not expose a resolver-policy seam for file-backed SDL parsing. The consumer API therefore parses supplied SDL content without a file context; an SDL document containing imports fails with sdl.imports-denied instead of performing network I/O or writing sdl/.aces/module-cache. The author CLI keeps file-backed parsing under its separately controlled environment. Catalog discovery, pack validators/tests, leak scanning, flag-placement joins, release readiness, and content-manifest verification are outside this consumer API.

See ADR 0013.

Status Meanings

  • draft: design/source only; the full live scenario is not stood up.

  • built: it stands up somewhere, but the declared ACES participant behavior has not been proven end to end from the participant surface.

  • golden: the declared live reference build exists, enters participant start state, and has participant-equivalent full-path proof.

Do not mark a pack golden because management-plane checks pass. Operator channels are useful for provisioning and diagnostics, but they do not prove the participant experience.

Reference Triangle

The reference triangle is one coherent proof surface:

  • build/ stands up the golden range.

  • tests/ exercises every required ACES participant behavior and objective against that live range.

  • docs/walkthroughs/ is the human-readable, command-by-command version of the same behavior.

The three must agree on the declared ACES behavior, objectives, and observable outcomes. A mismatch between them is a defect.

Compatibility Manifest

pack.yaml is the catalog entrypoint. When it contains compatibility_manifest: pack.compatibility.yaml, the referenced file is validated against the bundled pack-compatibility.schema.yaml by aces-pack-validate.

The compatibility manifest separates:

  • runtime/provider profiles (local_minimal, aws_minimal, aws_full);

  • delivery/audience bundles (guided, unguided, purple-team, agent-benchmark, demo);

  • participant/public, operator-only, oracle-only, private, and commercial artifact boundaries;

  • assets, operator surfaces, and validation gates.

It carries zero extensions to ACES semantics: scoring, validation-oracle, telemetry, and lifecycle (reset/rebuild/teardown) are ACES/runtime concerns and are deliberately not manifest layers (ADR 0009).

The manifest indexes existing pack-local files. It is not a runtime engine, dependency lockfile, provider adapter, scoreboard, telemetry product, or proof store.

Provenance Ledger

Every pack ships docs/provenance-ledger.yaml, referenced from pack.yaml (provenance_ledger:) and validated against the bundled provenance.schema.yaml by aces-pack-validate. It is the canonical, machine-readable record of:

  • sources[] — upstream corpora, frameworks, tooling, datasets, research, original design, and generated material, each with a stable id, license, usage, and attribution requirement, plus what was used and deliberately excluded.

  • artifacts[] — pack-relative roots classified for distribution as open, redistributable, internal-only, commercial-only, generated, or customer-specific. This open-vs-ACES-only axis is distinct from the compatibility manifest’s runtime-visibility export values.

  • content_safety{} — mandatory attestations (no real malware, third-party targets, credentials, or sensitive data; offensive-tooling boundary), all of which CI requires to be true.

  • review{} — a publication-review checklist modelled as data, covering licensing, attribution, sensitive-data, and offensive-tooling gates.

Customer overlays are declared as path-contained overlays[] slots; their content is customer-specific and may be removed without contaminating the base pack. docs/lineage.md stays human prose and cites the ledger.

The provenance schema is enforced for every pack: it is the content-safety and publication-review gate, and pack exports carry the ledger rather than bypassing it.

ACES is the authority for pack trust. A scenario pack is a reusable_scenario asset in the ACES reusable-asset trust policy (reusable-asset-trust-policy/v1; ACES ADR-071), which owns pack integrity and authenticity (integrity_digest, authenticity_signature, provenance_lock_record, governance_source, artifact_checksum). Per ADR 0009 and ADR 0010, this ledger is scoped to pack-domain facts ACES does not define — content-origin licensing/attribution (sources[]), distribution class (artifacts[]), content-safety attestations (content_safety{}), publication review (review{}), and customer-overlay containment (overlays[]) — and re-defines none of the ACES-owned trust concepts; those are established by ACES mechanisms (scenario-snapshot digest, aces.lock.json, RegistryTrustPolicy signatures). ACES schemas are stability: draft, so the ledger references the policy now and the explicit upstream pin lands once ACES marks it stable. The bundled layout contract carries the full field-by-field mapping.

ACES concept-authority governs concept vocabulary. ATT&CK and ATLAS offensive-behaviour tactics, UCO concept families, and the controlled vocabularies are published by ACES under contracts/concept-authority/ and are authored and validated in ACES SDL behaviour specifications, not restated in the pack format. The provenance ledger therefore carries no sources[].kind, and the canonical challenge contract carries no challenges[].category; both were free-text overlaps of governed concepts, and a challenge’s presentation grouping is an adapter-local CTFd concern. The remaining pack vocabulary (distribution class, source licensing/usage, review gates, challenge display text, delivery audiences, visibility) is genuinely pack-domain. This advanced the provenance schema to scenario-pack-provenance/v2 and the scenario-pack contract to version 3. See ADR 0014.

Pack Content Identity

A consumer that ingests a pack by reference (path/key + version + digest) needs to prove which concrete bytes the identity covers. ACES owns that portable model: associated-artifact-manifest-v1 attaches one exact payload set to a scenario parent, associated-artifact-set/v1 derives its set digest, and the ACES validator binds the parent, set, checksums, sizes, and staged bytes.

A pack opting into content identity points at the ACES JSON manifest from pack.yaml:

associated_artifact_manifest: associated-artifacts.json

Each artifact keeps its opaque manifest-local id and uses a pack-local locator such as aces-scenario-pack:/docs/operator-guide.md. The URI is not integrity evidence; this package resolves it inside the opened pack root and supplies the concrete bytes to ACES. The manifest carrier and sdl/.aces/module-cache/ are excluded, while every other regular file must be declared. Missing, extra, unsafe, or changed members fail closed.

The public, in-process API separates authoring derivation from consumer validation:

from aces_scenario_packs import (
    derive_pack_content_manifest,
    pack_content_digest,
    validate_pack_content_manifest,
    verify_pack_content_digest,
)

derived = derive_pack_content_manifest(pack_root)  # persist during authoring/release
declared = validate_pack_content_manifest(pack_root)
digest = pack_content_digest(pack_root)            # declared ACES set digest
verify_pack_content_digest(pack_root, expected)    # full validation, then compare

Derivation recomputes descriptor checksums, sizes, and the set digest from a caller-owned immutable staging area. Validation instead requires the manifest already shipped with the pack to agree with those bytes. Both parse the SDL parent through ACES. Neither runs git, a subprocess, pack-local code, network resolution, or archive extraction.

Set identity is not parent identity or trust. Per ADR 0012, this repository owns only pack layout, safe materialization, and exact inventory. ACES owns canonicalization and byte-binding semantics. The resulting associated_artifact_set identity is distinct from the scenario semantic digest; neither one proves authenticity, authorization, entitlement, or safe handling.

A directory API cannot make mutable storage atomic. Consumers must acquire and immutably stage a pack, validate and digest that exact staging area, atomically promote the same bytes, and reverify before use when the storage boundary does not make that redundant.

Validity and ACES participant behavior

The hydrated ACES SDL is the scenario specification. Declare attacker behavior with ACES participant semantics: bind the attacker through agents and behavior_specifications, declare governed actions through action_contracts, and use ACES preconditions, effects, failure classes, observation boundaries, outcome interpretation rules, objectives, evidence requirements, and workflows as the scenario requires. aces-pack-validate parses that SDL through the exactly pinned aces-sdl package; this package does not define a second behavior shape or decide that an ACES behavior specification is complete.

The reference build, automated rehearsal, and participant walkthrough then demonstrate that the live environment realizes the declared ACES behavior and objectives. Explanatory docs/attack-path.md prose may describe the intended route through the environment, but it is not system state or a machine-readable contract. CTFd, operator debriefs, and agent-benchmark reports are consumers of the same ACES-backed scenario and live evidence; none is a competing specification.

The compatibility manifest retains oracle_only and the oracle export value as release-boundary compatibility vocabulary. They classify restricted non-participant material only and confer no scenario semantics. Participant files must not expose restricted action ordering, raw evidence, answers, credentials, flags, or next-step hints.

Operating Profiles

ACES uses delivery/audience bundles for operating profiles: guided, unguided, purple-team, agent-benchmark, and demo. These are content overlays under profiles/; they are not runtime/provider profiles. Selecting a bundle changes which participant, facilitator, defender, benchmark, or presenter files are exposed. It does not change the hydrated ACES SDL, topology, planted content, flags, reference tests, or golden proof.

The standard bundle responsibilities are:

  • guided: staged objectives, progressive hints, teaching notes or facilitator pacing, and answer checks/checkpoints.

  • unguided: terse mission brief, participant objectives, rules of engagement, and operator-only post-run reveal material.

  • purple-team: defender injects, detection goals, expected alerts, blue-team tasks, and debrief prompts.

  • agent-benchmark: deterministic task envelope, participant-safe objective contract, no-facilitator run metadata, and operator-only execution evidence.

  • demo: shortened path, scripted reset reference, reduced-resource runtime compatibility, high-signal proof moments, and presenter script.

When a pack ships profiles/, pack.yaml sets contents.profile_bundles: true and points at profiles/bundles.yaml; the pack carries the entrypoint files, a profiles/validate_*.py gate, and profiles/tests. The compatibility manifest mirrors the shipped bundle rows in delivery_bundles for catalog and commercial consumers.

Build, Release & Versioning

Packaging and release verification is the repo-wide, static/read-only gate aces-pack-release, run in/behind the scenario-content gate. It lints profile-support consistency (a supported delivery bundle must actually ship its content, or the build fails fast), builds a boundary-split release tree (participant/, operator/, oracle/, commercial/) with the participant tier leak-scanned, smoke-tests that delivery-bundle selection changes participant exposure, and emits a versioned release.yaml (pack version, the scenario-pack contract version + digest from the bundled contract, supported profiles, and a bounded provenance summary). A pack is releasable once it ships a pack.compatibility.yaml with artifact_boundaries. Run aces-pack-release check --all locally; see the bundled layout contract (contract/pack-layout.md) for the full build/release section.