### 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 or `NULL`) * **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` default `TRUE` * **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 matching `schema_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 over `billing_ledger`. --- ## 7) Pricing Precedence When computing the **effective** pricing, billing mode, and max duration for a `(provider, service, currency)`: 1. **Provider override for (service, asset\_code)** — if present, use it. 2. **Service accepted currency override** — if present, use it. 3. **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` or `group_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 ```mermaid 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) ```mermaid 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 ```mermaid 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 raw `secret`. 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 on `REQUESTS` when transitioning to `running/succeeded`. * **Routing:** prefer `provider_service_runners` when a request targets a service directly; otherwise use the union of runners from `provider_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) ```sql -- 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` via `CREATE TYPE`; when extending, plan for `ALTER TYPE ... ADD VALUE`. * For high-write ledgers, consider partitioning `billing_ledger` by `created_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**