Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6329b4e
fix(billing): remove dead user-JWT signer mint + correct opaque-sessi…
seanhanca Jul 20, 2026
addfe2d
docs(e2e): Run 57 — billed E2E on LR-orch via composite-bearer path (…
seanhanca Jul 20, 2026
93ca211
docs(audit): trace unit_kind billing-unit lifecycle + OpenMeter unit-…
seanhanca Jul 20, 2026
94d2083
docs: add Storyboard signer routing (Daydream vs pymthouse) architecture
seanhanca Jul 20, 2026
5bb844b
docs: add pymthouse-e2e skill for the billed E2E test path
seanhanca Jul 20, 2026
f8c2ea7
docs: make liverunner-staging-1 (LR-orch) a first-class E2E scenario
seanhanca Jul 20, 2026
a0c9180
docs: make pymthouse-e2e a true hand-off skill for agents
seanhanca Jul 20, 2026
ae6262e
docs: add BYOC-to-live-runner migration analysis + merge strategy
seanhanca Jul 20, 2026
b9c1f23
docs(audit): resolve byoc-staging-1 orch branch/commit/image ambiguity
seanhanca Jul 20, 2026
cd1cea4
docs: add Storyboard MCP performance & capacity investigation
seanhanca Jul 20, 2026
2a69728
docs: add Storyboard MCP performance-bias skill / adaptive agent prop…
seanhanca Jul 20, 2026
6f50d4a
feat(skills): add storyboard-perf-mode two-mode perf skill (Option A …
seanhanca Jul 20, 2026
8afa8c8
docs(perf): add Storyboard PR #685 post-fix E2E assessment
seanhanca Jul 21, 2026
d88b503
docs(billing): add per-unit (per-ticket) metering design proposal
seanhanca Jul 21, 2026
a846b90
docs: network pricing/metering/billing architecture across agent, byo…
seanhanca Jul 21, 2026
79b6884
docs(pricing): consolidate to descriptor price segment as single sour…
seanhanca Jul 21, 2026
6d3f1b8
docs(pricing): add PR-cycle execution plan + design-review section
seanhanca Jul 21, 2026
a495fa9
docs(pricing): append independent code-verified review to execution plan
seanhanca Jul 21, 2026
0daef4e
docs(pricing): add A/B/C fee-locus assessment to NETWORK-PRICING-ARCH…
seanhanca Jul 21, 2026
01b4b61
docs: add scratch notes analyzing go-livepeer PR #3992 live pricing
seanhanca Jul 21, 2026
41bf57b
docs(pricing): consolidated rewrite resolving review blockers B1-B4/M…
seanhanca Jul 21, 2026
4b17cd0
docs(pricing): add §8 — Storyboard-independent PRs, regression class,…
seanhanca Jul 21, 2026
44328fc
docs: add upstream pricing scope for owners (John/Josh/Rick)
seanhanca Jul 21, 2026
45eb449
docs(pricing): add PRICING-DELIVERY-SUMMARY.html
seanhanca Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,473 changes: 1,473 additions & 0 deletions BILLED-E2E-BLOCKER-AUDIT.md

Large diffs are not rendered by default.

463 changes: 463 additions & 0 deletions BYOC-TO-LIVERUNNER-MIGRATION.html

Large diffs are not rendered by default.

879 changes: 879 additions & 0 deletions NETWORK-PRICING-ARCHITECTURE.html

Large diffs are not rendered by default.

244 changes: 244 additions & 0 deletions PR3992-FIXED-PRICING-NOTES.md

Large diffs are not rendered by default.

211 changes: 211 additions & 0 deletions PRICING-DELIVERY-SUMMARY.html

Large diffs are not rendered by default.

569 changes: 569 additions & 0 deletions PRICING-EXECUTION-PLAN.html

Large diffs are not rendered by default.

229 changes: 229 additions & 0 deletions PRICING-UPSTREAM-SCOPE-FOR-OWNERS.md

Large diffs are not rendered by default.

576 changes: 576 additions & 0 deletions PYMTHOUSE-PER-UNIT-METERING-DESIGN.html

Large diffs are not rendered by default.

216 changes: 216 additions & 0 deletions STORYBOARD-MCP-PERF-INVESTIGATION.html

Large diffs are not rendered by default.

207 changes: 207 additions & 0 deletions STORYBOARD-PERF-POSTFIX-E2E.html

Large diffs are not rendered by default.

205 changes: 205 additions & 0 deletions STORYBOARD-PERF-SKILL-PROPOSAL.html

Large diffs are not rendered by default.

251 changes: 251 additions & 0 deletions STORYBOARD-PERF-SKILL-PROPOSAL.md

Large diffs are not rendered by default.

197 changes: 197 additions & 0 deletions STORYBOARD-SIGNER-ROUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Storyboard Signer Routing — Daydream vs pymthouse (Production)

_Read-only investigation. How Storyboard (production) decides whether a
generation request uses the **Daydream signer** (`signer.daydream.live`,
`type:lv2v`) or the **pymthouse per-key signer** (DMZ, `type:byoc`, composite
`app_…_pmth_…` bearer, NaaP validate)._
Comment on lines +5 to +6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant doc and locate the cited composite-bearer formats.
git ls-files | rg 'STORYBOARD-SIGNER-ROUTING\.md|pmth|pymthouse|bearer|signer|routing'

echo
echo '--- file outline ---'
ast-grep outline STORYBOARD-SIGNER-ROUTING.md --view expanded || true

echo
echo '--- search for composite bearer grammar ---'
rg -n --hidden -S 'app_<publicClientId>|app_…_pmth_…|pmth_|composite bearer|pymthouse|NaaP validate|type:byoc|type:lv2v' .

echo
echo '--- relevant slices from STORYBOARD-SIGNER-ROUTING.md ---'
nl -ba STORYBOARD-SIGNER-ROUTING.md | sed -n '1,220p'

Repository: livepeer/naap

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- STORYBOARD-SIGNER-ROUTING.md (first 220 lines) ---'
nl -ba STORYBOARD-SIGNER-ROUTING.md | sed -n '1,220p'

echo
echo '--- exact composite-key grammar mentions ---'
rg -n -S 'app_<publicClientId>\.pmth_<secret>|app_<24hex>_<secret>|app_<clientId>_pmth_<secret>|app_…_pmth_…|app_.*pmth_' STORYBOARD-SIGNER-ROUTING.md docs packages plugins apps | head -n 200

echo
echo '--- builder-sdk / app key helpers ---'
rg -n -S 'isCompositeApiKey|composite api key|pmth_' apps/web-next/src plugins/developer-api/frontend/src packages docs | head -n 200

Repository: livepeer/naap

Length of output: 246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- STORYBOARD-SIGNER-ROUTING.md lines 1-180 ---'
sed -n '1,180p' STORYBOARD-SIGNER-ROUTING.md | cat -n

echo
echo '--- exact composite-format references in storyboard and docs ---'
rg -n -S 'app_<publicClientId>\.pmth_<secret>|app_<24hex>_<secret>|app_<clientId>_pmth_<secret>|app_…_pmth_…|pmth_.*composite|composite bearer|canonical composite' STORYBOARD-SIGNER-ROUTING.md docs apps plugins packages | head -n 200

echo
echo '--- builder-sdk helper references ---'
rg -n -S 'isCompositeApiKey|startsWith\(\'pmth_|includes\(\'\.pmth_\'\)|app_.*_pmth_' apps plugins packages docs | head -n 200

Repository: livepeer/naap

Length of output: 17100


Use one underscore-based composite-bearer form everywhere.
STORYBOARD-SIGNER-ROUTING.md mixes app_…_pmth_… with the dotted app_<publicClientId>.pmth_<secret> citation; align it to the repo’s app_<24hex>_<secret> / app_…_pmth_… form or mark the dotted variant as legacy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@STORYBOARD-SIGNER-ROUTING.md` around lines 5 - 6, Update the composite bearer
examples in STORYBOARD-SIGNER-ROUTING.md to use one underscore-based format
consistently, specifically the repository’s app_<24hex>_<secret> or app_…_pmth_…
form; remove or explicitly label any dotted app_<publicClientId>.pmth_<secret>
example as legacy.


---

## TL;DR — the one-paragraph answer

The SDK (livepeer-python-gateway) is **not** the authoritative decision-maker,
and neither is Storyboard. The real switch lives in the **SDK service**
(`sdk.daydream.monster`, code `simple-infra/sdk-service-build/app.py`) in the
function `_effective_signer`, gated by the server env flag
**`SIGNER_FROM_VALIDATE`** (plus `AUTH_VALIDATE_URL` being set). A request only
takes the pymthouse path when **all** of these hold at once: `SIGNER_FROM_VALIDATE=1`,
`AUTH_VALIDATE_URL` is set, the presented bearer is a **`naap_` key**, and NaaP's
`/api/v1/keys/validate` returns an **endpoint-form `signerSession` (`{url, headers}`)**.
Otherwise the service falls back to the static `SIGNER_URL = https://signer.daydream.live`
(the Daydream signer). The python-gateway SDK only performs a *derived* second
step: `_payment_type_for_signer(signer_url)` maps the chosen signer **hostname**
to a payment shape — `signer.daydream.live → type:lv2v`, anything else →
`type:byoc`. **In PRODUCTION today `SIGNER_FROM_VALIDATE` and `AUTH_VALIDATE_URL`
are unset**, so every request goes to the Daydream signer / `type:lv2v`; the
pymthouse per-key path is currently enabled only on the **canary/staging**
overlays. So the API-key format matters only as an *input* to the SDK service's
env-gated decision — it is not itself the switch.

---

## Was the user's assumption right? (SDK decides?)

**Partially, but mislocated.** There are three layers, and the *authoritative*
choice is the middle one, not the SDK library:

| Layer | What it decides | Where | Is it the real switch? |
|-------|-----------------|-------|------------------------|
| Storyboard (Next.js) | Which SDK **service base URL** to call (Daydream `sdk.daydream.monster` vs a NaaP front door) | `lib/sdk/provider-server.ts`, `lib/mcp-server/sdk-call.ts` | No — prod flag OFF ⇒ always Daydream service |
| **SDK service** (`app.py`) | Which **signer URL** (static Daydream vs per-key pymthouse DMZ) | `simple-infra/sdk-service-build/app.py` `_effective_signer` | **YES — this is the decision node** |
| SDK library (python gateway) | Payment **type** derived from the signer hostname (`lv2v` vs `byoc`) | `livepeer-python-gateway/.../byoc.py` `_payment_type_for_signer` | No — purely derived from the URL it was handed |

The SDK library's `_payment_type_for_signer` looks like "the SDK decides," but it
is only translating a signer URL that was already chosen upstream by the SDK
service's env-gated `_effective_signer`.

---

## Architecture diagram (production decision flow)

```mermaid
flowchart TD
A["Storyboard MCP/LLM request<br/>Authorization: Bearer &lt;key&gt;<br/>key = sk_… | naap_… | app_…_pmth_…"] --> B

subgraph SB["Storyboard (Next.js) — lib/sdk"]
B{"STORYBOARD_PROVIDER_SWITCH=1<br/>AND NAAP_PROVIDER=naap ?"}
B -- "no (PROD default: flag OFF)" --> C1["serviceUrl = sdk.daydream.monster<br/>(Daydream SDK service)"]
B -- "yes (canary only)" --> C2["serviceUrl = NAAP_BASE_URL<br/>(NaaP front door)"]
end

C1 --> SVC
C2 --> SVC

subgraph SVC["SDK service — app.py :: _effective_signer ◀ DECISION NODE ▶"]
D{"SIGNER_FROM_VALIDATE=1<br/>AND AUTH_VALIDATE_URL set<br/>AND bearer starts with naap_<br/>AND validate returns signerSession.url ?"}
D -- "no (PROD default)" --> E1
D -- "yes (canary/staging)" --> V["POST AUTH_VALIDATE_URL<br/>(NaaP /api/v1/keys/validate)<br/>Bearer naap_…"]
V --> E2
end

E1["signer_url = SIGNER_URL<br/>= https://signer.daydream.live<br/>signer_headers = caller Authorization"]
E2["signer_url = signerSession.url<br/>(pymthouse DMZ)<br/>signer_headers = signerSession.headers<br/>(composite app_…_pmth_… bearer)"]

E1 --> G
E2 --> G

subgraph GW["Python gateway SDK — byoc.py :: _payment_type_for_signer(signer_url)"]
G{"hostname == signer.daydream.live ?"}
G -- "yes → DAYDREAM BRANCH" --> P1
G -- "no → PYMTHOUSE BRANCH" --> P2
end

P1["type: lv2v<br/>payload.capability = string<br/>POST signer.daydream.live/generate-live-payment<br/>NaaP validate: NOT touched<br/>metering: shared Daydream wallet →<br/>api.daydream.live/webhooks/remote-signer"]
P2["type: byoc<br/>payload.capabilities = proto<br/>POST &lt;dmz-signer&gt;/generate-live-payment<br/>NaaP validate: YES (key→sub→plan)<br/>metering: caller's own funded wallet →<br/>caller's provider account (OpenMeter)"]

P1 --> O["Orchestrator /process/request/{cap}<br/>(discovery = DISCOVERY_URL, prod.json)"]
P2 --> O

style D fill:#ffe08a,stroke:#c77,stroke-width:3px
style SVC fill:#fff7e0
style P1 fill:#e3f0ff
style P2 fill:#ffe6e6
```

---

## Prose walk-through of each hop

### 1. Storyboard → SDK service base URL (NOT the signer switch)
Storyboard forwards the caller's `Authorization: Bearer <key>` unchanged and only
chooses **which SDK service to call**. The decision is the server flag
`STORYBOARD_PROVIDER_SWITCH` (default OFF) + `NAAP_PROVIDER` (`daydream`|`naap`).
With the flag OFF — **today's production** — it resolves to the Daydream SDK base
(`STORYBOARD_SDK_URL`/`SDK_URL`, default `https://sdk.daydream.monster`)
byte-for-byte (INV-1). The NaaP-front-door branch is a documented **config-only,
canary** rollout, not prod-live.

- `lib/sdk/provider-core.ts:28` — `DEFAULT_PROVIDER_ID = "daydream"`.
- `lib/sdk/provider-core.ts:52-71` — provider registry (daydream base `sdk.daydream.monster`; naap base is operator-supplied).
- `lib/sdk/provider-core.ts:186-198` — `resolveServiceUrlFrom` returns the Daydream fallback unless switch ON + `naap` + valid base.
- `lib/sdk/provider-server.ts:65-68` — `isServerProviderSwitchEnabled()` reads `STORYBOARD_PROVIDER_SWITCH` (default OFF).
- `lib/sdk/provider-server.ts:81-97` — `loadServerProviderInput()` returns a dormant Daydream input when the flag is OFF.
- `lib/mcp-server/sdk-call.ts:259-265` — the MCP path uses `resolveServerServiceUrl()`; only logs/degrades when actually routed to NaaP.

### 2. SDK service → which signer URL ◀ THE DECISION NODE ▶
Inside the SDK service, `_effective_signer` picks the signer. Default (prod): the
static `SIGNER_URL`. It diverts to a per-key pymthouse DMZ signer **only** when
`SIGNER_FROM_VALIDATE` is on, `AUTH_VALIDATE_URL` is set, the bearer is a `naap_`
key, and the NaaP validate response carries an endpoint-form `signerSession`.

- `simple-infra/sdk-service-build/app.py:519` — `SIGNER_URL = os.environ.get("SIGNER_URL", "")`.
- `simple-infra/sdk-service-build/app.py:540` — `AUTH_VALIDATE_URL = os.environ.get("AUTH_VALIDATE_URL", "").strip()`.
- `simple-infra/sdk-service-build/app.py:541` — `SIGNER_FROM_VALIDATE = … in ("1","true")`.
- `simple-infra/sdk-service-build/app.py:1001-1068` — `_resolve_validate_session`: POSTs `naap_` key to `AUTH_VALIDATE_URL`, reads `signerSession.{url,headers}` (endpoint form only).
- `simple-infra/sdk-service-build/app.py:1072-1091` — `_effective_signer`: **the branch**. Returns `(SIGNER_URL, caller-headers)` by default; returns `(signerSession.url, signerSession.headers)` when a per-key session resolved.
- `simple-infra/sdk-service-build/app.py:986-991` — `_extract_signer_headers`: forwards the caller `Authorization` header to the signer (this is how the composite `app_…_pmth_…` bearer reaches the DMZ signer).
- `simple-infra/sdk-service-build/app.py:2179-2180` — the inference dispatch uses `signer_url_eff = _current_signer_url.get() or (SIGNER_URL or None)`.

### 3. Python gateway SDK → payment type (derived from the signer hostname)
The gateway maps the signer host to a payment shape. This is the code that *looks*
like "the SDK decides," but it only reacts to the URL handed to it.

- `livepeer-python-gateway/src/livepeer_gateway/byoc.py:148` — `_LEGACY_DAYDREAM_SIGNER_HOST = "signer.daydream.live"`.
- `livepeer-python-gateway/src/livepeer_gateway/byoc.py:151-161` — `_payment_type_for_signer`: `signer.daydream.live → "lv2v"`, else `"byoc"`.
- `livepeer-python-gateway/src/livepeer_gateway/byoc.py:193-230` — `_create_byoc_payment`: `lv2v` sends `payload.capability` (string); `byoc` sends `payload.capabilities` (proto). Both POST `{signer}/generate-live-payment`.
- `livepeer-python-gateway/src/livepeer_gateway/lv2v.py:350-358` — the pure LV2V entrypoint `start_lv2v` hard-codes `type="lv2v"` (used by the live-video path).

### 4. Composite bearer `app_…_pmth_…` (pymthouse side)
The composite bearer is a pymthouse concept (public client id + secret), parsed
by the DMZ signer / pymthouse, **not** by the SDK library. The SDK service simply
forwards whatever `Authorization` header it holds (`_extract_signer_headers`, or
the `signerSession.headers` NaaP returns).

- `pymthouse/src/lib/signer/composite-app-api-key-bearer.ts:10-29` — parses `app_<publicClientId>.pmth_<secret>`.
- `pymthouse/src/app/api/v1/auth/validate/route.ts` — the NaaP/pymthouse `/api/v1/keys/validate` front door (gated by `BPP_VALIDATE_V2`), which returns the `signerSession` the SDK service consumes.

### 5. Metering destinations
- **Daydream branch:** signed with the **shared** signer wallet; billing webhook →
`https://api.daydream.live/webhooks/remote-signer`
(`simple-infra/environments/shared/signers.yaml:29-31`, `:54-63`).
- **pymthouse branch:** signed with the **caller's own funded wallet**
(`signerSession`), so usage is metered to the caller's provider account via
NaaP/OpenMeter (`signers.yaml:54-60`; `docs/per-key-remote-signer-sdk-service-spec.md`).

---

## Current PRODUCTION state (evidence)

Production is the **Daydream signer / `type:lv2v`** path. The pymthouse per-key
path is **off** in prod and enabled only on canary/staging.

| Setting | Production | Canary / staging (NaaP front door) |
|---------|-----------|------------------------------------|
| `SIGNER_URL` | `https://signer.daydream.live` | `https://signer.daydream.live` (static fallback) |
| `AUTH_VALIDATE_URL` | *(unset)* | `https://operator.livepeer.org/api/v1/keys/validate` |
| `SIGNER_FROM_VALIDATE` | *(unset → OFF)* | `1` |
| `DISCOVERY_URL` | `…/discovery/production.json` | staging/per-key |
| `STORYBOARD_PROVIDER_SWITCH` | *(OFF, default)* | `1` (canary) |
| Effective signer | **Daydream (`signer.daydream.live`, lv2v)** | **pymthouse DMZ (per-key, byoc)** |

Citations:
- Prod SDK-service env: `simple-infra/environments/production/byoc.values.yaml:34-41`
(`SIGNER_URL: https://signer.daydream.live`, no `AUTH_VALIDATE_URL`, no `SIGNER_FROM_VALIDATE`).
- Canary enablement: `simple-infra/environments/canary/byoc.canary.values.yaml:8-10`
(`AUTH_VALIDATE_URL: …operator.livepeer.org/api/v1/keys/validate`, `SIGNER_FROM_VALIDATE: "1"`).
- Staging front-door overlay: `simple-infra/environments/staging/sdk.naap-front-door.values.yaml:16-17`.
- Default compose values: `simple-infra/docker-compose/sdk-service.yaml:10,16,38`
(`SIGNER_URL` defaults to `signer.daydream.live`; `AUTH_VALIDATE_URL`/`SIGNER_FROM_VALIDATE` default empty).
- Guard rail: `simple-infra/scripts/validate-envs.sh:141-155` — `SIGNER_FROM_VALIDATE=1`
is rejected unless `AUTH_VALIDATE_URL` is also set.
- Prod discovery/signer shared identity: `simple-infra/environments/production/fleet.yaml:5-18`.

> Note: production env is inferred from the deploy configs in `simple-infra`
> (the live prod env vars were not read directly). The prod `byoc.values.yaml`
> header also notes production BYOC was `status: planned` as of 2026-04-14; the
> live Storyboard generation path in prod runs against the Daydream SDK service
> (`sdk.daydream.monster`) with the static `signer.daydream.live` signer.

---

## Summary of decision fields

| Question | Field / env | Where it lives |
|----------|-------------|----------------|
| Which SDK service? | `STORYBOARD_PROVIDER_SWITCH` + `NAAP_PROVIDER` | Storyboard (server env) |
| **Which signer URL?** | **`SIGNER_FROM_VALIDATE` + `AUTH_VALIDATE_URL` + `naap_` key + validate `signerSession`** | **SDK service `app.py` `_effective_signer`** |
| Which payment type? | signer **hostname** (`signer.daydream.live`?) | SDK library `byoc.py` `_payment_type_for_signer` |
Loading
Loading