WSDL folder-drop

Point XTR at a folder of WSDL files. Every wsdl:operation becomes a live POST /group/operation endpoint on next boot.

SOAP lane only. WSDL-driven generation produces SOAP DSLs (envelope + Handlebars). REST DSLs (kind: rest) are hand-written — see REST passthrough.

Enable

# xtr.yaml
wsdl_watch_dir: ./wsdl

Absent = feature off (only hand-written DSLs load).

Layout

wsdl/
├── <owner>/                    # URL prefix — often the org / vendor slug
│   └── <subsystem>/            # organisational grouping (optional)
│       ├── N.wsdl
│       └── N.meta.yaml         # sidecar: X-Road identity for the envelope
└── <single-service>/           # flat: no subsystem subdir
    ├── api.wsdl
    └── api.meta.yaml

Path mapping:

WSDL locationGenerated DSLURL
wsdl/foo/api.wsdlDSL/foo/<op>.ymlPOST /foo/<op>
wsdl/foo/bar/N.wsdlDSL/foo/bar-<op>.ymlPOST /foo/bar-<op>

Nesting is for human browsability. URL segments are always /<owner>/<optionally-prefixed-op> — 2 segments, no exceptions.

Sidecar meta.yaml

Placed next to each WSDL (api.wsdl + api.meta.yaml).

member_class: GOV
member_code: "70000123"
subsystem_code: my-service
# Optional: explicit service code (defaults to operation name).
service_code: someMethod
# Optional: public-HTTPS override for dual-mode vendors like
# Ariregister. When set, DSLs skip X-Road envelope wrapping and
# hit this URL directly. Vendor auth (username/password in SOAP
# body) still required.
service_url: https://vendor.example.com/soap

When service_url: is set → plain SOAP envelope, direct HTTPS. Otherwise → X-Road-wrapped envelope, routed via security_server:.

Marker header + collision rules

Every generated DSL starts with:

# GENERATED BY XTR from WSDL — do not edit; delete this line to convert into a hand-written override

Collision rules on the next boot:

Existing fileAction
Doesn't existWrite
Has markerOverwrite
No marker (hand-written)SKIP with WARN — your edits stand

Delete the marker line from a generated file to convert it into a hand-written override that survives regeneration.

Deterministic output

Same WSDL always produces byte-equal DSL YAML. git diff between two boots is empty. Any drift = real WSDL change (or a generator bug — file it).

Harvest more Estonian services

RIA publishes a public catalog at https://x-tee.ee/catalogue-data/EE/index.json listing every X-Road subsystem's WSDLs. The shipped scripts/harvest-xtee-wsdls.sh fetches on demand:

./scripts/harvest-xtee-wsdls.sh --member 70008440           # RR (Rahvastikuregister)
./scripts/harvest-xtee-wsdls.sh --subsystem liiklusregister
./scripts/harvest-xtee-wsdls.sh                             # everything (~421 subsystems)

Auto-writes .meta.yaml sidecars from catalog metadata. Groups under wsdl/<owner>/ per the script's built-in OWNERS map (extend the map for new memberCodes).

Scale limits

The current DSL loader validates every Handlebars template at boot. Practical caps:

SubsystemsEndpointsBoot
Ariregister only33~1 s
Climate orbit (shipped default)194~1 s
~20 subsystems~500–800~10 s
Full RIA catalog (~421)~3000+not viable today (task 014)

Use --subsystem / --member filters to keep the working set manageable.

Unsupported constructs

WSDL parser bails (with WARN, skips the operation, XTR still boots) on: xsd:choice, WSDL 2.0, RPC/encoded style, MIME attachments, xsd:import beyond framework schemas. Override with a hand-written DSL for those cases (see Getting started).

Why no admin HTTP endpoint

There's no POST /admin/wsdl-from-url. Ingestion is folder-only by design — admin and consumer surfaces stay hard-separated at the infrastructure layer, not muddled together via in-process auth. For URL-driven ingestion, curl -o wsdl/foo/api.wsdl <url> from your config-management job.