Skip to content

feat(scenario-renderer): canvas topology -> ScenarioSpec adapter #108

Description

@pparage

Context

The scenario renderer (app/core/scenario_renderer/) consumes a ScenarioSpec
(app/core/scenario_renderer/types.py) — a resolved, deployment-ready description of a
scenario: tiers, VMs, bundle attachments, templates, cross-tier groups.

The canvas emits something else: the topology/CatalogEntry model in
app/schemas/generated.py (Node with kind, role, networks[],
attachments[], config, children, cidr_template, bridge_template,
replication, …).

Nothing currently maps one to the other, so the renderer cannot be driven from a
canvas-authored project. This is the missing link between the UI and the renderer.

Scope

Write the adapter: canvas topology → ScenarioSpec.

The canvas model is a superset of what ScenarioSpec needs, so this is mostly a
projection, not an invention:

  • Node.kind == vm|lxcVmSpec (with role, template_vmid, config)
  • Node.kind == network → the bridge / CIDR / gateway that VM NICs reference
    (cidr_template, bridge_template)
  • Node.networks[] (NetworkAttachment: node_ref, ip, ip_template, dhcp) → the
    VM's NIC(s) — note this is already a list, i.e. the canvas can already express a
    multi-homed VM even though the manifest cannot yet represent one
  • Node.attachments[]BundleRef on the VM (stage_01) or on the tier group (baseline),
    resolved through the bundle registry by grammar name
  • role → tier membership (TierSpec: admin / student / ctf / team)
  • replication → author-time expansion into concrete per-team VMs

Depends on

  • The VMID/IP allocator: the canvas supplies no vmids, and VmSpec requires them.
    The clean split is: the adapter produces an unresolved draft, the allocator resolves
    it into a ScenarioSpec. Design the draft types with the allocator, not separately.

Notes

  • Validate early and loudly. ScenarioSpec.__post_init__ and VmSpec.__post_init__
    already reject unsafe scenario/VM names (they become filenames, ssh host tokens, and the
    workspace name the TUI parses) — the adapter should surface those failures with the
    offending canvas node id, not a bare ValueError.
  • Bundle references must resolve by grammar name
    (<tier>/<subject>.<verb>[.<object>]) through registry.py, never by physical path.

Acceptance

  • A canvas project round-trips to a ScenarioSpec and renders to a scenario directory
    that passes ansible-playbook --syntax-check against the real bundles and catalog roles.
  • An invalid canvas node (bad name, unknown bundle, missing network ref) produces an error
    naming the node.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions