12 KiB
12 KiB
2.1 Accounts
- id:
BIGINT
identity (non-negative), unique account id - pubkey:
BYTEA
unique public key for signing/encryption - display_name:
TEXT
(optional) - created_at:
TIMESTAMPTZ
2.2 Currencies
- asset_code:
TEXT
PK (e.g.,USDC-ETH
,EUR
,LND
) - name:
TEXT
- symbol:
TEXT
- decimals:
INT
(default 2)
3) Services & Groups
3.1 Services
- id:
BIGINT
identity - name:
TEXT
unique - description:
TEXT
- default_billing_mode:
ENUM('per_second','per_request')
- default_price:
NUMERIC(38,18)
(≥0) - default_currency: FK →
currencies(asset_code)
- max_request_seconds:
INT
(>0 orNULL
) - schema_heroscript:
TEXT
- schema_json:
JSONB
- created_at:
TIMESTAMPTZ
Accepted Currencies (per service)
- service_id: FK →
services(id)
- asset_code: FK →
currencies(asset_code)
- price_override:
NUMERIC(38,18)
(optional) - billing_mode_override:
ENUM
(optional) Primary key:(service_id, asset_code)
3.2 Service Groups
- id:
BIGINT
identity - name:
TEXT
unique - description:
TEXT
- created_at:
TIMESTAMPTZ
Group Memberships
- group_id: FK →
service_groups(id)
- service_id: FK →
services(id)
Primary key:(group_id, service_id)
4) Providers & Runners
4.1 Service Providers
- id:
BIGINT
identity - account_id: FK →
accounts(id)
(the owning account) - name:
TEXT
unique - description:
TEXT
- created_at:
TIMESTAMPTZ
Providers Offer Groups
- provider_id: FK →
service_providers(id)
- group_id: FK →
service_groups(id)
Primary key:(provider_id, group_id)
Provider Pricing Overrides (optional)
- provider_id: FK →
service_providers(id)
- service_id: FK →
services(id)
- asset_code: FK →
currencies(asset_code)
(nullable for currency-agnostic override) - price_override:
NUMERIC(38,18)
(optional) - billing_mode_override:
ENUM
(optional) - max_request_seconds_override:
INT
(optional) Primary key:(provider_id, service_id, asset_code)
4.2 Runners
- id:
BIGINT
identity - address:
INET
(must be IPv6) - name:
TEXT
- description:
TEXT
- pubkey:
BYTEA
(optional) - created_at:
TIMESTAMPTZ
Runner Ownership (many-to-many)
- runner_id: FK →
runners(id)
- provider_id: FK →
service_providers(id)
Primary key:(runner_id, provider_id)
Routing (provider → service/service_group → runners)
- provider_service_runners:
(provider_id, service_id, runner_id)
PK - provider_service_group_runners:
(provider_id, group_id, runner_id)
PK
5) Subscriptions & Spend Control
A subscription authorizes an account to use either a service or a service group, with optional spend limits and allowed providers.
- id:
BIGINT
identity - account_id: FK →
accounts(id)
- service_id xor group_id: FK (exactly one must be set)
- secret:
BYTEA
(random, provided by subscriber; recommend storing a hash) - subscription_data:
JSONB
(free-form) - limit_amount:
NUMERIC(38,18)
(optional) - limit_currency: FK →
currencies(asset_code)
(optional) - limit_period:
ENUM('hour','day','month')
(optional) - active:
BOOLEAN
defaultTRUE
- created_at:
TIMESTAMPTZ
Allowed Providers per Subscription
- subscription_id: FK →
subscriptions(id)
- provider_id: FK →
service_providers(id)
Primary key:(subscription_id, provider_id)
Intended Use:
- Subscribers bound spending by amount/currency/period.
- Merchant (provider) can claim charges for requests fulfilled under an active subscription, within limits, and only if listed in
subscription_providers
.
6) Requests & Billing
6.1 Request Lifecycle
- id:
BIGINT
identity - account_id: FK →
accounts(id)
- subscription_id: FK →
subscriptions(id)
- provider_id: FK →
service_providers(id)
- service_id: FK →
services(id)
- runner_id: FK →
runners(id)
(nullable) - request_schema:
JSONB
(payload matchingschema_json
/schema_heroscript
) - started_at, ended_at:
TIMESTAMPTZ
- status:
ENUM('pending','running','succeeded','failed','canceled')
- created_at:
TIMESTAMPTZ
6.2 Billing Ledger (append-only)
- id:
BIGINT
identity - account_id: FK →
accounts(id)
- provider_id: FK →
service_providers(id)
(nullable) - service_id: FK →
services(id)
(nullable) - request_id: FK →
requests(id)
(nullable) - amount:
NUMERIC(38,18)
(debit = positive, credit/refund = negative) - asset_code: FK →
currencies(asset_code)
- entry_type:
ENUM('debit','credit','adjustment')
- description:
TEXT
- created_at:
TIMESTAMPTZ
Balances View (example):
account_balances(account_id, asset_code, balance)
as a view overbilling_ledger
.
7) Pricing Precedence
When computing the effective pricing, billing mode, and max duration for a (provider, service, currency)
:
- Provider override for (service, asset_code) — if present, use it.
- Service accepted currency override — if present, use it.
- Service defaults — fallback.
If billing_mode
or max_request_seconds
are not overridden at steps (1) or (2), inherit from the next step down.
8) Key Constraints & Validations
- All identity ids are non-negative (
CHECK (id >= 0)
). - Runner IPv6 enforcement:
CHECK (family(address) = 6)
. - Subscriptions must point to exactly one of
service_id
orgroup_id
. - Prices and limits must be non-negative if set.
- Unique natural keys where appropriate: service names, provider names, currency asset codes, account pubkeys.
9) Mermaid Diagrams
9.1 Entity–Relationship Overview
erDiagram
ACCOUNTS ||--o{ SERVICE_PROVIDERS : "owns via account_id"
ACCOUNTS ||--o{ SUBSCRIPTIONS : has
CURRENCIES ||--o{ SERVICES : "default_currency"
CURRENCIES ||--o{ SERVICE_ACCEPTED_CURRENCIES : "asset_code"
CURRENCIES ||--o{ PROVIDER_SERVICE_OVERRIDES : "asset_code"
CURRENCIES ||--o{ BILLING_LEDGER : "asset_code"
SERVICES ||--o{ SERVICE_ACCEPTED_CURRENCIES : has
SERVICES ||--o{ SERVICE_GROUP_MEMBERS : member_of
SERVICE_GROUPS ||--o{ SERVICE_GROUP_MEMBERS : contains
SERVICE_PROVIDERS ||--o{ PROVIDER_SERVICE_GROUPS : offers
SERVICE_PROVIDERS ||--o{ PROVIDER_SERVICE_OVERRIDES : sets
SERVICE_PROVIDERS ||--o{ RUNNER_OWNERS : owns
SERVICE_PROVIDERS ||--o{ PROVIDER_SERVICE_RUNNERS : routes
SERVICE_PROVIDERS ||--o{ PROVIDER_SERVICE_GROUP_RUNNERS : routes
RUNNERS ||--o{ RUNNER_OWNERS : owned_by
RUNNERS ||--o{ PROVIDER_SERVICE_RUNNERS : executes
RUNNERS ||--o{ PROVIDER_SERVICE_GROUP_RUNNERS : executes
SUBSCRIPTIONS ||--o{ SUBSCRIPTION_PROVIDERS : allow
SERVICE_PROVIDERS ||--o{ SUBSCRIPTION_PROVIDERS : allowed
REQUESTS }o--|| ACCOUNTS : by
REQUESTS }o--|| SUBSCRIPTIONS : under
REQUESTS }o--|| SERVICE_PROVIDERS : via
REQUESTS }o--|| SERVICES : for
REQUESTS }o--o{ RUNNERS : executed_by
BILLING_LEDGER }o--|| ACCOUNTS : charges
BILLING_LEDGER }o--o{ SERVICES : reference
BILLING_LEDGER }o--o{ SERVICE_PROVIDERS : reference
BILLING_LEDGER }o--o{ REQUESTS : reference
9.2 Request Flow (Happy Path)
sequenceDiagram
autonumber
participant AC as Account
participant API as Broker/API
participant PR as Provider
participant RU as Runner
participant DB as PostgreSQL
AC->>API: Submit request (subscription_id, service_id, payload, secret)
API->>DB: Validate subscription (active, provider allowed, spend limits)
DB-->>API: OK + effective pricing (resolve precedence)
API->>PR: Dispatch request (service, payload)
PR->>DB: Select runner (provider_service_runners / group runners)
PR->>RU: Start job (payload)
RU-->>PR: Job started (started_at)
PR->>DB: Update REQUESTS (status=running, started_at)
RU-->>PR: Job finished (duration, result)
PR->>DB: Update REQUESTS (status=succeeded, ended_at)
API->>DB: Insert BILLING_LEDGER (debit per effective price)
DB-->>API: Ledger entry id
API-->>AC: Return result + charge info
9.3 Pricing Resolution
flowchart TD
A[Input: provider_id, service_id, asset_code] --> B{Provider override exists for (service, asset_code)?}
B -- Yes --> P1[Use provider price/mode/max]
B -- No --> C{Service accepted currency override exists?}
C -- Yes --> P2[Use service currency price/mode]
C -- No --> P3[Use service defaults]
P1 --> OUT[Effective pricing]
P2 --> OUT
P3 --> OUT
10) Operational Notes
- Secrets: store a hash (e.g.,
digest(secret,'sha256')
) rather than rawsecret
. Keep the original only client-side. - Limits enforcement: before insert of a debit ledger entry, compute period window (hour/day/month UTC or tenant TZ) and enforce
SUM(amount) + new_amount ≤ limit_amount
. - Durations: enforce
max_request_seconds
(effective) at orchestration and/or via DB trigger onREQUESTS
when transitioning torunning/succeeded
. - Routing: prefer
provider_service_runners
when a request targets a service directly; otherwise use the union of runners fromprovider_service_group_runners
for the group. - Balances: serve balance queries via the
account_balances
view or a materialized cache updated by triggers/jobs.
11) Example Effective Pricing Query (sketch)
-- Inputs: :provider_id, :service_id, :asset_code
WITH p AS (
SELECT price_override, billing_mode_override, max_request_seconds_override
FROM provider_service_overrides
WHERE provider_id = :provider_id
AND service_id = :service_id
AND (asset_code = :asset_code)
),
sac AS (
SELECT price_override, billing_mode_override
FROM service_accepted_currencies
WHERE service_id = :service_id AND asset_code = :asset_code
),
svc AS (
SELECT default_price AS price, default_billing_mode AS mode, max_request_seconds
FROM services WHERE id = :service_id
)
SELECT
COALESCE(p.price_override, sac.price_override, svc.price) AS effective_price,
COALESCE(p.billing_mode_override, sac.billing_mode_override, svc.mode) AS effective_mode,
COALESCE(p.max_request_seconds_override, svc.max_request_seconds) AS effective_max_seconds;
12) Indices (non-exhaustive)
services(default_currency)
service_accepted_currencies(service_id)
provider_service_overrides(service_id, provider_id)
requests(account_id)
,requests(provider_id)
,requests(service_id)
billing_ledger(account_id, asset_code)
subscriptions(account_id) WHERE active
13) Migration & Compatibility
- Prefer additive migrations (new columns/tables) to avoid downtime.
- Use
ENUM
viaCREATE TYPE
; when extending, plan forALTER TYPE ... ADD VALUE
. - For high-write ledgers, consider partitioning
billing_ledger
bycreated_at
(monthly) and indexing partitions.
14) Non-Goals
- Wallet custody and on-chain settlement are out of scope.
- SLA tracking and detailed observability (metrics/log schema) are not part of this spec.
15) Acceptance Criteria
- Can represent services, groups, and providers with currency-specific pricing.
- Can route requests to runners by service or group.
- Can authorize usage via subscriptions, enforce spend limits, and record charges.
- Can reconstruct balances and audit via append-only ledger.
End of Spec