Skip to content

Add example UDL federation plugin (ephemeris + conjunction assessments) - #14

Open
jakexcosme wants to merge 5 commits into
masterfrom
devin/1785344499-udl-federation-plugin
Open

Add example UDL federation plugin (ephemeris + conjunction assessments)#14
jakexcosme wants to merge 5 commits into
masterfrom
devin/1785344499-udl-federation-plugin

Conversation

@jakexcosme

@jakexcosme jakexcosme commented Jul 29, 2026

Copy link
Copy Markdown

Describe your changes:

Adds an example plugin simulating a Unified Data Library (UDL) federation node: a udl:node root exposing three simulated satellites (GPS III SV05, WGS-11, SBIRS GEO-6) with live ephemeris telemetry (altitude/lat/lon/velocity from simplified two-body circular-orbit math) and a Conjunction Assessments feed (Pc, miss distance, secondary object) with probability-of-collision screening limits (warning ≥ 1e-5, critical ≥ 1e-4).

Structure (mirrors example/generator and example/eventGenerator conventions):

  • example/udlFederation/plugin.js — registers udl.ephemeris / udl.conjunctions types, root object, object + composition providers, telemetry + limit providers
  • UDLTelemetryProvider.js — deterministic historical request() (honors options.size and strategy: 'latest', spreads capped results across the full window, hard-capped at 50k datums) and realtime subscribe() for both feed types
  • ConjunctionLimitProvider.js — Pc limit evaluator/levels
  • Registered as openmct.plugins.example.UDLFederation

The plugin is not installed in index.html by default (its root object changes the tree baseline that the e2e suite assumes). To enable it in the dev sandbox, add:

openmct.install(openmct.plugins.example.UDLFederation());

Smoke tested against npm start with the plugin enabled: tree renders the federation node, ephemeris plots stream, conjunction Pc plot renders with limit thresholds, telemetry table rows highlight red/yellow on Pc threshold crossings. Full E2E evidence (recording + screenshots) in the PR comments.

tree and ephemeris plot
conjunction assessments

Original prompt

Add a Unified Data Library (UDL) data-federation plugin to Open MCT: expose a
"UDL Federation Node" root containing live ephemeris telemetry streams for a
small satellite constellation (GPS III SV05, WGS-11, SBIRS GEO-6) and a
Conjunction Assessments feed with probability-of-collision screening limits
(warning at Pc 1e-5, critical at Pc 1e-4), following the existing
example/generator plugin conventions. Verify the plots render in the browser.

All Submissions:

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?
  • Is this a notable change that will require a special callout in the release notes? For example, will this break compatibility with existing APIs or projects that consume these plugins?

Author Checklist

  • Changes address original issue?
  • Tests included and/or updated with changes?
  • Has this been smoke tested?
  • Have you associated this PR with a type: label? Note: this is not necessarily the same as the original issue.
  • Have you associated a milestone with this PR? Note: leave blank if unsure.
  • Testing instructions included in associated issue OR is this a dependency/testcase change?

Reviewer Checklist

  • Changes appear to address issue?
  • Reviewer has tested changes by following the provided instructions?
  • Changes appear not to be breaking changes?
  • Appropriate automated tests included?
  • Code style and in-line documentation are appropriate?

Link to Devin session: https://app.devin.ai/sessions/d40ff69ddaa84563a7003d6330be7a2f
Requested by: @jakexcosme


Devin Review

Status Commit
⚪ Not started

Run Devin Review

💡 Connect your GitHub account to enable automatic code reviews.

Open in Devin Review (Staging)
Open in Devin Review

@jakexcosme jakexcosme self-assigned this Jul 29, 2026
@devin-ai-integration

Copy link
Copy Markdown
Original prompt from Jake

Devin, research the EDISON project for Space force, see meeting notes. Give me background and propose a suitable repo I could demo and some good ideas for a session I can run "Given the Space Force's focus on AI-native software development and rapid capability delivery, I think Cognition could be a strong teammate for the EDISON IDIQ, Advanced Tactical Fabric task orders, and related software modernization efforts.
"

@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

Open in Devin Review

Comment on lines +86 to +97
request(domainObject, options) {
const period =
domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS;
const start = Math.floor(options.start / period) * period;
const data = [];

for (let timestamp = start; timestamp <= options.end; timestamp += period) {
data.push(this.#datumFor(domainObject, timestamp));
}

return Promise.resolve(data);
}

@devin-ai-integration devin-ai-integration Bot Jul 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Ephemeris view could generate up to 50000 full-resolution datums per request

When no size is provided, request() defaults to MAX_REQUEST_DATUMS = 50000 and generates that many datums, each running trig-heavy orbital math (ephemerisDatum). Plots pass size: 1000 so this ceiling is normally not hit there, but other consumers (e.g. tables/exports without a size) could trigger large synchronous loops. Acceptable for an example plugin, but flagging the potential main-thread cost since the generator plugin offloads this to a web worker.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed — request() now honors options.size and applies a 50k-datum hard cap, keeping the most recent window when the requested range exceeds the bound, so very large custom time ranges can no longer allocate unbounded datum arrays.

Comment thread example/udlFederation/UDLObjectProvider.js
Comment on lines +99 to +109
subscribe(domainObject, callback) {
const period =
domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS;
const interval = setInterval(() => {
callback(this.#datumFor(domainObject, Date.now()));
}, period);

return function unsubscribe() {
clearInterval(interval);
};
}

@devin-ai-integration devin-ai-integration Bot Jul 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Realtime datum timestamps are unaligned versus aligned historical timestamps

request() aligns timestamps to period boundaries (Math.floor(options.start / period) * period, example/udlFederation/UDLTelemetryProvider.js:91), while subscribe() emits at Date.now() (:115), which is not period-aligned. At the historical→realtime handoff in a plot this produces a small timestamp discontinuity. Cosmetic only; values are deterministic functions of the timestamp so no data corruption occurs.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — as noted, conjunction datum content is slot-keyed and ephemeris is a continuous function of timestamp, so the unaligned live-edge timestamps stay consistent with historical values. Leaving as-is.

Comment thread example/udlFederation/UDLObjectProvider.js
… request size

Co-Authored-By: Jake Cosme <jake@cognition.ai>
@devin-ai-integration devin-ai-integration Bot added type:maintenance Maintenance/CI change no milestone PR intentionally has no milestone labels Jul 29, 2026
@devin-ai-integration

Copy link
Copy Markdown

✅ E2E test results — UDL Federation plugin

Tested in-browser against the local dev server (npm start) on this branch. All checks passed.

Test Result
Tree shows UDL Federation Node with 3 satellites + Conjunction Assessments
Satellite ephemeris plot renders and streams live in Real-Time mode
Conjunction table highlights Pc rows red (≥1e-4) / yellow (≥1e-5)
No console errors from the plugin
Regression: Sine Wave Generator create + plot

Test run preview:

E2E test recording preview

Conjunction Assessments telemetry table — limit highlighting (red ≥1e-4, yellow ≥1e-5):

Table limit highlighting

Tree + Grid View of UDL Federation Node

Grid view

Live ephemeris plot (GPS III SV05, altitude ~20180 km, Real-Time streaming)

Realtime plot

Pc plot with spikes to ~7e-4

Pc plot

Console was clean after a fresh reload (only a pre-existing Vue defineExpose warning unrelated to this plugin).

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 new potential issues.

Open in Devin Review

Comment thread test-report.md Outdated
Comment thread example/udlFederation/UDLTelemetryProvider.js Outdated
Comment thread example/udlFederation/satellites.js
Comment on lines +48 to +70
openmct.objects.addRoot({
namespace: 'udl',
key: 'node'
});

openmct.objects.addProvider('udl', new UDLObjectProvider());

openmct.composition.addProvider({
appliesTo: function (domainObject) {
return domainObject.identifier.namespace === 'udl' && domainObject.type === 'folder';
},
load: function () {
return Promise.resolve(
SATELLITES.map((satellite) => ({
namespace: 'udl',
key: satellite.key
})).concat([{ namespace: 'udl', key: 'conjunctions' }])
);
}
});

openmct.telemetry.addProvider(new UDLTelemetryProvider());
openmct.telemetry.addProvider(new ConjunctionLimitProvider());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Plugin registration and install conventions verified against existing examples

Verified plugins.example.UDLFederation = UDLFederationPlugin combined with openmct.install(openmct.plugins.example.UDLFederation()) is correct: UDLFederationPlugin() returns the install(openmct) closure, matching the EventGeneratorPlugin pattern (example/eventGenerator/plugin.js:27-28). The root object (type: 'folder', location: 'ROOT') plus the custom composition provider keyed on namespace udl + type folder correctly yields the three satellites and the conjunctions feed as children; leaf objects have no composition, so the default provider does not apply. No bug here.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

… add norad to cspell

Co-Authored-By: Jake Cosme <jake@cognition.ai>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

Open in Devin Review

Comment thread example/udlFederation/UDLTelemetryProvider.js Outdated
Comment thread example/udlFederation/UDLTelemetryProvider.js Outdated
Comment on lines +67 to +86
getLimits() {
return {
limits: function () {
return Promise.resolve({
WARNING: {
high: {
color: 'yellow',
pc: PC_WARNING
}
},
CRITICAL: {
high: {
color: 'red',
pc: PC_CRITICAL
}
}
});
}
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Limit provider interface and getLimits format match existing conventions

I verified ConjunctionLimitProvider against src/api/telemetry/TelemetryAPI.js:180-231,961-1016 and the existing example/generator/SinewaveLimitProvider.js. supportsLimits/getLimitEvaluator/getLimits signatures are correct, and the getLimits payload uses the range key (pc) inside the high objects consistent with Sinewave's use of sin/cos. No issue here — noted since the limit shape is easy to get wrong.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Co-Authored-By: Jake Cosme <jake@cognition.ai>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment on lines +67 to +86
getLimits() {
return {
limits: function () {
return Promise.resolve({
WARNING: {
high: {
color: 'yellow',
pc: PC_WARNING
}
},
CRITICAL: {
high: {
color: 'red',
pc: PC_CRITICAL
}
}
});
}
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Pc limit lines render a bogus value if a non-pc range is plotted

ConjunctionLimitProvider.getLimits() (example/udlFederation/ConjunctionLimitProvider.js:67-86) returns limit definitions keyed only under pc (e.g. { color, pc }). The plot's MCTChartAlarmLineSet.getLimitPoints() (src/plugins/plot/chart/MCTChartAlarmLineSet.js:71-89) draws a limit line for any level whose high exists, reading the series' y-value via series.getYVal(limitForLevel.high). If a user plots the missDistance (or another) range of the conjunction object, high has no missDistance key, so getYVal yields undefined and a meaningless/degenerate limit line could be drawn. The evaluator (:49-63) already guards on valueMetadata.key !== 'pc', so table highlighting is unaffected; only stray plot limit lines for non-pc ranges are the concern. This mirrors the simplicity of SinewaveLimitProvider, so it is an edge case rather than a clear defect.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +101 to +106
} else {
const step = count > 1 ? (options.end - start) / (count - 1) : period;
for (let i = 0; i < count; i++) {
data.push(this.#datumFor(domainObject, start + Math.round(i * step)));
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Historical request spaces datums evenly rather than at true cadence when capped

In UDLTelemetryProvider.request() the non-latest branch (example/udlFederation/UDLTelemetryProvider.js:101-106) computes step = (options.end - start) / (count - 1) where count = Math.min(requestedCount, size) (capped at MAX_REQUEST_DATUMS = 50000). When requestedCount exceeds the cap/size, datums are spread evenly across the window instead of at the true period cadence (1s ephemeris / 5s conjunction). For plots this is fine (effectively downsampling), but a telemetry table viewing large historical windows would show rows at non-period-aligned timestamps. Not a correctness bug for the example's purpose, just worth noting.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Co-Authored-By: Jake Cosme <jake@cognition.ai>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment on lines +123 to +131
#datumFor(domainObject, timestamp) {
if (domainObject.type === 'udl.conjunctions') {
return conjunctionDatum(timestamp);
}

const satellite = SATELLITES.find((candidate) => candidate.key === domainObject.identifier.key);

return ephemerisDatum(satellite, timestamp);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Ephemeris datum lookup assumes the satellite key always exists

example/udlFederation/UDLTelemetryProvider.js:128-130 looks up the satellite via SATELLITES.find(... === domainObject.identifier.key) and immediately dereferences it in ephemerisDatum (satellite.altitudeKm). If any object of type udl.ephemeris existed whose key is not in SATELLITES, this would throw. In the current design the only udl.ephemeris objects are the three provider-defined satellites (types are not creatable), so this is unreachable today. Worth noting if the type is ever made user-creatable.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread .cspell.json
Comment on lines +492 to +502
"Tabnabbing",
"elset",
"statevector",
"NAVSTAR",
"raan",
"sbirs",
"SBIRS",
"FENGYUN",
"STARLINK",
"norad",
"NORAD"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: cspell relies on 3-letter identifiers being under minWordLength

The PR adds many domain terms to .cspell.json (elset, statevector, NAVSTAR, raan, sbirs, FENGYUN, STARLINK, norad) but not 'udl', 'gps', 'geo', 'wgs', 'deb', which appear widely in the new code. This passes CI only because cspell's default minWordLength (4) skips those 3-char tokens, and multi-char terms like 'ephemeris'/'conjunction'/'iridium'/'cosmos' are standard dictionary words. If the spellcheck config or dictionary set changes, these could start failing. Not flagged as a bug since the current CI check passes.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration

Copy link
Copy Markdown

CI status: all checks green except e2e-couchdb, which is failing at the Codecov upload step (Token required - not valid tokenless upload with fail_ci_if_error: true) — the fork is missing the CODECOV_TOKEN secret. The Playwright couchdb tests themselves passed on the latest commit (16 passed, 2 flaky-passed on retry with unrelated network Failed to fetch console errors). The same job fails identically on other branches of this fork (e.g. runs 30397481722, 30395258199), so it's preexisting infra, not introduced by this PR.

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

Labels

no milestone PR intentionally has no milestone type:maintenance Maintenance/CI change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant