Documentation
¶
Overview ¶
Package authors is a royalty for open-source work: your repo runs, you get paid.
An author links GitHub, proves they own a repo, and earns a royalty on the metered spend of every org that deploys a project built from it — accrued per period and auto-paid, with a full audit trail behind the number.
It is the CREATOR member of the three programs built on the same shape; apps/referrals is the one-time bonus and apps/affiliates the partner commission. All three share the 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 (the scheduler's automatic loop; POST /v1/admin/authors/sweep as an operator override; 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 (20%), accrued at-most-once per (author, deploying_org, period).
- The AUTO-PAYOUT (same scheduler pass, right after accrual; POST /v1/admin/authors/:id/payout as an operator override) settles each author's pending royalty with NO human in the loop: "credits" issues a commerce grant into an external author's wallet; a Hanzo-MAINTAINED template's royalty is realized into the Hanzo treasury reserve instead ("pay ourselves"). A payout can never exceed pending (accrued − paid), guarded atomically — idempotent, never double-pays.
HANZO FORKS. A repo whose owner is a brand org (owner ∈ {hanzoai, hanzo-*}) is auto-attributed on first deploy to the treasury SYSTEM author (org = the brand slug), so Hanzo earns 20% on its OWN templates when other orgs deploy them, credited to the treasury reserve via the shared treasury client — no external wallet, no new ledger.
Surface:
GET /v1/authors (org) my status, login, verified, repos, deploys, accrued/pending/paid, payouts GET /v1/authors/basis (org) why my number is my number: share, cost model, immutable rows, reconciliation 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 (SuperAdmin) every author + a summary POST /v1/admin/authors/sweep (SuperAdmin) accrue royalty for every deploying org this period POST /v1/admin/authors/:id/approve (SuperAdmin) admit to earning (+ optional share override) POST /v1/admin/authors/:id/suspend (SuperAdmin) suspend POST /v1/admin/authors/:id/payout (SuperAdmin) record a payout (credits → grant; cash → record-only) GET /v1/admin/authors/:id/basis (SuperAdmin) the SAME basis payload the author reads (support mirror)
serve.go auto-registers GET /v1/authors/health.
Index ¶
- Constants
- func AccrueForOrg(ctx context.Context, deployingOrg string, spend int64, period string, ...) int
- func Mount(app cloud.Router, deps cloud.Deps) error
- func Shutdown() error
- type Accrual
- type Author
- type AuthorOrg
- type AuthorRepo
- type DeployEvent
- type LedgerRow
- type Payout
- type Store
- func (s *Store) Approve(ctx context.Context, id string, shareBps int64, now int64) (Author, error)
- func (s *Store) AuthorsDeployedBy(ctx context.Context, deployingOrg string, limit int) ([]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) EnsureSystemAuthor(ctx context.Context, id, org, githubLogin string, shareBps, now int64) (Author, 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, ...) (bool, error)
- func (s *Store) LedgerTotals(ctx context.Context, authorID string) (rows int, earningCents int64, err 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) ListLedger(ctx context.Context, authorID, period string, limit int) ([]LedgerRow, error)
- func (s *Store) ListOrgs(ctx context.Context, authorID string, limit int) ([]AuthorOrg, 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) UpsertVerifiedOrg(ctx context.Context, id, authorID, ownerURL, method string, now int64) (AuthorOrg, bool, error)
- func (s *Store) UpsertVerifiedRepo(ctx context.Context, id, authorID, repoURL, method string, now int64) (AuthorRepo, bool, error)
- func (s *Store) VerifiedOrgForURL(ctx context.Context, repoURL string) (AuthorOrg, 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 ( ProviderGitHub = "github" ProviderGitLab = "gitlab" )
Providers an author repo can live on. The provider is derived from the canonical repo host (github.com → github, gitlab.com → gitlab) so ONE code path serves both.
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" // MethodMaintainer = the repo is owned by the platform maintainer (owner ∈ the // brand's GitHub orgs, e.g. hanzoai / hanzo-*): ownership is intrinsic to the // namespace, so a Hanzo-maintained template is auto-verified under the treasury // SYSTEM author with no OAuth/file proof — its creator royalty accrues to the // Hanzo treasury ("pay ourselves") rather than an external author wallet. MethodMaintainer = "maintainer" )
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 ¶
func AccrueForOrg ¶
func AccrueForOrg(ctx context.Context, deployingOrg string, spend int64, period string, now int64) int
AccrueForOrg is the seam the unified affiliate accrual walk calls once per source org (with the spend it already read): it accrues royalty to EVERY approved author whose verified repo that org deployed (excluding the author's own org), latched at-most-once per (author, org, period). It resolves the mounted authors singleton; when authors is NOT mounted (a partial deploy, or an affiliates unit test that does not wire authors) it is a no-op returning 0 — the same degrade-gracefully contract treasury.Reserve uses. Returns the number of NEW royalty accruals latched.
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 (2000 = 20% of a deploying org's metered spend).
func (Author) PendingCents ¶
PendingCents is the royalty earned but not yet paid (never negative).
type AuthorOrg ¶
type AuthorOrg struct {
ID string `json:"id"`
AuthorID string `json:"authorId"`
OwnerURL string `json:"ownerUrl"`
Verified bool `json:"verified"`
Method string `json:"method"`
CreatedAt int64 `json:"createdAt"`
VerifiedAt int64 `json:"verifiedAt"`
}
AuthorOrg is one whole OWNER/org an author has claimed (e.g. github.com/acme). A verified org claim COVERS every repo under that owner, so any repo the author publishes there earns without a per-repo verify. OwnerURL is the canonical "host/owner" form (UNIQUE across the table — an owner belongs to at most one author, first-verify wins). It sits beside AuthorRepo: a deploy matches a per-repo claim OR an owner-wide org claim (per-repo takes precedence).
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 LedgerRow ¶
type LedgerRow 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"`
ComputeProof *string `json:"computeProof"` // nullable: hanzod attestation (follow-up)
CreatedAt int64 `json:"createdAt"`
}
LedgerRow is one immutable, on-chain-ready royalty record appended per latched accrual. ComputeProof is nil until a hanzod compute attestation binds the row (a follow-up) — it is never fabricated.
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"`
// Settlement is where the money landed — settlementTreasury (internal: a
// first-party author's royalty realized into our own reserve), settlementWallet
// (a commerce grant to an external author), or settlementCash. Captured at
// reservation, so the books say plainly which payouts were paid to ourselves.
Settlement string `json:"settlement,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) AuthorsDeployedBy ¶
func (s *Store) AuthorsDeployedBy(ctx context.Context, deployingOrg string, limit int) ([]Author, error)
AuthorsDeployedBy returns the DISTINCT APPROVED authors whose VERIFIED repo the given org deployed, EXCLUDING any author whose own org is the deploying org (no self-royalty). This is the per-compute attribution set the unified accrual walk folds over for one source org.
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) EnsureSystemAuthor ¶
func (s *Store) EnsureSystemAuthor(ctx context.Context, id, org, githubLogin string, shareBps, now int64) (Author, error)
EnsureSystemAuthor idempotently seeds a SYSTEM author (the Hanzo treasury "pay ourselves" identity) at status=approved, identity-verified, sharing shareBps. It owns the platform-maintained OSS templates so their creator royalty accrues to the treasury. A repeat call is a no-op that returns the existing row — never resetting its accrual, paid, or share. Returns the author keyed by org (org is UNIQUE, so one system author per treasury org).
func (*Store) LatchAccrual ¶
func (s *Store) LatchAccrual(ctx context.Context, accrualID, ledgerID, authorID, deployingOrg, period string, shareBps, spendCents, earningCents, now int64) (bool, error)
LatchAccrual atomically records ONE accrual for (author, deployingOrg, period), adds the earning to the author's accrued balance, AND appends the immutable royalty ledger row — all 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. ledgerID/shareBps stamp the ledger row; its compute_proof is written NULL (a hanzod attestation is a follow-up).
func (*Store) LedgerTotals ¶
func (s *Store) LedgerTotals(ctx context.Context, authorID string) (rows int, earningCents int64, err error)
LedgerTotals foots the WHOLE ledger — every row, every period. The reconciliation it feeds must never be computed from a paged window, or a truncated read would silently claim the books balance.
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) ListLedger ¶
func (s *Store) ListLedger(ctx context.Context, authorID, period string, limit int) ([]LedgerRow, error)
ListLedger returns an author's royalty ledger rows, newest-first, bounded. period narrows to ONE accrual bucket (the YYYY-MM periodKey mints); "" is every period.
func (*Store) ListOrgs ¶
ListOrgs returns an author's claimed owner-wide orgs, 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, settlement 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) UpsertVerifiedOrg ¶
func (s *Store) UpsertVerifiedOrg(ctx context.Context, id, authorID, ownerURL, method string, now int64) (AuthorOrg, bool, error)
UpsertVerifiedOrg records ownerURL as a VERIFIED owner-wide claim of authorID, idempotently. owner_url is UNIQUE: 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 ownership is refused via errOrgOwned. Mirrors UpsertVerifiedRepo — first-verify wins. Returns (org, created).
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) VerifiedOrgForURL ¶
VerifiedOrgForURL resolves a canonical REPO url to a VERIFIED owner-wide claim that COVERS it: it derives the repo's "host/owner" and looks up an author_orgs row. This is the org arm of deploy attribution — a repo with no per-repo claim still earns when its owner is a verified org. Returns errUnknownRepo (no claim) / errRepoNotVerified (claim exists but unverified), matching VerifiedRepoForURL so the deploy path treats both arms uniformly.
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.