Skip to content

Update LightningReceiveRequestStatus.ts - #134

Open
TwistedHardware wants to merge 2438 commits into
buildonspark:mainfrom
TwistedHardware:main
Open

Update LightningReceiveRequestStatus.ts#134
TwistedHardware wants to merge 2438 commits into
buildonspark:mainfrom
TwistedHardware:main

Conversation

@TwistedHardware

Copy link
Copy Markdown

Sync the SDK values for LightningReceiveRequestStatus with the values from GraphQL definition file.

reverendken and others added 30 commits May 1, 2026 02:40
GitOrigin-RevId: a085b0ec84082fa73d470513c710b31ae251f0b8
## Summary

bump `retypeCursorKeyPrefix` to v2 to reset the cursor position and take
another pass given that there were a slew of timeout errors on dkg0
yesterday. this is for sanity

## Test Plan

How was this tested? Include steps to verify the changes work. N/A if
not applicable.

GitOrigin-RevId: fd727a6800ff3623f50cab0ab1345ebff54dcc5b
## Summary

When saving the UTXO, the follower SO's chain view can be a few seconds behind the coordinator's, so it must wait till it catches up. This resolves on retry, but the initial error surfaces as a server failure, which causes alert noise. This reclassifies it.

GitOrigin-RevId: 0dfe3d78cac01bea532177d4db297d023f653f5a
## Summary

Adds `queryPendingTransfersMIMO` behind
`KnobReadMIMODataModelQueryPendingTransfers`. Partial-index-driven raw
SQL for the three production-relevant participant shapes (Receiver,
Sender, SenderOrReceiver). Routing falls back to legacy `queryTransfers`
when the knob is off (default) or `participant` is nil (audit-confirmed:
no production caller hits the nil case; added alerts to confirm
fallback.

#### Performance — verified post-Phase-4

Three test beds: dbseed `full` (62M rows, sparkoperator_0),
`realistic_ssp` (~92 pending, sparkoperator_1, prod-cardinality-matched
within 1% via `spark-rds.sh`), `stuck_user` (~120K pending
TRANSFER-only, sparkoperator_2). `EXPLAIN (ANALYZE, BUFFERS)`,
warm-cache repeats. "Real legacy" = non-MIMO `queryTransfers`.

**Query shape labels** (used in test names + the rows below):
- `R1` — receiver participant + network filter (bare)
- `R2` — receiver + types filter (e.g. `[SWAP]`)
- `R3` — receiver + transfer_id filter (singular lookup)
- `S1` — sender participant + network filter (bare)
- `SR1` — sender_or_receiver participant — UNION ALL of S1 + R1 arms

| Shape | Cardinality | New MIMO | Real legacy | Notes |
|---|---|---|---|---|
| R1 receiver bare + network | realistic SSP (92) | 0.19 ms | 0.06 ms |
both sub-ms |
| R1 receiver bare + network | medium (2.1K) | **0.74 ms** | 1.05 ms | 🟢
**MIMO ~1.4× faster** (1.05 / 0.74) |
| R1 receiver bare + network | extreme (104K) | **0.37 ms** | 0.69 ms |
🟢 **MIMO ~1.9× faster** (0.69 / 0.37) |
| R1 receiver bare + network | stuck-user (59K) | 0.08 ms | 0.077 ms |
both sub-ms, essentially tied |
| R2 receiver + types=`[SWAP]` | realistic SSP (92) | 0.11 ms | 0.09 ms
| both sub-ms |
| R2 receiver + types=`[SWAP]` | extreme (104K) | **85 ms** | 22.6 s | 🟢
**MIMO ~268× faster** (22 600 / 85) |
| R3 singular by `transfer_id` | realistic SSP (92) | **0.12 ms** | 0.85
ms | 🟢 **MIMO ~7× faster** (0.85 / 0.12) |
| **S1 sender bare + network** | extreme (T1, 6.2K sender-pending) |
1.14 ms | **0.041 ms** | ⚠️ **MIMO ~28× slower** — JOIN-based S1 can't
use the new partial's leading-equality predicate. Audit indicates no
internal callers; external SDK use unverified. Strengthens the case for
[SP-2914](https://lightspark.atlassian.net/browse/SP-2914?search_id=64a42ed8-5a84-4b86-8717-fa906e84cc2d)
(denormalize `transfers.status` onto `transfer_senders`, mirror the
receiver-arm pattern). |
| **S1 sender bare + network** | medium (T3, 105 sender-pending) | 83 ms
| **0.058 ms** | ⚠️ **MIMO ~1430× slower** — same root cause; medium
cardinality is the planner-flip regime where the partial gets walked
without leading scope. Same
[SP-2914](https://lightspark.atlassian.net/browse/SP-2914?search_id=64a42ed8-5a84-4b86-8717-fa906e84cc2d)
mitigation. |
| **SR1 UNION ALL** | realistic SSP (92) | **0.09 ms** | 0.13 ms | 🟢
**MIMO ~1.4× faster** (0.13 / 0.09) — Merge Append over UNION ALL |
| **SR1 UNION ALL** | medium (2.1K) | **0.12 ms** | 9.8 ms | 🟢 **MIMO
~82× faster** (9800 / 120) — the cardinality where an early JOIN-based
MIMO design wiped at 10.4 s |
| **SR1 UNION ALL** | extreme (104K) | **0.36 ms** | 3.84 s | 🟢 **MIMO
~10 000× faster** (3840 / 0.36) |

At production cardinality, R1/R2/R3/SR1 are sub-ms in both paths; legacy
edges out by tens to hundreds of µs (comfortably under the prod p50 of
5.5 ms per Grafana). At synthetic stress, legacy `queryTransfers`
catastrophically degrades on filter-heavy receiver and
sender-or-receiver queries — those paths show 80–10 000× MIMO speedups.

S1 is the outlier: MIMO regresses 28–1430× across all sender-pending
cardinalities. The MIMO MVP asymmetry — JOIN-based sender arm via
`transfer_senders` while the new partial keys on
`t.sender_identity_pubkey` — can't drive the leading-equality predicate
the partial wants.
[SP-2914](https://lightspark.atlassian.net/browse/SP-2914?search_id=64a42ed8-5a84-4b86-8717-fa906e84cc2d)
retires this asymmetry by denormalizing `transfers.status` onto
`transfer_senders` so the sender arm can use the same
column-on-edge-table pattern the receiver arm already uses.

A [follow-up PR](lightsparkdev/spark#6362) will
improve metric capture in these endpoints and allow us to more
accurately measure performance and actual query combinatorics used in
production callers.

## Test Plan

- `./scripts/check-migration-safety.sh` on the new migration — clean
- 28-case equivalence suite
(`transfer_handler_query_equivalence_test.go`) — pass — validates legacy
`queryTransfers` and `queryPendingTransfersMIMO` return identical
transfer IDs, pagination offsets, and per-transfer projections across
all pending pairs.
- 25+ post-Phase-4 `EXPLAIN ANALYZE` probes across R1/R2/R3/S1/SR1
shapes at SSP-realistic, medium, extreme, and stuck-user pubkey
magnitudes (numbers above).

Contributes to SP-2917 (parent — MIMO query rework with custom SQL and
stress testing)
Contributes to SP-1214 (epic — MIMO MVP)
Contributes to SP-2923 (Phase 4 read-path swap is in this PR)
Related to
[SP-2914](https://lightspark.atlassian.net/browse/SP-2914?search_id=64a42ed8-5a84-4b86-8717-fa906e84cc2d)
(follow-up: denormalize `transfers.status` onto `transfer_senders` to
retire the SR sender-arm asymmetry and the S1 regression documented in
the table above)
Related to SP-2916 (follow-up: consolidate MIMO code into spark/so/mimo
package)
Related to SP-2727 (counter-swap filter retirement, spark#6103)

[SP-2914]:
https://lightspark.atlassian.net/browse/SP-2914?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 628cb442c482681a79e8d9e7a71622afd10c85bd
## Summary

Merged a PR that added tests referencing a variable renamed upstream.

This fixes that.

GitOrigin-RevId: f66d2709be8f8134717a48d18d9679767ecd59c5
## Summary

Adds a deferred-exit `Info` log `"transfer query invoked"` to
`queryTransfers` and `queryPendingTransfersMIMO` capturing filter
parameters (network, types, statuses, participant pubkey hash,
pagination, time bounds) plus elapsed duration, result count, and error.
Lets us attribute query patterns to callers in OpenSearch — "which
wallets are calling with custom statuses, and what's their p99?" —
without adding high-cardinality Prometheus labels.

The log is gated by `KnobLogTransferQueryInvocations` (per-call rollout,
0–100, default 0). Steady-state cost is one knob check; bump to 100
during diagnosis or to a small percentage for a continuous low-cost
sample. Histograms record every call regardless.

The participant pubkey is hashed (first 8 bytes of `sha256`, 16 hex
chars) to avoid writing raw identity pubkeys to logs while preserving
per-wallet grouping.

#### Other changes

- Adds three boolean labels to the `spark_transfer_query_duration`
histogram: `has_pubkey`, `has_status_filter`, `has_type_filter`. Enables
PromQL slicing of "did the caller pass a status/type filter?" without
listing values (cardinality-safe).
- Refactors `newTransferQueryRecorder` to take a `transferQueryAttrs`
struct of named fields instead of positional bools (signature was
getting unwieldy past 5 params).
- Moves `shortPubkeyHash` and `logQueryTransfersInvocation` out of
`transfer_handler.go` into `transfer_query_observability.go` (renamed
from `transfer_query_metrics.go`). The file now houses both metric
recording and structured logging for transfer query paths.

### Technical Notes

The deferred-exit log uses named return values (`resp`, `err`) so the
closure captures the final response shape and any error returned via
early validation paths. Because `defer` registers the call before
validation runs, `logQueryTransfersInvocation` explicitly guards against
`filter == nil`: when nil, it emits a minimal record (`query_path` +
extras like `elapsed`/`error`) instead of dereferencing
`filter.Participant`.

## Test Plan

- Equivalence with the existing `transferQueryRecorder` API at all 6
call sites (3 in `ssp_request_handler.go`, 3 in `transfer_handler.go`);
migration to the new `transferQueryAttrs` struct preserves attribute
values bit-for-bit.
- Knob default behavior: with `KnobLogTransferQueryInvocations` at 0
(Tiltfile default), the function returns before any field construction;
histograms still record every call.
- Nil-filter resilience: the deferred closure in
`queryPendingTransfersMIMO` fires after the nil-participant validation
returns; the explicit `filter == nil` guard inside
`logQueryTransfersInvocation` prevents a panic when the sampling knob is
on.
- Post-deploy: search OpenSearch for `"transfer query invoked"` (after
bumping the knob) to confirm structured fields appear; query
VictoriaMetrics for
`spark_transfer_query_duration_milliseconds_bucket{has_pubkey="true"}`
to confirm new histogram labels are recorded.

Contributes to SP-1214
Contributes to SP-2917

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: f62e7ed01eef568e6d957bf550b843455050024c
## Summary

Adds targeted DEBUG-level SDK breadcrumbs for support debugging without
enabling TRACE-level method logging. The new logs cover sanitized fetch
and gRPC request lifecycle summaries, Bare transport abnormal events,
stream reconnect and heartbeat lifecycle, transfer claim summaries,
token sync summaries, and leaf-cache reconciliation/optimization
summaries.

The DEBUG logs intentionally avoid request/response bodies, headers,
auth tokens, query strings, raw invoices, and per-leaf detail; those
remain absent or TRACE-only.

## Test Plan

- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk test-cmd
src/tests/spark-fetch.test.ts src/tests/bare-http-transport.test.ts
src/tests/logging-service.test.ts`
- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk test-cmd
src/tests/spark-wallet-background-stream.test.ts
src/tests/leaf-manager/leaf-manager.test.ts`
- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk types`
- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk format`
- `git diff --check`

GitOrigin-RevId: c8b81ad044cddde79db6a98ab2947b40e5ceab8d
## Summary

When running with separate gRPC and HTTP listeners, the HTTP catch-all
handler was registered without the otelhttp wrapper, so HTTP metrics
were only collected in single-port (ServeHTTP multiplexing) mode.

Wrap the dual-port catch-all in otelhttp.NewHandler with the same
options used in the multiplexed path so metrics are emitted in both
configurations.

## Test Plan

N/A.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: f586b6e0bdf74b8f83c438683489bf1c1149eb59
## Summary

Removes the `tx-is-dirty` optimization that conditionally rolled back "clean" transactions instead of committing them, gated by the `spark.database.only_commit_dirty` knob. Main DB sessions now always finalize active transactions: commit on the success path, rollback on the error/panic path. The recent middleware fix that prevents rollback hooks from firing after successful commits (`mainCommitted` / `ephemeralCommitted` guards) is preserved.

This was an internal-only optimization. No proto, GraphQL, or wire-level changes.

## Why

The dirty-state mechanism was a footgun: it rolled back transactions that had only issued raw SQL writes through `db.QueryContext` unless those callers remembered to call `ent.MarkTxDirty(ctx)`. Forgetting that call silently discarded writes when the knob was rolled out. The IOPS savings from skipping commits on read-only transactions did not materialize meaningfully, so the complexity isn't worth keeping. It also caused connection churns on two occasions because changing the logic caused transactions to be neither committed nor rolled back, which made the connection unusable for the next task.

## Changes

- **`db.Session`**: dropped `currentIsDirty`, the clean-tx rollback branch in the commit hook, and the `knobs.Knobs` dependency. `Commit()` always invokes the underlying ent commit, then flushes notifications and clears session state on success.
- **Session interfaces**: removed `MarkTxDirty(context.Context)` from `ent.Session` and `entephemeral.Session`, deleted the `ent.MarkTxDirty` / `entephemeral.MarkTxDirty` package helpers, and removed all no-op implementations (read-only sessions, ephemeral sessions, chain `txBacked*` adapters, test fakes).
- **Call sites**: removed explicit `ent.MarkTxDirty(ctx)` calls in `transfer_handler.go` and `fix_leaf_keyshare_split_handler.go` (and the `txIsDirtyHook` / `txIsDirtyEphemeralHook` mutator hooks wired up in `bin/operator/main.go`).
- **Knob removal**: deleted `KnobDatabaseOnlyCommitDirty` and removed `spark.database.only_commit_dirty` from `so.template.config.yaml`, `docker/operator.config.yaml`, and `tilt/spark/Tiltfile`.
- **Factory signature**: `db.NewDefaultSessionFactory(dbClient *ent.Client)` no longer takes `knobs.Knobs`. All call sites in `bin/operator`, `so/task`, and tests updated.
- **Middleware**: kept `mainCommitted` / `ephemeralCommitted` guards and ephemeral-first commit ordering; updated the stale comment that referenced `MarkTxDirty`.
- **Tests**: replaced `TestSession_OnlyCommitDirtyRollsBackCleanTx` with `TestSession_CleanCommitClearsCurrentTx`, which verifies a write-free transaction goes through the commit path and clears `currentTx`. Updated test fakes to drop `MarkTxDirty`.

## Test plan

- [x] `go build ./...`
- [x] `go vet ./...`
- [x] `go test ./so/db/... ./so/grpc/... ./so/stream/... ./so/task/... ./so/ent/... ./so/entephemeral/... ./so/chain/... -count=1`
- [x] `rg "currentIsDirty|MarkTxDirty|OnlyCommitDirty|only_commit_dirty|KnobDatabaseOnlyCommitDirty"` — no remaining references.
- [x] `./scripts/lint-knobs.sh`
- [x] Integration tests (`mise test-grpc-minikube`)

## Deployment notes

External knob/config systems may still carry `spark.database.only_commit_dirty`. After this lands the knob is unused; clean it up out-of-band.

GitOrigin-RevId: a3fa5a309ba5ce04aaa20e81150358d290681dff
## Summary

Splits the SSP unilateral-exit integration tests into two RC workflow jobs so they run in parallel against fresh Kind clusters
instead of sharing one.

- `rc.yaml`: adds `trigger-ssp-hermetic-unilateral-exit-lightning` and `trigger-ssp-hermetic-unilateral-exit-transfer` jobs that dispatch `webdev/ssp-spark-hermetic-test.yaml` with `spark_ssp_test_suite` set to the matching suite; existing standard ssp<>so run now passes `spark_ssp_test_suite: "standard"`.
- `run-ssp-hermetic-test.yaml`: forwards `spark_ssp_test_suite: "standard"` to keep behavior unchanged for existing callers.
- `package.json`: replaces `test:integration:ssp:unilateral` with `:unilateral:lightning` and `:unilateral:transfer` scripts targeting the two test files individually.

GitOrigin-RevId: 394084d96d59dc0bdfbeb3e4a0690ef6fe5f2df5
## Summary

Removes dead code from the unilateral exit fee bump construction logic. The ephemeral anchor output script detection blocks (which iterated over transaction outputs looking for zero-amount outputs) were never actually used after being computed — the `anchorOutputScriptHex` variable was assigned but never referenced downstream. Additionally, the `previousFeeBumpTx` variable was declared but never used. These unused code paths have been cleaned up for both the node transaction and refund transaction handling sections.

## Test Plan

N/A — dead code removal only, no behavioral changes.

GitOrigin-RevId: 3992032e32859e10b980a9ea0def058a3c6997a3
## Summary

Fixes a bug in `constructUnilateralExitFeeBumpPackages` where the loop iterating over used UTXOs was referencing the outer `usedUtxos` variable instead of `refundFeeBump.usedUtxos`. This caused incorrect UTXO tracking when determining the `feeBumpOutPubKey`, as it was not scoped to the specific fee bump package being processed.

## Test Plan

Verify that unilateral exit fee bump package construction correctly identifies the `feeBumpOutPubKey` by ensuring the loop references the UTXOs associated with each individual `refundFeeBump` entry.

GitOrigin-RevId: 2e90b42ed0b819b7915f34e038671ab8c84cd4bf
## Summary

Allow Spark SDK wallet logging config to accept a bare log level such as
`log: "DEBUG"` as shorthand for `log: { level: "DEBUG" }`. This keeps
the existing object-form defaults, including TRACE method logging
defaults, while making the documented shorthand work at runtime and in
TypeScript.

## Test Plan

- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk format`
- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk test-cmd
src/tests/wallet-config.test.ts`
- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk types`
- `git diff --check`

GitOrigin-RevId: d1021f185431ca84796568be5340d76f94b4a6ff
## Summary

Replace the edge-join predicate in
`validateOutputsMatchSenderAndNetwork` with a direct equality on the
denormalized `created_transaction_finalized_hash` column on
`token_outputs`.

The previous form built `OR`'d `EXISTS` subqueries against
`token_transactions` for each input being spent. Postgres optimizes
those poorly: the OR'd-EXISTS shape forces it to pick the
`(owner_public_key, status, network)` index on its
leading-and-only-constrained column (`network`), then filter every row
through the EXISTS subqueries. With ~43M MAINNET token_outputs in prod
and no `owner_public_key` constraint (which only gets added when the
spark invoice carries a sender public key), this falls back to scanning
the whole network slice.

The denormalized `created_transaction_finalized_hash` column was added
in migration `20251206031312` together with a unique index on
`(created_transaction_finalized_hash, created_transaction_output_vout)`.
`FetchAndLockTokenInputs` already uses this fast path; this brings
`validateOutputsMatchSenderAndNetwork` in line.

This function is only reachable from
`validateSparkInvoicesForTransaction`, so the fix is scoped to the
invoice-attached transfer path — which is exactly where the slow query
was observed in production traces.

### Empirical impact (prod replica, spark-1)

Same query, same inputs, same data:

| Form | Plan summary | Execution time | Rows scanned |
|---|---|---|---|
| Before (edge join, OR'd EXISTS) | `Index Scan` on `(owner_public_key,
status, network)`, filter on EXISTS hashes | **132,985 ms** | 43,322,657
rows removed by filter |
| After (direct denormalized column) | `BitmapOr` of 3 `Bitmap Index
Scan`s on `(created_transaction_finalized_hash,
created_transaction_output_vout)` | **1.6 ms** | 3 rows |

Same impact in both: returns 3 rows. The new form is ~81,500× faster.
Cold runs of the old form exceed the 60s gRPC handler timeout, which is
exactly the symptom we observed in the OTel trace from the affected
`sign_token_transaction` peer call.

## Test Plan

- `mise lint` — clean.
- `go test ./so/handler/tokens/... -count=1 -short` — passes.
- Existing tests in `internal_prepare_token_handler_postgres_test.go`
exercise `validateOutputsMatchSenderAndNetwork` indirectly via
`validateSparkInvoicesForTransaction` and continue to pass.
- `EXPLAIN (ANALYZE, BUFFERS)` on the prod-replica `spark-1` database
(results in the table above) confirms the plan switches to bitmap-index
scans and execution time drops from ~133s to ~1.6ms.

GitOrigin-RevId: fed96b3fb18fe7d23f143e2b8ba382f33a04a8ff
## Summary

The `retype_ssp_compensation` scheduled task finished its prod rollout —
both pods reached `phase=Done` after the v1 sweep and the v2 cursor-bump
re-sweep, and subsequent invocations no-op. This PR removes the task,
its tunable batch-size knob
(`spark.so.retype_ssp_compensation_batch_size`), the Tiltfile
registration, and the companion unit test.

The generic `spark.so.task.enabled@retype_ssp_compensation` and
`spark.so.task.timeout@retype_ssp_compensation` knobs are dynamic config
with no source references; they become harmless dead config once the
task is gone and need no code change here.

## Test Plan

- Purely a deletion — no behavior change to any remaining task. `mise
test-unit` passes (3044 tests).
- Confirmed grep returns zero remaining references to `retype` /
`Retype` across the repo.

Contributes to SP-2727

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: e636d00021ebe7b6304cf1e42bb3a1e5204f2950
## Summary

The transfer query metric recorder labels both `QueryPendingTransfers`
(when `KnobReadMIMODataModelQueryPendingTransfers` is off) and
`QueryTransfers` / `QueryAllTransfers` traffic as
`query_path="query_transfers"`, making it impossible to baseline
pending-RPC traffic before ramping the MIMO knob. Adds a `pending_only`
label to `transferQueryAttrs` so the legacy bucket splits cleanly:
pre-rollout you can size pending traffic via
`query_path="query_transfers" pending_only="true"`, and during/after
rollout the same series A/B-compares against
`query_path="query_pending_transfers"`.

The other recorder call sites (`get_ssp_counter_swap_filter`, both
`get_stuck_transfers` paths, `query_node_transfer_history`) pick up
`pending_only=false` via Go's zero value — no per-call-site change
needed.

## Test Plan

- Pure observability label addition; no behavior change.
- Post-deploy: confirm
`spark_transfer_query_duration_milliseconds_count{query_path="query_transfers"}`
series now break out by `pending_only="true"` vs `"false"` (one extra
dimension; existing label values unchanged).

Contributes to SP-2917

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: a059c7d2ec3d7e76381ed24f159dc247e192f1a5
## Summary

Version the JS packages after publishing
`@buildonspark/spark-sdk@0.7.17`. This removes the consumed changeset,
updates generated changelog entries, bumps the linked workspace package
versions, and refreshes the JS lockfile to point dependents at the
published SDK version.

## Test Plan

- `yarn --cwd sdks/js install --immutable`
- `git diff --check`

---------

Co-authored-by: Lightspark Eng <engineering@lightspark.com>
GitOrigin-RevId: 9074c7bd827660f13f450a4c86c26a99e055ef60
## Summary

When coop exit key tweaking fails on one of the leaves,
the partial state causes an error that prevents progress later.
The coop exit then becomes stuck.
This fixes that by handling the partial state gracefully.

GitOrigin-RevId: 6e29bf2a2a725ec40894727d5c02eaa7c6d2bb09
## Summary

User errors are being returned as general errors in a variety of RPC
endpoints, which contributes to alert noise in monitoring.
This classifies them correctly.

GitOrigin-RevId: c3fc161d8cccd221e4e1679685bd036613b1d658
## Summary

Extracts the duplicated unilateral-exit test logic that was copy-pasted verbatim between `unilateral-exit-lightning.test.ts` and `unilateral-exit-transfer.test.ts` into two shared modules:

- **`tests/utils/unilateral-exit-helpers.ts`** — reusable test utilities: `didTxSucceed`, `makeExternalFundingUtxo` (faucets a fresh P2WPKH wallet and returns a ready-to-use UTXO + signing key), and `broadcastUnilateralExit` (drives the full fee-bump broadcast pipeline, mining timelocks and asserting confirmations).
- **`tests/integration/ssp/unilateral/shared.ts`** — shared constants (`DEPOSIT_AMOUNT`, `TRANSFER_AMOUNT`, `EXTERNAL_FUNDING_AMOUNT`) and wallet helpers (`closeWallets`, `waitForWalletBalance`, `waitForWalletLeaves`, `initializeWalletWithConnectedStream`, `createClaimedWallet`, `unilateralExitLargestLeaf`) used by both SSP unilateral-exit integration tests.

Both SSP test files now import from `shared.ts` instead of duplicating ~170 lines each. The `unilateral-exit.test.ts` "should unilateral exit" test is also simplified to use `broadcastUnilateralExit`, and the "watchtower" test is refactored to use `makeExternalFundingUtxo`. Describe labels are updated from `"SSP unilateral exit1/2"` to `"SSP unilateral exit — spark transfer"` and `"SSP unilateral exit — lightning receive"` for clarity.

## Test Plan

Existing integration tests cover this — no behavioral changes were made, only deduplication. Run the unilateral-exit integration test suites against a local Spark stack to verify.

GitOrigin-RevId: f52792e81d0a4d579f0ffe16f5cd790020dd4230
## Summary

Update cargo.lock file to include FROST 3.0.0

## Test Plan

CI

GitOrigin-RevId: 7297e1cfb6428450042f97cd5d9e27f30c87e4c9
…pening (#6414)

## Summary

Fixes a coop exit recovery issue where signing keyshare rotation could commit the ephemeral DB transaction during chain watcher block processing, leaving later leaf tweaks unable to continue. The chain watcher can now reopen ephemeral transactions after inline rotation commits, and coop exit key tweaking can resume when some leaves were already applied.

## Summary

- Allow chain watcher tx-backed ephemeral sessions to reopen a fresh ephemeral transaction after the current one is committed or rolled back.
- Roll back the current ephemeral session transaction instead of a stale captured transaction handle.
- Make coop exit key tweaking retryable by skipping leaves whose key tweak was already cleared.
- Freeze the signing keyshare dual-write decision for the whole coop exit tweak loop.
- Update ephemeral DB docs for chain watcher rotation behavior.

## Testing

- `mise exec -- go test ./so/chain`
- `mise exec -- go test ./so/ent ./so/db`
- `git diff --check`

GitOrigin-RevId: 5f91a1980babb0f4af3a7acee6f12c91e4b13103
## Summary

Bounds Spark SDK file logging shutdown so wallet cleanup cannot wait
indefinitely on a stalled file sink. The close path still flushes method
logs, remains idempotent, and treats file logging failures as non-fatal.

## Test Plan

- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk test-cmd
src/tests/logging-service.test.ts src/tests/wrapPublicMethod.test.ts`
- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk types`
- `yarn --cwd sdks/js workspace @buildonspark/spark-sdk format`
- `git diff --check`

GitOrigin-RevId: 768e427c68e7c537dd2e3db2515dead69a234ae3
…#6407)

## Summary

Phase 5 cleanup for the `RECEIVER_CLAIM_PENDING` rollout. Phase 2a
(#6354) added `INITIATED` as a transitional companion to
`RECEIVER_CLAIM_PENDING` in the MIMO claim path's receiver-status
switches. With Phase 2b dual-writes merged and the Phase 3 backfill task
drained + removed (#6379), every post-sender-tweak receiver row now goes
straight to `RECEIVER_CLAIM_PENDING`, so the transitional `INITIATED`
arms are unreachable in this code path.

Five sites in `so/handler/transfer_handler.go`:

- `ClaimTransfer` claimable-status check — drop `INITIATED` from the
"ok" cases. `validateTransferReadyForReceiverClaim` rejects
pre-sender-tweak transfers, so a receiver at `INITIATED` can no longer
legitimately reach this code.
- `ClaimTransfer` `useStoredKeyTweaks` switch — collapse the second case
group into a `default`, which captures the existing "no stored tweaks
yet" comment more directly. Satisfies the exhaustive lint without
re-enumerating the dead `INITIATED` arm.
- `InitiateSettleReceiverKeyTweak` `validateClaim` — drop `INITIATED`;
receiver at `INITIATED` now falls through to `default` with a clear
"unexpected status" error.
- `InitiateSettleReceiverKeyTweak` auto-promote — simplify from
`(INITIATED || RECEIVER_CLAIM_PENDING)` to just
`RECEIVER_CLAIM_PENDING`. Comment rewritten to drop the transitional
disclaimer.
- `SettleReceiverKeyTweak` rollback switch — drop `INITIATED` from the
"do nothing" case list.

Plus one doc-comment update on `mimoStuckReceiverStatuses` in
`so/handler/ssp_request_handler.go` documenting the deliberate exclusion
of `INITIATED` and `RECEIVER_CLAIM_PENDING` per the open question in
SP-2923's step 5 — pre-claim states aren't "stuck", they just haven't
been polled yet.

#### ⚠️ Risks

Each `INITIATED` removal is a behavior change at the case boundary:
branches that currently silently tolerate `INITIATED` now fall through
to `default`, which (mostly) returns an explicit error. The audit trail
is "Phase 2b dual-write + Phase 3 backfill drained → no post-tweak
`INITIATED` rows exist". A pre-flight prod check is in the Test Plan
below.

This PR ships independently of Phase 4 (#6345) ramp —
`KnobReadMIMODataModelQueryPendingTransfers` is fully off in prod, so
the read path swap is dormant code; the claim-path changes here are
gated only on dual-write + backfill, both done.

## Test Plan

- Pre-merge prod check: confirm no post-tweak rows are still at
`INITIATED`. Should return zero:

  ```sql
  SELECT COUNT(*)
  FROM transfer_receivers tr
  JOIN transfers t ON t.id = tr.transfer_id
  WHERE tr.status = 'INITIATED'
    AND t.status IN ('SENDER_KEY_TWEAKED', 'RECEIVER_KEY_TWEAKED',
'RECEIVER_KEY_TWEAK_LOCKED', 'RECEIVER_KEY_TWEAK_APPLIED',
                     'RECEIVER_REFUND_SIGNED');
  ```

- Existing handler test suite (`go test ./so/handler/`) covers each of
the 5 modified switches — including
`TestInitiateSettleReceiverKeyTweak_RefundSignedReturnsEarly`,
`TestSettleReceiverKeyTweak_RejectsEarlyTransferStatus`, and the
`revertClaimTransfer` table tests that assert receiver post-state — all
pass against the simplified branches.

Contributes to SP-2923

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 9d1afa66203ddfd0e8600c8a9353a331685e03d2
## Summary

Drops the legacy partial index
`idx_transferreceiver_pending_pubkey_time` on `transfer_receivers`,
which was added in #6109 to cover `INITIATED` + 4 `RECEIVER_*` stuck
statuses. After the `RECEIVER_CLAIM_PENDING` rollout completed (Phase
1's new partial in #6350, Phase 2b dual-write in #6351, Phase 3
backfill, Phase 4 read-path swap in #6345), the legacy index is no
longer driving any query and is dead weight on writes.

The new partial `idx_transferreceiver_claim_pending_pubkey_time`
(`RECEIVER_CLAIM_PENDING` + 4 `RECEIVER_*` stuck) takes over both
consumers cleanly:

- `queryPendingTransfersMIMO` receiver arm — Phase 4 dropped `INITIATED`
from `mimo.PendingReceiverStatuses()`, so it filters on the exact set
the new partial covers. (Note: this query is gated by
`KnobReadMIMODataModelQueryPendingTransfers`, which is fully off in prod
— so it isn't running today regardless.)
- `buildStuckTransferIDsQueryByPubkey` receiver arm — filters on the 4
stuck statuses, a strict subset of the new partial's `WHERE`. Same
leading-equality on `identity_pubkey`, same `(create_time DESC,
transfer_id DESC)` ordering. The planner picks it up; the residual
`r.status = ANY($3)` filter drops the `RECEIVER_CLAIM_PENDING` rows.

The receiver-arm comment in `ssp_request_handler.go` is updated to
reflect the new index, and the `dbseed` README + CLAUDE.md sections
describing partial-index population are simplified to the single
remaining receiver-pending partial.

#### ⚠️ Migration safety

`./scripts/check-migration-safety.sh` flagged `DROP INDEX` as REVIEW
(yellow). Analysis:

- The drop uses `DROP INDEX CONCURRENTLY IF EXISTS` with `--
atlas:txmode none` — non-blocking and idempotent.
- During the canary window where one prod pod has the legacy index and
one doesn't, both consumers' queries continue to work: pre-PR-2 pods
walk the legacy partial, post-PR-2 pods walk the new one. The read knob
being off prevents the most sensitive consumer
(`queryPendingTransfersMIMO`) from running at all.

## Test Plan

- `./scripts/check-migration-safety.sh
spark/so/ent/migrate/migrations/20260501185133_drop_legacy_pending_pubkey_time_index.sql`
— REVIEW (yellow) for `DROP INDEX`, no hard failures.
Backwards-compatibility analysis above.
- Existing `go test ./so/handler/ ./so/ent/...` test suite passes
against the regenerated schema.
- Pre-merge prod planner check (recommended): run `EXPLAIN (ANALYZE,
BUFFERS)` for the receiver arm of `buildStuckTransferIDsQueryByPubkey`
against `sparkoperator_0` with a medium-cardinality pubkey, both before
and after dropping the legacy index locally on a copy of the dataset.
Confirm the plan switches to
`idx_transferreceiver_claim_pending_pubkey_time` and exec time / buffer
counts stay in the same ballpark. Mirrors the perf probe approach used
for the rollout's earlier phases.

  ```sql
  EXPLAIN (ANALYZE, BUFFERS)
  SELECT r.transfer_id
  FROM transfer_receivers r
  INNER JOIN transfers t ON t.id = r.transfer_id
  WHERE r.identity_pubkey = '\\x...'
AND r.status = ANY(ARRAY['RECEIVER_KEY_TWEAKED',
'RECEIVER_KEY_TWEAK_LOCKED',
'RECEIVER_KEY_TWEAK_APPLIED', 'RECEIVER_REFUND_SIGNED'])
    AND r.create_time < NOW()
  ORDER BY r.create_time DESC, r.transfer_id DESC
  LIMIT 100;
  ```

Contributes to SP-2923

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 4cb30407d49812574d1906b6a5de24a5d7cdf476
…#6429)

## Summary

When a coordinator SO's request transaction aborts (e.g. the gRPC client
cancels mid-flow after `ConsensusPrepare` fan-out has already succeeded
against participants), its `FlowExecution` row never lands in the
database. Participant rows stay in `IN_FLIGHT` with locked resources,
and the reconciler — seeing `OUTCOME_UNSPECIFIED` from
`ConsensusQueryOutcome` — currently logs "possible data loss" and leaves
the row alone, forcing manual intervention.

This adds a presumed-abort recovery path:

- New nullable `prepare_payload` bytea column on `flow_executions`. Set
on PARTICIPANT rows at Create time by the consensus dispatcher;
immutable afterward. Schema hook is relaxed to permit this on
PARTICIPANT and to forbid it on COORDINATOR (mirroring the existing
`decision_payload` constraint).
- In the reconciler's `UNSPECIFIED` branch, if the row is past
`defaultFlowExecutionPresumedAbortAgeSec` (1200s — twice the existing
coordinator stall threshold) and carries a persisted prepare op,
synthesize a local `ConsensusRollback` gossip with the stored op as
`Operation` and dispatch it through `GossipHandler.HandleGossipMessage`.
That runs `FlowHandler.Rollback` to release any locked state and
transitions the participant row to `ROLLED_BACK` in one shot — no second
sweep tick required.
- Pre-rollout rows (NULL `prepare_payload`) hit the unchanged
log-and-skip path, so the change is safe to deploy ahead of any flips.
- Gated by the existing `KnobFlowExecutionReconcileEnabled` (off by
default); no new knobs introduced.

This is defense-in-depth. The underlying engine bug — coordinator
`FlowExecution` row write tied to the request transaction and cleanup
paths running on the user-cancellable context — will be fixed
separately. With this patch in, even a recurrence will self-heal once a
row crosses the 20-minute threshold.

## Test Plan

- New schema-hook tests in `spark/so/ent/flow_execution_test.go` cover
`COORDINATOR must not set prepare_payload` and `PARTICIPANT may set
prepare_payload`.
- New reconcile tests in
`spark/so/task/flow_execution_reconcile_test.go` cover the three cases:
legacy row (NULL payload, log+skip), young row + payload (under
threshold, leaves IN_FLIGHT), old row + payload (presumed-abort fires,
row → ROLLED_BACK).
- `mise lint` (Go) — `0 issues`.
- `mise test-go` — `3048 tests passed, 2 unrelated skips`.

GitOrigin-RevId: ded0be6b92111938905c8f45d0c9c123931bfeb2
## Summary

Pure relocation cleanup, no behavior change. Centralizes the status
sets, migration-phase compat helpers, and SQL fragment builders that the
pending- and stuck-transfer raw-SQL queries rely on into
`spark/so/mimo`. The package was introduced in #6345 with a deliberately
narrow initial scope (just `PendingReceiverStatuses` /
`PendingSenderStatuses`); this completes the migration.

### Technical Notes

The signature shift on the SQL helpers is the only non-mechanical bit:
the old `pendingCommonFilters(sqlArgs, args queryMIMOPendingArgs)`
couldn't move cleanly because `queryMIMOPendingArgs` is handler-private.
The new `mimo.AppendPendingCommonFilters(sqlArgs, network, types,
transferIDsFilter)` takes the individual fields, leaving the struct in
handler/ alongside the query builders that own it.

`appendNetworkFilter` and the network branch of `pendingCommonFilters`
were near-duplicates; both relocated as-is rather than deduped, to keep
this PR strictly relocation. A follow-up can collapse them once the
error-string difference (`"failed to convert proto network"` vs
`"invalid network"`) is reviewed.

## Test Plan

- Existing `mimo/status_test.go` (`TestPendingStatusesDisjoint`) and the
relocated `mimo/compat_test.go` suite (6 tests covering edge/column
fallback for sender + receiver) all pass against the new package.
- `so/handler/...` test suite green — including
`TestGetStuckTransfers_*` which exercises the relocated stuck-status
sets and SQL helpers end-to-end through `getStuckTransfersMIMO`, and
`TestQueryPendingTransfersMIMO_*` which covers all three pending-arm SQL
builders that now call `mimo.AppendPendingCommonFilters` /
`AppendPendingTimeFilter`.
- Net diff is −262 LOC; reviewer can sanity-check by greping for any
remaining references to the old unexported names — none should exist.

Resolves SP-2916

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 2e4c9baa0016aff039e2b9678c610203d4eafeaa
## Summary

Adds ESLint to the `@buildonspark/spark-sdk` package using the published
`@lightsparkdev/eslint-config` base config. The package lint scripts now
run ESLint directly, and the package declares the shared config, ESLint,
and eslint-watch as dev dependencies.

This first PR keeps the rollout intentionally broad-but-contained: it
adds the config and initial mechanical cleanup while leaving generated
artifacts, tests, and remaining dynamic boundary modules ignored so the
shared base rules can be enabled without weakening them. Follow-up
stacked PRs opt additional files in with focused cleanup.

## Test Plan

- `yarn workspace @buildonspark/spark-sdk lint`
- `yarn workspace @buildonspark/spark-sdk types`
- `yarn workspace @buildonspark/spark-sdk format`

---------

Co-authored-by: Lightspark Eng <engineering@lightspark.com>
GitOrigin-RevId: ab2c397bc61c4a9bae4ff9b357e78c643e25f9e6
…ound (#6440)

## Summary

Both `reconcile_stuck_flow_executions` and `sweep_stale_coordinator_flow_executions` now surface their findings as a task-level error so the scheduler's failure-logging pipeline routes them to the Slack alerting channel. Pre-change the tasks returned nil regardless of what they found, so a stuck participant row past the 300s threshold or a stale coordinator row past the 600s threshold only manifested via dashboard gauges or follow-on user-visible effects.

### Behavior

- **Healthy steady-state (no stuck rows) still returns nil** — quiet operators stay quiet.
- **Any stuck PARTICIPANT or stale COORDINATOR rows found by the threshold-based query → task returns an error.** This fires whether or not the reconciler successfully transitioned the rows on this tick: the alert is about the existence of the stuck condition, not the recovery outcome.
- **Recovery is unchanged.** Participant rows still go through the existing query-coordinator-then-dispatch-gossip path; coordinator rows still get bulk-transitioned to ROLLED_BACK (when the recovery knob is on) or logged-only (monitor-only mode). The error fires alongside that work.
- **Error message format** — `found N stuck PARTICIPANT/COORDINATOR flow_execution rows: {id=… op_type=… age=…}, {…}, {…} (+N more)`. Sample size capped at 3 with a count suffix so the Slack alert is human-skimmable but still indicates scale.

### Why no knob

Default-on (always alert when stuck rows are found). Stuck rows past these thresholds are abnormal under healthy operation; the existing alerting infrastructure already routes task errors, so the simplest implementation is an unconditional return-error. If a specific environment proves too noisy we can add a knob later — but with the engine cleanup-ctx fix in place this signal should be quiet most of the time.

## Test Plan

Updated all 13 existing reconcile/sweep tests to expect the new error contract via a small `requireStuckRowsErr(t, err, role)` helper — they continue to verify per-row recovery behavior alongside.

Three new tests:

- `TestReconcile_NoStuckRows_ReturnsNil` — steady-state participant reconcile must stay quiet.
- `TestSweepStaleCoordinatorFlows_NoStaleRows_ReturnsNil` — same for coordinator sweep.
- `TestStuckFlowExecutionError_IncludesActionableContext` — seeds 5 stuck rows and asserts the error message contains the role-qualified count, per-row `op_type=…`, and the `+2 more` suffix so the Slack alert reflects scale.

### Verification

- `mise lint` — `0 issues`.
- `mise test-go` — `3063 tests passed, 2 unrelated skips`.
- **Live integration sanity** — restarted the 5-SO `run-everything.sh` env, observed `reconcile_stuck_flow_executions` tick at the 30s interval and report `Task executed successfully` (no stuck rows in steady state). Confirms the task scheduler doesn't break under the new error contract when there's nothing to alert on.

GitOrigin-RevId: b3e8958bf4f06cb98ae87e63bbd07844e56a13a0
## Summary

Continues the spark-sdk ESLint rollout on top of the base config PR by
opting additional source modules and targeted test-adjacent files into
the shared base rules. The changes are grouped as typed-boundary
cleanup, async mock normalization, and small lint fixes needed before
enabling the remaining test tree.

This PR intentionally keeps the final broad `src/tests/**` opt-in out of
scope; that is isolated in the next stacked PR.

## Test Plan

- `yarn workspace @buildonspark/spark-sdk lint`
- `yarn workspace @buildonspark/spark-sdk types`
- `yarn workspace @buildonspark/spark-sdk format`

GitOrigin-RevId: 3cd138f33827d811360dcfbde8a3a2b0515a10e6
kphurley7 and others added 28 commits May 20, 2026 12:57
…ld (#6485)

## Summary

`ValidateShare` accepted fewer Feldman VSS proofs than required by the
threshold, causing the verification loop to degenerate to a
trivially-satisfiable check. An attacker could provide independent
random shares to each SO that all pass validation individually but are
not on the same polynomial, destroying FROST's threshold reconstruction
property.

## Test plan

- [ ] `go test ./common/secret_sharing/... -run
TestVerifiableSecretSharing` passes, including new
`CatchInvalidProofLengthTooFew` sub-test
- [ ] `golangci-lint run ./common/...` reports 0 issues
- [ ] Existing `CatchInvalidProofLengthTooMany` sub-test continues to
pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 103ef52895213ed7d25301e7e2d29ee48742bd6d
## Summary

This change closes a gap in the key tweak validation path: without the
cross-check, a client could submit a `SendLeafKeyTweak` whose
`SecretShareTweak` and `PubkeySharesTweak` are inconsistent. The SO
relies on `PubkeySharesTweak` to aggregate the combined public key after
a transfer, so an inconsistent value could corrupt the resulting leaf
ownership key.

## Test plan

- [ ] `golangci-lint run ./so/...` passes (0 issues)
- [ ] `TestValidateTransferPackage_PubkeyShareTweakMismatch` fails
before the fix and passes after
- [ ] All existing `TestValidateTransferPackage_*` tests continue to
pass
- [ ] Integration tests via `mise test-grpc-minikube` (pre-merge)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: c328ec7ee3e77d95ef4a7d4fa119407266578fff
## Summary

`SparkSspInternalService.CreateTree` accepted SSP/client-provided node
and refund transactions after checking only that their output totals did
not exceed the supplied previous output. It did not prove that the
transaction input actually spent the parent node/deposit outpoint before
building SO signing jobs. A malformed SSP request could therefore get SO
signature shares over a transaction that is not anchored to the
requested Spark parent, or store refund transactions that do not spend
the created node and make funds unrecoverable.

This change binds every CreateTree signing job to the expected outpoint:
- node and direct-node transactions must have exactly one input spending
the parent outpoint
- refund transactions must have exactly one input spending the node
output they refund
- direct refund transactions must spend the direct node output
- direct-from-CPFP refund transactions must spend the CPFP node output

For parent-node splits, CreateTree now also requires the parent node to
belong to the authenticated request identity and to be in a
split-eligible state (`CREATING` for in-flight tree construction or
`AVAILABLE` for an existing live node). This prevents an SSP-side
request from locking another owner's parent node or creating children
for nodes already locked by another operation.

## Test Plan

- `go test -tags lightspark ./so/handler -run
'TestPrepareSigningJobsRejects(NodeTxWrongParentOutpoint|RefundTxWrongNodeOutpoint|ParentNodeOwnerMismatch|IneligibleParentNodeStatus)'
-count=1`
- `go test -tags lightspark ./so/handler -count=1`
- `git diff --check`
- Attempted `go test -tags lightspark ./so/grpc_test_internal -run
'TestTreeCreationWithMultiLevels|TestTreeCreationAddressGeneration'
-count=1`; local cluster failed before exercising the patch with
`unknown service spark_ssp_internal.SparkSspInternalService`.
GitOrigin-RevId: 41fe18b89c68feff3110f4985cacc0944a2ecf6c
## Summary

Bind the SendTransferV3 refund signing helper to the concrete parent
transaction it signs against.

Previously `buildSigningJobForRefund` computed a Taproot sighash using
parent tx output 0, but did not check that the caller-provided refund
transaction actually spent `parent_tx:0`. The higher-level
StartTransferV3 path validates transfer packages before signing, but
this helper is the boundary that constructs the FROST signing message.
If a future path or validation gap reached it with a
same-value/same-script refund for a different outpoint, an SO could sign
the wrong spend.

This change rejects refund transactions unless they have exactly one
input and input 0 spends the parent tx output being signed. Tests now
cover the shared helper directly and the package-level CPFP, direct, and
direct-from-CPFP transfer-package lists that feed it.

## Test Plan

- `env -u MINIKUBE_IP go test ./so/handler -run
'TestBuildSigningJobForRefundValidatesParentOutpoint|TestBuildSendTransferAggregationJobsValidatesAllRefundPackageOutpoints'
-count=1 -timeout=180s`\n- `env -u MINIKUBE_IP go test ./so/handler
-count=1 -timeout=300s`\n- `git diff --check`
GitOrigin-RevId: 8a986e4e242a7bdd3ed6904f2542b7f85d0c105e
## Summary

Force-resolve transitive `protobufjs@8.0.0` to `8.0.1` to clear a Critical advisory ([GHSA-xq3m-2v4x-88gg](GHSA-xq3m-2v4x-88gg) — arbitrary code execution in protobufjs).

Dependency path (transitive only — nothing in the repo depends on it directly):

`sdks/js/apps/artillery` → `artillery@^2.0.31` → `@artilleryio/int-core@2.26.0` → `@opentelemetry/otlp-transformer@0.212.0` → `protobufjs@8.0.0`

`@opentelemetry/otlp-transformer@0.212.0` pins `protobufjs` exactly at `8.0.0` (no caret), so a normal upgrade won't pick up the fix. Added a Yarn `resolutions` entry in `sdks/js/package.json` forcing `protobufjs@8.0.0` → `8.0.1`. Rest of the workspace was already on the safe `protobufjs@7.5.7` line, and Artillery is dev-only, so production runtime exposure was effectively nil — this is hygiene to keep `yarn npm audit` clean.

## Test Plan

- `yarn install` from `sdks/js/` — succeeds; lockfile resolves `protobufjs@8.0.1`.
- `yarn why protobufjs` — otlp-transformer branch shows `8.0.1`; other branches still on `7.5.7`.
- `yarn npm audit --recursive --all | grep -c "Severity: critical"` → `0` (was `1`).

GitOrigin-RevId: 546397492c143ca2e0d312c96c01934ebd28eef4
## Summary
Recheck tree status immediately before creating exit transaction signing
jobs.

## Test Plan
- `/Users/kph/.local/share/mise/installs/go/1.25.1/bin/go test -tags
lightspark ./so/handler -run
TestSignExitTransactionRejectsTreeExitedAfterValidation -count=1 -v`

GitOrigin-RevId: fd8374277142e354ce68807c51ea8fc27962abd4
## Summary

Fail closed when a legacy claim path sees an existing multi-receiver
transfer.

## Test Plan

- go test -tags lightspark ./so/handler -run
'TestClaimTransferRejectsExistingMultiReceiverWhenMimoReadDisabled'
-count=1
GitOrigin-RevId: f639d784716f31103e1b5f1afde5c45a9bce489f
## Summary

Require verifiable secret shares to include exactly one polynomial
commitment per threshold coefficient. This prevents malformed short
proof sets from passing preimage-share validation.

## Test Plan

- `GOFLAGS=-tags=lightspark go test ./common/secret_sharing -run
'TestVerifiableSecretSharing' -count=1 -timeout=5m`\n-
`GOFLAGS=-tags=lightspark go test ./so/handler -run
'TestValidatePreimageShareRejectsShortProofSet' -count=1 -timeout=5m`

GitOrigin-RevId: 4d35d6a542a5f920a902824103357bf1477b00a0
## Summary

Keep unit testing to not rely on running services

GitOrigin-RevId: 023f36ece3b08bdf4cfeaff8c526cdaff7d0d7ba
## Summary

Extends the proto layer so transfer responses carry richer
per-participant data.

- Adds `id` and `completion_time` to `TransferReceiver` (`status` was
added in #7287)
- Adds a new `TransferSender` message with `id` + `identity_public_key`
- Adds `repeated TransferSender senders = 14` to `Transfer`. For
single-sender flows today this always has one entry mirroring
`sender_identity_public_key` — starts the deprecation clock on that
legacy field ahead of MIMO v1
- Adds `string transfer_receiver_id = 8` to `TransferLeaf` so consumers
can associate each leaf with its receiver (load-bearing for MIMO v0
multi-receiver transfers)

Go marshaling in `marshalWithLeaves` and `marshalTransferLeafProto`
populates the new fields from the preloaded `TransferSenders` /
`TransferReceivers` edges. All changes are additive — older SDK clients
that don't read the new fields are unaffected.

## Test Plan
- `TestMarshalProto_PopulatesSenders` — confirms `Senders[]` is
populated from the `TransferSenders` edge
- `TestMarshalProto_PopulatesReceiverIDAndCompletionTime` — confirms
`Id` populates always; `CompletionTime` only when the schema field is
non-zero
- `TestMarshalProto_PopulatesLeafTransferReceiverID` — confirms each
marshaled leaf carries its `transfer_receiver_id`

---
Contributes to SP-2721
created with claude session ec9dd432-5dc5-409c-9a8c-89e7cfd57e47

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
GitOrigin-RevId: 1f213e38cd2486a8e3f27672edba5e3011da363b
…ageRequests (#5528)

## Summary

Fixed a race condition where returning a stuck outbound transfer could
leave the corresponding inbound transfer claimable by the receiver.

## Test Plan
- New unit test
`TestReturnStuckTransfers_AlsoCancelsSiblingPreimageRequest`: creates
Transfer A and B sharing a `payment_hash`, calls `ReturnStuckTransfers`
with Transfer A, asserts both PreimageRequests are `RETURNED`.
- `mise lint && mise test-unit` pass.

---------

Co-authored-by: Claude <noreply@anthropic.com>
GitOrigin-RevId: a2e4032a5d31eaed2666532967b9df921978a6f8
## Summary

Swap transfer packages can include optional direct and direct-from-CPFP
refund transactions. The existing swap validation path only validated
CPFP refund transactions, while the signing path still built FROST
signing jobs for the optional direct refund jobs when they were present.

This change keeps those direct refund jobs optional for swap flows, but
validates any supplied direct or direct-from-CPFP refund transaction
before the transfer is persisted or signed. It adds regression coverage
for invalid direct refund payloads across swap, counter-swap, and
swap-v3 transfer types.

## Test Plan

- `go test ./so/handler -run 'TestValidateUserTxs_Swap_Package'
-count=1`\n- `go test ./so/handler -count=1`
GitOrigin-RevId: ee6bb405b372f0da87e36ffc94428d138a4ae9e9
## Summary

`PrepareTreeAddress` and `CreateTree` can operate from either an
on-chain deposit output or an existing parent node output. Both requests
are authenticated as `user_identity_public_key`, but the tree-creation
path did not verify every deposit-address row or parent node used by the
request belonged to that identity.

That left the SSP-facing path able to target another user identity
deposit address or available parent node. Even without the caller having
the victim user signing share, the operator-side work can allocate
derived keyshares, create incorrect in-flight tree state, and, for
parent-node splits, eventually mark the victim parent as split.

This change requires the request identity to match the parent deposit
address before `PrepareTreeAddress` or `CreateTree` continues, requires
`ParentNodeOutput` sources to match the parent node owner, and requires
every child output resolved during `CreateTree` to use a deposit address
owned by the same request identity. The tests cover parent-node owner
mismatch, `CreateTree` deposit-address owner mismatch,
`PrepareTreeAddress` deposit-address owner mismatch, and child-output
deposit-address owner mismatch.

## Test Plan

- `env -u MINIKUBE_IP go test -tags lightspark ./so/handler -run
"TestPrepare(SigningJobs|TreeAddress)Rejects(ParentNodeOwnerMismatch|DepositAddressOwnerMismatch|ChildDepositAddressOwnerMismatch)"
-count=1 -v -timeout=180s`
- `env -u MINIKUBE_IP go test -tags lightspark ./so/handler -count=1
-timeout=240s`
- `git diff --check`
GitOrigin-RevId: 4ce7ac8dd0e3fa6e5fbca47b7a3897eae22e0267
## Summary

PR 3 of 3 in the **migrate v3 send-transfer to 2PC** stack. Stacked on
#6560 (flow body) and #6528 (plumbing). Final cutover plus integration
tests.

Flips \`StartTransferV3\` to route through the 2PC engine when
\`KnobUseConsensusTransfer\` is set. The legacy \`syncTransferV3Init\` +
\`syncSettleSenderKeyTweaks\` fanout stays in place for one release
cycle behind the knob default of 0.

### Production change

- **\`StartTransferV3\`** reads \`KnobUseConsensusTransfer\`; non-zero
routes to \`startTransferV3Consensus\`, zero falls through to
\`startTransferV3Internal\`.
- **\`startTransferV3Consensus\` (new)** runs the coordinator-only gates
that participants can't redo from inside the engine — single-sender
guard, sender identity auth
(\`EnforceSessionIdentityPublicKeyMatches\`), the multi-receiver MIMO
knob check, and the transfer-size limit guard. Then builds the
coordinator flow via \`buildSendTransferCoordinatorFlow\`, fetches the
engine, and runs \`Execute\` over
\`CONSENSUS_OPERATION_TYPE_SEND_TRANSFER\`. The response is the one
\`BuildCommitPayload\` populated on the coordinator flow.

### Flow handler fixes (uncovered during end-to-end local verification)

Local verification baked the knob into \`so.template.config.yaml\`
temporarily (reverted before commit). Two issues showed up that didn't
surface in PR 2's unit tests:

1. **Per-job public-shares filter.** \`aggregateLeafSignature\` now
derives \`publicShares\` from the per-job signature shares it actually
received, not the global \`participantIDs\` set. For send-transfer,
different leaves can carry different round1 commitment sets (the user
picks the t-of-n signing set per leaf), so \`participantIDs\` is a
superset of any given job's signing set. The renew_leaf pattern got away
with the superset because every job in renew is for the same leaf and
thus the same signing set; transfer can't rely on that.
2. **Missing \`UserSignatureShare\`.** \`aggregateLeafSignature\` now
passes \`UserSignatureShare\` to \`AggregateFrost\`. The user provides
one signature share per refund tx via
\`UserSignedTxSigningJob.UserSignature\`; FROST aggregation needs that
share. Tracked alongside the \`helper.SigningJobWithPregeneratedNonce\`
on \`sendTransferLeafSigningJobs\` so \`BuildCommitPayload\` can thread
it through.

### Integration tests (\`send_transfer_consensus_test.go\`)

| Test | What it asserts |
|------|------------------|
| \`TestSendTransferV3_Consensus_HappyPath\` | Knob on, v3 send →
\`SENDER_KEY_TWEAKED\` on every operator's DB, receiver claims
successfully end-to-end. |
| \`TestSendTransferV3_Consensus_WritesFlowExecutionRows\` | Knob on, v3
send → each operator writes exactly one new \`FlowExecution\` row, same
id across operators, \`COMMITTED\`, \`COORDINATOR\` role on the
coordinator and \`PARTICIPANT\` on the rest. |
| \`TestSendTransferV3_Consensus_KnobOffUsesLegacyPath\` | Knob off, v3
send still succeeds and writes **no** SEND_TRANSFER \`FlowExecution\`
rows — guards against the routing check silently flipping. |

All three gate on \`HasLocalSparkIngressHost\` (canonical
"minikube-only" check) plus \`NewKnobController\` availability — same
pattern as the renew_leaf consensus tests.

## Proof of Work

End-to-end run against the local env (5 operators, knob baked into
static config, then reverted before commit):

\`\`\`
$ go test -v -run TestV3ClaimTransferSingleReceiver
... transfer reached the consensus path, completed signing, applied
tweaks ...

$ psql -d sparkoperator_N -c "SELECT status FROM transfers ORDER BY
create_time DESC LIMIT 1;"
operator_0: COMPLETED
operator_1: COMPLETED
operator_2: COMPLETED
operator_3: COMPLETED
operator_4: COMPLETED

$ psql -d sparkoperator_N -c "SELECT role, status, op_type FROM
flow_executions WHERE op_type = 4 ORDER BY create_time DESC LIMIT 1;"
operator_0: COORDINATOR | COMMITTED | 4
operator_1: PARTICIPANT | COMMITTED | 4
operator_2: PARTICIPANT | COMMITTED | 4
operator_3: PARTICIPANT | COMMITTED | 4
operator_4: PARTICIPANT | COMMITTED | 4
\`\`\`

## Test Plan

- \`go build ./...\` → clean
- \`golangci-lint run ./so/handler/... ./so/grpc_test/...\` → 0 issues
- \`go test ./so/handler/... ./so/knobs/...\` → all pass
- End-to-end local verification confirmed the consensus path produces
the same observable end-state as the legacy fanout (see Proof of Work
above).

GitOrigin-RevId: 667e956448d06aded48ce4facb1e0d63b1272383
## Summary

Adds a scheduled, knob-gated backfill for legacy `signing_keyshares` rows that still store their secret only in the main database.

The backfill copies each legacy `secret_share` into the ephemeral `signing_keyshare_secrets` table at version `0`, then updates the main `signing_keyshares.secret_version` pointer. It processes rows in bounded batches ordered by UUIDv7 `id`, uses `FOR UPDATE SKIP LOCKED` to avoid blocking production operations, and is idempotent if the ephemeral write succeeds but the main DB commit fails.

The task is disabled by default behind:

`spark.so.signing_keyshare.backfill_secrets_enabled`

## Test Plan

- `mise exec -- go test ./so/task -run TestBackfillSigningKeyshareSecrets`
- `mise exec -- go test ./so/task ./so/knobs`

GitOrigin-RevId: a5537a422d7034cb341096fe408737ba7571437f
## Summary

When `claim_transfer` hits a `NOWAIT` lock conflict on the transfer row,
the SO returns gRPC `ABORTED` with a message that leaks Postgres
`SQLSTATE 55P03` and the internal `"transfers"` table name — and no
retry hint. Third-party clients can't tell the operation is safe to
retry.

This PR sanitizes the wire message at all three `claim_transfer` paths
(`ClaimTransfer`, `ClaimTransferTweakKeys`, `claimTransferSignRefunds`)
and attaches `google.rpc.RetryInfo` (100ms) to
`AbortedConcurrentClaimConflict` and `AbortedTransactionPreempted`. The
original Postgres error stays in server-side logs for debuggability.

## Test Plan
- New unit tests in `errors_test.go` cover `RetryInfo` attachment,
wire-message sanitization, and retry-hint preservation across
`WrapErrorWithMessage` / `WrapErrorWithReasonPrefix`
- Round-trip test verifies `RetryInfo` survives serialization to a bare
`*status.Error` (the form non-SDK clients see)

---
Resolves SP-3153
created with claude session fb9e54f1-6a3a-4b69-95d6-debb37735543

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 0ecd8ebb92c65e7f22473d0fbe464b26d55c2b79
## Summary

The proto contract on `GetSigningCommitmentsResponse`
([`spark.proto:1007-1013`](../blob/main/protos/spark.proto#L1007-L1013))
states that commitments are returned in the requested node-ID order,
`[count] x [num_node_ids]`. The handler didn't enforce that — when one
or more requested IDs weren't in `tree_nodes`, it silently returned
commitments only for the IDs it found, with no indication the response
was truncated.

Downstream, that surfaces as an opaque `IndexError` in sparkcore's
`gen_signing_commitments` (which assumes the contract holds). This PR
rejects requests for unknown node IDs with `NotFound` and names the
missing IDs, so callers can fix the reference (or surface the failure
cleanly) instead of debugging an off-by-one in the consumer.

## Test Plan
- New `TestGetSigningCommitmentsRejectsUnknownNodeIDs` covers the
missing-ID path
- Updated the existing `TestGetSigningCommitments/non-existent_node_ID`
case (which codified the buggy behavior) to assert the new `NotFound`
response

---
Contributes to SP-3154
created with claude session fb9e54f1-6a3a-4b69-95d6-debb37735543

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 794fb53443bcf03888444a72d53e6c5bdf8fa7dd
## Summary

Adds CAS for StrorePreimage, updating the PreimageRequest with preimage
if status=WaitingForPreimage, returning idempotently if
status=PreimageShared or returning an error otherwise.

## Test Plan

- Added `TestStorePreimage_RejectsConcurrentReturnedWriteback` in
`lightning_preimage_exploit_test.go`:
  1. Creates a `PreimageRequest` in `WAITING_FOR_PREIMAGE`.
2. Simulates a racing cancel by directly setting the row's status to
`RETURNED` while keeping the in-memory entity stale.
3. Calls `StorePreimage` with the stale entity and asserts an error is
returned, the row stays `RETURNED`, and its `preimage` column is not
overwritten by the racing write.
- Verified the regression test fails against the pre-fix code (no error
returned; preimage persisted) and passes after the fix.
- `go test -tags lightspark ./so/handler/...` — green.
- `mise lint` — 0 issues.

[SP-3105]:
https://lightspark.atlassian.net/browse/SP-3105?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: d036cafb7e73fc94e7af9accf9f4c1121e3c5422
…af validation (#6486)

## Summary

Security: renew leaf validation now has explicit test coverage that a `refundTimelock` below `TimeLockInterval` (100 blocks) is rejected on both the regular and zero-timelock renewal paths, preventing a previous owner from racing against a recipient with zero CSV separation.
GitOrigin-RevId: dbb7f4e2f765e4424138ea08f2b6d2f86cbf8c9c
…7319)

## Summary

Legacy `queryTransfers` filters status on `transfers.status` (parent
axis). For multi-receiver MIMO transfers an individual
`transfer_receivers.status` can diverge — a receiver querying
`[COMPLETED]` while the parent lags silently loses their own row. This
adds an Ent-based by-participant fallback handler as the final routing
step in `QueryAllTransfers` for participant-bearing shapes. Receiver-arm
filters apply to `transfer_receivers.status` via the existing
translation in `mimo.ReceiverArmFilters`; sender and SR1 arms compose
naturally. Nil-participant traffic stays on legacy, which already has
the per-transfer access-check pass. Gated by
`KnobReadMIMODataModelByParticipantFallback` (0 prod, 100 Tiltfile).

The legacy path has multi-second outliers today on participant-bearing
shapes the fallback will claim. `KnobLogTransferQueryInvocationsSlowMs`
(ms threshold, default 0) makes `logQueryTransfersInvocation` emit
unconditionally when `elapsed >= threshold` (`slow_bypass=true`) —
diagnostic surface for slow callers without cranking the per-call
sampling knob to 100. Applies across all 7 transfer-query handlers.

## Test Plan

- Equivalence cases (12 shapes) confirm fallback matches legacy on
single-receiver fixtures: bare sender/receiver/SR1, sender + statuses,
SR1 + statuses, pagination, time bounds, ascending order,
negative-pagination / network-unset / invalid-status rejection
- `TestQueryAllTransfers_ByParticipantFallback_PerReceiverDivergence`
constructs a multi-receiver MIMO transfer where one receiver is
`COMPLETED` and the parent lags — fallback returns the row, legacy drops
it

---
Contributes to SP-3135
created with claude session b53e1f4d-94ac-4294-b860-d86c6874657a

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 43520b7cd2fffee58d179e182bdff9491e958345
## Summary

Update the maximum runtime for the ephemeral database backfill to 2 minutes. Currently, it keeps trying to backfill for up to 50s. If the last batch of backfill rows pushes the task time over a minute, that batch is lost (not permanently; it is retried on the next run) and will need to be redone in the next job.

This is not a breaking problem, but it does cause alert noise, so here we'll double the time "budget" to 2 minutes to let the last batch finish before the job cancels it.

## Test Plan

It builds / passes tests

GitOrigin-RevId: 48b476ca42ad52f9c5ef830c9485197acc7da861
## Summary

Fixes a wedged-RKT recovery hole in `claim_transfer`'s 2PC. Once
`persistCoordinatorClaimKeyTweak` has committed `leaf.KeyTweak` on the
coordinator (status `RECEIVER_KEY_TWEAKED`), a retry with a fresh
polynomial no longer silently overwrites the anchored proofs. Instead,
the retry drives the 2PC with the stored proofs and either heals to a
consistent commit (peers match) or rolls the cluster back to
`SENDER_KEY_TWEAKED` (peers diverge or never committed Phase 1). The
next user retry can then install a fresh polynomial cleanly.

Two changes to `ClaimTransfer` / `persistCoordinatorClaimKeyTweak`:

1. Add `ReceiverKeyTweaked` (and the MIMO
`TransferReceiverStatusKeyTweaked`) to the `useStoredKeyTweaks=true` set
so a retry at RKT reuses anchored proofs and skips forwarding the SDK's
fresh encrypted package to peers.
2. Restore the `len(leaf.KeyTweak) == 0` guard in
`persistCoordinatorClaimKeyTweak` so it never overwrites stored proofs.

The PR #7280 regression test that asserted the prior override behavior
is replaced with one that drives the full stranded-RKT → rollback →
retry-succeeds recovery through `wallet.ClaimTransferV2` and asserts
cluster keyshare consistency at the end. The companion
`…RejectedWhenPeerLockedAtRKL` test is unchanged.

## Test Plan

- [x] `golangci-lint run ./so/handler/ ./so/grpc_test/` clean.
- [x] `go test ./so/handler/` green (both `-tags=lightspark` and OSS).
- [ ] `go test ./so/grpc_test -run
'TestClaimTransferV2_SettleAtomicity|TestClaimTransferV2_StrandedRKT|TestClaimTransferV2_FreshPolynomialRejected'`
against a 5-SO local cluster.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 31edcd9943a26ba64218c4e500f314e90bf48cec
## Summary

Fixes a nil-pointer panic in the token signing path on
`SparkTokenInternalService/exchange_revocation_secrets_shares`.

Two call sites in `so/handler/tokens/internal_sign_token_handler.go`
were reading `RevocationKeyshare.SecretShare` directly off the ent entity instead of going through `SigningKeyshare.GetSecretShare(ctx)`:

1. `getSecretSharesNotInInputFromSpentOutputs` — dereferenced the field
with no nil check, panicking when `secret_share` was NULL on the
main DB row.
2. `buildOperatorPubkeyToRevocationSecretShareMap` — nil-checked the
field but returned an error instead of falling back to the ephemeral
store, so the RPC would have failed (no panic) for the same rows.

Since #5376 made `secret_share` `Optional().Nillable()` and the SP-3127 ephemeral-DB backfill began moving secrets out of the main DB, the hydration helpers (`ent.HydrateSigningKeyshareSecrets` +
`SigningKeyshare.GetSecretShare(ctx)`) are the only correct way to
resolve a keyshare's secret — direct field access only works on the
shrinking subset of rows that still have a populated `secret_share`
column (or while `KnobSoSigningKeyshareDualWriteSecret` is at 100%).
Both call sites in this file already use `GetSecretShare(ctx)`
correctly (see `canRecoverAndFinalizeTransaction` and
`recoverFullRevocationSecrets`); these two were missed.

### Changes

- `getSecretSharesNotInInputFromSpentOutputs`, `prepareResponseForExchangeRevocationSecretsShare`,
and `buildOperatorPubkeyToRevocationSecretShareMap` now take a `ctx context.Context`; ctx is threaded from the existing
`exchangeTransferRevocationSecrets` and `getSecretSharesNotInInput`
callers.
- Both functions batch-hydrate `RevocationKeyshare` secrets via a new
`hydrateRevocationKeyshareSecrets` helper before the per-output loop, so the subsequent `GetSecretShare(ctx)` calls hit the cache rather
than issuing N round-trips to the ephemeral DB.
- `TokenPartialRevocationSecretShare.SecretShare` accesses are
unchanged — that field is a value-type `keys.Private`, not
Optional/Nillable, and can't exhibit the same bug.

## Test Plan

- `go build ./so/handler/tokens/...`
- `go test ./so/handler/tokens/...` — passes
- `golangci-lint run ./so/handler/tokens/...` — 0 issues

Existing unit tests cover the populated-`secret_share` happy path
through `getSecretSharesNotInInput`. Pre-merge follow-up: add a
postgres test that creates a `SigningKeyshare` with NULL `secret_share`
and a populated `secret_version` (matching loadtest's post-backfill
shape) and exercises `exchange_revocation_secrets_shares` end-to-end to guard against this regressing.

GitOrigin-RevId: a8ea1d77ea2003069bec91bf4b05a5be2a9efc5e
## Summary

Add instructions to code agents on how to load signing keyshare secrets when the ephemeral database is active.

## Test Plan

Builds; this is doc-only

GitOrigin-RevId: fea1be8060af46b56564b9f3b5f511407f1d3b47
## Summary

Adds the published `@lightsparkdev/eslint-config` React app preset to
the Spark Vite example so it participates in workspace linting.

The Vite template had its app TypeScript config in `tsconfig.app.json`,
but the shared base lint config expects source files to be covered by
package-level `tsconfig.json`. This PR moves the app compiler options
into `tsconfig.json`, deletes `tsconfig.app.json`, and keeps the build
script typechecking both `tsconfig.json` and `tsconfig.node.json`.

Fixes the lint findings surfaced in the app source by typing the debug
window hook globally, formatting unknown errors explicitly, and marking
async UI callbacks as intentionally fire-and-forget.

## Test Plan

- `yarn workspace @buildonspark/spark-vite-app lint`
- `yarn workspace @buildonspark/spark-vite-app format`
- `yarn workspace @buildonspark/spark-vite-app build`
- `yarn turbo run lint --filter @buildonspark/spark-vite-app`

GitOrigin-RevId: a0cb6ff2a32c7142740ab777e51d2cea3590a4e8
…s (#7339)

## Summary

The OpenTelemetry SDK ships gRPC duration histograms with default boundaries that
top out at **10s**. Any unary handler that legitimately runs longer than 10s
(`dkg.DKGService/start_dkg`, large gossip batches, slow client RPCs) falls into
the `+Inf` bucket — `histogram_quantile` returns `+Inf` and the latency panels
flatline at the precise moment they are most useful.

This shows up in two existing panels on the Spark dashboard:

- **DKG Service Latency p95** (`rpc_server_duration_milliseconds`)
- **DKG Latency p95** (`rpc_client_duration_milliseconds`)

Both saturate during recent `spark-dkg-failures` incidents on `spark-dkg-0`,
where protocol phases consistently take ~46s, and the recent bump of the
`spark.so.grpc.server.unary_handler_timeout` and `spark.so.grpc.client.timeout`
knobs to 180s for `/dkg.DKGService/start_dkg` will keep many invocations in the
60–180s range.

### Change

Register two OpenTelemetry `MeterProvider` views — one for `rpc.server.duration`
and one for `rpc.client.duration` — that override the histogram boundaries with
an extended set. The new boundaries:

```
0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000,
2500, 5000, 7500, 10000, 15000, 30000, 60000, 90000, 120000, 180000
```

This preserves every existing boundary (so sub-100ms detail for fast RPCs is
unchanged) and adds five new high-end buckets at 15s, 30s, 60s, 90s, 120s, 180s.
The 180000 boundary matches the new handler/client timeout exactly, so timeouts
will count there rather than `+Inf`.

Cardinality impact is small: ~6 extra bucket series per existing histogram
labelset (rpc_service × rpc_method × pod × workspace), well under VictoriaMetrics
budget.

### Notes

- No behaviour change beyond histogram bucket layout. Existing alerts that
  threshold on p95/p99 will start returning real numbers instead of `+Inf` for
  long-tail RPCs.
- Histogram bucket changes are not retroactive — existing series for the old
  boundaries will continue to be written by older pods until they roll, and
  Prometheus will accumulate new labelsets with the extended `le` values from
  the moment this rolls out. p95/p99 over the rollout window will look noisy
  for a few minutes while both labelsets are live.

## Test Plan

- [x] `go build ./bin/operator/...` clean
- [x] `mise lint` clean (0 issues, no ent field removal violations)
- [ ] After deploy, confirm `count(group by (le) (rpc_server_duration_milliseconds_bucket{rpc_service="dkg.DKGService"}))` returns 22 boundaries (was 16) and that 'DKG Service Latency p95' renders an actual value during a slow run instead of `+Inf`.

GitOrigin-RevId: 2f2d574f712b7cd2362668fcf8460eee6335ba48
## Summary

Adds the published `@lightsparkdev/eslint-config` base config to
`@buildonspark/spark-frost-bare-addon` so the package participates in
workspace linting.

This package is intentionally CommonJS/Bare runtime code, so the local
ESLint config keeps the base rules but disables
`@typescript-eslint/no-require-imports` for JS files. The lint fixes
remove unused test bindings and an unused catch binding.

## Test Plan

- `yarn workspace @buildonspark/spark-frost-bare-addon lint`
- `yarn workspace @buildonspark/spark-frost-bare-addon format`
- `yarn workspace @buildonspark/spark-frost-bare-addon test`
- `yarn workspace @buildonspark/spark-frost-bare-addon package:checks`
- `yarn turbo run lint --filter @buildonspark/spark-frost-bare-addon`

---------

Co-authored-by: Lightspark Eng <engineering@lightspark.com>
GitOrigin-RevId: 4aff4ccb3c04cf91340c80cb9cd8d15f7dbea15a
Sync the SDK values for `LightningReceiveRequestStatus` with the values from GraphQL definition file.
@TwistedHardware

TwistedHardware commented Jun 8, 2026

Copy link
Copy Markdown
Author

Any chance this gets merged? Working with Hodl LN invoices without these status values is guess work instead of a clear process.

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.