Skip to content

Small upstream fixes: IPv6 pairing, EPIPE, Ollama model lookup, GPU labels, CSP - #33

Merged
JaredP94 merged 7 commits into
mainfrom
fix/small-upstream-ports
Nov 2, 2026
Merged

JaredP94 merged 7 commits into
mainfrom
fix/small-upstream-ports

Conversation

@JaredP94

@JaredP94 JaredP94 commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Description

Small upstream fixes that applied with little or no change:

Not included: scheduler priority retry (NVIDIA/Personal-AI-Router@d90590f) — its "retry after a failed write" can't happen in this fork, because the shared codec latches the first write error.

Validation

go test ./... in nvpair-cluster-manager; desktop typecheck, lint, test:unit (762 passed), dead-code:check, service-contracts:check; SPDX check.

Risk

Low. cluster-manager 2.1.1.

Checklist

  • I have read the Contributing Guidelines.
  • Every commit is signed off (git commit -s), certifying the Developer Certificate of Origin.
  • New or existing tests cover the change.
  • Relevant documentation is updated.
  • I checked the diff, changed filenames, and commit messages for credentials, private data, internal URLs, internal issue identifiers, and generated artifacts.
  • I recorded the validation commands and results above.
  • I bumped any affected component in services/versions.json, and described user-visible changes above so they reach the release notes.

Part of the upstream issue/PR review. Each branch is independent off main; they all bump services/versions.json, so the second one merged will need a trivial conflict resolution there.

JaredP94 and others added 7 commits November 2, 2026 20:51
I don't see any pdf.js code, so I presume we don't need this.

(cherry picked from commit 5365a48)

Co-authored-by: Kaylee Lubick <klubick@nvidia.com>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Closes NVIDIA#33.

## Problem

`NodeCardDetails.tsx:276` passed only the first GPU to the node card
header:

```tsx
gpuLabel={gpuInfo.length > 0 ? gpuInfo[0].name : undefined}
```

`NodeLabel` took a single `gpuLabel: string | undefined`, so every GPU
after the first was unrepresentable by construction. A host with a Tesla
P100 **and** an RTX 3060 rendered identically to one with only the 3060,
with no count or overflow hint that anything was missing.

The data was never wrong — `/v1/node-info` reports every GPU,
`inferenceHardwareIds` was absent (which by the contract in
`gpu-inference.ts` means "show every GPU"), and the detailed performance
view already charts all of them. Only the header dropped them.

## Change

Emit **one label segment per GPU**.

Segments are the right unit rather than a joined string: `NodeLabel`
applies wrapping, `·` separators and truncation *per segment*, so
`gpus.join(' · ')` would be a single segment that truncates after the
first name on a narrow card — the same blind spot in a new shape.
Per-GPU segments keep the existing layout behavior and let each name
wrap and truncate on its own.

Three supporting details:

- The segment builder moves to `ui/utils/node-label-segments.ts`. The
unit project runs `environment: 'node'` with no DOM and deliberately
excludes components, so a pure helper is testable there without adding
jsdom/testing-library to the tree. It also lands inside the existing
coverage scope ("the renderer's helper and constant modules").
- `gpuLabels` is memoized in `NodeCardDetails`. Building the array
inline gave it a new identity every render, and `NodeLabel` observes its
segments — that would have torn down and rebuilt the `ResizeObserver` on
every render.
- Blank GPU names are skipped, so a backend reporting an empty name
cannot render a separator with nothing after it.

Behavior for 0- and 1-GPU hosts is unchanged.

## Tests

`desktop/tests/modular/node-label-segments.test.ts` — 13 cases:

- name/address ordering, and the address-as-primary fallback
- single-GPU host unchanged
- the reported dual-GPU regression, asserting topology order
- `it.each([3, 4, 5, 6, 7, 8])` — every GPU present, in order, at each
count; 8-GPU servers are the case that matters most here
- unique, stable `gpu-N` ids across an 8-GPU host so React keys do not
collide
- blank names dropped without disturbing the rest
- no GPU segments for a host reporting none

## Verification

Run from `desktop/`, all green on this branch:

| Gate | Result |
|---|---|
| `npm run lint` | exit 0 (one pre-existing warning in
`node-info-poller.ts`, untouched here) |
| `npm run typecheck` | exit 0 |
| `npm run dead-code:check` | exit 0 — no dead code |
| `npm run test:unit` | 38 files, 221 tests passed |

---

*Proudly Made in Nebraska. Go Big Red! 🌽 <https://xkcd.com/2347/>*

(cherry picked from commit 54d2fe3)

Co-authored-by: Aaron K. Clark <cryptojones@owasp.org>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
When the inviter was reached over IPv6 link-local, outboundIP dropped the scope zone when advertising its address, so the joiner's Completion Exchange POST had no route and pairing failed after the PIN step. Keep the zone, and render peer addresses into URLs via net/url so the zone is percent-encoded per RFC 6874 (a raw % is rejected as an invalid URL escape).

(cherry picked from commit 93076e6)

Co-authored-by: Mallikh Kalkere <mkalkere@users.noreply.github.com>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
File append was already best-effort. Console write was not, so a closed
stdout pipe (parent gone, or a launch that inherited a pipe that later
closed) raised uncaught write EPIPE from afterWriteDispatched. Electron
then showed "A JavaScript error occurred in the main process".

Treat stdio the same as the log file: try/catch around write, and attach
error listeners so the async EPIPE event is not an uncaughtException.

(cherry picked from commit b82fe49)

Co-authored-by: Michael Gannotti <michael@smfworks.com>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
The Ollama model hub browses a committed snapshot
(desktop/src/electron/model-hub/ollama-models.json) regenerated by hand, so a
model published after that snapshot is invisible in the hub even though the
engine can pull it. The pull path never had this limit: pull_model forwards
whatever name it is given and never consulted the catalog, so the snapshot
gated discovery alone.

When a search matches nothing locally and the query is a well-formed model
reference, resolve that one exact name against registry.ollama.ai and fold the
answer into the list as an ordinary result row. The registry speaks the same
OCI distribution API ollama pull uses, so this asks the source the engine
would ask rather than scraping rendered HTML. It answers 404 for /tags/list,
so browsing still comes from the snapshot and this is an exact-name lookup
rather than a search.

A manifest carries no publication date and the registry sends no
last-modified, so ModelEntry.updatedAt becomes optional and a resolved row
omits its age rather than claiming it was updated just now.

Lookups arrive over IPC, so the reference is validated in the main process
rather than trusting the renderer's own check: without it "foo/.." walks out
of the namespace and "foo/bar#z" truncates the request path.

Refs NVIDIA#21

(cherry picked from commit 3fefdb1)

Co-authored-by: Shaun Y <me@shaunyap.ca>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
…registry lookup

Review follow-ups:

- A link-local zone names an interface of the machine that wrote it. The
  joiner dialled the inviter's advertised address with the inviter's own
  zone (its %en0), which works only when both machines name the interface
  the same. It now uses the zone of the connection the inviter reached it
  on. Pairing signals (decline, fail, expire, acknowledgements) also build
  their URL with peerURL, so a scoped target parses.
- The Ollama registry helpers took unknown, which AGENTS.md forbids; they
  now take JsonValue. A transient lookup failure is no longer cached, so
  the next search asks again; a 404 still is.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Review follow-up. Only the dialled address took the local zone; the
address recorded as the member's kept the inviter's, so pairing could
complete with an unusable member address. The joiner now scopes the
inviter's advertised link-local address before dialling or recording it,
and keeps it as an observation so a roster claim cannot replace it, as
the inviter already does for the joiner.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
@JaredP94
JaredP94 force-pushed the fix/small-upstream-ports branch from b0ac105 to 86c121a Compare November 2, 2026 18:52
@JaredP94
JaredP94 merged commit e0eeb78 into main Nov 2, 2026
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.

1 participant