Loadable scenarios and the runbook
Three pairs of scenarios and six singles, one task each, then the runbook that walks the whole demo in narrative order, then how to put the default branch back. Each task makes its own branch, loads its own scenario input and runs its own check, so nothing here asks you for a branch name. Every number below is produced by the engine and asserted by a unit test.
The demo guide carries the run order and links the other scenario pages.
Two loadable scenarios for the ODU layer​
Both are scenario input under demo/. They share one branch because they touch
no common wavelength, and one task owns it:
uv run invoke demo-odu
It makes odu-demo if it is not there, loads both files, provisions the eleven
London to Milan circuits the first one asks for, and runs the capacity check.
Run it again and it continues onto the same branch.
demo/04_odu_ten_in_one.yml, ten circuits in one wavelength. Provision
svc-lon-mil-sdh-01 through -10 and each maps into an ODU2 of 8 slots that
grooms into the same ODU4 line container. Ten eights are eighty, so the tenth
takes it to 80 of 80 and none of the ten lights a wavelength of its own.
svc-lon-mil-sdh-11 is then refused, reason no-slots, naming the container
it did not fit with both slot figures and then saying that no wavelength could be
lit either. Grooming is tried first and lighting second, so the file has to close
both escapes. It spends the last usable block on oms-fra-mil, leaving
532,400 MHz in 29 blocks. The widest of those, 38,000 MHz, is narrower than the
44,400 MHz the narrowest mode in the catalog occupies.
This one ends in an accepted refusal too. The file carries
refusal_accepted: true on svc-lon-mil-sdh-11 and on none of the ten that
provisioned. provisionable reads the refusal, sees it signed for and
passes. The scenario's point is that grooming is tried before lighting and a
refusal has to close both escapes, and none of that needs a red pipeline. The
flag also survives the generator: a rerun that refuses the service again leaves
the signature alone, and only the path where the service actually provisions
clears it.
The band split on that branch is odu4 5, full 1, no-odu 15. Only
oms-ams-lon turns red, and the two other sections the corridor crosses stay
green because they still carry empty ODUC4 wavelengths at 320 free.
demo/05_odu_mixed_fill.yml, every band at once. No service and no generator
run. It writes client containers directly, spread so that at least one section
lands in each of the five bands. The measured split is odu4 5, odu2 1,
odu0 1, full 2, no-odu 12, over 21 sections. Load it, fetch odu-map from any PoP, and
the legend can be read against the picture.
Read the map, then check it. Two minutes, and it is the step that says whether
the drawing works without its author standing next to it. Give someone the
rendered odu-map and nothing else, and ask for two lists: the sections that can
still take a 100G circuit, and the sections where nothing fits. Both come off the
colours, green for the first and red for the second. Then check the answer
against the containers on the branch rather than against the map, which is the
last thing demo-odu does.
The check passes, and its summary line counts parents that fit, parents with no known figure and overfilled ones. Those counts come from the same slot table the map coloured itself with. A reader who names a green section the containers say is full has found a real disagreement. There is nowhere for one to hide: the map, the check and the generator all call one implementation of the capacity rule.
Two loadable scenarios for the regenerator​
Both are scenario input under demo/, and they are a before and after. They need
two branches, because the point is the comparison: one branch keeps the
refusal, and the other is the proposal a reviewer holds against it.
uv run invoke demo-regenerator
One command tells both halves. It makes oeo-refused, loads 06, asks for
svc-mad-waw-400g and runs provisionable, which fails. Then it makes
oeo-closed, loads 06 and 07, asks again, and runs the same check, which
passes.
demo/06_mad_waw_16qam.yml, three regenerators and three refusals. Madrid
to Warsaw is 2970 km over four sections, and no single wavelength closes it on
any of the ten modes. The best is -0.021 dB at DP-QPSK 128GBd 400G. The file
puts an OtnOduSwitch at each of the three sites on that route where one could
go, oeo-par-01, oeo-fra-02 and oeo-prg-01, each holding the wavelength
pair its split needs. All three splits are refused, reason budget: Paris
returns -0.535 and -2.439 dB, Frankfurt -2.755, Prague -4.004. One run reports
all three.
The two later splits show one figure each because the generator stops budgeting a chain at its first failing segment. A verdict is a conjunction, so the second half cannot rescue the first and computing it would only produce a number nobody may act on. Both halves of all four attempts are in the link budget table, measured segment by segment.
That refusal is the scenario and not a fault in the file. A route's verdict is a conjunction over its segments, so one half short of OSNR refuses the whole circuit however much margin the other half has.
This scenario ends in a blocked merge, and it is the only one that does.
Nothing in demo/06_mad_waw_16qam.yml sets refusal_accepted, so a proposed
change from oeo-refused goes red. The check the task runs on that branch says
so:
svc-mad-waw-400g cannot be provisioned and was refused for budget: neither a
direct wavelength nor a chain serves this route. Direct:
oms-par-mad|oms-par-fra|oms-prg-fra|oms-prg-waw closes on no mode: best margin
-0.021 dB on DP-QPSK 128GBd 400G. Chain:
oc-ch070-mad-par|oeo-par-01|oc-ch070-par-waw does not close: segment 1 -0.535
dB, segment 2 -2.439 dB. It has not been accepted, so this branch does not
merge. Fix the network or the request, or set refusal_accepted on the service to
keep the refusal on the record
The service, the code and the detail are all in the one message, because a
proposed change shows a reader this line and nothing else. The same run adds
Judged 1 service(s). 1 refused and unaccepted, 0 refused with no readable code, 0 accepting a refusal that does not exist, 0 refused and signed for, 0 carrying an optical path.
Every other refusal in this guide is signed for. This one is deliberately
not, so the gate is seen firing at least once.
tests/unit/test_demo_scenarios.py::test_madrid_to_warsaw_signs_for_nothing_so_one_scenario_still_blocks
fails if somebody adds the flag here, because a feature every scenario accepts
its way past is a feature nobody watching the demo ever sees.
Two other checks are red on this branch for reasons that predate the gate.
osnr_margin fails on a Paris to Madrid deficit that is on every branch
including the default one. channel_collision fails because the three
regenerator wavelength pairs this file loads overlap in spectrum on purpose.
Neither is provisionable, and neither is this feature's doing.
demo/07_mad_waw_qpsk.yml, the mode that finishes the job. One more
wavelength pair on the same Frankfurt split, at DP-QPSK 128GBd 400G, on
oeo-fra-03. No new site, no new section, no edited margin. The service then
provisions as two segments:
no direct wavelength serves
oms-par-mad|oms-par-fra|oms-prg-fra|oms-prg-waw, so it takes the chain
oc-ch073-mad-fra|oeo-fra-03|oc-ch073-fra-waw in 2 segments regenerated at
oeo-fra-03 at Frankfurt, 2970 km, segment 1 +2.745 dB, segment 2
+5.740 dB, 14558.963 us including the framing delay
Because 06 is loaded underneath, the same run still refuses all three 16QAM
splits and says so. So one provisioning run tells the whole story: the fix for
this route is a regenerator and a mode change, and three attempts at the
regenerator alone were not enough. The link budget
page has the table and what a chain costs.
This scenario refuses nothing, and it is the trap in the pair. The run logs
three discarded 16QAM splits. A gate reading the log would block a branch on
which the service ended up fine. It reads the node instead:
svc-mad-waw-400g on oeo-closed is active, carries two path segments, and
holds no reason code, no detail and refusal_accepted false. provisionable
passes, and it is measured rather than argued.
tests/unit/test_demo_scenarios.py::test_the_qpsk_fix_accepts_nothing_because_it_refuses_nothing
holds the file to it, because a signature here would be one on a refusal that
does not exist, which is its own error.
channel_collision is red on oeo-closed for the same reason it is red on
oeo-refused: 06 is loaded underneath and its wavelength pairs overlap. That
is the branch below, not the fix on top of it.
Why oeo-fra-03 and not oeo-fra-02. One device terminating all four
Frankfurt wavelengths is a valid plant and it changes the answer, which was
measured. With the 16QAM pair and the QPSK pair on one device, the generator
takes the mixed cover and closes at +2.745 and +0.240 dB. Both halves close, so
it is a real circuit, and it is a poorer demonstration. The second half survives
on a quarter of a decibel, and a reader is left thinking the modulation mattered
on one side only. A junction needs one device terminating both of its
wavelengths, so keeping the QPSK pair on its own device makes that cover
unavailable.
Two loadable scenarios for declared diversity​
These two share one branch, because the point is one run reporting a satisfied promise and a broken one side by side.
uv run invoke demo-diversity
It makes diversity-demo, loads both files, provisions all four members and then
runs the check.
demo/08_diversity_mil_feeds.yml, a promise that holds. Milan's two
northern feeds arrive through different trenches. Vienna to Milan takes
oms-vie-mil, whose ducted span sits in cd-mil-northeast, and Geneva to Milan
takes oms-gva-mil, in cd-mil-northwest. No conduit is on both routes, so the
check passes. It passes because the routes are disjoint and not because
nobody asked, which is the distinction the file exists to draw. A satisfied
requirement and an absent requirement both look like a green check, and only one
of them is worth anything.
demo/09_diversity_fra_feeds.yml, a promise that does not. Amsterdam to
Frankfurt and Paris to Frankfurt are different cities on different sections, and
both enter Frankfurt through cd-fra-north. The check fails, which is the
expected result:
svc-fra-feed-ams-100g and svc-fra-feed-par-100g are both in diversity
group dg-frankfurt-feeds and their routes share cd-fra-north. One cut in
that duct takes both, so the diversity this group declares does not hold
With both files loaded the same run adds Checked 2 diversity group(s) over 4 routed service(s). 1 pair(s) share a conduit against a declaration and 0 member(s) have no route yet. Services declaring no group are not judged here.
The Milan pair is judged and passes in the same breath as the Frankfurt pair is
judged and fails. The last sentence is the check saying out loud what it did
not look at.
Run the same check against the demo branch and it reports PASSED in silence.
Those five services share four ducts between them, which the exposure report
lists in scenario seven. None of them declared a group, so this check has nothing
to say about any of it.
Provision before checking. A member with no route yet is reported as undetermined, and undetermined is not a pass. Running the check against one provisioned circuit proves nothing about the pair.
One loadable scenario for the missing monitor​
A single rather than a pair, because there is no second file worth reading beside it. The other scenarios on this page load something the network then refuses. This one loads a record an operator would sign off on, and the fault is that something else was never written. Deleting the file is the whole of the fix, so there is no "after" half to compare against.
uv run invoke demo-monitor-gap
demo/10_amplifier_without_monitor.yml, a stage nobody can measure. One
OtnAmplifier, amp-ham-ber-11, written in the same fields as the 306 the
dataset ships: every mandatory attribute set, every bound respected, no site
because an amplifier hut is not a PoP. The load succeeds, and the success is
the scenario. What is missing is an OtnAmplifierMonitor among its ports, and no
constraint in schemas/ can refuse that. A schema constrains what is written, so
it cannot notice a gap. ports peers the generic OtnGenericPort as well, and
Infrahub cannot filter a relationship to a generic by the kind of its peers, so
the schema has no way to say "at least one of these ports is a monitor" even in
principle.
The check fails, with exactly one finding:
amp-ham-ber-11 is an OtnAmplifier and carries no OtnAmplifierMonitor, so nothing
can compare its configured gain against what it is delivering. The drift report
skips a stage with no monitor rather than reporting it
The message names what stops working and not only what is absent. A reader who meets "no monitor" has to work out whether that matters; a reader who meets this one already knows that the gain drift report is now quietly one stage short.
The drift report is a task of its own, and this branch is the interesting place to run it:
uv run invoke demo-drift --branch monitor-gap
It compares the gain each amplifier and Raman pump is configured for against the
gain its monitor last reported, and lists the stages outside tolerance. The
dataset seeds a droop, so the report always has something to say. What it cannot
say anything about is amp-ham-ber-11: a stage with no monitor is a row the
report never prints, which is why the check above has to be the thing that
catches it.
The same run says what it counted:
Monitor coverage: 306/307 amplifiers, 9/9 Raman pumps, 59/59 transponders,
16/16 multiplexers, 42/42 ROADM degrees. Routers, patch panels and ODU switches
carry no monitor and are not judged here
Per kind rather than as one total, because the totals differ by two orders of
magnitude and 306 covered amplifiers would hide nine uncovered Raman pumps inside
a single percentage. The last sentence is the check naming the boundary of what
it judged, so a reader is not left inferring it from silence. Run the same check
against the default branch and the first figure reads 306/306 and the opening
word changes to Monitor coverage complete.
59/59 transponders counts monitors, not light. Sixteen of those fifty-nine
monitors sit on a transponder with no wavelength on either line port, and each
reports loss of signal: received power at the floor of its range, and no OSNR,
Q factor, dispersion or differential group delay. Before this dataset moved,
every receiver reported the same healthy 25.1 dB whether or not anything was
lit. This check has nothing to say about that either way. It asks whether a
device carries a monitor, and a monitor honestly reporting darkness is covered.
Concepts says why eight PoPs terminate nothing.
The new amplifier is in no section chain, and that is deliberate.
oms-ham-ber has four spans and five amplifiers in each direction, because the
budget engine pairs amplifier k with span k and puts a pre-amplifier after the
last span. A sixth amplifier on a four-span section raises before a decibel is
computed, so osnr_margin would turn red on a section this scenario has nothing
to say about, and the branch would carry two failures with only one of them the
point. The check does not need the relationship: it asks whether a device carries
a monitor, and that question never goes through a section.
Negative result: no other check moves on this branch.
channel_count_consistency still passes, because an amplifier lights no
wavelength and no channel count changes. container_capacity, diversity and
provisionable see no service and no container. A check that fired on an
unrelated change would be a check nobody reads twice.
One loadable scenario for the multiplexer channel binding​
The second single, and the closest relative of the one above: both load records that every schema constraint accepts and that the model is nevertheless wrong about.
uv run invoke demo-mux-binding
demo/11_mux_channel_binding.yml, two client ports on the wrong number of
channels. A multiplexer client port is one channel of the device, and it says
which through one of two optional relationships: dwdm_channel to the dense
50 GHz grid, or cwdm_channel to the coarse plan. CH094 on mux-fra-01 binds
neither. CH1531-2 on mux-ams-02 binds both, naming dense channel 94 and the
coarse 1531 nm the device already lights.
The load succeeds for both, and the reason is a limit worth naming. The two plans are separate kinds, and Infrahub has no cross-relationship constraint: the schema can make one relationship mandatory or leave both optional, and it cannot say "exactly one of these two". Making either mandatory would leave the other plan's ports with no way to be written at all. So the schema takes what it can, which is the peer kind and the cardinality of each edge, and the check takes the rest.
The check fails, with exactly one finding per port:
mux-fra-01 carries client port CH094, which binds neither a dense channel nor a
coarse wavelength. The port is one channel of the multiplexer and nothing says
which, so no query can tell what light it passes and no service can be traced
through it
mux-ams-02 carries client port CH1531-2, which binds dense channel 94 and coarse
wavelength 1531 nm at the same time. One filter slot passes one wavelength, so
one of the two is wrong and the port's own record does not say which
Two findings and not three. The double binding names a coarse wavelength
mux-ams-02 does list, so the check has nothing to add about an unlisted one,
and each port draws exactly one complaint.
The same run says what it judged and what it did not:
16 multiplexer(s) examined, 96 client port(s) judged for binding exactly one
channel and 16 line port(s) skipped, because a line port carries every channel
the device lights and binds none by design. 14 of the devices list no coarse
wavelength, so their client ports are judged for the binding alone: the graph
holds no dense equivalent of cwdm_channels to compare a dense binding against
Negative result: half the question has no data behind it. A coarse
multiplexer lists its wavelengths on cwdm_channels, so its ports and that list
are two records that can be held against each other, and the check does. A dense
one lists nothing, so a client port on the wrong dense channel is a fault this
check cannot see. Fourteen of the sixteen units are in that position and the
summary says so, because a count of what was skipped is the only thing that
keeps silence from reading as a pass.
Negative result: no other check moves on this branch. A multiplexer port
lights no wavelength on its own, carries no service and changes no channel
count, so channel_count_consistency, container_capacity, diversity,
provisionable and carrier_termination all read the branch exactly as they
read the default one.
One loadable scenario for the attenuator range​
The third single, and the smallest question on this page: two numbers on one device, and the comparison between them that no schema constraint can make.
uv run invoke demo-attenuator-range
demo/12_attenuator_range.yml, a VOA asked for more than it has.
voa-mil-01 is a 20.0 dB variable attenuator on the add stage at Milan, and the
file restates it at 24.0 dB. Every other field is the figure the dataset already
ships, so the setting is the only difference between this record and the racked
device.
The load succeeds, and the split is the point. attenuation_mdb carries
min_value: 0 and max_value: 30000, so the schema refuses a physically
impossible figure at write time, from an object file, the API, the UI or a hand
edit during a demo. 24,000 is inside that. What the schema cannot say is "not
more than this device's own maximum", because that bound is a sibling
attribute's value and Infrahub has no cross-attribute constraint. The schema
takes the half it can and the check takes the one comparison left over.
The check fails, with exactly one finding:
voa-mil-01 is set to 24.000 dB and can produce 20.000 dB. The device will sit at
its stop instead, so the plant delivers 4.000 dB more power than this record
says it does and every budget computed from it is optimistic by that much
The same run says what it judged:
2 variable attenuator(s) judged against their own maximum, 0 of them dialled to
exactly that maximum and passing, because the bound is inclusive. Fixed pads are
not judged here and carry no range to judge: the absolute 0 to 30 dB limit is a
schema constraint on both kinds and is refused at write time
The bound is inclusive. A VOA sitting exactly at its maximum is at the end of its travel and is still a setting the device can hold, so equality passes. The demo dials past the stop rather than to it, because a scenario that turned on an off-by-one would be about the check's arithmetic rather than about the network.
Negative result: the fixed pads cannot be judged at all. The two pads at the
ends of the CWDM tail have no max_attenuation_mdb, and that is the whole
reason the attenuator is two kinds rather than one: a pad with a range is not a
value to validate, it is a field that should not exist. The check reports that it
did not judge them rather than counting them as passes, because a green result
over something nobody looked at reads as evidence.
Negative result: no other check moves on this branch. An attenuator lights
no wavelength, carries no service, holds no channel and has no ports, so
osnr_margin, channel_count_consistency, container_capacity, diversity,
provisionable, carrier_termination and mux_channel_binding all read the
branch exactly as they read the default one. The one figure it would move is a
link budget, and no optical path in this dataset runs through an attenuator yet.
One loadable scenario for the transceiver placement​
The fourth single, and the first of two about the pluggable optics the three router wavelengths run on.
uv run invoke demo-transceiver-placement
demo/13_transceiver_placement.yml, a module in a port with no cage.
ZRP-BRU-01 is the OpenZR+ QSFP-DD in the Brussels router's line port. The file
restates it into amp-ams-bru-08 OUT, the booster port at the Brussels end of
the same section. That is the port an operator points at when asked where the
wavelength leaves the site, and it is also a fixed optical interface on an EDFA
with nothing to plug a module into.
The load succeeds, and the reason is the same limit the multiplexer scenario
runs into from another direction. OtnTransceiver.port peers the
OtnOpticalPort generic, and a relationship to a generic cannot be filtered by
the kind of its peers. So the schema can offer the field or withhold it, and it
cannot say "any of these three kinds and none of the other five". The port kind
is the one thing this check is left holding.
The check fails, with exactly one finding:
QDD-400G-ZRP ZRP-BRU-01 is fitted in amp-ams-bru-08 OUT, a port of kind
OtnAmplifierPort. A pluggable optic goes in a line, a client or a router port,
and no other port kind has a cage to take one, so either this unit is somewhere
else or the port on its record is wrong
The same run says what it judged:
9 transceiver(s) examined, 6 of them fitted in a port and judged, 3 on a shelf
and not judged. An unfitted unit is a spare, an RMA or a decommissioned module,
and holding one is why the port relationship is optional. Two modules in one
port is not judged here: the schema refuses that write
A port holding two optics has no branch, because the server refuses to build
one. OtnLinePort, OtnClientPort and OtnRouterPort each declare a
cardinality-one transceiver on the same otn_optical_port__transceiver
identifier OtnTransceiver.port uses, so both ends of the edge are cardinality
one and the second module is rejected at write time:
Node 18d19871-0d7f-6758-306b-1188b66bb9eb has 2 peers for
otn_optical_port__transceiver, maximum of 1 allowed
That rule used to live in this check. uniqueness_constraints: [["port"]] was
tried first and Infrahub accepts it only where the relationship is mandatory,
and a mandatory port leaves a spare, an RMA and a decommissioned unit with
nowhere to sit, which is three of the nine units here. The reverse edge buys the
same refusal at no such cost, and it is declared on the three concrete kinds
with a cage rather than on the OtnOpticalPort generic, which would have handed
the field to the five kinds that hold no module.
A second check speaks on this branch and blocks nothing. Pulling the module
out of rtr-bru-01 1/2/1 leaves oc-ch003-ams-bru with a pluggable at one end
and integrated optics at the other, and transceiver_mode_support reports that
as INFO:
oc-ch003-ams-bru is terminated by a pluggable at rtr-ams-01 1/2/1 and by
integrated optics at rtr-bru-01 1/2/1. The fitted end is judged against its own
part and the other end carries no part number to judge, which is a mixed
termination rather than a fault
Negative result: nothing here says the cage fits. A part carries a
form_factor and a port does not, so "a QSFP-DD module in a QSFP-DD cage" is a
comparison this model cannot make. The check refuses the port kind and says
nothing about the cage, which is the honest half.
One loadable scenario for the transceiver mode support​
The fifth single, and the one where two records that match on every visible field are still the wrong pair.
uv run invoke demo-transceiver-mode
demo/14_transceiver_mode_support.yml, a 400ZR on an OpenZR+ wavelength.
The OpenZR+ module comes out of the Amsterdam router's line port and the 400ZR
spare off the shelf goes in. Both parts are QSFP-DD. Both are DP-16QAM. Both are
400G. Both are tunable. What differs is forward error correction, cFEC against
oFEC, and therefore reach: 120 km against 1000 km, on a 220 km section.
The load succeeds, and no constraint could have refused it. supported_modes
is a cardinality-many relationship on the part and the mode is a cardinality-one
relationship on the carrier, two hops apart through a port. No attribute
constraint reaches across two nodes and Infrahub has no cross-relationship
constraint.
The check fails, with exactly one finding:
oc-ch003-ams-bru runs OpenZR+ 400G and rtr-ams-01 1/2/1 holds QDD-400G-ZR
ZR-SPARE-01, which supports 400ZR. The part does not list OpenZR+ 400G, so the
wavelength is planned on an optic that cannot produce it. Cage, constellation
and line rate say nothing here: the part number and the mode are the whole of
the difference
The same run says what it judged:
43 wavelength(s) examined, 3 judged against the parts fitted at their line ports
and 40 skipped for holding no pluggable at either end. A skipped wavelength is
terminated on integrated optics, which carry no part number and no mode list, so
this check's silence about them is not a verdict
Negative result: forty of the forty-three cannot be judged. A transponder lights its wavelength from a laser on the line card, so there is no part number and no mode list to compare anything against. Only the three router wavelengths carry a pluggable, and a run reporting no findings over forty-three skips would look exactly like a run that judged them all and found nothing. The split is printed for that reason.
Negative result: this check says nothing about whether the mode closes.
Whether OpenZR+ 400G reaches over the plant is osnr_margin's question, and
it does: +10.890 dB on oms-ams-bru. The 400ZR fitted here would fail on
chromatic dispersion rather than on power, over its 2400 ps/nm tolerance by
1340 ps/nm with 8 dB of OSNR still in hand. That is the argument for oFEC and it
belongs on the link budget page, not in this check.
One loadable scenario for the connector polish​
The sixth single, and the one where nothing about the object changed. What changed is the glass it faces.
uv run invoke demo-connector-polish
demo/15_connector_polish.yml, a blue jumper on pumped fibre.
span-vie-mil-01 is the first span of the one section this network pumps, and
its Vienna end is the only end of that section anyone can reach: spans 2 to 9
terminate in huts. This file re-patches that end. roadm-vie-01 DEG-MIL comes
off and xpdr-vie-02 L2 goes on, a transponder patched straight onto the line
past the ROADM. L2 is the one dark line port at Vienna, so nothing carrying
traffic moved.
Its endface is UPC, as every line port in the dataset is. That is the right endface where a coloured line port normally sits, behind an add/drop stage inside a building, where return loss is a specification. On the far side of the ROADM it is a hazard: the nine pumps on this section put half a watt into the fibre and a flat endface sends about 50 dB of it back down the span.
The load succeeds, and no constraint could have refused it. polish is an
attribute on the port and raman_pumps is a relationship on the span, two hops
apart, and Infrahub has no cross-relationship constraint.
The check fails, with exactly one finding:
xpdr-vie-02 L2 terminates span-vie-mil-01, which is Raman-pumped, and its
endface is UPC. A pump puts half a watt into this fibre and a UPC endface
reflects that back down it, so this connector has to be APC before the pump is
turned up
The same run says what it judged:
133 span(s) examined, 9 of them Raman-pumped, 9 judged against their terminating
ports and 0 unjudgeable for naming none. An unjudgeable span is not a passing
one, and a run reporting no findings over a pumped section it could not reach
has seen nothing
That last zero is the figure to read. A span reaches its ports through
OtnFiberSpan.terminating_ports, and a pumped span naming none is reported as
unjudgeable rather than clean, because an empty relationship and two correct APC
ends look identical from inside the check. The dataset generator populates all
133 spans so the count reads zero, and a run where it does not is a run whose
green result means nothing.
Negative result: the relationship had to be added before the check could be
written. No port kind had any edge to a fibre span or to an optical multiplex
section in either direction. The nearest reachable ports were three hops away
through the section and its ROADMs, and a ROADM carries one degree port per
direction whose connected_to is null on every one of them, so the only thing
naming the port facing a given span was the far site's shortname inside the
port's name string. Parsing that is a naming convention doing a relationship's
job, and the schema carries the edge instead.
Negative result: polish is judged on pumped glass and nowhere else. The 124 unpumped spans terminate on APC degree ports and the check says nothing about them, and it says nothing about the 126 line ports either, because none of them faces pumped fibre on the default branch. A flat endface is a normal, correct choice almost everywhere in this network.
The runbook​
About thirty minutes, one branch until step 12, in narrative order.
Setup, once. The object load takes a few minutes; start it before the audience arrives.
uv run invoke start
uv run invoke demo-setup
Every step below runs on the demo branch and none of them needs you to say
so. Each one ends by naming the step that follows, and a step run out of order
says which task to run first instead of failing inside the client. To watch the
whole thing without typing between steps:
uv run invoke demo
1. The network, on one page. Two minutes, in the browser, before any
command. Open OTN Sites, pick Frankfurt, open its Artifacts tab, and
open network-map.
Fourteen PoPs in their real positions, 21 routes coloured by how much OSNR
margin a 400G DP-16QAM carrier has on each. Point at the red route into Madrid, then at
the chip on Frankfurt to Milan. Several of the findings the next twelve steps
compute are already on this drawing, which is what makes it worth two minutes.
The network map page has the rest of the legend.
odu-map is on the same Artifacts tab and is mostly grey here. Say what it is,
leave it closed, and come back to it at step 12.
2. What is in the network, and what is left. Two minutes.
uv run invoke demo-capacity
Cover the 4,134,400 of 4,800,000 MHz first, then the eight-against-one gap between free spectrum and usable anchors. Then the sixteen empty sections, and the difference between a section's free spectrum and a route's.
3. Where the cheap parts reach. Two minutes. Run it before anybody asks.
uv run invoke demo-reach
Zero of twenty-one. Pause here: this is the step where the model returns an answer nobody wanted, computed from data rather than assumed.
4. Ask for a service and watch it choose. Four minutes.
uv run invoke demo-provision
Six candidate routes, Hamburg wins on fewest sections then margin. Read the discarded ones out loud: each one names the route, the mode, the margin and the reason it lost.
5. Provision the rest. One minute, and it is setup for what follows.
uv run invoke demo-provision-all
Two groom and two light, in that order, every time. svc-fra-mil-ai-400g and
svc-ams-mil-ai-400g take tributary slots in wavelengths that are already up,
oc-ch047-fra-mil and oc-ch002-ams-mil. Then svc-fra-gva-hpc-400g and
svc-vie-mil-hpc-400g find no line container with room and each light channel 2,
on oms-fra-gva and oms-vie-mil. Channel 2 and not channel 1, because a
79,600 MHz carrier centred on channel 1 falls off the bottom of the band. Say the
grooming out loud: it is the ODU layer choosing not to buy spectrum, and it is why
the cut in step 7 drops seven wavelengths and not eight.
6. Trace one of them. Two minutes.
uv run invoke demo-trace
Thirty-five hops, router to router. Point at the conduit column; it sets up step 8.
7. Cut the fiber. Three minutes. Leave room for questions on this one.
uv run invoke demo-impact
Seven wavelengths, 2.8 Tbps, one AI service, one customer, and six
wavelengths the model holds with no customer behind them. Then the duct
breakdown, and the fact that cd-fra-north also carries Paris to Frankfurt.
Then mistype it, deliberately:
uv run invoke demo-impact --section oms-ams-frs
It refuses. An impact tool that answers "no impact" to a typo is worse than one that answers nothing.
8. Who is not diverse. Two minutes.
uv run invoke demo-srlg
Frankfurt to Geneva and Amsterdam to Milan, no shared section, one shared duct.
9. The latency budgets. Two minutes.
uv run invoke demo-latency
Then the electronics column, and the sentence about there being no trade-off.
10. The one service that states its handover. Two minutes.
uv run invoke demo-infiniband
Frankfurt to Prague at 212 Gbps with client_signal: IB-HDR-4X, and the
container comes back ODUflex. Drop that one line from the file and the same
request provisions 400GBASE-FR4, with no error. The rate rule picks only
from catalog rows marked auto_selectable, which the two InfiniBand rows are
not.
212 Gbps and not 200: HDR signals at 212.5, which is 170 tributary slots, and a
200G wavelength offers 160. The request lands on DP-16QAM 64GBd 400G, whose
ODUC4 offers 320, so the ODUflex takes 170 and leaves 150 free.
11. Congestion in two layers. Three minutes. Fill both layers of the Frankfurt to Milan corridor, then ask twice.
uv run invoke demo-refusal
The 400G is refused on slots: it needs a whole ODUC4 and the roomiest wavelength there has 240 of 320 free. The 100G then provisions on the same corridor without lighting anything. 532,400 MHz free in 29 blocks none of which reaches 44,400 says no new wavelength can be lit; it says nothing about room inside the ones already lit.
12. The other map, and one question off it. Three minutes, in the browser. This one wants its own branch, which the task makes:
uv run invoke demo-odu
Open odu-map on any PoP on odu-demo. Same coastline, same fourteen discs,
different question. Ask the room for two lists off the colours alone: which
sections can still take a 100G circuit, and which can take nothing. Green is the
first, red is the second, and the panel's FITS column spells both out. The task
finishes by checking the answer against the containers instead of against the
map.
The two cannot disagree. The map, the check and the provisioning generator all call one implementation of the capacity rule. Finish on the grey: the sections carrying no wavelength at all, which the map says "not known" about rather than "available".
13. The branch. One minute. Open the proposed change and show the diff and the checks. Everything in the last thirty minutes happened on a branch, the default branch is untouched, and the checks decide whether any of it merges.
Clean up​
uv run invoke demo-clean
With no argument it deletes every branch a scenario task creates, naming each one
as it goes and reporting how many of them it found. Nothing is removed without
saying so, and --branch still takes a single name.
The default branch is back to the state it shipped in: the fourteen PoPs and their plant, the customer campus on the end of its coarse tail, no services, and the OSNR check still failing on Paris to Madrid. Every service in this guide was provisioned on a branch, which is the point.