Skip to content

feat(dwdm): optical channels, port function, and object templates - #77

Open
iddocohen wants to merge 4 commits into
mainfrom
ic/add-optical-u5o17
Open

feat(dwdm): optical channels, port function, and object templates#77
iddocohen wants to merge 4 commits into
mainfrom
ic/add-optical-u5o17

Conversation

@iddocohen

@iddocohen iddocohen commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What

Adds end-to-end optical channel modelling to extensions/dwdm, and rescales the physical
quantities in experimental/optical_transport to units that are exact as integers.

Why the unit rescale

Infrahub's Number attribute is exposed as GraphQL BigInt. A fractional value is not
rounded, it is rejected:

Expected value of type 'BigInt', found 193.1.

193.1 THz could not be stored at all. Every quantity is now scaled to an exact integer and
carries its unit in the name: THz to GHz, nm to pm, dBm to tenths, km to m, dB to tenths.
frequency_ghz, wavelength_pm, tx_power_ddbm, total_distance_m, loss_ddb.

extensions/dwdm

  • New DcimOpticalChannel — one wavelength booked between two endpoints, carrying its
    A-to-Z and Z-to-A grid points, its line ends, and the cables it crosses. Only new node in
    this PR.
  • Separate TX and RX channels on WDM interfaces, so single-fibre WDM is data rather than
    a naming convention.
  • ITU grid linkage on DcimWdmChannel via grid, band, frequency_ghz, wavelength_pm.
    The old wdm_type, wavelength and frequency are deprecated, not removed.
  • optical_function on DcimGenericOadmInterface: line, client, express,
    expansion, band, monitor, osc, amplifier, otdr. Front-vs-rear says
    client-vs-line but cannot express pass-through, upgrade stages, taps, supervisory or OTDR.
    Vocabulary is functional and vendor-agnostic. Note expansion (upgrade port, labelled EXP
    by some vendors) and express (pass-through) are different functions.
  • generate_template: true on DcimOpticalMultiplexer. A mux model is a fixed shape, so
    cloning a curated example beats rebuilding every port by hand.
  • Namespaced identifiersfront_interfaces / rear_interfaces become
    optical_multiplexer__*. The bare strings are also used by extensions/patch_panel.
  • Adds the extensions/cable dependency.

experimental/optical_transport

No nodes added or removed.

  • Integer-scaled units, as above.
  • FiberLink.endpoints split into a_end / z_end; OpticalNode.links into
    links_a / links_z; ChannelMapping.segment into path_segments.
  • Missing inverse relationships added: mux_assignments, services, transponder_modules,
    optical_services, roadm_degrees, amplifier_modules.
  • Comments recording why the OpticalPath roll-ups cannot be computed attributes: a computed
    attribute must be Text or URL, and Jinja2 cannot see a cardinality-many relationship.

Relationship shapes

OpticalService.primary_path and .backup_path move from kind: Component /
cardinality: one to kind: Attribute + on_delete: cascade. Component pairs with
cardinality: many by convention, so the combination read as a collection when it is not one.
The explicit on_delete: cascade preserves the behaviour Infrahub derives from Component
automatically.

OpticalMultiplexer.rear_interface stays Component. Only Component children are
carried into an object template. As Attribute, measured on a live instance: no
TemplateDcimOadmRearInterface was generated, and every mux cloned from a template came out
with rear_interface: NULL, silently missing its COM port. All three properties hold either
way except that last one:

Attribute + cascade Component / one
add a 2nd peer rejected, maximum of 1 allowed rejected, same message
delete the parent cascade-deletes the child cascade-deletes the child
carried into an object template no yes

This leaves dwdm and optical_transport using different shapes for the same pattern.
Deliberate: only dwdm has templates. If reviewers prefer the convention, the alternative is
dropping generate_template from this node.

primary_path / backup_path were deliberately not collapsed into one paths collection.
Two singular relationships enforce "exactly one primary, at most one backup" in the schema
rather than in a check.

Docs

tasks/docs.py gains two optional .metadata.yml keys, use_cases and not_covered,
rendered as a ## Use cases section. Extensions omitting both render nothing, so the other 40
pages are unaffected by the template itself. DWDM is the first user.

Six unrelated reference pages are regenerated here (sfp, security, lag, mlag,
dcim, circuit_contract, plus a home.mdx row). They are not edits. They are pre-existing
drift on main: for example sfp.mdx still named DcimInterface where the schema has said
InterfacePhysical since #74. The generator rewrites all 52 pages in one pass, and this PR has
to run it, so the backlog comes along. Reverting them by hand does not hold: I confirmed that
any invoke docs.generate reproduces them exactly.

Root cause: ci.yml:201 runs git diff --exit-code -- docs/reference/ from the repo root,
but the pages live at docs/docs/reference/. The path matches nothing, so the gate has passed
unconditionally since #65. Left unfixed here — correcting the path would start failing CI
on anything still stale elsewhere, which is a bigger cleanup than this PR should carry.

Testing

Loaded onto a live Infrahub 1.10.6 instance. .metadata.yml records that optical_transport
is not designed to be loaded with dwdm, so each went onto its own branch off a base-only
main. All loads clean.

  • Schema shapes confirmed via /api/schema: every relationship resolved to a single
    bidirectional edge per identifier, with kind, cardinality, optional, on_delete as written.
  • Full wavelength stack built and read back through a three-level traversal. Exact:
    frequency_ghz 192100, wavelength_pm 1560606, band.start_frequency_ghz 191300,
    grid.reference_frequency_ghz 193100. channel_name renders CH21.
  • Complete DcimOpticalService (2 transponders, channel, primary and backup path) traversed
    both ways. A second service claiming the same primary path is rejected. Deleting the service
    cascade-deleted both paths.
  • optical_function applied as a real migration over pre-existing data: schema check dry run
    reports one added attribute, changed: {} / removed: {}; existing interfaces survive with
    null; all nine choices accepted, out-of-vocabulary rejected.
  • Object templates: all three Template* kinds generated. Two muxes cloned from one template
    each got 3 front ports and the rear port, with optical_function and grid links intact.
    8 distinct port objects across 2 clones, so real copies rather than shared references.

invoke linter.yaml clean repo-wide. Both schema files validate against
schema.infrahub.app/infrahub/schema/latest.json with 0 errors.

CI does not cover any of the above: tasks/schemas.py:92 skips experimental/ unless
TEST_EXPERIMENTAL is set, and no workflow sets it.

Known issues, left alone

  • ci.yml:201 docs-freshness path bug, above.
  • experimental/optical_transport and extensions/topology both render to
    reference/topology.mdx, so one page silently overwrites the other. Not triggered by this PR.
  • .metadata.yml is not in the canonical form invoke linter.sort-metadata produces; running
    it rewrites 87 lines. Nothing in CI checks it, and the reflow would bury this diff.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 13, 2026

Copy link
Copy Markdown

Deploying schema-library with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4c38bc4
Status: ✅  Deploy successful!
Preview URL: https://b31bc70e.schema-library.pages.dev
Branch Preview URL: https://ic-add-optical-u5o17.schema-library.pages.dev

View logs

@iddocohen
iddocohen requested a review from BeArchiTek August 13, 2026 14:30
@iddocohen
iddocohen marked this pull request as ready for review August 13, 2026 14:31
Model a wavelength booked end to end over a span, and stop optical
measurements from being unstorable: the Number attribute kind is exposed
as GraphQL BigInt, so a fractional value is rejected outright rather than
rounded. Verified on Infrahub 1.10.6 -- 193.1 fails with "Expected value
of type 'BigInt', found 193.1".

extensions/dwdm:

- Add DcimOpticalChannel: one wavelength between two endpoints, with its
  A-to-Z and Z-to-A channels, its line ends and the cables it crosses.
- Give WDM interfaces separate transmit and receive channels, so
  single-fibre WDM is expressed as data rather than as a naming
  convention.
- Tie channels to the ITU grid via grid, band, frequency_ghz and
  wavelength_pm.
- Namespace the front_interfaces and rear_interfaces identifiers to the
  OpticalMultiplexer node pair. The bare strings are also used by
  extensions/patch_panel, and a shared identifier is a trap for the next
  person to reuse.
- Add the extensions/cable dependency.

experimental/optical_transport:

- Rescale every physical quantity to a unit that is exact as an integer:
  THz to GHz, nm to pm, dBm to tenths of a dBm, km to m, dB to tenths of
  a dB.
- Split links into links_a and links_z, and add the missing inverse
  relationships (mux_assignments, services, path_segments, roadm_degrees,
  amplifier_modules, optical_services).
- Record why the OpticalPath roll-ups cannot be computed attributes.

Relationship shapes, in both files:

- Replace kind: Component on the three cardinality: one relationships
  (OpticalMultiplexer.rear_interface, OpticalService.primary_path and
  OpticalService.backup_path) with kind: Attribute plus on_delete:
  cascade. Component is documented as pairing with cardinality: many, so
  the combination misread as a collection.

  This is a convention fix, not a behaviour change. Both shapes were
  loaded onto separate branches of a live instance and behave
  identically: each rejects a second peer with the same message ("has 2
  peers for optical_multiplexer__rear_interfaces, maximum of 1 allowed"),
  and each cascade-deletes the child with its parent. The explicit
  on_delete: cascade is what keeps that second property, since Infrahub
  derives cascade for Component automatically and dropping Component
  without it would have silently lost the cascade.

tasks/docs.py:

- Keep the home page's trailing newline, which Jinja2 strips and
  markdownlint MD047 then flags.

Also ignores .claude/settings.local.json.
@iddocohen
iddocohen force-pushed the ic/add-optical-u5o17 branch from e4b4eab to 2272432 Compare August 13, 2026 15:05
@iddocohen
iddocohen marked this pull request as draft August 13, 2026 15:06
@iddocohen
iddocohen marked this pull request as ready for review August 13, 2026 15:19
Add an optional optical_function dropdown on DcimGenericOadmInterface, so
both front and rear interfaces can record what a port actually does in the
optical path. Front-vs-rear only expresses client-vs-line; it cannot label
express, expansion, monitor, OSC, amplifier or OTDR ports, which forced
vendor port names to be parsed to tell them apart. The vocabulary is
functional and vendor-agnostic, so it holds for passive filters and active
line gear alike.

Enable generate_template on DcimOpticalMultiplexer. A mux model is a fixed
shape, so cloning a curated example beats rebuilding every port by hand.

Switch rear_interface from Attribute + on_delete: cascade to Component.
Only Component children are carried into an object template: as Attribute
no TemplateDcimOadmRearInterface was generated and every mux cloned from a
template came out with no line port. Component on a singular child holds
up, a second peer is rejected with "maximum of 1 allowed" and deleting the
mux still takes the rear interface with it.
Reference pages are fully generated, and the only writable prose slot was
the metadata description, which also feeds the home page card and so is a
poor place for a longer list. Schema YAML comments do not reach the docs
either, since the generator dumps parsed YAML.

Add two optional metadata keys, use_cases and not_covered, rendered as a
"Use cases" section. Extensions that omit both render nothing, so this is
additive and leaves the other pages untouched. DWDM is the first user and
points readers at experimental/optical_transport for the active line gear
it deliberately does not model.
These six pages drifted from their schema files: earlier changes edited the
YAML without running invoke docs.generate, so the rendered tables still
describe the previous shape. Nothing here is hand-written, it is all
generator output catching up (for example sfp now shows InterfacePhysical
where the schema had already moved off DcimInterface).

CI does not catch this. The check-documentation job runs
"git diff --exit-code -- docs/reference/" from the repo root, but the pages
live at docs/docs/reference/, so the path matches nothing and the check
passes unconditionally. The wrong path is left alone here, since fixing it
would make the job start failing on anything still stale elsewhere.
@iddocohen iddocohen changed the title feat(dwdm): add optical channels and rescale optical units to integers feat(dwdm): optical channels, port function, and object templates Aug 14, 2026
@BeArchiTek

Copy link
Copy Markdown
Contributor

I will agree to it for the moment, there is a bigger work/clean-up to be done to unify the library as we have it as experimental and as extension with slight difference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants