Phase 5 — centralized aggregator + admin surface #6
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Consumer side of the billing pipeline. Reads what Phase 4 (#5) writes to
lhumina_code/hero_onboarding_billingand applies each UsageRecord to the centralBilling.credit_balance_cents. Also lifts the placeholder admin page into the real Phase 5 admin surface (read-only views).Tracking parent: #1 §Centralized aggregator + §Admin.
Wire-format + repo-layout contract is locked by D-14: single repo
lhumina_code/hero_onboarding_billing, per-node subdirnodes/<node-name>/usage_<YYYY-MM-DD-HH>.otoml, OTOML wire format withUsageBatch { records: Vec<UsageRecord> }wrapper.Scope
Aggregator core
BillingAggregatorState— per node-subdir:last_commit_sha,last_run_at,records_consumed_total.@indexonnode_name.ConsumedRecordIndex— per applied UsageRecord:idempotency_key @index,applied_at,cost_cents,user_sid,node_name. Drives the dedup of already-applied records on commit-replay / retry.crates/hero_onboarding_server/src/usage_aggregate.rs(~350 LOC + unit tests):AggregateConfig(mirror ofPushConfig— same forge token + repo identity)aggregate_unconsumed_records(osis, http, cfg)enumeratesnodes/in the billing repo, then for each node-subdir:GET /repos/.../commits?path=nodes/<node>&sha=main&limit=1→ if the latest sha equals storedlast_commit_sha, skip; otherwise listnodes/<node>/contents, GET eachusage_*.otoml, parseUsageBatch, for each record: checkConsumedRecordIndexbyidempotency_key→ if absent, decrementBilling.credit_balance_centsbycost_centsand appendConsumedRecordIndexrow; finally updateBillingAggregatorState.push_unsent_recordserror semantics).Server + admin wiring
POST /admin/aggregate-now(admin-secret-gated) — mirror of/admin/push-usage-now. Returns aggregate summary JSON.GET /admin/list-users,GET /admin/list-payments,GET /admin/aggregator-state,GET /admin/list-billing(admin-secret-gated) — read-only JSON feeds for the admin UI.hero_onboarding_admin aggregate --onceCLI subcommand (mirror ofpush-usage --once).hero_onboarding_aggregator_cronaction withSchedulePolicy.interval_ms = 300_000(5 min — balance updates feel live).Real Phase 5 admin UI
Replaces the Phase 4 placeholder at
crates/hero_onboarding_admin/src/main.rs. All routes admin-secret-gated; admin reads via reqwest against the server's new list endpoints (no second OSIS handle on the admin process).GET /— overview tiles (total users, total balance, consumed records, aggregator health)GET /users— list of User + Billing rowsGET /payments— PaymentEvent audit logGET /aggregator— per-nodeBillingAggregatorStaterows (last commit, last run, records consumed)Smoke
scripts/smoke_aggregate.sh— seed test billing repo with syntheticnodes/A/usage_*.otoml; runaggregate --once; assert balance decremented by sum ofcost_cents; assertConsumedRecordIndexpopulated; assert second aggregate is no-op (commit-sha skip); add a new record via direct forge commit; assert next aggregate consumes only the new record. Cleanup.Acceptance
lab build --release --install --workspaceVICTORY 3/3lab infocheck3/3 cleancargo fmt --checkcleancargo clippy --workspace --all-targets -- -D warningscleanscripts/smoke_aggregate.shend-to-end green against realforge.ourworld.tfPossible follow-ups
aggregator-hash-resume) if recovery semantics (per-node halt-on-failure, idempotency-key dedup index) turn out to be load-bearing across operators.