ADR-0015: Package Asset Resolution and Override Model
Status
Accepted
Date
2026-05-28
Based On
Context
ADR-0012 defines the .tckpkg package format and ADR-0014 defines the Flat Compilation IR. However, neither specifies clearly:
- What goes inside the compiled JSON vs. what remains as separate files in the package archive.
- How assets (schemas, testdata) are referenced from the compiled IR.
- Whether assets can be overridden at execution time without recompilation.
Forces
- Reusability: The same TCK package should be executable against different SUT implementations with different testdata payloads or schema versions — without recompilation.
- Certification flexibility: A certification body distributes the TCK, but SUT vendors may need to override testdata to match their specific implementation details.
- Package portability: The
.tckpkgmust remain self-contained — it must work without network access or external file dependencies. - Debuggability: The compiled IR (
tck-execution.json) should remain human-inspectable. Embedding 50MB PDFs as base64 inside JSON kills readability and tooling. - Security: In encrypted mode, the TCK author must control whether asset overrides are permitted.
- Simplicity: The Player should resolve assets with a single, predictable mechanism — no special-casing per asset type.
Decision
1. Package Structure
A .tckpkg is a ZIP archive containing three components:
1.1 Plain Mode
my-tck.tckpkg (ZIP)
├── manifest.yaml # Package metadata (always unencrypted)
├── tck-execution.json # Full execution payload (env, preconditions, compiled_tests)
└── assets/
├── schemas/
│ ├── business_partner_certificate.json
│ └── notification_header.json
└── testdata/
├── available_notification.json
└── sample_certificate.pdf
1.2 Encrypted Mode
my-tck.tckpkg (ZIP)
├── manifest.yaml # Unencrypted metadata + security block (NO tests: section)
├── payload.enc # AES-256-GCM encrypted tar (tck-execution.json + assets/)
└── signature.sig # Hybrid Ed25519 + ML-DSA-65 signature
Encrypted mode omits tests: from manifest
In encrypted mode, the tests: section is NOT present in manifest.yaml — it is inside the encrypted payload. This prevents leaking test IDs and source paths to unauthorized parties.
2. Asset References in tck-execution.json
The compiled IR does NOT inline asset content. Instead, it stores asset keys that the Player resolves at boot time.
2.1 Schema References
"env": {
"schemas": {
"certificate_schema": {
"asset_key": "business_partner_certificate_schema.json",
"type": "application/json"
}
}
}
2.2 Testdata References
"env": {
"testdata": {
"available_notification_body": {
"asset_key": "available_notification_body.json",
"type": "application/json"
},
"sample_certificate": {
"asset_key": "sample_certificate.pdf",
"type": "application/pdf"
}
}
}
2.3 Asset Entry Fields
| Field | Type | Required | Description |
|---|---|---|---|
asset_key |
string | Yes | Filename or relative path within the assets/ folder inside the ZIP. Used for resolution and decompilation. |
type |
string | Yes | MIME type of the asset content. |
3. What is Inlined vs. What is an Asset
| Data | Location | Reason |
|---|---|---|
| Variables | Inlined in tck-execution.json → env.variables |
Small strings, overridable via --env flag |
| Services (factory recipes) | Inlined in tck-execution.json → env.services |
Config objects, not bulk data |
| Preconditions | Inlined in tck-execution.json → preconditions (root level) |
Metadata, not files |
Precondition seed mappings |
Inlined in tck-execution.json → preconditions[].seed |
Env variable seeding metadata (see ADR-0013 §10) |
| Instructions + symbol tables | Inlined in tck-execution.json → compiled_tests[] |
Core IR — never swapped |
| Schemas | Asset reference → assets/schemas/ |
May be versioned/overridden per SUT |
| Testdata (JSON/XML/text) | Asset reference → assets/testdata/ |
Primary override target |
| Testdata (binary) | Asset reference → assets/testdata/ |
Never belongs in JSON |
Rule: Anything that a SUT vendor or test operator might legitimately need to swap without recompilation is stored as a separate asset file.
4. Player Asset Resolution (Layered Override Model)
The Player resolves assets using a priority chain:
4.1 Player Boot Sequence
The Player executes this sequence at startup before running tests:
1. Load global_symbols → apply defaults + --env overrides
2. Load assets (schemas, testdata) + overrides
3. Execute preconditions IN ORDER:
a. Run precondition step → get output dict
b. Validate output matches `returns` contract
c. For each `seed` entry: if env var NOT already set by --env → write output value
4. Re-resolve services that depend on seeded variables (lazy init)
5. Run tests
Seeding priority:
4.2 CLI Interface
# Run with package assets (default — no overrides)
testlab run my-tck.tckpkg
# Override specific assets
testlab run my-tck.tckpkg --override-assets ./my-overrides/
# Override environment variables + assets
testlab run my-tck.tckpkg \
--env provider_url=https://my-sut.com \
--override-assets ./my-custom-data/
Override folder structure mirrors the assets/ layout:
my-overrides/
├── schemas/
│ └── business_partner_certificate.json ← custom schema version
└── testdata/
└── available_notification.json ← custom payload
Only files present in the override folder are overridden — missing files fall through to the package default.
5. Override Control (Security)
The TCK author controls whether their package allows asset overrides via the manifest.yaml:
compilation:
compiled_at: "2026-05-28T14:00:00Z"
compiler_version: "0.5.0"
fingerprint:
nonce: "blake2b:..."
public_key: "ed25519:..."
checksum: "blake2b:..."
allow_asset_override: true # or false
5.1 Override Policies
allow_asset_override |
Plain mode | Encrypted mode |
|---|---|---|
true |
Overrides permitted | Overrides permitted (after decryption) |
false |
Overrides permitted (warning logged) | Overrides rejected — AssetOverrideProhibitedError |
| (not set) | Overrides permitted | Overrides rejected (secure default) |
Rationale: - Plain mode is for development — always allow overrides (with a warning if explicitly disallowed). - Encrypted mode is for distribution/certification — respect the author's intent.
6. Compiler Changes
The compiler's packaging phase (ADR-0012 §2, phase 8) is updated:
| Previous behavior | New behavior |
|---|---|
| Inline schema content into IR | Copy schema file to assets/schemas/, store asset_key in IR |
| Inline JSON testdata into IR | Copy testdata file to assets/testdata/, store asset_key in IR |
| Inline binary testdata as base64 | Copy binary file to assets/testdata/, store asset_key in IR |
The compiler still validates schemas and testdata at compile time (parsing, type checking). It just doesn't embed the content in the IR.
7. Symbol Table Impact
The symbol table entries for schemas and testdata remain unchanged:
"env.schemas.certificate_schema": {
"source": "env.schemas",
"produced_by": -1,
"type": "object"
},
"env.testdata.request_certificate_body": {
"source": "env.testdata",
"produced_by": -1,
"type": "object"
}
The Player seeds these symbols from the resolved asset content at boot time — the instructions don't know (or care) whether the value came from the package or an override folder.
8. Concrete Example
See docs/examples/certificate-management-v2/plain/ for a full working example with:
- manifest.yaml — grouped sections (package/tck/compilation) with fingerprint block
- tck-execution.json — env, tests, preconditions at root, compiled_tests array
Consequences
Positive
- Reusable packages: Same TCK runs against different SUTs with different payloads — no recompilation.
- Readable IR:
tck-execution.jsoncontains only references, not bulk data — stays human-inspectable. - Binary-safe: PDFs, certificates, and other binaries are proper files, not base64 blobs in JSON.
- Schema evolution: Test against different schema versions by overriding the schema file.
- Secure by default: Encrypted packages reject overrides unless explicitly allowed by the author.
- Simple resolution: One algorithm, one priority chain, one CLI flag.
Negative
- ZIP-internal file reads: The Player must read files from within the ZIP archive.
- Override validation gap: Overridden assets are not compile-time validated — a malformed JSON override causes a runtime error.
- Partial overrides: Overriding testdata without updating assertions could cause false test failures (user responsibility).
Risks
| Risk | Mitigation |
|---|---|
| Override breaks test assumptions | Player validates overridden JSON schemas at boot time. Logs all overrides at startup. |
| Override used to bypass certification | allow_asset_override: false in encrypted mode blocks all overrides. |
| Asset path traversal attack | Player rejects asset_key containing .. or absolute paths. Resolution confined to assets/ prefix. |
| Missing asset in package | Compiler validates all asset_key entries exist in the ZIP before finalizing. |
Supersedes
This ADR amends ADR-0012 §3 (package structure) and ADR-0014 §3.4.3–3.4.4 (inlined schemas/testdata):
- ADR-0012:
.tckpkgnow containstck-execution.json+assets/instead of raw YAMLtests/+assets/. - ADR-0014:
env.schemasandenv.testdatauseasset_keyreferences instead of inlinedcontent/content_base64.
All other aspects of ADR-0012 (encryption, signing, integrity, CLI) and ADR-0014 (instructions, symbol table, $ref resolution) remain unchanged.