Skip to content

feat(spec): kit royalties, slice 1: licenseHash, the lineage split, and an exact computeKitSplit (R4) - #492

Merged
LamaSu merged 3 commits into
masterfrom
feat/r4-kit-royalties
Oct 4, 2026
Merged

LamaSu merged 3 commits into
masterfrom
feat/r4-kit-royalties

Conversation

@LamaSu

@LamaSu LamaSu commented Oct 3, 2026

Copy link
Copy Markdown
Owner

R4 contributor pay, slice 1 (board item 13): kit royalties, priced exactly

Contributors are paid when their code executes. A Kit version carries a #360 License whose one payment requirement is a percent of the gross of every unit that runs the kit, paid to the kit's fork lineage. It is paid as ordinary payout legs in that unit's release: no second ledger, no claim step. The design, fee levels and lineage rule are in the economics lane's return (r4-contributor-pay/DESIGN.md). Kits (#4102) and adk (#4056) agreed the shape.

Stacked on #360 (R15-core, astra SHIP; merge-ready at 8fe16b6). Retarget to master after #360 merges.

What this adds (pure functions in @pcc/spec economics)

  • licenseHash(license): H("PCC:license:v1", License), "0x" + 64 lowercase hex. The License is schema-checked first, so a malformed one is refused, never hashed. Kits pin it as economics.economicTermsHash.
  • kitLineageDistribution(lineage):
    • Nearest first: 1, then 2 : 1, then 4 : 2 : 1. Nothing past the grandparent is paid.
    • A self-fork merges into one member, in lowest terms.
    • Members are sorted by party id, the order the compiler compares a requirement in.
  • kitRoyaltyRequirement({bps, lineage}): the License's payment requirement. It is a percent of gross, per using unit, paid to that distribution. A free kit requires nothing.
  • computeKitSplit({quoteMinor, currency, feeBps, licenseBps, lineage}): what a buyer funds for an operator's quote with the royalty on top.
    • It returns the smallest gross the accepted-plan seam accepts, PCC's fee, the royalty, the operator's part and each contributor's share.
    • It runs the compiler on a one-unit agreement (kitSplitAgreement), so the numbers are exactly what settlement pays.

Example: a $25.00 quote at PCC's 2.35% fee with a 0.50% royalty to a fork of a fork.

USDC
Gross (the buyer funds) 25.128666
PCC fee 0.590523
Royalty 0.125643 (carol 0.071796, bob 0.035898, ann 0.017949)
Operator 24.4125 (= the quote less the fee on it)

Tests

  • 9 new tests, including a 200-case sweep:
    • the real seam (netSplitterFor) accepts each computed gross and refuses one base unit less;
    • every unit conserves to the base unit.
  • Spec 1295/1295; tsc clean.

Not in this slice

  • Folding a share under the smallest leg into the forker's leg.
  • The per-unit cap over all kits.
  • Both need a compiler rule, after the operator rules on the rates (operator item 99). Real payouts stay behind item 99 too.

🤖 Generated with Claude Code

LamaSu and others added 2 commits October 2, 2026 18:39
…nd an exact computeKitSplit (R4)

R4 / board item 13: contributors are paid when their code executes. A Kit version carries a #360
License whose one payment requirement is a percent of the gross of every unit that runs the kit,
paid to the kit's fork lineage, as ordinary payout legs in the unit's release (DESIGN.md in the
lane's return; kits #4102 and adk #4056 agreed the shape). This slice is the pure economics side:

- licenseHash(license): H("PCC:license:v1", License), "0x" + 64 lowercase hex. The License is
  read once as plain JSON and schema-checked first, so equal Licenses hash equal and a malformed
  one is refused. Kits pin it as economics.economicTermsHash.
- kitLineageDistribution(lineage):
  - Nearest first: the publisher alone, 2 : 1 for a direct fork, 4 : 2 : 1 for the nearest
    three. Nothing past the grandparent is paid.
  - A self-fork merges into one member with the summed weight, in lowest terms.
  - Members are sorted by party id, the order the compiler compares a requirement in.
- kitRoyaltyRequirement({bps, lineage}): a percent of gross, per using unit, to that
  distribution. A free kit (0 bps) requires nothing.
- computeKitSplit({quoteMinor, currency, feeBps, licenseBps, lineage}): what a buyer funds for
  an operator's quote with the royalty on top. The result is the smallest gross the accepted-plan
  seam accepts, its fee, the royalty, the operator's part and each contributor's share. It runs
  the compiler on a one-unit agreement (kitSplitAgreement, exported), so the numbers are exactly
  what settlement pays. For a $25.00 quote at PCC's 2.35% fee with a 0.50% royalty: gross
  25.128666, fee 0.590523, royalty 0.125643, operator 24.4125 (the quote less the fee on it).

Tests: 9 new. They include a 200-case sweep in which the real seam (netSplitterFor) accepts each
computed gross and refuses one base unit less, and every unit conserves. Spec 1295/1295; tsc
clean.

Not in this slice: folding a share under the smallest leg into the forker's, and the per-unit cap
over all kits. Both need a compiler rule after the operator rules on the rates (item 99).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ts, refuse a free kit and non-canonical quotes

Cross-family review EC4 (astra, SHIP-WITH-FIXES at 320687f; three MEDIUM, two LOW). Each finding
was reproduced at 320687f first:

- M1: computeKitSplit read the caller's lineage more than once. Astra's getter, which answers ann
  for six reads and bob after, made it report bob receiving the royalty compiled for ann
  (`expected [ 'bob' ] to be 'refused'`), and `new Array(1)` threw "Reduce of empty array". Now
  every field is read once, as an own data property: the quote, currency, fee, rate and lineage.
  The lineage must be a dense array of party ids, copied and frozen. The distribution is computed
  once and used for both the agreement and the shares. Accessors, holes and non-ids are refused,
  never thrown. kitLineageDistribution and kitRoyaltyRequirement read their input once too.
- M2: KIT_LINEAGE_WEIGHTS was a mutable array, so assigning [0] = 8 gave 8 : 2 : 1. It is now
  frozen, and the assignment throws.
- M3: kitSplitAgreement crashed on a free kit (TypeError on a null requirement). It now refuses
  any input computeKitSplit would refuse with a RangeError, a free kit included.
- L1: quotes "0x10", "+16" and " 16 " were accepted as 16. A string quote must now match the
  economics amount grammar (digits only, no sign, no leading zero), or the quote is a
  non-negative bigint.
- L2: the sweep's Number LCG passed 2^53 and degenerated. Run in the test's own arithmetic, it
  never generated lineages 3 and 5 (counts {0:65, 1:1, 2:65, 4:69}). The sweep now runs every
  lineage x fee x rate explicitly, with BigInt quotes (270 cases), and asserts that every
  lineage ran.

Documentation only, from astra's notes:
- licenseHash is a content hash of the License as written, so reordering an array changes it.
- computeKitSplit assumes the operator is not one of the lineage's payees.

Tests: spec 1300/1300 (14 in economics-kits); tsc clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Oct 3, 2026
… defaults money

Verdict 115b on #464. 16 new guards in starter/bin failed at 932c0ae.
Now 41 tests pass:

- F2: "completed" is sent only after an evidence receipt that says
  "stored": true for this jobId. The relay answers 200 with stored:false
  for an unknown job or a storage failure. New index event:
  verify.evidence-not-stored.
- F3: nothing calls the result verified. It is a registered kernel
  exercised by one real job, whose evidence stays unverified until the
  operating agent signs it (README, AGENTS, CLAUDE, phases 5, 6, 9, graph).
- F4: the capability is priced from .pcc/intake.json, not a literal 25.
  register-device lists no capabilities, so no zero-priced duplicate.
  The payout wallet is asked in phase 0 and sent at provisioning, where
  it becomes the operator id. The buyer test stops (verify.price-mismatch)
  unless the quote equals the human's price. The license rate is never
  offered or written as a default. The publish text follows economics
  (#4807, #492).
- F5: the e-stop drill says what it shows (the gateway refuses new jobs
  for a stopped kernel; the device's own stop works) and what it doesn't.
  pcc-node 0.1.1 does not read the flag.
- F6: the envelope digest is re-checked against envelope.confirmed.json
  before any run (phases 6 and 7).
- F7: the kernel is fetched and checked before its signing key is
  registered.
- F8, F9: runbook.json matches the phases. Register no longer needs
  pcc-node start, verify asks for the drill, and target.default is null.
- F10, F11: pcc-report never raises on a malformed gateway address or
  unwritable state, and repairs a permissive .pcc to 0700. Phase 0
  chmods .pcc too.
- The kernel's text address goes in physicalAddress.

Also for 0.1.1: pcc-node needs https or http to 127.0.0.1/[::1]. Don't
run pcc-node start beside the loop, because its heartbeat says
acceptingJobs: false. The install pin moves to release r2 @dcc44db9.
F1 (operator-route ownership) is the gateway's WP-C; until it carries it,
onboard only on a private gateway.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@LamaSu
LamaSu changed the base branch from feat/economics-agreement-compiler to master October 4, 2026 00:45
#360 merged as 0972247, so #492 now targets master. No file changed on both sides since 8fe16b6; the PR's own diff is unchanged from the reviewed delta 8fe16b6..8fbbc86 (astra EC4b SHIP).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@LamaSu
LamaSu marked this pull request as ready for review October 4, 2026 01:18
@LamaSu
LamaSu merged commit a4e7847 into master Oct 4, 2026
9 checks passed
LamaSu added a commit that referenced this pull request Oct 4, 2026
…arrel conflict resolved)

#492 merged as a4e7847. Both PRs append exports to packages/spec/src/economics/index.ts after "./examples.js": #492 adds kits.js, and #394 adds preview.js, rates.js and { snapshotJson } from input.js. The resolution is the union of the two reviewed lists, in that order, with no other line changed. It is the same resolution simulated and posted to the steward (#6350). Every other file merged without a conflict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant