Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/adapters/gfa-store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ postgres = ["sqlx/postgres", "sqlx/tls-rustls-ring-native-roots"]
gfa-api-types.workspace = true
gfa-service.workspace = true
serde_json.workspace = true
serde.workspace = true
futures-util.workspace = true
sqlx.workspace = true

[dev-dependencies]
Expand Down
13 changes: 13 additions & 0 deletions crates/adapters/gfa-store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,16 @@ cargo test -p gfa-store --no-default-features --features postgres --locked -- --
```

Retention policies and materialized replay snapshots are separate increments.


## LLM call journal and spending accounts

Both backends implement `gfa_service::llm_ledger::LlmLedger`. A call reservation writes the complete credential-free request and reserves a conservative token/cost bound against its match and provider-key accounts, plus an optional benchmark-run account. Account references are host-generated names such as `key:primary-anthropic`; never use an API key as a reference. Limits are immutable for an account's lifetime, so a new budget period uses a new reference.

Only `ReserveResult::Reserved` authorizes a new HTTP call. An existing call ID returns its stored response/status and never authorizes retransmission. Reusing an ID with different input, or using a different ID for the same match/seat/turn/attempt, fails. SQLite reserves its writer before checking balances. PostgreSQL inserts/locks account rows in a fixed order; every call, reservation, and settlement shares those same locks. All affected balances and the journal update commit together.

Timeouts and cancelled calls keep their full reservation as `Uncertain`. The host may reconcile a known response later. Settlement computes cost from the immutable pricing snapshot, separates all token classes, and is idempotent for an identical provider response. If reported usage exceeds the host's reservation, the actual amount is recorded and `exceeded_reservation` is true. Exhausted accounts reject subsequent reservations; the host must surface the estimation failure rather than reporting the call as free or silently releasing funds.

Transcripts are paged by `(turn, attempt)` within a host-authorized match/seat. Pages contain at most 100 records and ordinarily at most 8 MiB of stored payload; a larger single record is returned whole. Continue from the last returned call until an empty page. These trusted persistence methods do not expose an HTTP endpoint, hold provider keys, or enable model seats on their own.

Integration tests exercise concurrent reservations/settlements, duplicate dispatch attempts, rollback across scopes, unknown billing, restart recovery, actual usage above a reservation, and transcript ordering against SQLite and PostgreSQL.
1 change: 1 addition & 0 deletions crates/adapters/gfa-store/build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Rebuild embedded migrations when their source changes.
fn main() {
println!("cargo:rerun-if-changed=migrations/sqlite");
println!("cargo:rerun-if-changed=migrations/postgres");
}
14 changes: 14 additions & 0 deletions crates/adapters/gfa-store/migrations/postgres/0002_llm_ledger.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CREATE TABLE gfa_llm_accounts (
id TEXT PRIMARY KEY NOT NULL,
payload TEXT NOT NULL
);

CREATE TABLE gfa_llm_calls (
id TEXT PRIMARY KEY NOT NULL,
match_id TEXT NOT NULL REFERENCES gfa_matches(id),
seat INTEGER NOT NULL CHECK (seat BETWEEN 0 AND 255),
turn BIGINT NOT NULL CHECK (turn >= 0),
attempt INTEGER NOT NULL CHECK (attempt BETWEEN 0 AND 1000),
payload TEXT NOT NULL,
UNIQUE (match_id, seat, turn, attempt)
);
Loading
Loading