Hero onboarding website + billing backend — full plan #1
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?
Hero onboarding website + billing backend — full plan
Source: meeting Kristof + Emre, 2026-05-20.
This issue captures the full plan for the onboarding website (login + payment + dashboard) and the billing backend (per-node forge billing-record repos + centralized aggregator). It is intentionally one meta-issue rather than many small ones — the parts are tightly coupled and the cross-cutting design decisions need a single review surface.
Sub-tasks are listed under "Phased delivery" below. Each phase will get its own follow-up issue once this meta-plan is agreed.
Scope
In scope (this repo):
Out of scope (tracked elsewhere):
hero_embedder/hero_aibroker/hero_voice/hero_proxymoving from per-user to multi-tenant common hosts attached over Mycelium. Owners: embedder / voice / aibroker / proxy teams.Pricing model (from meeting notes)
Discount ladder (applies to Hero OS hourly + LLM):
Auth model
Mycelium-address only. Users authenticate by proving control of a Mycelium address. No email/password fallback in v0.
This matches the
hero_logindesign pattern: every Hero context is mycelium-addressable, so the same identity flows through onboarding, billing, and later service install / start-stop admin actions.Email/password / OAuth providers can be added later if a real user requirement surfaces — kept out of v0 to avoid surface area we don't yet need.
Architecture
Service shape
kind=webper the canonical lab / hero_proc convention). Deployed via lab + hero_proc on a VM. No fancy orchestration in v0 — single instance, redeploy via the existinglab build --upload+lab build --download --installflow.hero_onboarding_server/(web),hero_onboarding_admin/(admin daemon),hero_onboarding/(CLI launcher),hero_onboarding_schema/(oschema definitions), 3×service.toml,build.rsfor codegen.Schema
User data + billing state live in an OSIS schema written in oschema (the existing Hero schema definition language under
hero_skills/skills/oschema/).build.rsrunsoschema_code_generationto produce Rust types + OSIS handlers + an RPC server.Entities (v0):
User— mycelium address (canonical identifier), display name, created-at, last-active.Billing— credit balance, lifetime paid, current discount tier (week / month).UsageRecord— append-only log of "user X consumed Y units of resource Z at timestamp T on node N." This is the atomic unit pushed to per-node Forge repos.PaymentEvent— Stripe / ClickPesa webhook outcome: provider, amount, currency, status, external-ref, applied-to-user.Schema is the canonical interface; everything else (RPC, UI, billing aggregator) generates from it.
Per-node billing-record repos (the "real" design, no shortcuts)
Each Hero node — embedder host, aibroker host, hero_proxy host, individual user VM host, etc. — runs a
hero_proccron service that pushes its local usage log to a dedicated per-node Forge repo every hour.hero_opsorhero_deploy— naming TBD; see Open questions).hero_ops/billing-<node-name>— naming TBD).lab repo pushmechanic (or equivalent).Hash-resume semantics (the "HARD part" called out in the meeting):
git log <last-hash>..HEADenumerates new commits → new usage records.UsageRecordcarries an idempotency key (proposed:(node_id, local_seq, timestamp)). The aggregator rejects duplicates.Centralized aggregator
A separate Hero service (
hero_onboarding_aggregator/— naming TBD) that:git pull→ walk new commits → parse newUsageRecordentries → apply to the corresponding user'sBillingrow in the central OSIS schema.Provisioning (agnostic, plug-in)
Define a
Provisionertrait / interface. v0 ships one impl + room for more:PoolAssignmentProvisioner— ops pre-deploys a pool of VMs (the same way the existing herodemo VM was deployed); the website assigns an unallocated VM from the pool when a user pays.TfchainAutoDeployProvisioner— full TFchain contract creation on demand, no pre-deployed pool. Separate effort, separate session.This way the website's payment-success flow and the underlying VM acquisition flow are decoupled; we don't lock in a design that's wrong for v1.
Reused in-house components
A neighboring in-house product has already shipped, end-to-end-tested, and proven in production all three external integrations Phase 2 / 3 / 6 need. We lift, we don't reinvent.
kyc.oschemaschema — written in the same canonical Hero schema language we're using forhero_onboarding. Direct copy possible at the schema layer; zero rewrite.openrpc.json+rpc.rs), code-generated types (types_generated.rs), and core logic cleanly separated into acore/module. Transfers cleanly modulo renaming.step_kyc_terms.rs+ multi-step wizard integration) — same UI impedance-mismatch as Stripe / ClickPesa; logic transfers, UI rewrites.hero_skills/skills/oschema/) — canonical Hero schema definition language. No separate template to wait on.kind=webscaffold with pages, blog, auth, admin.Phased delivery
Each phase will get its own follow-up issue linked back to this meta-issue once this plan is agreed.
Phase 1 — Schema scaffold + skeleton web service
Cargo.toml, 3 crates, 3service.toml,build.rs).hero_onboarding_schema/—User,Billing,UsageRecord,PaymentEventin oschema.hero_onboarding_server/—kind=webskeleton, mycelium-address login, dashboard (balance + placeholder for active services), payment stubbed (button → 200 OK → fake credit applied).hero_onboarding_admin/—kind=admindaemon skeleton.lab build --release --installclean;lab infocheckclean;lab service hero_onboarding_server --startopens the page; mycelium-address login + dashboard render.Phase 2 — Stripe sandbox integration
hero_procsecrets context (per existing META env convention).PaymentEventrecorded →Billing.credit_balanceincremented.Phase 3 — ClickPesa sandbox integration
Phase 4 — Per-node billing-record push (hero_proc cron + lab repo push)
UsageRecordwire format (TSV or JSONL; TBD).hero_proccron service that, every hour, commits the new records and pushes to the node's Forge repo.Phase 5 — Centralized aggregator + hash-resume idempotency
hero_onboarding_aggregator/service.Phase 6 — Production keys, Idenfy KYC, pool-assignment Provisioner, live wiring
kyc.oschemaintohero_onboarding_schema/; lift the backend crate (rename + adapt to hero_onboarding's OSIS dispatcher); rewrite the Dioxus wizard step into a Tera template + small JS for the Idenfy browser redirect; reuse the 6+ Playwright e2e scenarios as the acceptance gate. Decision: same Idenfy account or fresh tenant credentials.PoolAssignmentProvisionerimpl: ops pre-deploys VMs, the website assigns one on payment success.Optional rescope: Idenfy KYC could move earlier (e.g. between Phase 3 and Phase 4) if KYC is a hard gate before any payment — that decision is one of the open questions below. Phase 6 keeps it as the default to land it after sandbox payment flows are clean.
Open questions for Kristof + Emre
hero_ops/hero_deploy/hero_billing/ something else?hero_ops/billing-<node-name>/hero_ops/<node-name>-billing/ something else? The aggregator's discoverability depends on this being stable.UsageRecordwire format: TSV / JSONL / oschema-OTOML? Trade-off: TSV is easiest forgit diff, JSONL is easiest for tooling, OTOML matches the rest of the Hero stack.Out of scope / follow-up issues to file
PoolAssignmentProvisionerimpl withTfchainAutoDeployProvisionerimpl; separate session post-v0.Related repos / patterns
hero_website_framework—kind=webskeleton.hero_skills/skills/oschema/+oschema_code_generation— schema → codegen pipeline.hero_proc— supervision + cron + secrets context.hero_router— service entry / discovery / MCP gateway (the onboarding website registers behind it).hero_loginpattern (when / if it exists as a separate crate; otherwise wire mycelium-address auth directly using the documented context + claim format).lab— build / install / publish / repo-push orchestrator used by Phase 4's cron.Cross-link: email / notifications strategy (cross-arc)
Filed on the free-demo arc side: home#236 — META Email / notifications strategy, locked at D-20 (
decisions/D-20-email-provider-sendgrid.md).Decision: SendGrid for all transactional emails originated by either arc. No Resend, no self-hosted SMTP. Provider abstraction (
EmailSendertrait) wraps the SendGrid impl.Acceptance criteria for the first hero_onboarding session that ships an email-sending code path are listed in home#236 — pick sender domain, source API key + DNS records operationally, implement the trait + SendGrid impl, smoke-test deliverability, append the chosen domain to D-20 as a follow-up note.
No immediate action — the rule applies whenever an
hero_onboardingphase first wires transactional email.Cross-link: two-channels overview
Added a cross-arc overview doc at home/docs/channels/free-and-paid.md (commit
bfbf552on the home repo).Audience: engineers + stakeholders. Walks through the paid commercial product (this issue's scope) and the free testing channel (home#235) end-to-end — four UX flows, shared substrate, where the channels touch each other, and explicit out-of-scope per channel.
Not a replacement for this issue. This issue stays the engineering tracker — per-phase status, design decision lockfile, scope splits. The new doc is the cross-arc reader's-eye view this issue intentionally doesn't try to be.
Q#7 (discount ladder mechanics) — LOCKED pre-session for s2-014 Phase 12 (2026-05-21).
Documenting here so the meta-issue open-Qs section reflects the lock without needing a body edit. Phase 12 implementation (next session) builds against these exact answers.
Lock summary
(a) "Continuous usage" measurement = wall-clock since
Billing.first_paid_at. No inactivity reset in v0.Billing.last_active_at: otimereserved (cron will write it from the start, but doesn't consume it for tier promotion in v0).last_active_atwrite atvm_allocate_post+ the aggregator decrement site.(b) Discount applicability =
hero_os_hourly+llmONLY; VM cost NEVER discounted.ResourceCategoryenum onUsageRecord.applies_towith variantshero_os_hourly | llm | vm; defaulthero_os_hourlyfor back-compat with the existing rows from Phase 5 aggregator.applies_toenum + use a single multiplier).(c) Stacking math = multiplicative.
none= ×1.0,week= ×0.5,month= ×0.25 (total 75% off at month-tier).(record.applies_to, billing.discount_tier)at theusage_aggregate.rs:367decrement site.0.25→0.5at theMontharm).Canonical multiplier table (s2-014 ships this verbatim)
What lands at s2-014 Phase 12
Billing.first_paid_at: otime+Billing.last_active_at: otime(additive);UsageRecord.applies_to: ResourceCategory(additive);VmAllocation.plan_id: str+VmAllocation.plan_allocation_sid: str(additive, for multi-VM grouping).hero_onboarding_discount_ladder_cron(daily): walks Billing rows; promotesdiscount_tierwhennow - first_paid_atcrosses the 7-day / 30-day thresholds; writeslast_active_atopportunistically from the aggregator's per-user touch.Planconfig (NOT a schema rootobject — env-var JSON loaded at startup): defaults{"5vms": {cost_cents: 1000, duration_hours: 720, vm_count: 5}, "10vms": {cost_cents: 2000, duration_hours: 720, vm_count: 10}}matching the meta-issue pricing table.vm_allocate_post: multi-VM allocate (vm_countrows in a single transaction sharingplan_allocation_sid); atomicity TBD at s2-014 design pass (single OSIS write batch vs per-VM with rollback).scripts/smoke_discount_ladder.sh(~20 checks): seed billing withfirst_paid_at8 days ago + add hero_os_hourly UsageRecord + run aggregate → assert balance decremented by 50% of raw cost; 31-day tenure → assert 75% discount; VM allocation NOT discounted; week-1 user NOT discounted.D-20 expected
s2-014 /stop2 mints D-20 locking the three Q#7 answers above + the Plan registry shape + multi-VM allocation atomicity semantics.
This comment supersedes the previous Q#7 phrasing in the meta-issue body's open-Qs section as the canonical Phase 12 input.
Signed-by: mik-tf mik-tf@noreply.invalid