ADR-014: Scenario Description Language (SDL)¶
Status: Accepted Date: 2026-03-29 Deciders: Brad Edwards
Historical note: ADR-035 supersedes this ADR as current scenario-authoring authority. The APTL-local SDL parser and models described here were removed after the RAES SDL cutover; this record remains historical context.
Context¶
APTL's scenario format was an ad-hoc YAML schema validated only by Pydantic structural checks. The DSL-001 requirement called for a formal specification language with a documented grammar, parser, and semantic validation. Research across 12 cybersecurity SDLs, 10 adjacent DSLs, 6 security standards, and 6 agent evaluation frameworks (documented in internal research notes) identified the Open Cyber Range (OCR) SDL as the closest existing precedent.
The OCR SDL is a YAML-based language with 14 sections (nodes, infrastructure, features, conditions, vulnerabilities, metrics/evaluations/TLOs/goals, entities, injects/events/scripts/stories) parsed by a Rust library. It separates logical topology from physical deployment and includes a full scoring pipeline and exercise orchestration model.
However, the OCR SDL lacks: data/content modeling, user accounts, network access controls, OS classification, asset values, service exposure, platform-targeted commands, relationships between services (authentication, trust, federation), agent specifications, and parameterization.
Decision¶
Use the OCR SDL as the starting surface for aptl.core.sdl, preserve coverage across the OCR-derived sections, extend that base with 7 new sections adapted from existing systems (not invented), and decouple the language from any specific deployment backend.
Architecture¶
The SDL is a specification language, not a deployment tool. It describes what a scenario is. A separate provider binding layer (future work) translates SDL specifications into concrete infrastructure.
Sections (21 total)¶
14 OCR-derived base sections + 7 new:
- content (from CyRIS)—data placed into systems
- accounts (from CyRIS)—user accounts within nodes
- relationships (from STIX SRO)—typed edges between elements
- agents (from CybORG)—autonomous participants
- objectives (from OCR scoring + CACAO workflow context)—declarative experiment semantics
- workflows (from CACAO workflow patterns)—branching and parallel objective composition
- variables (from CACAO)—parameterization
Identity Model¶
Identity is not a separate section. It emerges from the combination of:
- Accounts: who exists where (username, groups, SPN, password strength)
- Features: what provides authentication (AD, LDAP, RADIUS services)
- Relationships: how services connect (authenticates_with, trusts, federates_with)
This is simpler and more composable than a dedicated identity layer.
Validation¶
Two-phase validation: 1. Structural (Pydantic)—types, ranges, required fields, intra-model constraints 2. Semantic (SemanticValidator)—22 named passes checking cross-references, dependency cycles, IP/CIDR consistency, typed VM/network references, OCR count constraints, workflow graph integrity, and SDL domain rules
The validator collects all errors rather than failing on the first.
Contract Guards¶
icontract may be used as a fail-fast guard over already-defined SDL state,
but it is not a third schema layer. Scenario contracts must protect stable
model/query boundaries such as "this returned objective/step belongs to this
scenario" or "a caller requesting a concrete workflow step supplied a declared
workflow and step name." They must not duplicate Pydantic field constraints,
semantic cross-reference passes, YAML normalization, shorthand expansion, or
runtime/deployment readiness checks.
Contract predicates and descriptions must be pure, cheap, and safe to expose:
no filesystem, Docker, network, environment, secret-reading, or backend calls;
no interpolation of whole scenario objects or raw YAML; and no raw
icontract violation text crossing CLI/API/log/persistence boundaries. Public
scenario loading still reports failures as ScenarioValidationError, wrapping
SDLParseError or SDLValidationError as appropriate.
Use contracts only where they add boundary clarity that the SDL validation
pipeline does not already provide. If an invariant can be expressed as a
Pydantic model validator or as a collecting SemanticValidator pass, keep it
there so authors get normal field paths or aggregated validation errors.
Parser¶
The parser handles:
- Case-insensitive field keys (preserving user-defined names)
- Shorthand expansion (source strings, infrastructure counts, role strings, min-score integers, feature lists)
- SDL-only parsing with clean rejection of removed legacy metadata scenarios
- Clean error messages for all failure modes
SCN-001 Reconciliation Guardrail¶
SCN-001's original vocabulary (metadata, mode, container requirements,
attack steps, hints, and expected detections) predates the SDL-only boundary.
Any future work that reopens those concepts must reconcile them through the
current SDL surface rather than restoring a second scenario schema.
The historical authoring schema for this ADR was the local SDL model and parser.
After ADR-035, those local parser/model APIs are no longer authoritative. Any
future scenario-level fields such as mode, stable IDs, versions, difficulty,
or estimates must be designed through RAES SDL and the APTL catalog/runtime
handoff; they must not be read from raw YAML side channels, inferred from
filenames, or reintroduced through a legacy local scenario schema.
Backend/runtime concerns stay out of the specification layer. Container
requirements should flow through nodes, infrastructure, backend capability
validation, and runtime planning. Objective checks, Wazuh queries, command
execution, file checks, and expected detection probes belong in conditions,
metrics/evaluations, evaluator adapters, or orchestration bindings; they should
not become a second objective-validation schema embedded in SDL.
Backward Compatibility¶
None by design. This branch establishes an SDL-only boundary:
- Legacy APTL scenario YAMLs are archived as reference-only fixtures
- aptl.core.scenarios remains only as shared scenario/session exceptions
- The old scenario CLI/API/runtime entrypoints were removed rather than left as partial shims
Consequences¶
Positive¶
- 19 real-world scenarios validated from 8 platforms (OCR, CybORG, CALDERA, Atomic Red Team, CyRIS, KYPO, HTB, Locked Shields)
- 1,050+ fuzz test inputs with zero unhandled crashes
- Every SDL element traces to a published precedent
- Backend-agnostic: no Docker, OpenStack, or cloud provider coupling
- The OCR-derived coverage gaps identified in branch review were closed: entity facts and orchestration time grammar now align with the verified OCR surface
- One clear specification surface for follow-on provisioner/runtime work
Negative¶
- 21 source files in
aptl.core.sdl/—significant surface area - Variables (
${var}) are still unresolved at parse time; existence is checked, but backend substitution semantics remain future work - Existing APTL scenario YAMLs require migration to SDL format
- No module composition system yet (Terraform-style imports)
- No formal verification (VSDL's SMT / CRACK's Datalog)
- Agent action semantics are strings, not typed operations
Risks¶
- The SDL was designed and tested by one system (this project). Practitioner feedback may reveal ergonomic issues or missing concepts
- The relationship model uses a flat
propertiesdict which could become a maintenance burden as relationship types proliferate - Variable resolution semantics are undefined—instantiation backends will need to agree on substitution rules