Skip to content

Conjunction SSA plugin (tested rewrite) + agent scaffolding - #9

Open
jakexcosme wants to merge 4 commits into
masterfrom
devin/1782919184-conjunction-ssa-plugin
Open

Conjunction SSA plugin (tested rewrite) + agent scaffolding#9
jakexcosme wants to merge 4 commits into
masterfrom
devin/1782919184-conjunction-ssa-plugin

Conversation

@jakexcosme

@jakexcosme jakexcosme commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Supersedes the original Conjunction SSA implementation on this branch with a
unit-tested rewrite, and adds repository agent scaffolding.

This PR contains two related but separable bodies of work. They were combined
at the requester's direction; reviewers may wish to read them as two passes.

1. Conjunction SSA plugin — rewritten

The previous version on this branch bundled object seeding, layout config and
telemetry into large modules with no test coverage. It has been replaced
with a provider-separated design:

Module Responsibility
propagator.js Keplerian + J2 propagation, ECI to geodetic, GMST
pc.js Foster/Alfano-style 2D isotropic probability of collision
tles.js Seed TLE set + TLE parsing
models.js Domain object models (roots, folders, condition set, table, plot, layout)
ConjunctionEngine.js Pair screening, golden-section TCA refinement, capped history buffers
SsaObjectProvider.js / SsaMetadataProvider.js / SsaTelemetryProvider.js Open MCT provider surfaces

Removed: ConjunctionTelemetryProvider.js, OrbitPropagator.js,
conditionSetConfig.js, layoutConfig.js, seedObjects.js.

36 unit tests across four spec files cover propagation, Pc computation,
pair screening, the worst-case summary, and plugin registration.

2. Agent scaffolding

AGENTS.md, .devin/rules/, .devin/workflows/, and .agent/knowledge/
documenting project conventions, plugin anatomy, telemetry datum shapes, and
the time API. Documentation only — no runtime effect.

Notable behaviour: NaN miss distance

When a pair has no close-approach bracket inside the look-ahead window, the
engine emits missKm: NaN / tcaOffsetS: NaN and pc: 0. summarize()
deliberately excludes those pairs from the worst-case summary. This is the
intended contract and is now covered by two explicit tests.

Test plan

  • npm run lint — clean (js, vue, spelling)
  • npm test — failure set identical to master baseline; no regressions
  • 1003 passing vs 968 on master (+36 tests added by this plugin)
  • Reviewer: npm start and confirm the Conjunction SSA root renders

Baseline comparison

Run Failed Passed
master baseline (run 1) 7 968
master baseline (run 2) 8 967
this branch 8 1003

The 7–8 failures are pre-existing on master (Object API Search x4, Image
Exporter, URLIndicator clock, fps NaN) plus one flaky Imagery test that appears
intermittently on master too — it showed up in baseline run 2 and not run 1, so
it is not attributable to this change.

Test breakdown

Spec Tests
pluginSpec.js 15
propagatorSpec.js 9
ConjunctionEngineSpec.js 8
pcSpec.js 4
Total 36

Defects found and fixed while preparing this branch

The rewrite did not arrive green. Everything below was broken and has been
fixed in this PR:

  1. 26 ESLint errors — 24 Prettier-autofixable, 2 func-style violations
    fixed by hand (arrow-assigned consts converted to function declarations).
  2. 15 pluginSpec tests timing out at 6000ms. The outer beforeEach
    awaited the start event while startHeadless() was only called in nested
    beforeEach blocks, which run afterwards — the event could never fire.
    Every other spec in the codebase pairs on('start', done) with
    startHeadless() in the same block; this one didn't.
  3. ConjunctionEngineSpec asserting against NaN. It ran Math.min over
    unfiltered pair miss distances. The engine intentionally emits NaN for
    non-converging pairs and summarize() excludes them; the test didn't mirror
    that contract. Now documented in the plugin README and covered by two new
    tests.
  4. Two assertions using toBeUndefined() on openmct.types.get(), which
    returns an UNKNOWN_TYPE sentinel and never undefined.
  5. A request() test with no time bounds — the fixed 2024 test timestamp
    fell outside the Time API's real-now default window, so it got zero rows.
  6. 13 missing cspell dictionary entrieslint:spelling is part of
    npm run lint and would have failed CI.
  7. Reverted a junk change to e2e/playwright-ci.config.js that added an
    unused lodash import, violating the repo's
    you-dont-need-lodash-underscore rule.

Known CI failure (not caused by this PR)

e2e-couchdb fails on every PR in this fork. All 17 e2e tests pass; the job
then fails at the Codecov upload step with Token required - not valid tokenless upload and Token length: 0.

codecov/codecov-action@v5 runs with fail_ci_if_error: true and this fork has
no secrets configured (gh secret list returns empty), so both the DockerHub
login and the Codecov upload fail. Fixing it requires adding CODECOV_TOKEN as
a repository secret, or setting fail_ci_if_error: false for forks — a
repo-settings change, not a code change.

Generated with Devin

Co-Authored-By: Jake Cosme <jake@cognition.ai>
@jakexcosme jakexcosme self-assigned this Jul 1, 2026
@devin-ai-integration

Copy link
Copy Markdown
Original prompt from Jake

Create a new self-contained Open MCT plugin in the repository COG-GTM/openmct under the directory src/plugins/conjunctionSSA/. The plugin follows the standard Open MCT pattern: a factory function returning install(openmct). Model the structure on existing plugins such as example/generator/plugin.js, src/plugins/correlationTelemetryPlugin/plugin.js, src/plugins/condition/plugin.js, and src/plugins/CouchDBSearchFolder/plugin.js.

#``# Files to create

#``#``# 1. src/plugins/conjunctionSSA/plugin.js
Export a default factory ConjunctionSSAPlugin(options) returning function install(openmct). Inside it:

  • Register the domain-object type via openmct.types.addType('conjunctionSSA.trackedObject', { name: 'Tracked Object', description: 'A satellite or debris object tracked via TLE for conjunction screening.', cssClass: 'icon-object', creatable: false, initialize(obj) { obj.telemetry = { values: [...] }; } }). The metadata values must include:
    • a utc time value: { key: 'utc', name: 'Time', format: 'utc', hints: { domain: 1 } } (required so the time conductor works — the active time system key must be present).
    • lat, lon, alt (with range hints, units deg, deg, km).
    • miss_distance_km (range hint, unit km), tca (format utc), probability_of_collision (range hint).
      Add an objectCategory field distinguishing satellite vs debris.
  • Register the telemetry provider(s) via openmct.telemetry.addProvider(...). Implement supportsSubscribe/subscribe, supportsRequest/request returning true/data for objects with type === 'conjunctionSSA.trackedObject'. See separate provider files below.
  • Seed the objects in the tree: define a root folder object and the Tracked Objects. Use openmct.objects.addRoot({ namespace: 'conjunctionSSA', key: 'ssa-root' }, openmct.priority.HIGH), register openmct.objects.addProvider('conjunctionSSA', { get(identifier) {...} }) that returns the folder (type folder, location: 'ROOT')... (7242 chars truncated...)

@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[bot]

This comment was marked as resolved.

…ss window

Co-Authored-By: Jake Cosme <jake@cognition.ai>
devin-ai-integration[bot]

This comment was marked as resolved.

Supersedes the original implementation on this branch with a rewrite
that is unit tested and separates concerns across providers.

The previous version bundled object seeding, layout config and telemetry
into large modules with no test coverage. This replaces them with:

  - propagator.js   Keplerian + J2 propagation, geodetic conversion
  - pc.js           Foster/Alfano-style probability of collision
  - tles.js         seed TLE set and TLE parsing
  - models.js       domain object models for roots, folders, views
  - ConjunctionEngine.js  screening, TCA refinement, history buffers
  - Ssa{Object,Metadata,Telemetry}Provider.js  Open MCT providers

Adds 35 unit tests across five spec files covering propagation,
Pc computation, pair screening, and plugin registration.

Also adds agent scaffolding (.devin/, .agent/, AGENTS.md) documenting
project conventions, and registers the plugin in plugins.js and the
index.html demo host.

Verified: npm run lint clean; unit suite failure set identical to
master baseline (no regressions), 1003 passing vs 968 on master.
@jakexcosme jakexcosme changed the title Add self-contained Conjunction SSA plugin Conjunction SSA plugin (tested rewrite) + agent scaffolding Aug 28, 2026
The engine emits NaN missKm/tcaOffsetS for pairs with no local minimum
of separation in the look-ahead window. This is a deliberate sentinel,
not an error, and the worst-case summary excludes such pairs.

Undocumented until now, and the omission caused a unit test to compute
Math.min over unfiltered values and assert against NaN.

@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.

Devin Review

Comment on lines +1 to +11
const DEFAULT_HARD_BODY_RADIUS_M = 10;
const DEFAULT_POSITION_SIGMA_M = 100;

function probabilityOfCollision(missKm, options = {}) {
const hardBodyRadiusM = options.hardBodyRadiusM ?? DEFAULT_HARD_BODY_RADIUS_M;
const positionSigmaM = options.positionSigmaM ?? DEFAULT_POSITION_SIGMA_M;
const missM = missKm * 1000;
const sigmaSq = positionSigmaM * positionSigmaM;
const radiusSq = hardBodyRadiusM * hardBodyRadiusM;
const exponent = -(missM * missM) / (2 * sigmaSq);
return (radiusSq / (2 * sigmaSq)) * Math.exp(exponent);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 RED watch level likely unreachable with default Pc sigma

probabilityOfCollision with the default 100 m sigma yields pc > 1e-4 only for miss below ~0.28 km, but the RED condition needs missKm < 5 AND pc > 1e-4. The ISS/CHASER pair is only asserted to close within 200 km, so the RED indicator the README promises to demonstrate can stay dark.

Devin Review

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

if (Number.isNaN(datum.missKm)) {
return;
}
if (!worst || datum.pc > worst.pc || datum.missKm < worst.missKm) {

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: Worst-case OR selection is redundant but safe

In summarize the worst pair is chosen when pc is larger OR missKm is smaller. Since pc is a strictly decreasing function of missKm under shared options, the two clauses never disagree, so this always yields the smallest-miss pair.

Devin Review

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

Comment on lines +109 to +128
for (let t = startMs; t <= endMs; t += stepMs) {
const primaryState = propagateEci(primaryEntry.elements, t);
const secondaryState = propagateEci(secondaryEntry.elements, t);
const d = distanceKm(primaryState.position, secondaryState.position);

if (
previousPreviousDistance !== Infinity &&
previousDistance < previousPreviousDistance &&
previousDistance < d &&
previousDistance < bestBracketMiss
) {
bestBracketMiss = previousDistance;
bestBracket = [previousPreviousTime, t];
}

previousPreviousDistance = previousDistance;
previousPreviousTime = previousTime;
previousDistance = d;
previousTime = t;
}

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: Close approach at window start reported as no-conjunction

screenPair needs three coarse samples to bracket a minimum, so a pair already closest at or one step past the start time is never bracketed and emits NaN miss. Acceptable given the look-ahead intent, but forward-passing conjunctions near now are missed.

Devin Review

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

Comment on lines +26 to +29
getMetadata(domainObject) {
const values = domainObject.type === TRACKED_TYPE ? TRACKED_VALUES : PAIR_VALUES;
return { ...domainObject.telemetry, values };
}

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: Correct metadata depends on provider registration order

getMetadata supplies the value list only because addProvider unshifts it ahead of the core default provider, which also matches these objects via their plain telemetry field. Correct today, but order-dependent.

Devin Review

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

@juliaarhee

Copy link
Copy Markdown

❌ Cannot revive Devin session - the session is too old. Please start a new session instead.

View session

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