Help query pricing, IPR charges, cross-peer RoC time credit transfers, settlement reconciliation, and multi-source billing breakdown.
Advanced Last updated: 2026-06-04Billing Models
Module: 10 | β Agreement Routing | Next: LangGraph Migration β Source: Addendum Β§B (help query pricing), Β§G (IPR and cross-peer RoC)
B. Help Query Pricing: Platform Hourly Rate = 0
B.1 Decision
@rosie help queries have hourly_rate = 0 and ipr_hourly_rate = 0 on the peer agreement with trustrosie-io. Help is a platform service, not a billable feature.
B.2 Transaction Recording
Even at zero cost, help queries are recorded as transactions for analytics:
- Help query volume (admin KPIs: "how often are users asking for help?")
- Hot Topics: the LLM tags each query with topic labels that feed the Hot Topics Dashboard. This applies to help queries as well β "what are users struggling with?" is valuable analytics.
- Per-document retrieval frequency (which help docs are most useful?)
{
"transaction_type": "query_usage",
"platform_fee": 0.0,
"ipr_cost": 0.0,
"peer_agreement_id": "agr_trustrosie_help",
"peer_org_id": "trustrosie_io",
"is_help_query": true,
"query_source": "https://trustrosie.io/help/"
}
No balance deduction. Anonymous users and users with zero balance can still get help.
B.3 Cost Absorption
The LLM inference cost for help queries is absorbed by the consumer site's platform (the RAG generation happens on the consumer's edge function using the consumer's LLM key). The vector retrieval cost is negligible. This is equivalent to hosting a help page.
C. Billing Duration Configuration
C.1 Duration Modes
The billable duration for each query is configurable per org via public.orgs.settings.pricing.billing_mode:
| Mode | billing_mode value |
What's billed | Default |
|---|---|---|---|
| Response time | "response_time" |
Full request after auth β includes retrieval, agreement routing, source grading, LLM generation. Excludes cold-start and auth overhead. | Yes |
| LLM only | "llm_only" |
Only LLM inference time β agreement routing LLM + topic extraction + response generation. Excludes retrieval, graph setup, persistence. | No |
C.2 Configuration
Set in public.orgs.settings JSON:
{
"pricing": {
"query_hourly_rate": 25,
"currency": "CAD",
"roc_split_percent": 60,
"billing_mode": "response_time"
}
}
When billing_mode is not set, defaults to "response_time".
C.3 Platform Fee Calculation
platform_fee = (billable_duration_seconds / 3600) Γ query_hourly_rate
Example at $25/hr with a 5.5s response-time query: (5.5 / 3600) Γ 25 = $0.038
C.4 Timeline of Duration Changes
| Date | Change |
|---|---|
| Pre-2026-03-14 | Full request time including cold-start |
| 2026-03-14 | Billing clock moved to LLM invocation (excluded cold-start) |
| 2026-03-16 | Added agreement routing + topic extraction LLM time |
| 2026-04-02 | Made configurable per org. Default changed to response-time (post-auth) |
C.5 Scope
Billing mode applies only to query_usage transactions (rag-v2). Other transaction types (llm_suggest, document_add, etc.) always bill on actual execution time.
G. Dual Billing: IPR and Cross-Peer RoC Transfers
G.1 The Distinction
Not all cross-peer content sharing involves IPR charges. There are two revenue models for peer-retrieved content, plus the free model for platform help:
| Model | When | Revenue Flow | Example |
|---|---|---|---|
| IPR | Provider sets ipr_hourly_rate > 0 |
User pays platform_rate + ipr_rate. IPR revenue goes to provider peer's organisation. |
Premium content marketplace, licensed knowledge bases |
| RoC Transfer | Agreement has roc_eligible = true, ipr_hourly_rate = 0 |
Consumer site calculates RoC for retrieved content. RoC amount is transferred to the author's account on the provider site. | Individual contributor whose content is shared via their site's agreement |
| Free | hourly_rate = 0, ipr_hourly_rate = 0, roc_eligible = false |
No cost, no RoC. Analytics only. | Platform help documentation |
G.2 The Problem with Requiring Author Accounts on Consumer Sites
A contributor authors a document on site-A.rosie.io (their home site). Site-A's governing body creates a sharing agreement with site-B.rosie.io. A user on site-B queries and retrieves the contributor's content.
The contributor has no account on site-B and shouldn't need one. They may not even know the agreement exists β their site's governing body negotiated it. It's not feasible to expect contributors to register on every site their content might be shared with.
The RoC must flow back to the contributor on their home site (the provider) via an inter-site transfer.
G.3 Cross-Peer RoC Transfer Flow
User on consumer-site (site-B) queries β retrieves content from provider-site (site-A)
1. Consumer-site calculates RoC time credits for the retrieved content:
roc_seconds = duration_seconds Γ roc_split_percent Γ attention_score
This is the same RoC formula used for local contributors.
The contributor's identity is the document's user_id from schema.documents β the user who uploaded the content.
2. Consumer-site records a local RoC credit transfer (non-cash redeemable time credits):
{
"transaction_type": "roc_credit_transfer_out",
"roc_seconds": 3.6, β time credit amount in seconds
"peer_agreement_id": "agr_abc123",
"provider_peer_org_id": "site_a_org_id",
"contributor_user_id": "uuid-of-contributor",
"source": "https://partner-site.example.com/compliance/data-governance/",
"attention_score": 0.20,
"status": "pending"
}
3. Consumer-site calls provider-site's RoC transfer RPC:
provider.rpc('receive_peer_transfer', {
p_transfer_type: 'roc', // or 'ipr'
p_contributor_user_id: "uuid-of-contributor",
p_roc_seconds: 3.6,
p_source: "https://partner-site.example.com/compliance/data-governance/",
p_consumer_org_id: "site_b_org_id",
p_agreement_id: "agr_abc123"
})
4. Provider-site validates the transfer:
a. Verify the calling peer's agreement is active
b. Verify the author exists on this site
c. Verify the agreement has roc_eligible = true
d. Credit the author's balance
e. Record a "roc_credit_transfer_in" transaction
f. Return confirmation
5. Consumer-site updates its local transaction status to "confirmed"
Consumer-side ledger: schema.peer_transfers table tracks outbound
transfers with status (pending β confirmed / failed / unresolvable).
This is separate from schema.transactions.
Provider-side ledger: schema.transactions WHERE
transaction_type = 'peer_transfer_in'.
There is no peer_transfers table on the provider side.
Transfer timing: fire-and-forget from persistHistoryNode. The query
completes and the user receives their response before transfers
confirm. Failed transfers are tracked in peer_transfers (status:
'failed') and retried manually via the Network Health dashboard.
The sourcesContributed gate: transfers only fire when the LLM
determines that retrieved sources meaningfully contributed to the
answer. If sourcesContributed is false (low confidence, sources
not relevant), no transfers fire and no billing occurs for that query.
G.4 Provider-Side RPC: Receive Peer Transfer
A single RPC handles both RoC and IPR transfers. The p_transfer_type
parameter determines behaviour ('roc' or 'ipr').
CREATE OR REPLACE FUNCTION schema.receive_peer_transfer(
p_transfer_type text, -- 'roc' or 'ipr'
p_contributor_user_id uuid, -- documents.user_id (schema.users.id, NOT auth_id)
p_roc_seconds numeric(10,4) DEFAULT 0,
p_ipr_amount numeric(10,4) DEFAULT 0,
p_currency text DEFAULT 'CAD',
p_source_url text DEFAULT '',
p_attention_score numeric(5,4) DEFAULT 0,
p_consumer_org_id text DEFAULT '',
p_agreement_id text DEFAULT ''
)
RETURNS jsonb
Validation:
- Caller must be an active peer agreement user (auth.uid() β agreements.auth_user_id)
- Transfer type must match agreement terms:
- 'roc': agreement.roc_eligible must be true
- 'ipr': agreement.ipr_hourly_rate must be > 0
- Contributor lookup: schema.users WHERE id = p_contributor_user_id (NOT auth.users, NOT users.auth_id β documents.user_id references users.id)
Balance crediting:
- RoC: users.balance (same column as creditRocEarnings)
- IPR: users.ipr_balance (separate column β income, not spending credits)
Transaction recording:
- transaction_type: 'peer_transfer_in'
- Amount stored in platform_fee column (transactions table has no 'amount' column)
- Currency stored in metadata JSONB (transactions table has no 'currency' column)
- Status stored in metadata JSONB (transactions table has no 'status' column)
Missing contributor handling:
- If user not found, record transaction with status 'unresolvable' in metadata
- Return accepted: true, escrowed: true (transfer accepted, funds held)
G.5 Edge Case: Deleted Contributors
Since contributor identity is schema.documents.user_id (always present when a document exists), the "unresolvable author" problem is eliminated for active documents. The only edge case is when a user account is deleted but their documents remain shared via peer agreements.
In this case, the RoC credit transfer is recorded with status: 'unresolvable' on the provider site. The provider admin can credit it to an organisational account or handle it per their retention policy. No escrow mechanism needed β this is a rare administrative edge case, not a systemic problem.
G.6 Agreement Table: Supporting Both Models
CREATE TABLE agreements (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
scope text NOT NULL, -- 'peer', 'document', 'user', 'org'
peer_org_id text,
access_level_min int DEFAULT 0,
access_level_max int DEFAULT 0,
source_prefixes text[],
peer_scopes text[], -- confidential scoping (Β§F)
permissions text[] DEFAULT ARRAY['read'],
auth_user_id uuid, -- Supabase auth user for this agreement
-- Billing: one or both can apply
hourly_rate numeric(10,4) DEFAULT 0, -- platform rate for cross-peer queries
ipr_hourly_rate numeric(10,4) DEFAULT 0, -- IPR charge (provider sets this)
roc_eligible boolean DEFAULT false, -- if true, cross-peer RoC transfers are enabled
-- Agreement metadata
description text, -- semantic description for LLM routing (Β§H)
topic_tags text[], -- topic tags for LLM routing (Β§H)
currency text DEFAULT 'CAD',
status text DEFAULT 'active',
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
G.7 Billing Flow by Model
IPR model (ipr_hourly_rate > 0):
User queries content from provider-peer:
Total cost = (duration / 3600) Γ (platform_rate + ipr_hourly_rate)
Distribution:
Consumer platform β platform_rate (balance deduction from user)
Local contributors β RoC time credits (non-cash, redeemable for query time) for local sources
Provider contributor β ipr_hourly_rate Γ attention_score
(for peer sources, to schema.documents.user_id via
receive_peer_transfer RPC, credited to users.ipr_balance)
RoC transfer model (ipr_hourly_rate = 0, roc_eligible = true):
User queries content from provider-peer:
Total cost = (duration / 3600) Γ platform_rate (no IPR surcharge to the user)
Distribution:
Consumer platform β platform_rate (balance deduction from user)
Provider contributor β RoC time credits transferred to contributor's balance
on the PROVIDER site via receive_peer_transfer RPC
β non-cash, redeemable for query time on the provider site
The user pays the same platform rate as for local content.
The difference is where the RoC credits go β to the provider site's contributor, not a local user.
Free model (help docs β hourly_rate = 0, ipr_hourly_rate = 0, roc_eligible = false):
No cost. No RoC. Recorded for analytics only.
G.8 Transaction Metadata for Multi-Source Queries
{
"transaction_type": "query_usage",
"platform_fee": 0.0236,
"ipr_cost": 0.0028,
"roc_distributions": [
{
"user_id": "local-contributor-uuid",
"source": "local://policy-doc",
"attention_score": 0.45,
"roc_seconds": 3.8,
"type": "local_roc"
}
],
"roc_transfers": [
{
"provider_peer_org_id": "site_a_org_id",
"contributor_user_id": "uuid-of-contributor",
"source": "https://partner-site.example.com/compliance/data-governance/",
"attention_score": 0.2,
"roc_seconds": 3.6,
"transfer_status": "confirmed",
"type": "cross_peer_roc_transfer"
}
],
"peer_sources": [
{
"peer": "site_a_org",
"sources": 1,
"attention": 0.2,
"fee": 0.0056,
"ipr": 0.0028,
"roc_seconds": 3.6
},
{
"peer": "trustrosie_io",
"sources": 1,
"attention": 0.35,
"fee": 0,
"ipr": 0,
"roc_transfer": 0
}
]
}
NOTE: The transactions table uses platform_fee (not amount) for the monetary value, and stores currency and status in the metadata JSONB field. The schema shown here represents the logical structure; see the actual table definition for column names.
Additional fields added during Phase 4:
{
"ipr_cost": 0.0028,
"peer_transfers_summary": {
"total_roc_seconds_out": 3.6,
"total_ipr_amount_out": 0.0028,
"ipr_currency": "CAD",
"transfer_count": 2
},
"actual_duration_seconds": 12.4
}
A separate 'transfer-summary' SSE event fires from persistHistoryNode milliseconds after the transaction-summary event, providing the actual ipr_cost and per-source transfer details (the transaction-summary fires from generateResponseNode with ipr_cost: 0 because transfers haven't been calculated yet).
G.9 Settlement and Reconciliation
RoC and IPR transfers fire in near-real-time (fire-and-forget per query). Failed transfers are tracked in the consumer-side peer_transfers table and surfaced through three channels:
- Stack logs β log.error() with full 15-field diagnostic context
- Google Chat webhook β immediate admin alerting
- In-app system notification β sendSystemNotification to admin panel
- Network Health dashboard β "Failed Transfers" attention card with [Retry] button
Reconciliation is admin-driven (not automated):
- Admin reviews failed transfers in Network Health dashboard
- Admin clicks [Retry Now] to manually retry all failed transfers
- The retry endpoint (POST /adminReconcile/reconcile/retry-transfers) recreates peer clients and re-calls receive_peer_transfer
Automated reconciliation (pg_cron, exponential backoff, batch processing) can be added when production transfer volume justifies it.
Stale pending recovery: transfers stuck in 'pending' for 30+ minutes (Edge Function timeout/crash) are automatically marked 'failed' during manual retry.
G.10 Contributor Visibility
Contributors on the provider site can see in their dashboard:
- RoC earned from local queries (existing)
- RoC earned from cross-peer transfers (tagged with source: cross_peer)
- IPR earned from cross-peer transfers (users.ipr_balance)
- Which documents earned cross-peer RoC/IPR
- They do NOT see which specific consumer sites queried their content (consumer_org_id and agreement_id are gated on access_level >= 9)
Balance separation:
- users.balance: spending credits (prepaid query credits + RoC earned)
- users.ipr_balance: income (IPR earnings from cross-peer content usage) These are displayed separately in the dashboard.