12 KiB
12 KiB
2.1 Accounts
- id:
BIGINTidentity (non-negative), unique account id - pubkey:
BYTEAunique public key for signing/encryption - display_name:
TEXT(optional) - created_at:
TIMESTAMPTZ
2.2 Currencies
- asset_code:
TEXTPK (e.g.,USDC-ETH,EUR,LND) - name:
TEXT - symbol:
TEXT - decimals:
INT(default 2)
3) Services & Groups
3.1 Services
- id:
BIGINTidentity - name:
TEXTunique - 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:
BIGINTidentity - name:
TEXTunique - 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:
BIGINTidentity - account_id: FK →
accounts(id)(the owning account) - name:
TEXTunique - 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:
BIGINTidentity - 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:
BIGINTidentity - 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:
BOOLEANdefaultTRUE - 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:
BIGINTidentity - 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:
BIGINTidentity - 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_idorgroup_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 onREQUESTSwhen transitioning torunning/succeeded. - Routing: prefer
provider_service_runnerswhen a request targets a service directly; otherwise use the union of runners fromprovider_service_group_runnersfor the group. - Balances: serve balance queries via the
account_balancesview 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
ENUMviaCREATE TYPE; when extending, plan forALTER TYPE ... ADD VALUE. - For high-write ledgers, consider partitioning
billing_ledgerbycreated_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