@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.
- package/LICENSE +201 -0
- package/README.md +114 -0
- package/dist/assertion.d.ts +867 -0
- package/dist/assertion.js +63 -0
- package/dist/assurance.d.ts +353 -0
- package/dist/assurance.js +551 -0
- package/dist/citizen-market.d.ts +468 -0
- package/dist/citizen-market.js +16 -0
- package/dist/citizenship.d.ts +215 -0
- package/dist/citizenship.js +26 -0
- package/dist/client.d.ts +288 -0
- package/dist/client.js +370 -0
- package/dist/commercial-payment.d.ts +243 -0
- package/dist/commercial-payment.js +16 -0
- package/dist/contract-schema.generated.d.ts +14 -0
- package/dist/contract-schema.generated.js +3107 -0
- package/dist/errors.d.ts +171 -0
- package/dist/errors.js +28 -0
- package/dist/index.d.ts +621 -0
- package/dist/index.js +624 -0
- package/dist/marketplace-receipt.d.ts +306 -0
- package/dist/marketplace-receipt.js +116 -0
- package/dist/marketplace-settlement.d.ts +298 -0
- package/dist/marketplace-settlement.js +16 -0
- package/dist/money.d.ts +84 -0
- package/dist/money.js +34 -0
- package/dist/organisation.d.ts +60 -0
- package/dist/organisation.js +16 -0
- package/dist/purchase.d.ts +531 -0
- package/dist/purchase.js +107 -0
- package/dist/restriction.d.ts +262 -0
- package/dist/restriction.js +316 -0
- package/dist/routes.d.ts +155 -0
- package/dist/routes.js +128 -0
- package/dist/schema-types.d.ts +101 -0
- package/dist/schema-types.js +16 -0
- package/dist/schema.d.ts +24 -0
- package/dist/schema.js +16 -0
- package/dist/wallet.d.ts +92 -0
- package/dist/wallet.js +59 -0
- 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 {};
|
package/dist/money.d.ts
ADDED
|
@@ -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 {};
|