Phase 4 — Per-node billing-record push (hero_proc cron + Forgejo REST + OTOML) #5

Open
opened 2026-05-20 21:37:50 +00:00 by mik-tf · 0 comments
Owner

Tracks Phase 4 of the onboarding milestone (parent: #1). Lands the producer side of the billing pipeline: each Hero node accumulates UsageRecord rows locally, a hero_proc cron action ticks every hour, the server-side push handler batches new records into per-hour OTOML files and commits them to a centralized billing repo. Phase 5 (aggregator) reads from the same repo to update the central Billing row.

Scope landing this session (s2-006)

  1. POST /record_usage on hero_onboarding_server — admin-only via X-Hero-Onboarding-Admin-Secret header. Accepts (node_id, user_sid, amount_cents, resource_kind, idempotency_key, description), writes a UsageRecord via OSIS. Idempotency dedupe on idempotency_key.
  2. POST /admin/push-usage-now on hero_onboarding_server — admin-secret-gated, triggers a single push immediately. Invoked by the admin CLI + smoke script.
  3. crates/hero_onboarding_server/src/usage_push.rs (new) — push_unsent_records() reads ~/hero/var/hero_onboarding/last_pushed_sid marker, enumerates new UsageRecord rows, groups by hour bucket, serializes each batch as OTOML (UsageBatch { records: Vec<UsageRecord> }), commits to the billing repo via Forgejo REST PUT /repos/{owner}/{repo}/contents/{path} at nodes/<node-name>/usage_<YYYY-MM-DD-HH>.otoml. Auto-creates the billing repo on first push if missing. Marker updates only after successful commit; forge-unreachable → log warn, retry next tick.
  4. hero_onboarding_admin push-usage --once — thin CLI subcommand that loads the admin secret and POSTs to http://127.0.0.1:9920/admin/push-usage-now. Non-zero exit on failure so hero_proc cron sees the failure.
  5. hero_proc cron actionhero_onboarding --start registers a 3rd action hero_onboarding_billing_cron alongside the existing server + admin actions: script = ~/hero/bin/hero_onboarding_admin push-usage --once, schedule_policy.interval_ms = 3_600_000, not is_process().
  6. scripts/smoke_usage_push.sh — white-box E2E against real local Forgejo: bootstrap test user → POST /record_usage × 3 + duplicate-idempotency-key check → admin push-usage --once → curl Forgejo to verify OTOML file landed → idempotency check (re-trigger, no duplicate commit) → cleanup (delete test billing repo, reset marker).

D-14 — per-node billing layout (locked this session, revisable when Kristof/Emre answer #1 open questions)

Choice Locked Issue #1 open question
Service-account / org lhumina_code (existing org; sidesteps hero_ops org creation) Q#1
Repo naming single repo lhumina_code/hero_onboarding_billing (per-node disambiguation via subdirectory) Q#2
Per-file path nodes/<node-name>/usage_<YYYY-MM-DD-HH>.otoml (<node-name> from HERO_NODE_ID env, hostname fallback) Q#2
Wire format OTOML — [[records]] array of UsageRecord rows via oschema serde_otoml Q#3
Cron mechanism hero_proc cron action with interval_ms = 3_600_000 invoking admin CLI n/a
Push tracking marker file ~/hero/var/hero_onboarding/last_pushed_sid (no schema churn) n/a

Full rationale + the consumer-side implications for Phase 5 in decisions/D-14-per-node-billing-layout.md.

Acceptance gates

  • cargo check --workspace green
  • cargo test -p hero_onboarding_server (push module unit tests if any) green
  • lab build --release --install --workspace VICTORY (3 binaries built / 0 failed)
  • lab infocheck 3/3 clean
  • scripts/smoke_usage_push.sh end-to-end green against real local Forgejo (OTOML file lands, idempotency holds, cleanup leaves no test artifacts)

Out of scope (Phase 5)

  • Centralized aggregator service that reads the billing repo and updates central Billing rows.
  • Multi-node distributed-push correctness (multiple Hero nodes pushing simultaneously — locked to disjoint per-node subdirs to sidestep most race shapes).
  • KYC gating order (Phase 6).

Parent: #1

Tracks Phase 4 of the onboarding milestone (parent: #1). Lands the producer side of the billing pipeline: each Hero node accumulates `UsageRecord` rows locally, a hero_proc cron action ticks every hour, the server-side push handler batches new records into per-hour OTOML files and commits them to a centralized billing repo. Phase 5 (aggregator) reads from the same repo to update the central `Billing` row. ## Scope landing this session (s2-006) 1. **`POST /record_usage`** on `hero_onboarding_server` — admin-only via `X-Hero-Onboarding-Admin-Secret` header. Accepts `(node_id, user_sid, amount_cents, resource_kind, idempotency_key, description)`, writes a `UsageRecord` via OSIS. Idempotency dedupe on `idempotency_key`. 2. **`POST /admin/push-usage-now`** on `hero_onboarding_server` — admin-secret-gated, triggers a single push immediately. Invoked by the admin CLI + smoke script. 3. **`crates/hero_onboarding_server/src/usage_push.rs`** (new) — `push_unsent_records()` reads `~/hero/var/hero_onboarding/last_pushed_sid` marker, enumerates new `UsageRecord` rows, groups by hour bucket, serializes each batch as OTOML (`UsageBatch { records: Vec<UsageRecord> }`), commits to the billing repo via Forgejo REST `PUT /repos/{owner}/{repo}/contents/{path}` at `nodes/<node-name>/usage_<YYYY-MM-DD-HH>.otoml`. Auto-creates the billing repo on first push if missing. Marker updates only after successful commit; forge-unreachable → log warn, retry next tick. 4. **`hero_onboarding_admin push-usage --once`** — thin CLI subcommand that loads the admin secret and POSTs to `http://127.0.0.1:9920/admin/push-usage-now`. Non-zero exit on failure so hero_proc cron sees the failure. 5. **hero_proc cron action** — `hero_onboarding --start` registers a 3rd action `hero_onboarding_billing_cron` alongside the existing server + admin actions: script = `~/hero/bin/hero_onboarding_admin push-usage --once`, `schedule_policy.interval_ms = 3_600_000`, not `is_process()`. 6. **`scripts/smoke_usage_push.sh`** — white-box E2E against real local Forgejo: bootstrap test user → POST `/record_usage` × 3 + duplicate-idempotency-key check → admin `push-usage --once` → curl Forgejo to verify OTOML file landed → idempotency check (re-trigger, no duplicate commit) → cleanup (delete test billing repo, reset marker). ## D-14 — per-node billing layout (locked this session, revisable when Kristof/Emre answer #1 open questions) | Choice | Locked | Issue #1 open question | |---|---|---| | Service-account / org | `lhumina_code` (existing org; sidesteps `hero_ops` org creation) | Q#1 | | Repo naming | single repo `lhumina_code/hero_onboarding_billing` (per-node disambiguation via subdirectory) | Q#2 | | Per-file path | `nodes/<node-name>/usage_<YYYY-MM-DD-HH>.otoml` (`<node-name>` from `HERO_NODE_ID` env, hostname fallback) | Q#2 | | Wire format | OTOML — `[[records]]` array of `UsageRecord` rows via oschema serde_otoml | Q#3 | | Cron mechanism | hero_proc cron action with `interval_ms = 3_600_000` invoking admin CLI | n/a | | Push tracking | marker file `~/hero/var/hero_onboarding/last_pushed_sid` (no schema churn) | n/a | Full rationale + the consumer-side implications for Phase 5 in `decisions/D-14-per-node-billing-layout.md`. ## Acceptance gates - `cargo check --workspace` green - `cargo test -p hero_onboarding_server` (push module unit tests if any) green - `lab build --release --install --workspace` VICTORY (3 binaries built / 0 failed) - `lab infocheck` 3/3 clean - `scripts/smoke_usage_push.sh` end-to-end green against real local Forgejo (OTOML file lands, idempotency holds, cleanup leaves no test artifacts) ## Out of scope (Phase 5) - Centralized aggregator service that reads the billing repo and updates central `Billing` rows. - Multi-node distributed-push correctness (multiple Hero nodes pushing simultaneously — locked to disjoint per-node subdirs to sidestep most race shapes). - KYC gating order (Phase 6). Parent: #1
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_onboarding#5
No description provided.