Documentation
¶
Overview ¶
Package authors mounts the Hanzo Cloud /v1/authors/* OSS-author surface: a native-Go, per-org program on Base/SQLite that pays open-source AUTHORS a royalty on the metered platform spend of the orgs who DEPLOY their projects on Hanzo. It sits next to clients/referrals (a one-time credit for both sides) and clients/affiliates (an ongoing partner commission on referred customers) as the THIRD growth loop — the CREATOR one — and mirrors their structure exactly: one SQLite store, server-side tenant isolation, one Mount, HIP-0106, and the SAME commerce ledger path (a credits payout is a grant, tag grant:author).
The loop, end to end:
- An author CONNECTS GitHub (POST /v1/authors/connect): we link the caller's org to a GitHub login — from IAM's linked GitHub account when available (identity verified), else a login the caller supplies (verified per-repo below). We mint a stable per-author VERIFY CODE for the file method.
- The author VERIFIES a repo (POST /v1/authors/repos/verify): ownership is proven either by an IAM-linked GitHub token showing ADMIN/PUSH permission on the repo (OAuth method), or by a hanzo.json on the repo's default branch carrying the author's verify code (file method — proves default-branch control). A verified repo can now earn.
- When a published project whose sourceRepo matches a VERIFIED author repo is DEPLOYED by ANY org, the deploy path records it (POST /v1/authors/deploys/ record): deploying_org↔repo↔project, idempotent per (repo, project, org). hanzo.app persists sourceRepo on the published project so the deploy is attributable.
- The ACCRUAL SWEEP (POST /v1/admin/authors/sweep, the cron path; also lazy on the author's own dashboard read) folds over each approved author's DISTINCT deploying orgs (excluding the author's own): royalty = that org's metered spend THIS PERIOD × the author's share (default 5%), accrued at-most-once per (author, deploying_org, period).
- Staff PAY OUT accrued royalty (POST /v1/admin/authors/:id/payout): "credits" issues a commerce grant into the author's wallet; cash methods are record-only. A payout can never exceed pending (accrued − paid), guarded atomically.
Surface:
GET /v1/authors (org) my status, login, verified, repos, deploys, accrued/pending/paid, payouts POST /v1/authors/connect (org) link GitHub (IAM-linked account or supplied login) + mint verify code POST /v1/authors/repos/verify (org) verify repo ownership (oauth admin-check OR hanzo.json file) POST /v1/authors/deploys/record (org=deployer) record a deploy of a verified author repo (provenance → royalty) GET /v1/admin/authors (global-admin) every author + a summary POST /v1/admin/authors/sweep (global-admin) accrue royalty for every deploying org this period POST /v1/admin/authors/:id/approve (global-admin) admit to earning (+ optional share override) POST /v1/admin/authors/:id/suspend (global-admin) suspend POST /v1/admin/authors/:id/payout (global-admin) record a payout (credits → grant; cash → record-only)
serve.go auto-registers GET /v1/authors/health.
Index ¶
- Constants
- func Mount(app *zip.App, deps cloud.Deps) error
- func Shutdown() error
- type Accrual
- type Author
- type AuthorRepo
- type DeployEvent
- type Payout
- type Store
- func (s *Store) Approve(ctx context.Context, id string, shareBps int64, now int64) (Author, error)
- func (s *Store) Close() error
- func (s *Store) Connect(ctx context.Context, id, org, githubLogin, verifyCode string, shareBps int64, ...) (Author, bool, error)
- func (s *Store) DeployCountsByAuthor(ctx context.Context) (map[string]int, error)
- func (s *Store) DistinctDeployingOrgs(ctx context.Context, authorID, authorOrg string, limit int) ([]string, error)
- func (s *Store) GetByID(ctx context.Context, id string) (Author, error)
- func (s *Store) GetByOrg(ctx context.Context, org string) (Author, error)
- func (s *Store) LatchAccrual(ctx context.Context, accrualID, authorID, deployingOrg, period string, ...) (bool, error)
- func (s *Store) ListAll(ctx context.Context, limit int) ([]Author, error)
- func (s *Store) ListApproved(ctx context.Context, limit int) ([]Author, error)
- func (s *Store) ListDeploys(ctx context.Context, authorID string, limit int) ([]DeployEvent, error)
- func (s *Store) ListPayouts(ctx context.Context, authorID string, limit int) ([]Payout, error)
- func (s *Store) ListRepos(ctx context.Context, authorID string, limit int) ([]AuthorRepo, error)
- func (s *Store) RecordDeploy(ctx context.Context, id, authorID, repoURL, project, deployingOrg string, ...) (DeployEvent, bool, error)
- func (s *Store) RecordPayout(ctx context.Context, payoutID, authorID string, amountCents int64, ...) (Payout, error)
- func (s *Store) RepoCountsByAuthor(ctx context.Context) (map[string]int, error)
- func (s *Store) SetPayoutTxn(ctx context.Context, payoutID, txn string) error
- func (s *Store) Suspend(ctx context.Context, id string, now int64) (Author, error)
- func (s *Store) UpsertVerifiedRepo(ctx context.Context, id, authorID, repoURL, method string, now int64) (AuthorRepo, bool, error)
- func (s *Store) VerifiedRepoForURL(ctx context.Context, repoURL string) (AuthorRepo, error)
- func (s *Store) VoidPayout(ctx context.Context, payoutID, authorID string, amountCents int64) error
Constants ¶
const ( StatusConnected = "connected" StatusApproved = "approved" StatusSuspended = "suspended" )
Status values. An author advances connected → approved (and can be suspended). Only an APPROVED author accrues earnings; a connected author can still verify repos (ownership proof is independent of program admission).
const ( MethodOAuth = "oauth" MethodFile = "file" )
Verification methods for a repo. "oauth" = the caller's IAM-linked GitHub token proved admin permission on the repo; "file" = a hanzo.json on the repo's default branch contained the author's verify code (proves default-branch control).
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Accrual ¶
type Accrual struct {
ID string `json:"id"`
AuthorID string `json:"authorId"`
DeployingOrg string `json:"deployingOrg"`
Period string `json:"period"`
SpendCents int64 `json:"spendCents"`
EarningCents int64 `json:"earningCents"`
CreatedAt int64 `json:"createdAt"`
}
Accrual is one per-period royalty event: the deploying org's spend for that period × the author's share. UNIQUE(author, deploying_org, period) makes the sweep at-most-once per period — the royalty latch.
type Author ¶
type Author struct {
ID string `json:"id"`
Org string `json:"-"` // the author's own org; admin view re-exposes it
GithubLogin string `json:"githubLogin"`
VerifyCode string `json:"-"` // per-author token for the hanzo.json file method
Status string `json:"status"`
AccruedCents int64 `json:"accruedCents"` // lifetime earnings accrued
PaidCents int64 `json:"paidCents"` // lifetime earnings paid out
CreatedAt int64 `json:"createdAt"`
VerifiedAt int64 `json:"verifiedAt"` // GitHub identity OAuth-verified time (0 = file-only)
ApprovedAt int64 `json:"approvedAt"`
SuspendedAt int64 `json:"suspendedAt"`
}
Author is one OSS author org enrolled in the deploy-royalty program. Org is UNIQUE (one author per org). GithubLogin is the linked GitHub identity; VerifyCode is the stable per-author token placed in hanzo.json for the file method. ShareBps is the royalty rate in basis points (500 = 5% of a deploying org's metered spend).
func (Author) PendingCents ¶
PendingCents is the royalty earned but not yet paid (never negative).
type AuthorRepo ¶
type AuthorRepo struct {
ID string `json:"id"`
AuthorID string `json:"authorId"`
RepoURL string `json:"repoUrl"`
Verified bool `json:"verified"`
Method string `json:"method"`
CreatedAt int64 `json:"createdAt"`
VerifiedAt int64 `json:"verifiedAt"`
}
AuthorRepo is one repository an author has claimed. RepoURL is the canonical host/owner/name form (UNIQUE across the table — a repo belongs to at most one author, first-verify wins). Verified flips true once ownership is proven.
type DeployEvent ¶
type DeployEvent struct {
ID string `json:"id"`
AuthorID string `json:"authorId"`
RepoURL string `json:"repoUrl"`
Project string `json:"project"`
DeployingOrg string `json:"deployingOrg"`
CreatedAt int64 `json:"createdAt"`
}
DeployEvent is one attribution edge: a deploying org deployed a project sourced from a verified author repo. UNIQUE(repo_url, project, deploying_org) makes the record idempotent (a re-deploy of the same project by the same org is one edge).
type Payout ¶
type Payout struct {
ID string `json:"id"`
AuthorID string `json:"authorId"`
AmountCents int64 `json:"amountCents"`
Method string `json:"method"`
Reference string `json:"reference"`
Txn string `json:"txn,omitempty"`
CreatedAt int64 `json:"createdAt"`
}
Payout is one recorded disbursement of accrued royalty. A "credits" method issues a commerce grant (Txn set); cash methods (wire/paypal/…) are record-only.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the authors database. ONE SQLite file holds every org's author record, claimed repos, deploy-attribution edges, accrual events, and payouts. A repo→author lookup is a GLOBAL directory by design (any deploying org presents a repo minted by ANY author); every /v1/authors read is scoped by the caller's org server-side.
func (*Store) Approve ¶
Approve admits an author to earning (status=approved) and optionally overrides the share rate. Idempotent. errNotFound if missing.
func (*Store) Connect ¶
func (s *Store) Connect(ctx context.Context, id, org, githubLogin, verifyCode string, shareBps int64, identityVerified bool, now int64) (Author, bool, error)
Connect enrolls org as an author at status=connected, idempotently. It records the GitHub login + verify code and, when identityVerified, stamps verified_at. A repeat connect UPDATES the login (re-link) but never resets status/accrual/verify_code — first connect owns the code. Returns (author, created).
func (*Store) DeployCountsByAuthor ¶
DeployCountsByAuthor returns author_id → deploy-event count in ONE GROUP BY.
func (*Store) DistinctDeployingOrgs ¶
func (s *Store) DistinctDeployingOrgs(ctx context.Context, authorID, authorOrg string, limit int) ([]string, error)
DistinctDeployingOrgs returns the DISTINCT orgs that have deployed one of an author's repos, EXCLUDING the author's own org (no self-royalty). This is the accrual set — one accrual per (author, deploying_org, period).
func (*Store) LatchAccrual ¶
func (s *Store) LatchAccrual(ctx context.Context, accrualID, authorID, deployingOrg, period string, spendCents, earningCents, now int64) (bool, error)
LatchAccrual atomically records ONE accrual for (author, deployingOrg, period) and adds the earning to the author's accrued balance — in a single transaction. A UNIQUE violation means this period was already accrued (returns false, no error, no double-accrual). Returns won=true only when THIS call created the accrual.
func (*Store) ListApproved ¶
ListApproved returns every approved author (the sweep set), oldest-first.
func (*Store) ListDeploys ¶
ListDeploys returns an author's deploy events, newest-first, bounded.
func (*Store) ListPayouts ¶
ListPayouts returns an author's payout history, newest-first, bounded.
func (*Store) RecordDeploy ¶
func (s *Store) RecordDeploy(ctx context.Context, id, authorID, repoURL, project, deployingOrg string, now int64) (DeployEvent, bool, error)
RecordDeploy records a deploy-attribution edge, idempotently. UNIQUE(repo, project, deployingOrg) makes a re-deploy a no-op returning the FIRST edge. Returns (edge, created).
func (*Store) RecordPayout ¶
func (s *Store) RecordPayout(ctx context.Context, payoutID, authorID string, amountCents int64, method, reference string, now int64) (Payout, error)
RecordPayout atomically RESERVES amountCents against the author's pending royalty (accrued − paid) and records a payout row — in one transaction. The WHERE guard makes it impossible to pay out more than is owed, even under concurrency (RowsAffected 0 → errInsufficientPending). The commerce grant (credits payout) happens AFTER, outside the tx; SetPayoutTxn records the receipt. errNotFound if the author is missing.
func (*Store) RepoCountsByAuthor ¶
RepoCountsByAuthor returns author_id → verified-repo count in ONE GROUP BY (the admin directory's per-row count, no N+1 fan-out).
func (*Store) SetPayoutTxn ¶
SetPayoutTxn records the commerce ledger transaction id after a credits payout deposit lands (best-effort receipt; the pending reservation is the authority).
func (*Store) Suspend ¶
Suspend moves an author to suspended (stops NEW accrual; earned royalty is unaffected). errNotFound if missing.
func (*Store) UpsertVerifiedRepo ¶
func (s *Store) UpsertVerifiedRepo(ctx context.Context, id, authorID, repoURL, method string, now int64) (AuthorRepo, bool, error)
UpsertVerifiedRepo records repoURL as a VERIFIED repo of authorID, idempotently. repoURL is UNIQUE across the table: if it already belongs to THIS author the row is refreshed (method/verified_at updated); if it belongs to ANOTHER author the insert conflicts and errUnknownRepo-style ownership is refused via errRepoOwned. Returns (repo, created).
func (*Store) VerifiedRepoForURL ¶
VerifiedRepoForURL resolves a canonical repo url to its VERIFIED owning author + repo, or errUnknownRepo / errRepoNotVerified. The deploy-record attribution path.
func (*Store) VoidPayout ¶
VoidPayout reverses a RecordPayout that could not be BACKED by the treasury reserve: it deletes the payout row and restores the reserved amount to pending (paid_cents −= amount), in one transaction. It is the compensating action when the fund cannot cover a payout the pending-guard already reserved — so a blocked payout leaves the author's pending royalty intact, honestly, instead of silently burning it.