Repository navigation
feat(spec): economic agreements v1, an exact compiler to V-next per-unit payouts - #360
Merged
Merged
Conversation
…nit payouts pcc-economics (df42dbe5), ledger rows R15/R17/R18, technical pack section 8. Nothing on master turned an accepted agreement into V-next PayoutEntry[] (no TypeScript producer existed) and nothing represented a license as a versioned, compatibility-checked object. This adds both, as one pure bigint compiler in @pcc/spec (subpath @pcc/spec/economics, and the `economics` namespace from the root): - EconomicAgreementV1: payment clauses in economic language (fixed, per_use, pass_through, percent of a named base with min/max, residual, nested splits) kept separate from rights (licenses + intended use). Separate rightsTermsHash / economicTermsHash, bound by agreementHash. - One rounding rule (largest remainder, exact integers). Each unit's legs sum to its net exactly, so the escrow's PayoutSumMismatch is unreachable for compiled output. - Fail closed: unknown rights refuse, incompatible grants refuse, a remainder with no named residual recipient refuses, and cross-job metered/downstream terms refuse (OD-4 is the operator's decision). 24 named refusal codes, each provoked by a test. - Participation: a clause can apply only where a component runs, so it rides on that unit's own evidence release or refund. - Leg identity (payTo, role, subject): one wallet in two roles stays two legs; compaction by wallet only when the 16-leg limit requires it. - verifyAcceptedAgreement: a license or rate changed after acceptance is refused and named as a rights or economics change. - simulateEconomics: what-if scenarios on the same compiler. - Adapters from CompositionManifest, TrainingManifest and an optional ContributionGraph produce the same IR (no second engine). - Five worked example agreements plus a refused incompatible-license case. Spec: docs/ECONOMIC_AGREEMENTS.md. Tests: 69 (exact math, required lane tests, determinism under shuffling, every refusal provoked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
…c agreements pcc-economics (df42dbe5). Pins the five worked agreements' exact payouts and hashes (fixtures/economics-golden-v1.json, hand-checked), the simulator's escrow outcome rules (release pays legs and fee, refund returns gross with no fee, pending stays reserved, an unfundable price is the compiler's own refusal), and the adapters: - CompositionManifest: co-authors share ONE role allocation (the old buildPayoutMap paid each the full rate). - TrainingManifest: a 3-level lineage subdivides the model allocation and sums to it exactly (the old rich walker paid it twice). - ContributionGraph: compiles to byte-identical V-next units as the same splits written by hand, so it is a template and not a second engine; unaccepted and non-participating contributors are paid nothing; a diamond is paid once per path without inflating the pool. @pcc/spec: 43 files, 888/888 tests; economics 91. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
…ts span jobs pcc-economics (df42dbe5), for composition #351 (CompileDeps.splitNet, unitRef = nodeId). - Id grammar is now exactly composition's ID_PATTERN (1-128 printable ASCII, no space), so every plan node id is a valid unitRef. Canonical JSON still escapes only quote and backslash, so hashes stay cross-language exact. - An agreement may span several V-next jobs (one per operator): the unit cap is 256; the escrow's per-job limits (16 units, 256 legs) are enforced by the accepted-plan compiler, which groups units into jobs. The 16-legs-per- unit cap stays here. Golden vectors unchanged. Tests: economics 93. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
pcc-economics (df42dbe5), for composition #351 CompileDeps.splitNet (R12 <-> R15). The agreement is never taken on the caller's word. The splitter refuses, never repairs, unless (1) the agreement still hashes to what the payer accepted (verifyAcceptedAgreement; the refusal names rights / economics / envelope), (2) every authority-bearing field equals server state: the plan's fee and currency, the unit set and each unit's server-quoted gross, and every license equals the server's registry copy verbatim, including its authority level, and (3) compileEconomics accepts it with the server's sealed schedule bodies and forbidden recipients. It snapshots its inputs at construction, re-checks the fee rule against the plan's f/n, and returns units in plan order in composition's NetSplitResult shape (conformance typechecked against a verbatim copy of #351 69012b4's NetSplitter type). @pcc/spec: 44 files, 899/899 tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
…eal escrow pcc-economics (df42dbe5). Invariant 12: prove the seam, not a unit. VNextEconomicsSeam.t.sol funds a real VNextSettlementEscrow with the exact per-unit payouts compileEconomics emits for three worked agreements and releases them. Every party, the fee recipient and the payer end with exactly the compiled amounts: - print-and-mail: fixed upstream prices, postage at cost (two legs, one wallet), a per-use method fee, composer margin; payer spends 22.000000 - guild repair: the nested split's leftover base units land where the largest-remainder rule put them - deck milestones: design and inspection release, the build step is refunded after its deadline; only that step's parties go unpaid and no fee is taken on it Negative controls on-chain: one base unit over -> PayoutSumMismatch; 17 legs -> BadLegCount; the settlement token as a recipient -> ForbiddenRecipient; the same legs reordered cannot fund the clone committed to the compiled order -> PolicyRootMismatch. economics-forge-parity.test.ts parses the Solidity literals and fails if they differ from compileEconomics output (4/4). Forge runs in CI (the submodules are not vendored on the Spark and are not fetched here). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
… allocation pcc-economics (df42dbe5), defects re-verified at ac86a40 (reconciliation note D1-D3). D1: assertScheduleIsWellFormed let an open-ended segment (endTime null) reset its overlap guard, so every later segment escaped the check and, since evaluation takes the first covering segment, could never apply. It now refuses any segment after an open-ended one. The live publish route POST /api/contributors/schedules never called the guard at all and sealed malformed schedules immutably under their hash; it now returns 400 malformed_schedule before sealing. D2: buildPayoutMap paid each co-author of one role the full rate when groupBps was absent, contrary to CompositionEntry's documented equal split (two co-authors = 2x, and different recipients passed setPayoutMap's dedup). Co-authors sharing (role, ipId, rateScheduleHash) now split the role's bps exactly (floor each, leftover bps to the earliest in manifest order). Explicit groupBps behaviour is unchanged. Marked @deprecated for new deals: V-next funds exact amounts via economics.compileEconomics. D3/D5: LicensingEngine.getRoyaltyDistributionRich is @deprecated for payouts (resolves rates by ipId from a mutable cache; pays a model's share to its author and again to its datasets). No production callers; the correct subdivision is economics.splitsFromTrainingManifest. Mutation-checked: the new route tests and co-author tests fail against the previous code and pass now. spec rate-schedule 37/37; gateway contributors 39/39; contracts TS 12 files 248/248; tsc clean (spec, contracts). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
… fixtures pcc-economics (df42dbe5), from readmodels' server-side fabrication census (2026-09-24), which assigned this family to economics. - POST /api/swf/epochs/:epochId/distribute scored every participant with Math.random() (jobs, reputation, uptime, votes) and then DISTRIBUTED the epoch on those scores. It now refuses with 501 not_available until real per-epoch contribution inputs exist; the epoch is left untouched. - /api/rewards/*, /api/certificates* and /api/treasury/summary served fixtures: reward epochs and payouts, "claimed" claims with a fake tx hash, a mint that reported minted:true, and a 50000 USDC / 85000 USD treasury. All now answer 501 not_available with pointers to the real reads (escrow, jobs, capabilities, compliance), following PR #362's pattern. Paths stay registered so clients get an honest answer. - MCP pcc_depin_stats and the CLI depin command report each part on its own (pccFetchEach): an unavailable part is { unavailable, reason }, never zero and never a thrown whole. Tests: gateway economics-no-fabrication 10/10 (asserts no fixture value leaks and the refused distribution leaves the epoch unchanged); full gateway 2939 passed locally (the 2 capture files need @pcc/verifier built; CI builds it). mcp-server 71/71, tsc clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
…sioned
pcc-economics (df42dbe5), board row N10b (N10a, the /api/ip/* route
authorization, is gateway's WP-E).
Story Protocol real mode (STORY_MOCK=false) returned success it never
earned: payJobRoyalty, claimRevenue and distributeRoyaltyTokens returned
made-up deterministicHex transaction hashes, getRevenueSnapshot returned a
zero-revenue "vault" at a made-up address, raiseDispute returned a dispute
never raised, registerJobAsDerivative registered a fabricated child IP id
with a hard-coded license token, registerCapabilityAsIP committed a
fabricated metadata hash, and the reads answered from an in-process map
("not registered" after any restart). Each now throws
StoryNotExecutedError (code STORY_NOT_EXECUTED): nothing was sent and no
transaction hash exists. Routes should map it to 501 not_executed. The SDK
wiring that was removed stays in git history (master ac86a40).
Mock mode is unchanged but every mock result now carries simulated: true
(optional field on the Story spec types), so no consumer can take a
simulated royalty or balance for a real one.
LicensingEngine.setTerms was an unconditional overwrite. Terms are now
versioned and append-only: a change appends version n+1, earlier versions
stay readable (getTermsVersion), identical terms are a no-op, and reads
return copies. setTerms returns the version in force.
Tests: story-ip-service 42/42 (11 new: every real-mode operation rejects
with STORY_NOT_EXECUTED; mock results are simulated); licensing-engine +3;
contracts TS 12 files 262/262; tsc clean (spec, contracts).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
pcc-economics (df42dbe5). An independent Python implementation written from docs/ECONOMIC_AGREEMENTS.md alone (returns/pcc-economics-work/cleanroom) reproduced every payout of the five golden agreements (9 units, 27 legs) and 13 of 15 hashes. Both misses had one cause: the spec never said to sort a license requirement's payee.distribution (the code sorts it by party). Its 16 probes found further places where two faithful implementations could disagree. The spec now fixes each one, and the code follows: - canonical form: payee.distribution sorted by party; Hash defined (either case, lowercased); bounds and uniqueness rules stated (Amount <= 78 digits, per-unit components/measures <= 64, versions <= 1e9, unique requirementIds and distribution parties, outbound share-alike tag rule) - refusals: identity is (code, path) and messages are informative, so the compiler now dedupes on (code, path) only (it keyed on messages); paths compare element by element (ids may contain '/'); a normative path grammar for every code; the money-check order per unit; one FEE_INVALID at most; SPLIT_CYCLE per split on a cycle; SPLIT_TOO_DEEP for any split; rights checked once per component; SCHEMA_INVALID normative by code only - a payee distribution naming an unknown party is now UNKNOWN_REFERENCE (structure), not a payment mismatch - result orders stated for attribution, zeroLegs, clauses, rights, rates - verifyAcceptedAgreement's documented output corrected to what it returns Golden vectors unchanged; the fixture records its compile options. Economics tests 110/110. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
An adversarial review (FINDINGS.md) found 1 critical, 3 high and 7 medium defects, all confirmed by repro. Critical - C1: the split fan-out is counted before anything is expanded. The new refusal TOO_MANY_ALLOCATIONS fires above 65,536 allocations. The argument spread that overflowed the stack is gone. A 32 KB agreement that killed the process is now refused in milliseconds. High - H1: a pinned rate is re-evaluated at asOf, for every unit's own value in cents, with the server's rate facts. The composer's recorded evaluatedAt and context are no longer trusted. - H2: use.commercial is the literal true, because every compiled deal is paid work. - H3: license requirements and clauses pair one to one, by a key. Two identical requirements need two clauses, and a surplus clause is UNMATCHED. Medium - M1: oncePerJobUsing becomes oncePerAgreementUsing. Its fixed or pass-through amount is spread over the using units by gross, so a refunded unit refunds only its share. - M2: the option fee is the server's fee. Any other fee is FEE_INVALID [fee, server-fee]. - M3 and N5 (bind): asOf must be a recent server moment, and use is the server's intended use. Licensed parties are paid at their registry addresses, and unit components, uses and measures are the server's. - M4 and L1: options are schema-validated. A bad option is SCHEMA_INVALID [options], never ignored and never thrown. - M5 is specified: a routing node is a pool. - M6: the root obeys participation. - M7: the training manifest's own schema is enforced (MANIFEST_INVALID). Low - L2: input and options are read once into plain JSON data, so an accessor, a Proxy or a cycle is a refusal, not an exception. - L3: a schedule body is trusted only for the hash it has. - L4: a schema failure is one refusal at [], independent of array order. - L5 and N1: scenario overrides are unique per unit, with limits sized for 256 units. - L6: a required payment must be about the licensed subject. - L7 and L8: adapter lookups use own keys only; ids must be valid; zero-weight co-authors are dropped. - L10: bind returns agreementHash, and the spec says acceptance binds it. Notes - N2: canonical string escaping is pinned in the spec. - N3: rates are evaluated in exact integers, and exponential decay is unverifiable. SCHEDULE_HASH_MISMATCH is removed: a mislabeled body is now a malformed option. There are 28 refusal codes. AGREEMENT_TEMPLATES is added for ADK. Golden vectors are unchanged. Economics tests pass 160/160 and the spec suite 958/958. 35 of the 45 new review tests fail on f6beb9e. The other 10 are controls, exact-arithmetic unit tests, behaviors fixed or specified earlier, and one read-once consistency check. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
…omics #2755) Economics asked (#2755, #360 @bad29552) to bind agreementHash, not only the two terms hashes. Those do not cover the agreement's envelope (asOf, version, deadline), so sealing only them would leave asOf mutable after acceptance. agreementHash covers both terms hashes and the envelope. - NetSplitResult ok now REQUIRES agreementHash. It is now structurally identical to economics' PlanSplitResult. - snapshotSplit reads it exactly once, with the other scalars. A missing, malformed or non-string value is economics-malformed "agreement-hash"; a throwing getter is "result". It is never a throw. - CompiledAcceptedPlan.agreementHash (lowercased) is null when no agreement split the payouts. It is part of the acceptedDealDigest preimage, and the domain moves to PCC:accepted-deal:v2 because the preimage schema changed. Nothing is persisted under v1: the R13 store does not exist yet, and VCR carries the digest as opaque bytes. Tests: 70 -> 73, spec suite 870/870, and the test file typechecks. Six mutation checks were all killed: the hash left out of the preimage, not digest-checked, re-read from the answer, not lowercased, dropped by compile, and swapped for the economic hash. This changes #351 after its round-5 SHIP, so it goes back to cross-family review (board rule 1). pcc-composition 8a0f4de0, goal pcc-reconciliation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
This was referenced Sep 24, 2026
…efusals on duplicate ids) A clean-room reimplementation from the spec at bad2955 (Python, spec only) matched all 24 hashes, all 27 payout legs and 82 of 86 probes. This closes the 4 mismatches and the gaps it named. - P28 (code bug): with a duplicated split id, the refusals depended on array order, because the split map kept the later definition. When any DUPLICATE_ID is reported, the checks that resolve ids now do not run: SPLIT_CYCLE, SPLIT_TOO_DEEP, TOO_MANY_ALLOCATIONS and RATE_PIN_MISMATCH. - P62: each rateFacts field may be omitted (meaning null), as §2.4 says. - P73/P74: the spec now lists every schedule well-formedness rule the options check enforces. - The server's fee option obeys §2's rule itself: it has a recipient exactly when the fee is nonzero. - Specified as a v1 limitation, pinned by a test: a required scheduled royalty whose units fall in different value bands is refused, never underpaid. - Spec states what both implementations already agreed on silently: - labels count code points; - versions sort numerically; - attribution lists positive allocations only; - the membership of rights and byParty; - a pin that applies to no unit is verified; - null options are refused; - duplicate forbidden recipients change nothing. Economics tests: 165/165. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
…efuses pcc-economics N10b (#360) makes Story real mode refuse operations it cannot execute, with StoryNotExecutedError (code STORY_NOT_EXECUTED). Whichever PR merges first, the IP routes now answer that refusal as 501 not_executed, never as a generic 500 that a client could mistake for a transient fault. settle-royalties answers 501 when Story executed none of the payments. A mix of paid and failed rows stays 502 settlement_incomplete, with the per-row outcomes. The check is duck-typed on err.code, so it holds on master before #360 lands. ip-authz: 20/20. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
…n top (seam floor) Composition asked (#2762) which rule compileEconomics assumes for royalties: (a) inside the operator's quote, or (b) on top. The answer is (b). An operator's quote is its price for its own work, and anything the agreement adds (licenses, modules, the composer's margin) is paid by the buyer on top of it. - PlanSplitUnit carries the operator's live `quote`. `g` is the unit gross the buyer funds, which is the agreement's. - New QUOTE_NOT_COVERED: a unit's g is below the quote. - New OPERATOR_BELOW_QUOTE: after the compile, the legs to the operator's payout address must total at least quote - floor(quote * feeBps / 10000). - New agreementUnitGross(): the plan compiler reads each unit's gross to reserve it before compiling. The splitter re-checks everything, so reading it early grants nothing. - Spec section 9 states the rule. Economics tests: 167/167. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
coord-watch's astra verdict on 2225ad5 (review-router/r15core-360-econ-astra) was DO-NOT-SHIP, with four P1 findings. All four are fixed here. P1-1 (server facts were not validated at runtime): a missing or NaN `now` made both time comparisons false and skipped the check, and missing facts threw. - ServerEconomicsFactsSchema now validates a read-once copy. - Malformed or unreadable facts give economics:SERVER_FACTS_INVALID:<field>; malformed plan units give PLAN_UNITS_INVALID. - Nothing throws and nothing is skipped. P1-2 (only license-named parties were bound to the registry): a residual, fixed or split recipient could be paid at an address the registry contradicts. After the compile, every party the compile pays must be registered at the same address (PARTY_NOT_REGISTERED / PARTY_MISMATCH). This includes the reviewer's counterexample and a split. P1-3 (an unlicensed clause with an unknown or unevaluable rate source still paid out): RATE_UNVERIFIED is now a structure refusal at ["clause", id, "rateSource"] for any clause that names a rate source and pays in some unit, whether or not a license requires it. A clause that pays in no unit is not refused. P1-4 (an expired offer passed on the agreement's own asOf): the binding refuses OFFER_EXPIRED when the server's now is past terms.acceptBy. Also fixed: the example builders shared nested objects (terms, fee, currency, use), so mutating one build changed later ones. They now return fresh objects; a test pins that, and the golden hashes are unchanged. Spec sections 2.4, 3, 4.1, 5 and 9 are updated. Economics tests 174/174, spec 973/973, tsc clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
…und 3 Clean-room round 3 (an independent Python implementation written from the spec at 0ab9cdb) matched 24/24 hashes, 27/27 payout legs and 97 of 99 probes. Both misses had one cause, a spec gap. It also listed eight places where the spec made it guess, and one of them was a real defect: - Fix: a clause that pays in no unit was reported `verified: true` whenever its schedule body was supplied, even when the pinned bps contradicted the schedule, because nothing was ever compared. The PX-12 preview turns that flag into "Checked against the published rate schedule". Such a pin is now `verified: false`. It is still not refused, since it pays nothing. Spec (docs/ECONOMIC_AGREEMENTS.md), current behavior now written down: - Unknown fields in a schedule body are dropped before hashing at every depth, including inside segments, as the registry's publish route does (P84). byClass is the one closed object. - Every agreement field is required; nullable ones are present as null. - A `units` name that is not a unit is UNKNOWN_REFERENCE and selects nothing (P89). - Once-per-agreement spread weights are each gross as written; all-zero weights give zero shares (P78). - Exactness depends only on the segment at asOf and on rateFacts, so RATE_PIN_MISMATCH and RATE_UNVERIFIED never both apply to one clause. This replaces a precedence rule that could never fire. - The option fee's feeBps is 0..1000. - How input depth and the value count are measured; only own enumerable properties are read. Tests: 8 new. The P81 test fails on the pre-fix code. Spec suite 981/981, tsc clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
…ied) into PX-12 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
With #360 at 15208e3, a compiled deal marks a pinned rate unchecked only when its clause pays nothing in the agreement; every rate that pays out is checked or the deal is refused. The preview used to explain any unchecked rate as "the published rate schedule was not available", which is not the reason. It now says "Not checked, because this clause pays nothing in this agreement". Any other unchecked rate gets a neutral "Not checked against the published rate schedule". Spec 999/999, tsc clean. Dashboard 227/227 and gateway economics 20/20 against the rebuilt spec dist. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
…se clean-room round 3b Clean-room round 3b (the spec at 15208e3) matched 24/24 hashes, 27/27 legs and 113 of 114 probes. It confirmed the round-3 fixes as a black box. The one mismatch, P100c, was a spec gap: integers inside a rate-schedule body had no range. - A body with thresholdCents 2^53 + 1 reads as 2^53 in JavaScript, so the lane refused its hash and an exact reader did not. - Nothing refused 2^53 itself, or a version above 10^9. The same gap let JSON 1e400, which parses as Infinity, reach the exact evaluator: compileEconomics THREW a RangeError on an adoption-indexed scale of Infinity, breaking its never-throws contract. - assertScheduleIsWellFormed gains rule 5. Integer fields must be 0..2^53-1 and scale/decayPerSecond finite. When a version is given, it must be 1..10^9. - The compiler (option schedules), the registry's publish route and the licensing engine all use this check, so the registry can no longer seal a body the compiler cannot read. - Spec: well-formedness rule 5, and the known fields outside the hash are still type-checked. Canonical JSON now says how a schedule's real numbers are written: ECMAScript Number.prototype.toString, so 1e-9, not 1e-09. Tests: 4 in spec and 1 in gateway. The P100, P100b, Infinity and registry tests fail on the pre-fix code. Spec 985/985, contracts ts 262/262, gateway contributors 40/40. tsc clean on spec, gateway and contracts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
…omics #3025, option b) Economics chose option (b), royalties ON TOP. With an agreement, a unit's gross is the agreement's unit gross, and the operator's own live quote (R10) is at most that gross. Economics holds the operator's legs to at least quote - floor(quote*feeBps/1e4) (OPERATOR_BELOW_QUOTE, #360 @c28bd34e) and needs the quote to do it. - AcceptedPlanNode.quoteBaseUnits (optional) is copied by the read-once snapshot. It must be a positive bigint no greater than the gross, else invalid-node-field "quoteBaseUnits". Absent means the quote IS the gross, as when there is no agreement. - SplitUnitInput.quote now matches economics' PlanSplitUnit. - Nothing new is sealed. The live quote is committed through matchedCapabilityDigest, and the grossed-up price through g, f, n and the payout legs. The golden vector and the digest are unchanged. Tests: 3 new, compiler 76/76, spec 886/886. 4 mutation checks, all killed: the quote not passed, not validated, above the gross, not snapshotted. pcc-composition 8a0f4de0, goal pcc-reconciliation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
…nto PX-12 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
…try schedules
Found in a self-review of the PX-12 preview.
1. Size. Every allocation became its own payee line. A valid 256-step
agreement paying 16 pools of 15 members (61,696 allocations) produced a
12 MB preview that no browser or person can use, from one ~0.5 s
request. Each payee now shows at most 24 lines, and they always re-add
exactly to its total:
- one line per step and reason, when that fits;
- else one line per reason, summed over its steps ("256 steps");
- else the largest reasons, with the rest summed per kind of payment
("20 more payments: Licenses and contributors").
The worst case is now well under 1 MB. Small agreements are unchanged.
PreviewLine.unitRef is null for a line that sums several steps.
2. Unusable registry bodies. A schedule sealed before the registry
checked number ranges (#360 at 6760a05), or a stored copy that no
longer hashes to its label, used to refuse the whole agreement as
malformed options. It is now left out, so the pin it would have
verified is refused at its own clause (RATE_UNVERIFIED). The compiler
still re-checks both.
Tests: 3 in spec and 1 in gateway. Both large-agreement tests and the
route test fail on the old code. Spec 1006/1006, dashboard 227/227,
gateway economics 21/21, tsc clean on spec, gateway and dashboard.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
Clean-room round 3c (the spec at 6760a05) matched all 127 compiles, with no mismatches and no throws, and reported nothing left to guess. Its P104/P105 probes put unknown numeric fields (1.5, 1e400) inside a segment. The compiler and the registry accepted them, as the spec says: their zod parse strips unknown keys before the check runs. But the licensing engine hands raw objects to assertScheduleIsWellFormed, and there the new loop over every numeric field refused a field the spec says is ignored. The check now walks only the fields a segment kind defines: the eleven integer fields, plus scale and decayPerSecond. Test: unknown fields pass the check even without a parser, while a known field out of range still throws. It fails on the previous loop. Spec 986/986, tsc clean; contracts ts 262/262, gateway contributors 40/40. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
This was referenced Sep 24, 2026
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
… demo path Economics #3315 and the steward's SWF ruling (operator item 69): the fund stays a design artifact and its money routes stay 501; distribute is on N46's money-floor list. So POST /api/swf/epochs/:epochId/distribute refuses whatever PCC_DEMO_ROUTES says, before any read, score or write (the random-score demo distribution is removed, and with it swf.ts's last Math.random calls and its ratchet entry). The refusal keeps the reviewed wording and METHOD-path `see` pointers, now also pointing at GET /api/swf/accruals. #360 makes the same handler an unconditional 501, so whichever lands second resolves a wording-only conflict. agent: pcc-readmodels (c255d7dc)
…s at 0 Cross-family review EC1 (astra, DO-NOT-SHIP at 8dc089e; all four P1s CLOSED). Each finding was reproduced at 8dc089e with a test that fails there, then fixed: - H1 (HIGH): netSplitterFor validated plan units by reading the caller's objects, then read them again to enforce. Astra's counterexample, a $12.30 quote that reads 12300000 twice and 0 after, returned ok at 8dc089e instead of OPERATOR_BELOW_QUOTE. Now readPlanUnits copies each unit's six decision fields once, as own data properties, into frozen plain data, and every later check reads only that copy. An accessor is refused (PLAN_UNITS_INVALID), as snapshotJson refuses one in the agreement. A Proxy's get trap is never consulted, so a lying proxy is judged on its one data read (OPERATOR_BELOW_QUOTE). - H2 (HIGH): a Proxy unit whose get trap throws escaped from the seam ("Error: trap"). So did a Proxy around the input itself. Every read is now an own-data read inside try/catch: a throwing unit, a revoked unit list, and a throwing or revoked input all refuse (PLAN_UNITS_INVALID, SERVER_FACTS_INVALID:unreadable). An input whose accepted field cannot be read is checked and refused, never treated as null. - M3 (MEDIUM): rule 5 checked Number.isSafeInteger only, so a correctly hashed schedule with startTime -1 passed assertScheduleIsWellFormed and LicensingEngine.setRateSchedule (which hands it raw objects). The range now starts at 0, as the parsers' does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Cross-family review EC1b (astra, SHIP-WITH-FIXES at a3d3d76): H1 and H2 CLOSED; M3 still OPEN because the shared helper was narrower than RateScheduleSchema for raw callers. Reproduced at a3d3d76. assertScheduleIsWellFormed accepted all ten of these, and RateSegmentSchema refuses each: - an adoption-indexed scale of 0; - an exponential decayPerSecond of 0 or -1; - a bps of 10001, or a bps given as a string; - a capture class rate of 10001 or -1, or an unknown class (CC9); - an unknown kind; - an empty segment list. LicensingEngine.setRateSchedule stored the correctly hashed scale-0 schedule. Fixed: after rule 5's own checks, which keep their messages, every segment must pass RateSegmentSchema, and a schedule needs at least one segment. Unknown keys stay ignored, as the schema ignores them (round 3c's P104/P105 test still passes). setRateSchedule and unsafeReplaceRateSchedule now refuse all ten, and store nothing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu
marked this pull request as ready for review
September 30, 2026 02:59
#360 was SHIP at c73d708 (EC1c) with full CI green, but master moved on 9/30–10/01 (#424, #459 N80, #436, #338, #477, #483 and others), and three files conflicted. Resolutions: - packages/spec/src/index.ts: both sides added exports at the same place. Both are kept: #360's `economics` namespace, and master's money-status and readmodels exports. - packages/gateway/src/routes/swf.ts: both sides made POST /api/swf/epochs/:epochId/distribute answer 501 not_available before anything is read. Master's hunk (#421's, reviewed and merged with #424) is kept. The behavior is identical. - packages/gateway/src/routes/rewards.ts: #360 made the reward, certificate and treasury routes answer 501 and deleted their fixtures. Master's N80 (#459, reviewed and merged) changed only the certificates: an empty list, 404 for one, and a 501 mint that claims nothing. It kept the reward and treasury fixtures. Kept: master's N80 certificates, and #360's 501s for rewards and treasury, with no fixture import. economics-no-fabrication.test.ts drops its three certificate rows (certificates-n80.test.ts pins N80), and the reward and treasury rows still assert 501. On the merged tree: spec 1286/1286; contracts 267/267; mcp-server 76/76; gateway 3717 passed, 13 skipped, 0 failed; dashboard 353/353; tsc clean for contracts, mcp-server, gateway and dashboard. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
#360 merged master @753feb43 after EC1c's SHIP (three conflicts, resolved in 8fe16b6). PX-12 stays stacked on #360's head. This merge had no conflicts. On the merged tree: spec 1311/1311, gateway 3732 passed (13 skipped), dashboard 361/361; tsc clean for gateway and dashboard. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
…5678) #474 and #360 (MERGE NOW #8) both add exports to packages/spec/src/index.ts right after ./payouts.js, so #474 sits behind #360 on the merge line. This merge takes #360's head @8fe16b69 now, so the conflict is resolved and reviewed before #360 lands. The only conflict is packages/spec/src/index.ts. It is resolved as the union of both sides, verbatim: #474's intake and research barrels, then #360's namespaced `economics` export. Every other path that differs from 069f9df (44 of them) equals #360's blob exactly, so no file was auto-merged. Spec: 58 files, 1731 tests pass; both typechecks are clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
LamaSu
added a commit
that referenced
this pull request
Oct 4, 2026
A plain merge with no manual edits. #474 had two merge bases with master (7d688ce and 8fe16b6), a criss-cross left by its pre-staged merge-up with #360, so GitHub showed it DIRTY although it merges cleanly. The resulting tree equals git merge-tree --write-tree cd9d877 b4bf06b. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
LamaSu
added a commit
that referenced
this pull request
Oct 4, 2026
#360 merged as 0972247, so #394 now targets master. One file changed on both sides since 8fe16b6, packages/gateway/src/server.ts, and git merged it with no conflict; #394's own hunk there is unchanged. Every other file in the PR's diff is unchanged from the reviewed delta 8fe16b6..92613a3 (astra EC3b SHIP). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reconciliation lane pcc-economics (df42dbe5). Ledger rows R15, R17 and R18 (plus R16 as an optional template); technical pack §8. Draft: independent verification is still running (see Status).
Why
Nothing on master turns an accepted agreement into the V-next escrow's
PayoutEntry[].git grep PayoutEntry\|UnitConfig\|prePolicyRoot -- '*.ts'finds no TypeScript producer. Nothing represents a license as a versioned, compatibility-checked object either. The existing royalty walkers have verified defects:buildPayoutMapdouble-pays co-authors (payouts.ts:231-234).getRoyaltyDistributionRichpays a model author's share twice (licensing-engine.ts:648-655+:717-752).evaluateLicensefails open when no terms exist (:152-165).rate-schedule.ts:379).What
One pure
bigintcompiler in@pcc/spec(subpath@pcc/spec/economics, and theeconomicsnamespace from the root). Normative spec:docs/ECONOMIC_AGREEMENTS.md.rightsTermsHashandeconomicTermsHashare both bound byagreementHash.PayoutSumMismatchis unreachable for compiled output.ECONOMICS_UNDECIDED_OD4; OD-4 is the operator's decision).(payTo, role, subject). One wallet in two roles stays two legs. Legs are merged by wallet only when needed to fit the escrow's 16-leg limit.verifyAcceptedAgreement. A license or rate changed after acceptance is refused, and the refusal says which half moved.simulateEconomics. What-if scenarios (price, participation, released / refunded / pending) run on the same compiler.CompositionManifest,TrainingManifestand an optionalContributionGraphV1produce the same IR. The graph compiles to byte-identical units as the same splits written by hand: one engine, not two.netSplitterFor. The economics half of composition's accepted-plan seam (feat(spec): accepted-plan compiler — composition half of MS-01 (R12) #351CompileDeps.splitNet). It refuses unless the agreement matches what was accepted and every authority-bearing field matches server state: fee, currency, per-unit quote, and licenses as the server's registry copies, including authority.It does not encode
UnitConfig/prePolicyRoot. That is the escrow lane's V-next compiler, which this output feeds.Status (head
8dc089e5)@pcc/spec: 46 files, 986/986 tests (economics: 187).tsc --noEmitis clean. Contracts ts 262/262 and gateway contributors 40/40 after the rate-schedule change.2225ad5f: DO-NOT-SHIP, 4 x P1. All four are fixed @0ab9cdbf. The re-review is requested on the current head,8dc089e5(#3086, #3131).SERVER_FACTS_INVALID), so a missing orNaNclock no longer skips checks.RATE_UNVERIFIEDat the clause).OFFER_EXPIREDis judged at the server's time.bad29552, or specified where the code was right and the text was not.TOO_MANY_ALLOCATIONS).asOf, per unit value, in exact integers.use.commercial=falsecould dodge a noncommercial license. It is now the literaltrue.returns/pcc-economics-work/review/DISPOSITION.md. 35 of the 45 new regression tests fail on the pre-fix code.f6beb9e6.bad29552, reproduced all 24 hashes and all 27 payout legs, and 82 of 86 probes (P01–P78).2225ad5f. One was a real bug: with a duplicated split id, refusals depended on array order.returns/pcc-economics-work/cleanroom/round2/REPORT.md.0ab9cdbf) matched 97/99 probes, 3b (15208e38) 113/114, and 3c (6760a050) 127/127, with no throw and nothing left to guess. Every miss was closed:15208e38: a pin on a clause that pays in no unit was reportedverified: truewithout being compared, and the PX-12 preview showed it as checked. It is nowverified: false. The spec also covers 8 points it left implicit.6760a050: well-formedness rule 5. Schedule-body integers are0..2^53−1,versionis1..10^9, andscale/decayPerSecondare finite. JSON1e400(Infinity) used to makecompileEconomicsthrow. The registry's publish route and the licensing engine apply the same rule, so the registry can no longer seal a body the compiler can't read. The spec also says how a schedule's real numbers are hashed: ECMAScriptNumber::toString.8dc089e5: rule 5 walks only a segment's defined fields, so unknown fields stay ignored for the licensing engine too.returns/pcc-economics-work/cleanroom/round3/REPORT.md.273bd9f1): compiled payouts fund a realVNextSettlementEscrowand release to the exact legs. CI passed 7/7; the whole forge run passed 974/974.Interface changes in
bad29552:oncePerJobUsingbecomesoncePerAgreementUsing, andper: "job"becomes"agreement". The amount is spread over the using units by gross.use.commercialistrue.fee,rateFacts) are validated.ServerEconomicsFactsgainsnow,intendedUse,partiesandunitFacts.PlanSplitResultcarriesagreementHash.AGREEMENT_TEMPLATESis exported for agents.Also in this PR:
buildPayoutMap(52fbfac9).df24aa2e).STORY_NOT_EXECUTED, and licensing terms are versioned and append-only (6e0a48cb).Also since the review:
c28bd34e, the operator's quote floor, agreed with composition (#2762 → #3025):PlanSplitUnit.quote,QUOTE_NOT_COVERED,OPERATOR_BELOW_QUOTEandagreementUnitGross().Review: needs a coord-watch cross-family verdict on the exact merge SHA (money path).
Merging is the operator's decision.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K