@percayso-community/cayso-contracts 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +114 -0
  3. package/dist/assertion.d.ts +867 -0
  4. package/dist/assertion.js +63 -0
  5. package/dist/assurance.d.ts +353 -0
  6. package/dist/assurance.js +551 -0
  7. package/dist/citizen-market.d.ts +468 -0
  8. package/dist/citizen-market.js +16 -0
  9. package/dist/citizenship.d.ts +215 -0
  10. package/dist/citizenship.js +26 -0
  11. package/dist/client.d.ts +288 -0
  12. package/dist/client.js +370 -0
  13. package/dist/commercial-payment.d.ts +243 -0
  14. package/dist/commercial-payment.js +16 -0
  15. package/dist/contract-schema.generated.d.ts +14 -0
  16. package/dist/contract-schema.generated.js +3107 -0
  17. package/dist/errors.d.ts +171 -0
  18. package/dist/errors.js +28 -0
  19. package/dist/index.d.ts +621 -0
  20. package/dist/index.js +624 -0
  21. package/dist/marketplace-receipt.d.ts +306 -0
  22. package/dist/marketplace-receipt.js +116 -0
  23. package/dist/marketplace-settlement.d.ts +298 -0
  24. package/dist/marketplace-settlement.js +16 -0
  25. package/dist/money.d.ts +84 -0
  26. package/dist/money.js +34 -0
  27. package/dist/organisation.d.ts +60 -0
  28. package/dist/organisation.js +16 -0
  29. package/dist/purchase.d.ts +531 -0
  30. package/dist/purchase.js +107 -0
  31. package/dist/restriction.d.ts +262 -0
  32. package/dist/restriction.js +316 -0
  33. package/dist/routes.d.ts +155 -0
  34. package/dist/routes.js +128 -0
  35. package/dist/schema-types.d.ts +101 -0
  36. package/dist/schema-types.js +16 -0
  37. package/dist/schema.d.ts +24 -0
  38. package/dist/schema.js +16 -0
  39. package/dist/wallet.d.ts +92 -0
  40. package/dist/wallet.js +59 -0
  41. package/package.json +41 -0
@@ -0,0 +1,298 @@
1
+ /**
2
+ * `marketplace-settlement` — the two money-grade marketplace operations Piper calls,
3
+ * their requests, their answers and their closed refusal unions (CAY-0082 §7,
4
+ * CAY-0083 §3).
5
+ *
6
+ * Piper presents Store Platform's signed order to settle a marketplace sale
7
+ * (`settle-marketplace-order`), and Store Platform's signed refund decision to
8
+ * refund one (`settle-marketplace-refund`), each under the buyer's assertion (Store
9
+ * decision V, `STO-0040`). Store Platform never calls either. Both are money-grade in
10
+ * `ASSURANCE_POLICY`, which is an allowlist, so `dev-bypass` is never admissible and
11
+ * a future method is not admissible without a policy decision (CAY-0029).
12
+ *
13
+ * **This is the contract surface only.** No route serves either operation, and
14
+ * nothing here settles anything. The sale table, the registry of Store Platform order
15
+ * keys and the ledger stay in this proprietary tree (CAY-0083 §3).
16
+ *
17
+ * ## Two operations, because two authorities are checked
18
+ *
19
+ * A sale moves the buyer's cents on the buyer's say, with Store Platform's signed
20
+ * order saying what is being bought and who is paid. A refund moves cents *from* a
21
+ * publisher on Store Platform's authority, backed by the publisher's consent in
22
+ * advance through the offer's terms, and the buyer is the payee (CAY-0083 §3). One
23
+ * operation with a mode flag would blur which authority is being checked, so each
24
+ * has its own request, its own answer and its own refusal union, and a credential
25
+ * minted for one does not carry the other.
26
+ *
27
+ * ## What Piper presents is exactly what Store Platform signed
28
+ *
29
+ * {@link StoreSignedStatement} carries the bytes Store Platform's order key signed,
30
+ * and the signature over them. Cayso verifies the signature over those bytes as
31
+ * they arrived, and reads nothing from them as a fact until it has. So there is no
32
+ * second encoding of Store Platform's statement on this side of the boundary, and
33
+ * no way for a re-encoding to disagree with the signer by a space. Piper produces
34
+ * the bytes with Store Platform's own published encoder
35
+ * (`encodeSignedOrderStatement`, `encodeRefundDecisionStatement` in
36
+ * `@percayso/store-contracts`), and never edits them.
37
+ *
38
+ * The statements' fields are Store Platform's to define (`STO-0043`) and are not
39
+ * restated here: a second copy of another pillar's format is two formats that
40
+ * disagree eventually. What Cayso reads from them, once verified, is CAY-0082's
41
+ * list — the order digest, the checkout's idempotency key, the price, the paying
42
+ * wallet, the payee wallet or the house-publisher fact, and the allocation-policy
43
+ * version.
44
+ *
45
+ * **Nothing about the sale is taken from the caller's word.** The house-publisher
46
+ * fact, the payee wallet, the price and the idempotency key are all inside the
47
+ * signed bytes (CAY-0076, CAY-0082 §2), and there is no request field beside them.
48
+ * In particular the key is not presented twice: a request whose key disagrees with
49
+ * its signed order is not a request these types can express. The status read asks
50
+ * with the same key, read from the same order (`MarketplaceReceiptReadRequest`).
51
+ *
52
+ * A statement that decodes to something other than the statement the operation
53
+ * takes — a refund decision presented as an order, or the reverse — is
54
+ * `invalid_request`, and is never read as the other.
55
+ *
56
+ * ## One answer or nothing, so there is no status
57
+ *
58
+ * A settlement is one movement, written or not (CAY-0023): its answer is a signed
59
+ * receipt or one refusal, and a refusal means nothing was written. There is no
60
+ * pending, failed or reversed sale on these shapes. A caller that lost the answer
61
+ * asks the status read, or presents the same signed order again: **a replay under
62
+ * the same key and the same order answers the receipt already written**, in the same
63
+ * shape as the first answer, and is not a second sale (CAY-0082 §4).
64
+ *
65
+ * The receipt each answer carries is narrowed to the operation. An order is answered
66
+ * with a marketplace settlement's or a house-publisher charge's receipt, and a refund
67
+ * with a marketplace refund's, so an order answered with a refund receipt is not a
68
+ * value of {@link MarketplaceOrderResponse}. Both are `MarketplaceReceipt`s,
69
+ * the same one the status read returns, and Store Platform verifies them the same
70
+ * way.
71
+ *
72
+ * ## The paying wallet is the verified subject's own
73
+ *
74
+ * The buyer's wallet is resolved through the holders mapping from the verified
75
+ * assertion's subject, which is then discarded (CAY-0054, CAY-0082 §1). The order
76
+ * names the wallet the Citizen confirmed paying from, and an order naming any other
77
+ * wallet is refused rather than paid from either: Cayso never trusts a statement
78
+ * about who the caller is (CAY-0021), including Store Platform's.
79
+ *
80
+ * ## What is not here
81
+ *
82
+ * - **No unconfigured adapter in either union.** A seam this deployment has not
83
+ * been told about is a `503` in the refusal envelope's own code (`errors.ts`),
84
+ * never a member here (CAY-0082 §7). No receipt key is
85
+ * `receipt_signing_not_configured`. CAY-0083 §4's registry of Store Platform
86
+ * order keys, empty by default, closes both operations and the status read
87
+ * with a `503` too; its spelling is published by the slice that builds it.
88
+ * - **No figure.** The allocation between publisher and Community is Store
89
+ * Platform's canon's, named by the version the order carries. The refund window
90
+ * and the restricted-refund period are canon figures too, and an unset one
91
+ * closes the refund it gates rather than defaulting.
92
+ * - **No second payee.** A paid sale has exactly one publisher leg (CAY-0082 §8),
93
+ * and a split among publishers is not designed (CAY-0079 §6).
94
+ * - **No Percayso identity**, in any request, answer or refusal (CAY-0006).
95
+ */
96
+ import type { HousePublisherChargeReceiptStatement, HousePublisherRefundReceiptStatement, MarketplaceSettlementReceiptStatement, ThirdPartyRefundReceiptStatement } from './marketplace-receipt.js';
97
+ /**
98
+ * A statement Store Platform signed with its order key, exactly as it was signed.
99
+ * Opaque to Piper: carried, never edited.
100
+ */
101
+ export interface StoreSignedStatement {
102
+ /** The exact bytes Store Platform's order key signed, as unpadded base64url. */
103
+ readonly statement: string;
104
+ /** Store Platform's ES256 signature over those bytes: `r || s`, 64 bytes, as unpadded base64url. */
105
+ readonly signature: string;
106
+ }
107
+ /**
108
+ * Piper presenting Store Platform's signed order to settle a marketplace sale, under
109
+ * the buyer's assertion (`settle-marketplace-order`). The idempotency key, the price
110
+ * and who is paid are all inside the signed order, and nowhere beside it.
111
+ */
112
+ export interface MarketplaceOrderRequest {
113
+ readonly signedOrder: StoreSignedStatement;
114
+ }
115
+ /**
116
+ * Piper presenting Store Platform's signed refund decision, under the buyer's
117
+ * assertion (`settle-marketplace-refund`). The decision names the sale by its order
118
+ * digest and carries its own idempotency key, never the sale's (`STO-0043`). There is
119
+ * no amount: a refund is of the whole sale it names.
120
+ */
121
+ export interface MarketplaceRefundRequest {
122
+ readonly signedRefundDecision: StoreSignedStatement;
123
+ }
124
+ /**
125
+ * The receipt an order is answered with: a third-party sale's marketplace
126
+ * settlement, or a house-publisher sale's charge (CAY-0082 §1, §2). Never a refund's.
127
+ * It is a `MarketplaceReceipt`.
128
+ */
129
+ export interface MarketplaceOrderReceipt {
130
+ readonly statement: MarketplaceSettlementReceiptStatement | HousePublisherChargeReceiptStatement;
131
+ /** Cayso's ES256 signature over `encodeMarketplaceReceiptStatement(statement)`. */
132
+ readonly signature: string;
133
+ }
134
+ /**
135
+ * The receipt a refund is answered with: the marketplace refund of a third-party
136
+ * sale or of a house-publisher sale (CAY-0082 §5). Never a sale's. It is a
137
+ * `MarketplaceReceipt`.
138
+ */
139
+ export interface MarketplaceRefundReceipt {
140
+ readonly statement: ThirdPartyRefundReceiptStatement | HousePublisherRefundReceiptStatement;
141
+ /** Cayso's ES256 signature over `encodeMarketplaceReceiptStatement(statement)`. */
142
+ readonly signature: string;
143
+ }
144
+ /**
145
+ * The sale was written. Answered for the presentation that wrote it and for every
146
+ * replay of its key with the same order, in the same shape.
147
+ */
148
+ export interface MarketplaceOrderSettled {
149
+ readonly outcome: 'settled';
150
+ readonly receipt: MarketplaceOrderReceipt;
151
+ }
152
+ /** The order was refused, with one member of the closed union. Nothing was written. */
153
+ export interface MarketplaceOrderRefused {
154
+ readonly outcome: 'refused';
155
+ readonly error: MarketplaceOrderRefusal;
156
+ }
157
+ /**
158
+ * The ONE declared answer to `settle-marketplace-order`, discriminated by `outcome`.
159
+ * A consumer switches on `outcome` and never sniffs which fields are present.
160
+ */
161
+ export type MarketplaceOrderResponse = MarketplaceOrderSettled | MarketplaceOrderRefused;
162
+ /**
163
+ * Why a marketplace sale was refused: CAY-0082 §7's set for a sale, closed, spelled
164
+ * here. Every member means the same thing about money: nothing moved.
165
+ *
166
+ * A consumer renders a member by its name and never pattern-matches on prose. A new
167
+ * reason is a new member and a contract version, never a free-text field.
168
+ */
169
+ export type MarketplaceOrderRefusal =
170
+ /**
171
+ * The verification method is outside the money-grade assurance profile
172
+ * (`ASSURANCE_POLICY['settle-marketplace-order']`). `dev-bypass` is never inside it.
173
+ */
174
+ 'assurance_not_money_grade'
175
+ /**
176
+ * No wallet is bound to the verified subject now, for example after erasure, so
177
+ * there is no buyer's wallet to pay from. A sale never provisions one.
178
+ */
179
+ | 'wallet_has_no_live_binding'
180
+ /** The order is signed by a key the registry of Store Platform order keys does not hold (CAY-0083 §4). */
181
+ | 'order_key_not_recognised'
182
+ /** The order's signature does not verify over the bytes presented. */
183
+ | 'order_signature_invalid'
184
+ /** The order's digest is not the digest of the order facts it carries. */
185
+ | 'order_digest_mismatch'
186
+ /**
187
+ * The order names a paying wallet that is not the verified subject's own. Refused,
188
+ * and never paid from either wallet: Store Platform's statement about who is
189
+ * paying does not stand in for the assertion (CAY-0021).
190
+ */
191
+ | 'order_payer_wallet_not_the_buyers'
192
+ /**
193
+ * The key already landed something else for this buyer's wallet: a sale of a
194
+ * different order, or a refund. The key is unique per paying wallet across both
195
+ * sale kinds (CAY-0082 §4), and the two orders cannot both be what the buyer asked
196
+ * for, so neither is answered.
197
+ */
198
+ | 'idempotency_key_reused_for_a_different_order'
199
+ /**
200
+ * The allocation-policy version the order names has no figure configured here, so
201
+ * a third-party sale's split cannot be computed. A closed door, never a default
202
+ * split. A house-publisher sale has no split and never meets it.
203
+ */
204
+ | 'allocation_policy_version_not_configured'
205
+ /** No wallet has the payee's id. */
206
+ | 'payee_wallet_not_found'
207
+ /** The payee's wallet is an Organisation Wallet or the Treasury. A paid sale's publisher is a Citizen (CAY-0082 §8). */
208
+ | 'payee_wallet_not_a_citizens'
209
+ /** No Citizen is bound to the payee's wallet now, for example because the binding was erased. */
210
+ | 'payee_wallet_has_no_live_binding'
211
+ /**
212
+ * A third-party sale: the buyer's unrestricted cents are short. Restricted cents
213
+ * are never counted and never drawn for one (CAY-0076), so a wallet holding enough
214
+ * only once restricted cents are included is still short.
215
+ */
216
+ | 'unrestricted_cents_insufficient'
217
+ /**
218
+ * A house-publisher sale: the buyer's cents are short, restricted and unrestricted
219
+ * together. It is a charge, the one act a restricted lot may pay for (CAY-0082 §2),
220
+ * so it is kept apart from `unrestricted_cents_insufficient`: telling a buyer to
221
+ * look at their unrestricted cents would be wrong here.
222
+ */
223
+ | 'cents_insufficient'
224
+ /** A holding the draw needs is frozen. A freeze is not restriction. */
225
+ | 'cents_frozen'
226
+ /**
227
+ * Too many concurrent draws on the buyer's wallet, and the compare-and-swap retries
228
+ * ran out. Nothing moved, and the same signed order may be presented again.
229
+ */
230
+ | 'carve_contended';
231
+ /**
232
+ * The refund was written. Answered for the presentation that wrote it and for every
233
+ * replay of its key with the same refund decision, in the same shape.
234
+ */
235
+ export interface MarketplaceRefundSettled {
236
+ readonly outcome: 'refunded';
237
+ readonly receipt: MarketplaceRefundReceipt;
238
+ }
239
+ /** The refund was refused, with one member of the closed union. Nothing was written. */
240
+ export interface MarketplaceRefundRefused {
241
+ readonly outcome: 'refused';
242
+ readonly error: MarketplaceRefundRefusal;
243
+ }
244
+ /**
245
+ * The ONE declared answer to `settle-marketplace-refund`, discriminated by `outcome`.
246
+ */
247
+ export type MarketplaceRefundResponse = MarketplaceRefundSettled | MarketplaceRefundRefused;
248
+ /**
249
+ * Why a marketplace refund was refused: CAY-0082 §7's set for a refund, closed,
250
+ * spelled here. Every member means nothing moved, and the sale is untouched whatever
251
+ * the answer.
252
+ */
253
+ export type MarketplaceRefundRefusal =
254
+ /**
255
+ * The verification method is outside the money-grade assurance profile
256
+ * (`ASSURANCE_POLICY['settle-marketplace-refund']`). `dev-bypass` is never inside it.
257
+ */
258
+ 'assurance_not_money_grade'
259
+ /** No wallet is bound to the verified subject now, so there is no buyer's wallet to refund. */
260
+ | 'wallet_has_no_live_binding'
261
+ /** The decision is signed by a key the registry of Store Platform order keys does not hold (CAY-0083 §4). */
262
+ | 'refund_decision_key_not_recognised'
263
+ /** The decision's signature does not verify over the bytes presented. */
264
+ | 'refund_decision_signature_invalid'
265
+ /**
266
+ * The order digest the decision names is not a marketplace sale this buyer's wallet
267
+ * paid for. Somebody else's sale is not found either: the answer does not say
268
+ * whether one exists.
269
+ */
270
+ | 'sale_not_found'
271
+ /**
272
+ * The key already landed something else for this buyer's wallet: a refund on a
273
+ * different decision, or a sale. A refund carries its own key, never the sale's.
274
+ */
275
+ | 'idempotency_key_reused_for_a_different_refund'
276
+ /** The refund window after the sale has passed (CAY-0082 §5). */
277
+ | 'refund_window_passed'
278
+ /** This listing was already refunded to this Citizen. A Citizen gets at most one refund per listing (CAY-0082 §5). */
279
+ | 'listing_already_refunded'
280
+ /**
281
+ * A canon figure the refund needs — the refund window, or the period a restricted
282
+ * refund's new expiry runs for — is not configured. A closed door, never a
283
+ * default: the refund is refused (CAY-0082 §5).
284
+ */
285
+ | 'refund_figure_not_configured'
286
+ /**
287
+ * A third-party sale's refund: the publisher's unrestricted cents cannot meet their
288
+ * leg. Refused, never covered: the Treasury never makes up a publisher's shortfall,
289
+ * and Store Platform raises an operator case (CAY-0082 §5).
290
+ */
291
+ | 'publisher_unrestricted_cents_insufficient'
292
+ /** A holding the draw needs is frozen. A freeze is not restriction. */
293
+ | 'cents_frozen'
294
+ /**
295
+ * Too many concurrent draws, and the compare-and-swap retries ran out. Nothing
296
+ * moved, and the same signed refund decision may be presented again.
297
+ */
298
+ | 'carve_contended';
@@ -0,0 +1,16 @@
1
+ /*
2
+ * Copyright 2026 Percayso Community
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export {};
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The amount a movement may carry, as a type that cannot express the violation —
3
+ * the second layer CLAUDE.md requires beside the schema's own rule, at the one
4
+ * place cents actually move (CAY-0045).
5
+ *
6
+ * ## Why this is here and not beside the carve
7
+ *
8
+ * CLAUDE.md: "Every money invariant is enforced twice: a rule in the schema, AND a
9
+ * type that cannot express the violation. One layer does not count." For the amount
10
+ * a carve accepts there was one layer. The branded money types that WOULD have been
11
+ * the second — `HoldingCents` and `MovementCents` in `apps/api/src/db` — are
12
+ * unreachable from `packages/ledger`, which may not depend on an app (CLAUDE.md, the
13
+ * coupling scan). So the one module that moves cents could not reach the one module
14
+ * that types them, and the gap was architectural rather than careless (pen-test
15
+ * C3-1). This package is the module BOTH already depend on, which is why the brand
16
+ * lives here.
17
+ *
18
+ * ## Why `Cents` is not itself branded
19
+ *
20
+ * `Cents` (`./wallet`) stays the permissive wire alias it has always been, and that
21
+ * is deliberate:
22
+ *
23
+ * - A brand is a compile-time discipline of this repository. JSON has no brands, so
24
+ * the served contract schema would have to describe a branded `Cents` as a plain
25
+ * number anyway — a generator exception written to keep the published schema
26
+ * truthful is an admission that the brand is not part of the wire contract.
27
+ * - The wire's cents are REPORTED figures, and one of them is legitimately signed:
28
+ * `Headroom.headroomCents` is `ceiling − unrestricted` and is negative when
29
+ * outstanding Cayso sits above the ceiling, which an operator must be able to
30
+ * see. A type meaning "safe to move as money" is the wrong type for that field.
31
+ * - `Cents` is Apache-2.0 and published (CAY-0026). Branding it would stop a
32
+ * consumer building a `Balance` from a plain number compiling, for a distinction
33
+ * that consumer cannot observe.
34
+ *
35
+ * So there are two money types on purpose: `Cents` describes what crosses the wire,
36
+ * and {@link PositiveCents} describes what a caller may ask the economy to MOVE. The
37
+ * distinction is the one `apps/api/src/db/holdings.ts` already drew — the wire keeps a
38
+ * permissive shape, the write path keeps an unforgeable one — and what C3-1 found was
39
+ * that the unforgeable one was in the wrong package to be used.
40
+ *
41
+ * ## What is published here, and what is not
42
+ *
43
+ * A brand and its constructor, over the unit this contract already speaks in. No
44
+ * holding, no carve and no movement: those nouns are not Piper's business (CAY-0026),
45
+ * and `HoldingCents` — which admits zero, because a lot carved to nothing is still a
46
+ * row — stays in `apps/api` where the holdings table is. Nothing here changes the wire
47
+ * surface: no type on this module appears in a request or a response, and the served
48
+ * contract schema is unchanged by its arrival.
49
+ *
50
+ * There are no figures in this file. The safe-integer edge is decided against
51
+ * `Number.MAX_SAFE_INTEGER` — a limit of the language, not an amount — and every
52
+ * amount, rate, tier and threshold lives in the canon PRD.
53
+ */
54
+ import type { Cents } from './wallet.js';
55
+ /**
56
+ * A strictly-positive, safe-integer count of cents: the amount a movement may
57
+ * carry, and the type half of `movements.cents BIGINT NOT NULL CHECK (cents > 0)`.
58
+ *
59
+ * The brand is a private symbol, so the ONLY way to obtain one is
60
+ * {@link positiveCents}. A negative, a fraction, a zero and anything past the safe
61
+ * integer range are therefore unspellable on the money path rather than merely
62
+ * refused late — which is what "enforced twice" asks for (CAY-0018, CAY-0045).
63
+ *
64
+ * It refines `Cents` rather than sitting beside it, so a validated amount is still
65
+ * an ordinary cents figure everywhere a cents figure is read, reported or rendered.
66
+ * The narrowing goes one way only: every `PositiveCents` is a `Cents`, and no
67
+ * `Cents` is a `PositiveCents` without passing the constructor.
68
+ */
69
+ declare const positiveCentsBrand: unique symbol;
70
+ export type PositiveCents = Cents & {
71
+ readonly [positiveCentsBrand]: true;
72
+ };
73
+ /**
74
+ * The only way to obtain a {@link PositiveCents}. Refuses everything a movement's
75
+ * amount must never be: a fraction, a `NaN`, an infinity, a magnitude past the range
76
+ * where a JS number still represents an integer exactly, and anything at or below
77
+ * zero — a zero-value movement is not a movement.
78
+ *
79
+ * It throws and never rounds. Rounding a money amount to make it acceptable is how a
80
+ * cent goes missing, and the caller that handed over a fraction has a defect this
81
+ * hides.
82
+ */
83
+ export declare function positiveCents(value: number): PositiveCents;
84
+ export {};
package/dist/money.js ADDED
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Copyright 2026 Percayso Community
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * The only way to obtain a {@link PositiveCents}. Refuses everything a movement's
18
+ * amount must never be: a fraction, a `NaN`, an infinity, a magnitude past the range
19
+ * where a JS number still represents an integer exactly, and anything at or below
20
+ * zero — a zero-value movement is not a movement.
21
+ *
22
+ * It throws and never rounds. Rounding a money amount to make it acceptable is how a
23
+ * cent goes missing, and the caller that handed over a fraction has a defect this
24
+ * hides.
25
+ */
26
+ export function positiveCents(value) {
27
+ if (!Number.isInteger(value) || !Number.isSafeInteger(value)) {
28
+ throw new Error(`cents must be a safe whole number, not ${value} — money is integer cents.`);
29
+ }
30
+ if (value <= 0) {
31
+ throw new Error(`a movement's cents must be strictly positive, not ${value}.`);
32
+ }
33
+ return value;
34
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * `organisation` — what `POST /api/organisation/provision` answers (CAY-0059).
3
+ *
4
+ * An Organisation Wallet is keyed by the **Organisation ID** Percayso ID holds and
5
+ * attests, never by a Piper Instance. An Organisation OWNS an Instance, a
6
+ * possession cannot be the permanent identity of its owner, one Organisation may
7
+ * own several, and an Instance moves between Hosts without the Organisation or its
8
+ * Wallet changing. Naming the owner after the possession inverts the relationship
9
+ * and breaks on the second Instance (CAY-0059 §2).
10
+ *
11
+ * ## The request carries NOTHING
12
+ *
13
+ * This file declares no request shape, and that absence is the decision rather
14
+ * than an omission. The Organisation ID comes from the organisation-authority
15
+ * claim on the verified assertion (`AssertionClaims.organisationAuthority`) and
16
+ * from nowhere else — not a path, not a body, not a query (CAY-0059 §4). A
17
+ * downloadable Instance is a public client and an identifier a caller supplies is
18
+ * a request rather than evidence (CAY-0057), so a request shape naming the
19
+ * organisation would be a wallet-creation oracle: anybody able to name a UUID
20
+ * could bring an Organisation Wallet into being, or select one. The decision
21
+ * REMOVES the oracle rather than guarding it, which means there is no parameter to
22
+ * guard.
23
+ *
24
+ * ## It is IDEMPOTENT, and there is no "already provisioned" answer
25
+ *
26
+ * Calling it twice provisions once and answers the same wallet both times
27
+ * (CAY-0059 §4.6, the shape CAY-0031 set for citizenship). So {@link
28
+ * OrganisationView} carries no `state` discriminator: a call that reaches this
29
+ * answer has an Organisation Wallet, whether this call made it or an earlier one
30
+ * did. A caller cannot tell the two apart and has no decision that turns on it.
31
+ *
32
+ * ## No grant, no cents, no figures
33
+ *
34
+ * Provisioning an organisation moves no cents. The sign-up grant is a citizen's
35
+ * (CAY-0031) and nothing here has an amount, a rate, a tier, a schedule or a
36
+ * threshold — every economic figure lives in the canon PRD (CLAUDE.md). This
37
+ * answer is a wallet and nothing else.
38
+ *
39
+ * ## The Organisation ID is not echoed back, and that is deliberate
40
+ *
41
+ * The answer carries the {@link WalletRecord} — the opaque, operator-facing handle
42
+ * (CAY-0017) — and not the Organisation ID the claim carried. The caller already
43
+ * holds that value; repeating it here would make this response a second home for
44
+ * an external identity, and the mapping from an identity to a wallet lives in the
45
+ * holders mapping and nowhere else (CAY-0006, CLAUDE.md).
46
+ */
47
+ import type { WalletRecord } from './wallet.js';
48
+ /**
49
+ * The answer to `POST /api/organisation/provision`: the Organisation Wallet bound
50
+ * to the attested Organisation ID.
51
+ *
52
+ * One field, and an object rather than a bare {@link WalletRecord}, for the reason
53
+ * every other view on this surface is an object: a response that is a record has
54
+ * nowhere to grow a second field without breaking every consumer that decoded it
55
+ * as one.
56
+ */
57
+ export interface OrganisationView {
58
+ /** The Organisation Wallet. `holderType` is always `'organisation'` here. */
59
+ readonly wallet: WalletRecord;
60
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ * Copyright 2026 Percayso Community
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export {};