Context
app/core/scenario_renderer/registry.py maps bundle grammar names
(<tier>/<subject>.<verb>[.<object>]) to physical bundle paths, and assigns each bundle a
BundleKind (VM / GROUP / XTIER / INFRA) — the call-site contract that decides where a
bundle may legally attach.
That registry is currently hardcoded. Every bundle the renderer can use is a literal
in Python. Adding a bundle to range42-playbooks does not make it usable by the renderer,
and a UI palette cannot list what actually exists.
Scope
Replace the hardcoded registry with an index built by scanning linked Sources.
The machinery mostly exists: /v1/catalog/sources (CRUD + refresh + entries) already
clones linked repos and walks them for manifests. The gap is that it indexes catalog
content (CVEs, containers, roles) and not bundles.
- Extend the scanner to index bundles — walk
bundles/ in a linked source, derive
the grammar name from the path, and derive kind from the bundle's own main.yml
hosts: rather than requiring a descriptor file.
- Registry reads that index instead of its literals.
- Palette endpoint — group by subject, badge by verb/kind, so the UI can present
what is genuinely available.
- Fork — copy a bundle across Sources into a writable project/personal Source, so
range42-playbooks stays read-only/curated while user experiments live somewhere
writable. The UI already has the Sources/PAT store and a git-provider client.
- Wire the existing guided bundle scaffolder (
authoring.py) to write into that writable
Source.
Deliberate non-goal: no per-bundle descriptor file
A bundle.r42.yml was considered and rejected. Of the five fields it would have carried:
name — is the path.
install_flag — is not a property of a bundle. The same wazuh bundle is imported by
9 scenarios with opposite default polarity; the flag belongs to the import site, and to
the scenario's own feature_flags.yml.
kind — derivable from main.yml hosts: for 100% of the bundles the renderer uses.
The scanner must comment-strip and scan all plays, not just the first. (~13 legacy
create-vms-* / linux-ubuntu / ping bundles resist derivation, but they are
retirement-bound.)
requires_vars — the renderer knows these per-kind.
ports — admin bundles declare them in catalog_try.yml or their own firewall play.
CTF ports are the one real gap, and the fix belongs inside the bundle: promote the
# TODO BUNDLE_PORTS_TCP comment that already sits in those bundles into a real key in
their main.yml.
Principle: derive everything, author nothing. Anything a scanner can compute must not
become a file a human has to maintain in sync.
Naming
The <tier>/<subject>.<verb>[.<object>] grammar has a closed vocabulary — subjects
{software, system, network, credentials, repo, template, vm}, verbs {install, build, create,
configure, baseline, clone, bootstrap}; the verb is always the second segment; ctf/ is
exempt (identity-addressed by CVE). The verb list is the anti-anarchy lever: it is what
stops synonyms proliferating. The scanner should reject, or at least flag, a bundle whose
path does not parse.
Note
core/ is not yet renamed on disk, so the registry currently keeps an alias from grammar
name to the physical path. Delete the alias once the rename lands — the scanner should
not inherit it.
Context
app/core/scenario_renderer/registry.pymaps bundle grammar names(
<tier>/<subject>.<verb>[.<object>]) to physical bundle paths, and assigns each bundle aBundleKind(VM / GROUP / XTIER / INFRA) — the call-site contract that decides where abundle may legally attach.
That registry is currently hardcoded. Every bundle the renderer can use is a literal
in Python. Adding a bundle to
range42-playbooksdoes not make it usable by the renderer,and a UI palette cannot list what actually exists.
Scope
Replace the hardcoded registry with an index built by scanning linked Sources.
The machinery mostly exists:
/v1/catalog/sources(CRUD + refresh + entries) alreadyclones linked repos and walks them for manifests. The gap is that it indexes catalog
content (CVEs, containers, roles) and not bundles.
bundles/in a linked source, derivethe grammar name from the path, and derive
kindfrom the bundle's ownmain.ymlhosts:rather than requiring a descriptor file.what is genuinely available.
range42-playbooksstays read-only/curated while user experiments live somewherewritable. The UI already has the Sources/PAT store and a git-provider client.
authoring.py) to write into that writableSource.
Deliberate non-goal: no per-bundle descriptor file
A
bundle.r42.ymlwas considered and rejected. Of the five fields it would have carried:name— is the path.install_flag— is not a property of a bundle. The same wazuh bundle is imported by9 scenarios with opposite default polarity; the flag belongs to the import site, and to
the scenario's own
feature_flags.yml.kind— derivable frommain.ymlhosts:for 100% of the bundles the renderer uses.The scanner must comment-strip and scan all plays, not just the first. (~13 legacy
create-vms-*/linux-ubuntu/pingbundles resist derivation, but they areretirement-bound.)
requires_vars— the renderer knows these per-kind.ports— admin bundles declare them incatalog_try.ymlor their own firewall play.CTF ports are the one real gap, and the fix belongs inside the bundle: promote the
# TODO BUNDLE_PORTS_TCPcomment that already sits in those bundles into a real key intheir
main.yml.Principle: derive everything, author nothing. Anything a scanner can compute must not
become a file a human has to maintain in sync.
Naming
The
<tier>/<subject>.<verb>[.<object>]grammar has a closed vocabulary — subjects{software, system, network, credentials, repo, template, vm}, verbs {install, build, create,
configure, baseline, clone, bootstrap}; the verb is always the second segment;
ctf/isexempt (identity-addressed by CVE). The verb list is the anti-anarchy lever: it is what
stops synonyms proliferating. The scanner should reject, or at least flag, a bundle whose
path does not parse.
Note
core/is not yet renamed on disk, so the registry currently keeps an alias from grammarname to the physical path. Delete the alias once the rename lands — the scanner should
not inherit it.