Skip to content

feat(signer): BYOC per-capability pricing + real capability/model usage labels#3972

Open
seanhanca wants to merge 18 commits into
feat/add-model-id-signer-kafkafrom
feat/byoc-per-cap-pricing-and-usage-labels
Open

feat(signer): BYOC per-capability pricing + real capability/model usage labels#3972
seanhanca wants to merge 18 commits into
feat/add-model-id-signer-kafkafrom
feat/byoc-per-cap-pricing-and-usage-labels

Conversation

@seanhanca

Copy link
Copy Markdown

Summary

Combines #3966 (usage-attribution labels) and #3967 (per-capability pricing) into a single PR. Both change-sets live entirely on the remote signer (server/remote_signer.go GenerateLivePayment and its cmd/livepeer/starter flag plumbing); no orchestrator code is touched. This PR supersedes and replaces #3966 and #3967, which are being closed in favor of this one.

The two features are complementary and are both included in full:

  • Usage attribution (from fix(signer): meter real BYOC capability + model_id for usage events #3966): additive Capability / ModelID fields on RemotePaymentRequest override the metered pipeline/model_id labels on the create_signed_ticket usage event, decoupling usage attribution from Type (which still drives fee/pixel routing). Includes the addressed review fixes: sanitizeUsageLabel (TrimSpace + 128-rune cap via maxUsageLabelLen) to bound Kafka payload size / label cardinality, corrected docstring, and hardened TestResolveUsageLabels (per-subtest require, whitespace-only + oversized cases).
  • Per-capability pricing (from feat(signer): charge BYOC live payments at the per-capability price #3967): resolveByocPrice resolves the BYOC fee from the orchestrator's advertised OrchestratorInfo.CapabilitiesPrices (keyed on req.Capability), gated behind the new default-OFF -byocPerCapPricing flag (LivepeerNode.ByocPerCapPricing). Includes the addressed review fixes: additional Type==lv2v gate on the pricing branch, and skip-invalid-duplicate-and-keep-scanning in the price resolver. The resolved price is written back to oInfo.PriceInfo so state init / initialPrice / max-price ceiling / ExpectedPrice / the validatePrice doubling guard stay cap-vs-cap consistent.

When the flag is OFF (default), req.Capability is empty, Type != lv2v, or no usable cap price matches, behavior is byte-identical to the base-price path — zero regression.

Provenance / review fixes carried over

Cherry-picked #3966 head f7d2f83 on top of #3967 head 84c706a; the two edit resolveUsageLabels/TestResolveUsageLabels complementarily and merged cleanly (no manual conflict). All previously-addressed Copilot review feedback on both #3966 and #3967 is preserved. Commits retain original authorship (incl. the Co-authored-by: Cursor trailer on the pricing feat commit).

Test plan

  • resolveUsageLabels / sanitizeUsageLabel / maxUsageLabelLen present and used (labels union).
  • resolveByocPrice / -byocPerCapPricing / Type==lv2v gate / invalid-duplicate skip present (pricing union).
  • gofmt-clean, no conflict markers, imports (math, strings) present.
  • CI: hermetic CGO-free tests TestResolveUsageLabels, TestResolveByocPrice, TestGenerateLivePayment_ByocPerCapPricing (local run blocked only by host ffmpeg/lpms CGO mismatch; validated via CI).

Made with Cursor

eliteprox and others added 14 commits June 25, 2026 17:12
…3946)

- Introduced a new function `LiveAIDisableRTMPOutput` to determine if RTMP egress should be skipped based on the environment variable `LIVE_AI_DISABLE_RTMP_OUTPUT`.
- Updated the RTMP output collection logic in `setupStream` and `StartLiveVideo` methods to conditionally append outputs based on the new function.
- Improved logging to indicate whether RTMP output is disabled, providing better visibility into the streaming configuration.

These changes optimize the handling of RTMP outputs, particularly for WHEP scenarios that utilize an in-memory ring buffer instead.
Record the security fix from a6c4b1e in CHANGELOG_PENDING.md. The merge
commit ("Merge commit from fork") did not document the user-facing change:
validateTicketParams returned early on ExpirationBlock == 0, skipping the
economic caps; it now rejects such tickets.
Bump VERSION to 0.8.11 and document the PRs merged since v0.8.10 in the
changelog.
…ng tests

- Implemented the ModelIDForCapability method in the Capabilities struct to return the constrained model ID for a given capability, ensuring deterministic results when multiple models are present.
- Added unit tests for ModelIDForCapability to cover various scenarios, including nil receivers, no constraints, single constrained models, and handling of ambiguous model cases.
- Updated the remote signer to include the model ID in the event payload for LiveVideoToVideo capabilities.
…al resolution

- Modified the test for ModelIDForCapability to reflect that multiple models resolve to the lexicographically first ID instead of returning an empty string.
- Adjusted the expected output in the test case to return "comfyui" for the Capability_LiveVideoToVideo capability.
- Updated the payment processing logic to include an optional `AuthID` in the `RemotePaymentState` and `authResponse` structures.
- Modified the `doPaymentWithStateAndAuthID` function to accept and set the `AuthID` from the request header or callback response.
- Added tests to verify the correct handling of `AuthID` in various scenarios, including fallback behavior and error handling when the `AuthID` changes unexpectedly.
- Ensured that the `AuthID` is included in the response payload for better tracking and debugging.
Shared livepeerci/build:cache caused PR Docker tags (pr-3947,
feat-add-model-id-signer-kafka) to point at an older livepeer binary while
metadata listed the PR head SHA. Skip registry cache on pull_request builds
and pass GIT_REVISION to bust the compile layer.
The remote signer hardcoded the create_signed_ticket `pipeline` to the
lv2v constant whenever the gateway sent `type:"lv2v"` (which BYOC jobs
always do for fee/pixel routing), and derived `model_id` only from the
LiveVideoToVideo capability constraints. For BYOC capabilities (e.g.
nano-banana) this emitted pipeline=live-video-to-video and an empty
model_id, which the OpenMeter collector recorded as
live-video-to-video / unknown.

Add two additive, backward-compatible fields to RemotePaymentRequest:
`capability` and `model_id`. When set by the gateway, they override the
metered pipeline + model_id labels, decoupling usage attribution from
`Type` (which still drives fee/pixel routing). When empty, behavior is
byte-identical to before (lv2v constant + capabilities-derived model id;
collector defaults empties to "unknown").

Label resolution is extracted into a pure resolveUsageLabels helper with
a hermetic table test (TestResolveUsageLabels) that runs without the CGO
ffmpeg toolchain.
The remote signer's GenerateLivePayment charged every BYOC generation a
flat, model-independent fee: it read only oInfo.PriceInfo (the base price)
and synthesized lv2v pixels (1280*720*30 * billableSecs), so nano-banana,
recraft-v4, ltx-*, etc. all cost the same regardless of the per-capability
prices the orchestrator already advertises in oInfo.CapabilitiesPrices.

Resolve the fee per capability instead, keyed on req.Capability (added for
metering by the stacked #3966): scan oInfo.CapabilitiesPrices for the
Capability_BYOC entry whose Constraint matches the capability and charge
that USD->wei/sec rate over compute-seconds (pixels = ceil(billableSecs)).
This aligns the gateway's paid amount with the orchestrator's existing
per-capability per-second accounting (byoc.JobPriceInfo * seconds).

The resolved price is written back to oInfo.PriceInfo so it is the single
source for state init, initialPrice, the max-price ceiling, the payment's
ExpectedPrice (which the orch uses to set its fixed per-session price), and
the price-doubling guard in validatePrice (which reads
sess.OrchestratorInfo.PriceInfo) -- keeping all of them cap-vs-cap
consistent and preventing a false "price more than doubled" rejection when
base and cap diverge.

Gated behind a new default-OFF flag (-byocPerCapPricing /
LivepeerNode.ByocPerCapPricing). When OFF, or when req.Capability is empty,
or when no usable cap price matches, behavior is byte-identical to the
base-price path (zero regression). resolveByocPrice is a pure, hermetically
testable function; ticket params (faceValue/winProb) are passed through
unchanged so EV/ticket math stays valid.

Tests: TestResolveByocPrice (resolution, fallback, non-BYOC/zero-rate
ignored) and TestGenerateLivePayment_ByocPerCapPricing (flag-off
zero-regression, per-cap seconds fee, 2:1 tariff ratio, unknown-cap
fallback, doubling-guard not tripped), both CGO-free.

Co-authored-by: Cursor <cursoragent@cursor.com>
Addresses Copilot review feedback on PR #3967:

- resolveByocPrice now skips a matched-but-invalid (non-positive rate) entry
  and continues scanning instead of returning nil, so a later valid duplicate
  entry for the same constraint (e.g. from a misconfiguration) is still
  honored. Added a regression test case. (Copilot, remote_signer.go:431)

- BYOC per-capability pricing is now additionally gated on
  Type==RemoteType_LiveVideoToVideo. Enabling it changes the billing basis
  (overrides req.InPixels and bypasses lv2v pixel synthesis), so it must only
  apply to the lv2v job type that BYOC jobs always use, never to a non-lv2v
  request that happens to carry a capability. (Copilot, remote_signer.go:488)
Addresses Copilot review feedback on PR #3966:

- resolveUsageLabels now sanitizes the gateway-supplied capability/model_id
  override labels (TrimSpace + cap at 128 runes via sanitizeUsageLabel) before
  they are emitted to the create_signed_ticket usage event, bounding Kafka
  payload size and downstream label cardinality. req.Capability/req.ModelID
  come from the request body, so an unbounded value could otherwise inflate
  cardinality (pipeline was previously a small fixed set).

- Corrected the resolveUsageLabels docstring: the capability/model_id overrides
  apply regardless of Type, not only for lv2v; clarified the empty-field
  fallback wording.

- TestResolveUsageLabels now constructs a fresh require.New(t) inside each
  subtest so failures are attributed to the correct subtest, and adds cases for
  whitespace-only (ignored) and oversized (length-capped) override labels.
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc24bfd4-af5d-4c5d-8a9f-fde98547b62c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/byoc-per-cap-pricing-and-usage-labels

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

seanhanca and others added 2 commits July 10, 2026 21:13
Align staging orch with DMZ FlattenBYOCJob signing and accept explicit
type:byoc on /generate-live-payment alongside the existing lv2v path.

Co-authored-by: Cursor <cursoragent@cursor.com>
Changed the pipeline assignment in the remote signer to use the new capability-to-pipeline conversion function, ensuring that the correct pipeline is returned for BYOC capabilities. Updated the corresponding test to reflect this change in expected output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

go Pull requests that update Go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants