Archipelago — open-source initial import
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
---
|
||||
phase: 01-federation-mesh-hardening
|
||||
plan: 08
|
||||
type: execute
|
||||
wave: 4
|
||||
depends_on: ["01-02", "01-06", "01-07"]
|
||||
files_modified:
|
||||
- neode-ui/src/components/LightningChannelModal.vue
|
||||
- neode-ui/src/components/LightningChannelsPanel.vue
|
||||
- neode-ui/src/api/rpc-client.ts
|
||||
- neode-ui/mock-backend.js
|
||||
- neode-ui/src/components/__tests__/LightningChannelModal.test.ts
|
||||
autonomous: true
|
||||
requirements: [FED-05]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "A user can copy their own node's Lightning URI from the channel-open surface; the copy button label flips to Copied! for about two seconds"
|
||||
- "The displayed own-node URI truncates to its container with the full value in a title tooltip, and the full untruncated value is what reaches the clipboard"
|
||||
- "Trusted federated nodes that advertise Lightning are listed by hostname with a one-click Open Channel action"
|
||||
- "Meshed peers that have Lightning installed are listed separately with a Request Channel action, never a direct open — they are not bilaterally trusted"
|
||||
- "A peer that is both a trusted federated node and a meshed Lightning peer appears exactly once, in the trusted list (FED-05 adjacency edge)"
|
||||
- "Both picker lists render in a deterministic order that does not reshuffle between refreshes (FED-05 ordering edge)"
|
||||
- "When both lists are empty a single shared empty state renders once — not one per column"
|
||||
- "Both lists show the house loading treatment while fetching and the house error row on failure, matching the existing federation node list and Lightning channels panel conventions"
|
||||
- "Each list row shows the node name with truncation and a title tooltip, its trust badge, and its transport badge, mirroring the existing federation node row"
|
||||
- "Clicking Open Channel twice, or opening two channels to the same peer at once, results in one open attempt — the action is disabled while a request is in flight (FED-05 concurrency edge)"
|
||||
- "A manually pasted pubkey with no host still works, falling back to the address-less open path the Lightning channels panel already relies on"
|
||||
- "The manual-paste field is reached through a de-emphasised Paste URI Manually entry point below both lists, not as a third equal-weight column"
|
||||
- "The modal renders through the house modal shell so its backdrop covers the full screen and a click outside closes it"
|
||||
- "The request flow reuses the existing peer-request modal pattern — optional message field, Send Request submit, Sending… busy label"
|
||||
- statement: "When both lists are empty the shared empty state renders exactly once rather than once per list"
|
||||
verification: backstop
|
||||
- statement: "A manually pasted URI that is not in pubkey@host:port form is rejected client-side with a format message before any open call is made"
|
||||
verification: backstop
|
||||
prohibitions:
|
||||
- statement: "A channel-open request sent to a meshed peer MUST NOT be displayed as an open, pending-funding, or connected channel anywhere in the UI — until the recipient acts, it is a sent request and nothing more"
|
||||
category: transparency
|
||||
- statement: "The picker MUST NOT present a meshed peer's advertised URI with the same visual authority as a bilaterally-trusted federated node — the two lists stay visually distinct and the meshed action stays a request, so a user cannot mistake an unverified advertisement for a trusted target"
|
||||
category: safety
|
||||
artifacts:
|
||||
- path: neode-ui/src/components/LightningChannelModal.vue
|
||||
provides: "Own-URI share, trusted-node picker, meshed-peer request picker, manual-paste fallback"
|
||||
min_lines: 150
|
||||
- path: neode-ui/src/components/__tests__/LightningChannelModal.test.ts
|
||||
provides: "State coverage for empty, loading, error, populated, dedup, ordering, and double-click"
|
||||
min_lines: 60
|
||||
key_links:
|
||||
- from: neode-ui/src/components/LightningChannelsPanel.vue
|
||||
to: neode-ui/src/components/LightningChannelModal.vue
|
||||
via: "the panel's existing Open Channel button opens the new picker modal"
|
||||
pattern: "LightningChannelModal"
|
||||
- from: neode-ui/src/components/LightningChannelModal.vue
|
||||
to: neode-ui/src/api/rpc-client.ts
|
||||
via: "federation.list-nodes, mesh.lightning-peers, lnd.getinfo, lnd.openchannel, mesh.request-channel"
|
||||
pattern: "lightning-peers"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Make channel opening between nodes first-class UI: share your node's Lightning URI, open a channel
|
||||
with a trusted federated node in one click, and request a channel from a meshed peer that has
|
||||
Lightning installed.
|
||||
|
||||
Purpose: FED-05's user-facing half, with scope locked in CONTEXT.md (the second list is *meshed peer
|
||||
nodes that have Lightning installed* — not `lnd listpeers`, not a curated directory, not a live
|
||||
LN-graph query) and its visuals fixed by 01-UI-SPEC.md (copy, colours, spacing, the shared empty
|
||||
state, the de-emphasised manual-paste fallback, and the hard modal rule).
|
||||
Output: a new picker modal built from the house design system, reached from the Lightning panel's
|
||||
existing Open Channel button, working against the demo and against archi-dev.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/01-federation-mesh-hardening/01-CONTEXT.md
|
||||
@.planning/phases/01-federation-mesh-hardening/01-UI-SPEC.md
|
||||
@.planning/phases/01-federation-mesh-hardening/01-PATTERNS.md
|
||||
@.planning/phases/01-federation-mesh-hardening/01-06-SUMMARY.md
|
||||
@.planning/phases/01-federation-mesh-hardening/01-07-SUMMARY.md
|
||||
@neode-ui/src/components/BaseModal.vue
|
||||
</context>
|
||||
|
||||
## Artifacts this phase produces
|
||||
|
||||
Created or changed by **this plan**:
|
||||
|
||||
| Symbol | Kind | File |
|
||||
|---|---|---|
|
||||
| `LightningChannelModal.vue` | new Vue component (picker modal) | `neode-ui/src/components/LightningChannelModal.vue` |
|
||||
| `meshLightningPeers()`, `requestChannel()`, `sendLightningInfo()` | new rpc-client wrappers | `neode-ui/src/api/rpc-client.ts` |
|
||||
| `mesh.lightning-peers`, `mesh.send-lightning-info`, `mesh.request-channel` | new mock RPC cases | `neode-ui/mock-backend.js` |
|
||||
| `identity_pubkey` / `uris` on the mock `lnd.getinfo` result | extended mock response | same |
|
||||
| `lightning_uri` on the mock `federation.list-nodes` nodes | extended mock response | same |
|
||||
| `LightningChannelModal.test.ts` | new vitest suite | `neode-ui/src/components/__tests__/LightningChannelModal.test.ts` |
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="tracer" tdd="true">
|
||||
<name>Task 1: End-to-end — open a channel with a trusted federated node in one click</name>
|
||||
<precondition>`federation.list-nodes` emits `lightning_uri` (plan 01-06) and `mesh.lightning-peers` is registered in the dispatcher (plan 01-04) — confirm both by grepping `core/archipelago/src/api/rpc/federation/handlers.rs` and `core/archipelago/src/api/rpc/dispatcher.rs` before starting.</precondition>
|
||||
<files>neode-ui/src/components/LightningChannelModal.vue, neode-ui/src/api/rpc-client.ts, neode-ui/mock-backend.js, neode-ui/src/components/__tests__/LightningChannelModal.test.ts, neode-ui/src/components/LightningChannelsPanel.vue</files>
|
||||
<read_first>
|
||||
- `neode-ui/src/components/BaseModal.vue` — the whole file. It already wraps `Teleport to="body"`,
|
||||
the full-screen `bg-black/60 backdrop-blur-md` backdrop, `@click.self` close, the pinned
|
||||
`text-xl font-semibold` title, and the scrolling body. The new modal MUST use it; never nest a
|
||||
modal inside a transform-affected ancestor.
|
||||
- `neode-ui/src/components/LightningChannelsPanel.vue` lines 246-360 (its current bespoke
|
||||
open-channel modal, its `openError` ref, `isStartupNotice()` amber-vs-red distinction, and the
|
||||
fee-preset block) and lines 505-630 (`showOpenModal`, `defaultOpenForm()`, `openForm`,
|
||||
`openingChannel`, and the validate-before-RPC sequence including the 20,000-sat minimum and the
|
||||
`pubkey@host:port` split with an optional address).
|
||||
- `neode-ui/src/views/federation/NodeList.vue` lines 40-140 and 155-190 — the row layout to
|
||||
mirror (truncated name with `:title`, transport badge, trust badge, action button), the
|
||||
`trustedNodes` / `peerNodes` computed filters, and the "Loading nodes..." row.
|
||||
- `neode-ui/src/api/rpc-client.ts` lines 795-850 — the one-line
|
||||
`this.call({ method: '<ns>.<verb>', params })` wrapper convention and the existing
|
||||
`federation.list-nodes` wrapper.
|
||||
- `neode-ui/mock-backend.js` — the `lnd.getinfo`, `lnd.openchannel`, and `federation.list-nodes`
|
||||
cases, and the parity harness added by plan 01-02.
|
||||
- `.planning/phases/01-federation-mesh-hardening/01-UI-SPEC.md` — Design System, Spacing,
|
||||
Typography, Color, and the full Copywriting Contract. Every string in this modal comes from
|
||||
that table verbatim.
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Mounted with a node list containing one trusted node that has a Lightning URI and one that does
|
||||
not, the trusted list renders exactly one row.
|
||||
- The row shows the node name (truncated, with a `title`), its trust badge, its transport badge,
|
||||
and an Open Channel button.
|
||||
- Clicking Open Channel calls the open RPC once with that node's URI; clicking it twice in
|
||||
immediate succession still results in exactly one call, and the button is disabled while in
|
||||
flight.
|
||||
- While the node list is loading, the loading treatment renders and no empty state renders.
|
||||
- When the node fetch rejects, the error row renders with the contract's error copy.
|
||||
- The modal root renders through the house modal shell, so the backdrop is a full-screen sibling
|
||||
of the card rather than a child of a transformed ancestor.
|
||||
</behavior>
|
||||
<action>
|
||||
Write the test file first and confirm it fails.
|
||||
|
||||
Add rpc-client wrappers for `mesh.lightning-peers`, `mesh.send-lightning-info`, and
|
||||
`mesh.request-channel` following the existing one-line convention. Extend the mock backend so the
|
||||
demo answers all three, so `lnd.getinfo` returns an `identity_pubkey` and a `uris` array, and so
|
||||
`federation.list-nodes` nodes carry `lightning_uri` — mirroring the real handlers per the mock's
|
||||
established "cite the daemon source" comment convention. The plan-01-02 parity harness must stay
|
||||
green.
|
||||
|
||||
Create `LightningChannelModal.vue` using `BaseModal` as its shell, title "Open Lightning Channel".
|
||||
In this task implement the trusted-node section only: fetch the federated node list, keep nodes
|
||||
whose trust level is trusted AND which have a Lightning URI, sort by display name with a stable
|
||||
tiebreak so the order does not shuffle between refreshes, and render each as a row mirroring the
|
||||
federation node row — truncated name with a `title` tooltip, the transport badge reusing
|
||||
NodeList's existing FIPS/Tor logic, the trust badge, and an Open Channel button on the right.
|
||||
|
||||
Wire Open Channel to the existing open-channel RPC, reusing the panel's proven sequence: validate
|
||||
before calling, keep the 20,000-sat minimum, split the URI into pubkey and optional address, and
|
||||
reuse the `openError` ref plus the `isStartupNotice()` amber-vs-red distinction rather than
|
||||
inventing a new error idiom. Guard against double submission with an in-flight flag keyed to the
|
||||
target so the button is disabled and a second click is a no-op.
|
||||
|
||||
Point the Lightning panel's **existing** Open Channel button at this modal instead of its bespoke
|
||||
one. Do not add a new nav entry, route, card, or dashboard tile — the user places new entry
|
||||
points, this plan only upgrades the one that already exists. Leave the panel's channel list,
|
||||
close-channel flow, and fee presets untouched.
|
||||
|
||||
Follow the UI-SPEC tables exactly: spacing on the 4px grid, the two-weight typography scale,
|
||||
accent orange reserved for the primary action buttons, the bolt icon path already used elsewhere
|
||||
in the app, and the copy strings verbatim.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd neode-ui && test -f src/components/__tests__/LightningChannelModal.test.ts && npx vitest run src/components/__tests__/LightningChannelModal.test.ts && node scripts/mock-rpc-parity.mjs</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- The test file exists and `npx vitest run src/components/__tests__/LightningChannelModal.test.ts` exits 0 with all six behaviors present as named cases (the `test -f` guard is required — `vitest.config.ts` sets `passWithNoTests: true`).
|
||||
- A test asserts two immediate clicks produce exactly one open call.
|
||||
- `grep -c 'BaseModal' neode-ui/src/components/LightningChannelModal.vue` is at least 2 (import + usage).
|
||||
- `grep -c 'Open Channel' neode-ui/src/components/LightningChannelModal.vue` is at least 1 and the copy matches the UI-SPEC Copywriting Contract verbatim.
|
||||
- `grep -c 'LightningChannelModal' neode-ui/src/components/LightningChannelsPanel.vue` is at least 2.
|
||||
- `grep -c "'mesh.lightning-peers'" neode-ui/src/api/rpc-client.ts` equals 1.
|
||||
- `cd neode-ui && node scripts/mock-rpc-parity.mjs` exits 0 with zero missing methods.
|
||||
- `cd neode-ui && npx vitest run` exits 0 and `npm run build` exits 0.
|
||||
- No new route, nav item, or dashboard card was added — confirmed by `git diff --stat` showing no change to the router or any layout/nav component, recorded in the SUMMARY.
|
||||
</acceptance_criteria>
|
||||
<done>A trusted federated node can be picked by hostname and a channel opened with one click, through the house modal shell, on the demo and against a real node.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: The meshed Lightning peers list and the Request Channel flow</name>
|
||||
<files>neode-ui/src/components/LightningChannelModal.vue, neode-ui/src/components/__tests__/LightningChannelModal.test.ts, neode-ui/mock-backend.js</files>
|
||||
<read_first>
|
||||
- `neode-ui/src/components/federation/PeerRequestModal.vue` — the whole file (66 lines): the
|
||||
optional message field, the `sending` → "Sending…" busy label, and the
|
||||
`$emit('send', message)` / `$emit('cancel')` contract. Reuse this component rather than
|
||||
building a second request modal.
|
||||
- `.planning/phases/01-federation-mesh-hardening/01-UI-SPEC.md` — the "FED-05 Visual Anchor"
|
||||
section (trusted list is primary, meshed list second) and the empty-state copy rows.
|
||||
- `neode-ui/src/views/federation/NodeList.vue` — the empty-state block treatment to mirror.
|
||||
- The plan-01-07 SUMMARY — the exact params `mesh.request-channel` expects.
|
||||
</read_first>
|
||||
<action>
|
||||
Add the meshed-Lightning-peers section below the trusted list: fetch via `mesh.lightning-peers`,
|
||||
render rows in the same layout with a Request Channel button in place of Open Channel, and sort
|
||||
with the same stable ordering rule.
|
||||
|
||||
Deduplicate across the two lists: a peer that is both a trusted federated node and a meshed
|
||||
Lightning peer appears only in the trusted list. Match on the identity available in both payloads
|
||||
(the node's Lightning URI is the reliable common key; fall back to the peer's archipelago identity
|
||||
key when present). Never match on display name.
|
||||
|
||||
Wire Request Channel to `PeerRequestModal` — mount it with the optional message field, and on its
|
||||
send event call `mesh.request-channel` with the target peer and the message. While a request is
|
||||
in flight the row's button is disabled and shows the busy label; a second click is a no-op. On
|
||||
success show a sent-request confirmation on the row. That confirmation must not claim a channel
|
||||
exists, is pending funding, or is connected; it says a request was sent and nothing more.
|
||||
|
||||
Add the shared empty state: when the trusted list and the meshed list are BOTH empty, render the
|
||||
UI-SPEC's empty heading and body exactly once for the pair — not once per list. When only one is
|
||||
empty, that section renders nothing rather than its own empty state. Render the house loading
|
||||
treatment per section while its fetch is in flight, and the contract's error row on a failed
|
||||
fetch, using the same `openError` / startup-notice idiom as Task 1.
|
||||
|
||||
Extend the mock backend so `mesh.lightning-peers` returns a small demo peer set and
|
||||
`mesh.request-channel` records the request in the session store so the demo shows the same sent
|
||||
state a real node does.
|
||||
|
||||
Extend the test suite: both-empty renders one empty state; one-empty renders none for that
|
||||
section; a peer present in both lists renders once and in the trusted list; ordering is identical
|
||||
across two consecutive renders of a shuffled input; a double click on Request Channel produces one
|
||||
call; the sent confirmation contains no open or connected wording.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd neode-ui && npx vitest run src/components/__tests__/LightningChannelModal.test.ts && node scripts/mock-rpc-parity.mjs</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `cd neode-ui && npx vitest run src/components/__tests__/LightningChannelModal.test.ts` exits 0 with the six cases above present by name.
|
||||
- The both-empty case asserts an element count of exactly 1 for the empty-state element, not merely that it is present.
|
||||
- `grep -c 'PeerRequestModal' neode-ui/src/components/LightningChannelModal.vue` is at least 2.
|
||||
- `grep -c 'Request Channel' neode-ui/src/components/LightningChannelModal.vue` is at least 1.
|
||||
- `cd neode-ui && node scripts/mock-rpc-parity.mjs` exits 0.
|
||||
- `cd neode-ui && npx vitest run && npm run build` exits 0.
|
||||
</acceptance_criteria>
|
||||
<done>Meshed Lightning peers are listed and requestable, deduplicated against the trusted list, with a single shared empty state and no misleading channel wording.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Share your own URI, and the manual-paste fallback</name>
|
||||
<files>neode-ui/src/components/LightningChannelModal.vue, neode-ui/src/components/__tests__/LightningChannelModal.test.ts</files>
|
||||
<read_first>
|
||||
- `neode-ui/src/components/SendBitcoinModal.vue` — its `copyDetail` / "Copied!" clipboard
|
||||
feedback pattern (the label flips for about two seconds). Reuse it; do not invent a new
|
||||
copy-feedback idiom.
|
||||
- `neode-ui/src/components/LightningChannelsPanel.vue` lines 250-270 and 595-625 — the
|
||||
`pubkey@host:port` placeholder, the `Format: pubkey@host:port` helper text, and the
|
||||
validate-before-RPC sequence including the address-optional split.
|
||||
- `.planning/phases/01-federation-mesh-hardening/01-UI-SPEC.md` — the Copywriting Contract rows
|
||||
for "Primary CTA — own URI" and "Manual fallback entry point", and the UI Considerations rows
|
||||
marked backstop for the manual-paste form.
|
||||
</read_first>
|
||||
<action>
|
||||
Add the own-node URI block at the top of the modal: read this node's Lightning identity from
|
||||
`lnd.getinfo`, display the URI truncated to its container with the full value in a `title`
|
||||
tooltip, and add a copy button whose label flips to the confirmation string for about two seconds.
|
||||
The clipboard receives the full untruncated value regardless of visual truncation. When the node
|
||||
has no Lightning URI available, the block explains that instead of showing an empty field or a
|
||||
fabricated address.
|
||||
|
||||
Add the manual-paste fallback below both lists as a de-emphasised disclosure, not a third
|
||||
equal-weight column: the entry point reveals a peer URI input with the placeholder and helper text
|
||||
reused verbatim from the Lightning panel. Validate client-side before calling the open RPC — a
|
||||
value that is not in `pubkey@host:port` form is rejected with the format message and no RPC is
|
||||
issued; a bare pubkey with no host is accepted and passes an undefined address through, which is
|
||||
the behavior the open RPC already supports. Reuse the same error ref and startup-notice treatment.
|
||||
|
||||
Extend the test suite: the copy button places the full untruncated URI on the clipboard and its
|
||||
label flips then reverts; the URI element carries a `title` with the full value; an invalid
|
||||
pasted value shows the format message and issues no RPC call; a bare pubkey issues the open call
|
||||
with an undefined address; the no-URI-available state renders its explanation rather than an
|
||||
empty field.
|
||||
|
||||
Then verify on the dev preview against archi-dev before this plan is considered complete, per the
|
||||
user requirement in CONTEXT.md: the preview at the dev port, the copy button, the trusted list,
|
||||
the meshed list, the request flow, and the manual paste. Record what was exercised in the SUMMARY.
|
||||
The blocking human sign-off is consolidated into plan 01-09.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd neode-ui && npx vitest run src/components/__tests__/LightningChannelModal.test.ts && npm run build</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `cd neode-ui && npx vitest run src/components/__tests__/LightningChannelModal.test.ts` exits 0 with the five cases above present by name.
|
||||
- A test asserts the clipboard receives the full untruncated URI even when the rendered element is truncated.
|
||||
- A test asserts an invalid pasted value results in zero RPC calls (assert on the call count, not merely on the message being visible).
|
||||
- `grep -c 'Copy Lightning URI' neode-ui/src/components/LightningChannelModal.vue` is at least 1.
|
||||
- `grep -c 'Paste URI Manually' neode-ui/src/components/LightningChannelModal.vue` is at least 1.
|
||||
- `grep -c 'pubkey@host:port' neode-ui/src/components/LightningChannelModal.vue` is at least 2 (placeholder + helper text).
|
||||
- `cd neode-ui && npx vitest run && npm run build` exits 0.
|
||||
- The SUMMARY lists the dev-preview steps exercised against archi-dev and what was observed.
|
||||
</acceptance_criteria>
|
||||
<done>A user can share their own node's Lightning URI and fall back to a pasted URI with real client-side validation, verified on the dev preview against a real node.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| daemon RPC → browser | Peer-advertised Lightning URIs, some of them from unauthenticated radio peers, are rendered and offered as payment targets |
|
||||
| browser → clipboard | This node's payment endpoint is copied for the user to share out of band |
|
||||
| user click → `lnd.openchannel` | A UI action commits real funds to a channel |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-01-31 | Spoofing | a meshed peer's advertised URI presented with the same authority as a bilaterally-trusted federated node, luring funds to an attacker | high | mitigate | The two lists stay visually and semantically distinct; the meshed action is Request Channel, never a direct open; the prohibition above states this and a test asserts the meshed row's action wording |
|
||||
| T-01-32 | Tampering | a peer-supplied node name or URI containing markup that renders as UI | high | mitigate | Vue's default text interpolation escapes; the plan uses no `v-html` anywhere. A test asserting a name containing angle brackets renders as text is required before this row can be dispositioned |
|
||||
| T-01-33 | Repudiation | a sent request being read as an open channel, so a user believes they have inbound liquidity they do not | high | mitigate | The sent confirmation is worded as a request only; a test asserts the confirmation contains no open or connected wording |
|
||||
| T-01-34 | Denial of Service | a double click or a fast repeat committing two channel opens to the same peer | high | mitigate | An in-flight flag keyed to the target disables the action and makes a second click a no-op, backed by the plan-01-07 backend suppression window; a test asserts exactly one call for two immediate clicks |
|
||||
| T-01-35 | Information Disclosure | this node's Lightning URI being displayed to a shoulder-surfer or copied in a shared session | low | accept | A Lightning URI is a public payment endpoint by design; it is deliberately shareable and carries no spend authority |
|
||||
| T-01-36 | Elevation of Privilege | the modal bypassing the open RPC's server-side validation by calling with unvalidated input | medium | mitigate | Client-side validation is additive only; the existing server-side pubkey-format and amount-bounds validation in the open handler is reused unchanged and is the authority |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `cd neode-ui && npx vitest run` — green.
|
||||
- `cd neode-ui && node scripts/mock-rpc-parity.mjs` — green, zero missing methods.
|
||||
- `cd neode-ui && npm run build` — green.
|
||||
- Dev-preview walkthrough against archi-dev recorded in the SUMMARY (own URI copy, trusted open, meshed request, manual paste), per CONTEXT.md's "verified on the dev preview before any deploy" requirement.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Own-node URI is displayed, truncated with a tooltip, and copied in full.
|
||||
- Trusted federated nodes with Lightning are listed by hostname with a one-click open.
|
||||
- Meshed Lightning peers are listed separately and requestable, deduplicated against the trusted list.
|
||||
- Shared empty state renders once; loading and error states follow the house conventions.
|
||||
- Manual paste validates client-side and supports a bare pubkey.
|
||||
- Double-submission is impossible; a request is never shown as a channel.
|
||||
- No new route, nav entry, or dashboard card was added.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/01-federation-mesh-hardening/01-08-SUMMARY.md` when done.
|
||||
Stage by explicit path, commit, and `git push gitea-ai main`.
|
||||
</output>
|
||||
Reference in New Issue
Block a user