@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
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The path and method for each operation on this contract surface, in one place
|
|
3
|
+
* so that no consumer hand-writes a URL.
|
|
4
|
+
*
|
|
5
|
+
* A URL spelled out at each call site drifts: one consumer writes `/wallet`,
|
|
6
|
+
* another `/api/wallet`, and the two disagree the day the server moves. Naming
|
|
7
|
+
* every route once, as an `as const` table the consumer reads, is how the method
|
|
8
|
+
* and path travel with the types they belong to — a change here is a change every
|
|
9
|
+
* consumer sees at compile time.
|
|
10
|
+
*
|
|
11
|
+
* This is a table, not a router. It carries no handler, no schema and no
|
|
12
|
+
* validator — those live in `apps/api`, which is the only module that serves
|
|
13
|
+
* these paths and the only one that may open a database connection (CLAUDE.md).
|
|
14
|
+
* The route for a purchase's result is keyed by `:purchaseId` because a purchase
|
|
15
|
+
* has a lifecycle a caller polls; a wallet read has none, so it takes no
|
|
16
|
+
* parameter.
|
|
17
|
+
*
|
|
18
|
+
* ## Why citizenship is TWO PATHS rather than one path with two methods
|
|
19
|
+
*
|
|
20
|
+
* `citizenship` reads whether the calling citizen has been provisioned;
|
|
21
|
+
* `citizenshipProvision` provisions them. `GET /api/citizenship` and
|
|
22
|
+
* `POST /api/citizenship` would have been the tidier pair of URLs and would have
|
|
23
|
+
* been wrong: the scope a credential must carry is registered PER PATH at
|
|
24
|
+
* CAY-0030's gate, so two operations sharing one path share one scope
|
|
25
|
+
* requirement — and the read would inherit the write's, or the write the read's.
|
|
26
|
+
* Neither is acceptable, and neither would be visible in this table. Distinct
|
|
27
|
+
* paths keep one path meaning one operation, which is the property the gate is
|
|
28
|
+
* entitled to assume (CAY-0031).
|
|
29
|
+
*
|
|
30
|
+
* ## Why cancelling is its OWN path, and a POST rather than a DELETE
|
|
31
|
+
*
|
|
32
|
+
* `purchaseCancel` sits under the purchase it cancels and adds a segment of its
|
|
33
|
+
* own rather than reusing `purchaseResult`'s path with a second method, for
|
|
34
|
+
* exactly the reason citizenship is two paths: one path carries one scope
|
|
35
|
+
* requirement at CAY-0030's gate, and a read that shared a path with a
|
|
36
|
+
* value-affecting write would share its scope (CAY-0032).
|
|
37
|
+
*
|
|
38
|
+
* It is a `POST` and not a `DELETE` because nothing is deleted. Cancelling moves a
|
|
39
|
+
* purchase to a terminal status and releases its reservation; the purchase row
|
|
40
|
+
* remains, readable at `purchaseResult` forever after. A `DELETE` would describe
|
|
41
|
+
* the removal of a resource, and a money record that can be removed is a money
|
|
42
|
+
* record nobody can audit.
|
|
43
|
+
*
|
|
44
|
+
* ## Why `organisationProvision` takes no parameter, and never will
|
|
45
|
+
*
|
|
46
|
+
* `POST /api/organisation/provision` carries NOTHING that names the organisation.
|
|
47
|
+
* No path parameter, no query, no body field. The Organisation ID comes from the
|
|
48
|
+
* organisation-authority claim on the verified assertion and from nowhere else
|
|
49
|
+
* (CAY-0059 §4), and a parameter here is precisely the wallet-creation oracle that
|
|
50
|
+
* decision removes rather than guards: an identifier a caller supplies is a
|
|
51
|
+
* request, never evidence (CAY-0057). A later edit adding `:organisationId` to
|
|
52
|
+
* this path would reintroduce the oracle in one line, so the absence is recorded
|
|
53
|
+
* where the path is written.
|
|
54
|
+
*
|
|
55
|
+
* ## Why `citizenshipFounderClaim` takes no parameter, and never will
|
|
56
|
+
*
|
|
57
|
+
* `POST /api/citizenship/founder-claim` carries NOTHING that names a Citizen, a
|
|
58
|
+
* wallet or an amount. The Citizen is the verified assertion's subject. The founder
|
|
59
|
+
* wallet is named by the founder claim reference, and that reference travels INSIDE
|
|
60
|
+
* the signed assertion, never in the path, a query or a body (CAY-0081 §2): a
|
|
61
|
+
* reference a caller supplies beside the credential would be the caller's claim about
|
|
62
|
+
* which allocation is theirs (CAY-0021). The amount is the allocation already in the
|
|
63
|
+
* wallet, and nothing about the claim moves it. It is its own path beside
|
|
64
|
+
* `citizenshipProvision`, for citizenship's reason above: scope is registered per
|
|
65
|
+
* path, and a credential minted to claim must not provision, nor the reverse.
|
|
66
|
+
*
|
|
67
|
+
* ## Why `commercialPayment` names both wallets in the BODY, and nobody in the path
|
|
68
|
+
*
|
|
69
|
+
* `POST /api/commercial-payments` is an Organisation paying a Citizen (CAY-0079). The
|
|
70
|
+
* paying wallet and the payee's wallet travel in the body as wallet ids, beside the
|
|
71
|
+
* cents, the idempotency key and the authority the caller invokes. Neither is a
|
|
72
|
+
* path parameter, because neither is a resource the caller owns and polls: a
|
|
73
|
+
* commercial payment has no lifecycle, so there is nothing under the path to read
|
|
74
|
+
* back. Nothing in the request names a Percayso identity. The acting party is the
|
|
75
|
+
* verified assertion's subject, resolved to their own wallet and never echoed
|
|
76
|
+
* (CAY-0079 §2). A path that took a payee's identity would make this route a
|
|
77
|
+
* directory of identities for anyone holding a mandate.
|
|
78
|
+
*
|
|
79
|
+
* ## Why the citizen market is FOUR PATHS, and none of them names a wallet
|
|
80
|
+
*
|
|
81
|
+
* `citizenMarket` reads the book, `placeQuote` places a quote, and `withdrawQuote`
|
|
82
|
+
* withdraws one (CAY-0080). They are three paths for citizenship's reason: one path
|
|
83
|
+
* carries one scope at CAY-0030's gate, and a read must not share the scope of a
|
|
84
|
+
* write that reserves cents, nor a withdrawal the scope of a placement.
|
|
85
|
+
*
|
|
86
|
+
* None takes a wallet or a holder. The holder is the verified subject, resolved
|
|
87
|
+
* inside `apps/api` from the holders mapping, and a parameter naming a wallet would
|
|
88
|
+
* be a claim about who the caller is (CAY-0021). The withdrawal names its quote in
|
|
89
|
+
* the body, as a purchase's cancellation cannot, because a quote is not a resource
|
|
90
|
+
* this surface lets a caller poll by id: its progress is on the book's read, among
|
|
91
|
+
* the caller's own quotes. It is a `POST` because nothing is deleted: a withdrawn
|
|
92
|
+
* quote leaves the book once, and its row stays.
|
|
93
|
+
*
|
|
94
|
+
* `requestBuyBack` asks the Treasury to buy back (CAY-0080 §2.2). It is a fourth path
|
|
95
|
+
* for the same reason: selling to the Treasury is not a quote (Open decision G), and a
|
|
96
|
+
* credential scoped to place one must not thereby cash out. It names no wallet either.
|
|
97
|
+
*/
|
|
98
|
+
export declare const ROUTES: {
|
|
99
|
+
readonly wallet: {
|
|
100
|
+
readonly method: "GET";
|
|
101
|
+
readonly path: "/api/wallet";
|
|
102
|
+
};
|
|
103
|
+
readonly citizenship: {
|
|
104
|
+
readonly method: "GET";
|
|
105
|
+
readonly path: "/api/citizenship";
|
|
106
|
+
};
|
|
107
|
+
readonly citizenshipProvision: {
|
|
108
|
+
readonly method: "POST";
|
|
109
|
+
readonly path: "/api/citizenship/provision";
|
|
110
|
+
};
|
|
111
|
+
readonly citizenshipFounderClaim: {
|
|
112
|
+
readonly method: "POST";
|
|
113
|
+
readonly path: "/api/citizenship/founder-claim";
|
|
114
|
+
};
|
|
115
|
+
readonly organisationProvision: {
|
|
116
|
+
readonly method: "POST";
|
|
117
|
+
readonly path: "/api/organisation/provision";
|
|
118
|
+
};
|
|
119
|
+
readonly purchaseAuthorise: {
|
|
120
|
+
readonly method: "POST";
|
|
121
|
+
readonly path: "/api/purchases";
|
|
122
|
+
};
|
|
123
|
+
readonly purchaseResult: {
|
|
124
|
+
readonly method: "GET";
|
|
125
|
+
readonly path: "/api/purchases/:purchaseId";
|
|
126
|
+
};
|
|
127
|
+
readonly purchaseCancel: {
|
|
128
|
+
readonly method: "POST";
|
|
129
|
+
readonly path: "/api/purchases/:purchaseId/cancel";
|
|
130
|
+
};
|
|
131
|
+
readonly commercialPayment: {
|
|
132
|
+
readonly method: "POST";
|
|
133
|
+
readonly path: "/api/commercial-payments";
|
|
134
|
+
};
|
|
135
|
+
readonly citizenMarket: {
|
|
136
|
+
readonly method: "GET";
|
|
137
|
+
readonly path: "/api/citizen-market";
|
|
138
|
+
};
|
|
139
|
+
readonly placeQuote: {
|
|
140
|
+
readonly method: "POST";
|
|
141
|
+
readonly path: "/api/quotes";
|
|
142
|
+
};
|
|
143
|
+
readonly withdrawQuote: {
|
|
144
|
+
readonly method: "POST";
|
|
145
|
+
readonly path: "/api/quote-withdrawals";
|
|
146
|
+
};
|
|
147
|
+
readonly requestBuyBack: {
|
|
148
|
+
readonly method: "POST";
|
|
149
|
+
readonly path: "/api/buy-backs";
|
|
150
|
+
};
|
|
151
|
+
readonly version: {
|
|
152
|
+
readonly method: "GET";
|
|
153
|
+
readonly path: "/api/version";
|
|
154
|
+
};
|
|
155
|
+
};
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
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 path and method for each operation on this contract surface, in one place
|
|
18
|
+
* so that no consumer hand-writes a URL.
|
|
19
|
+
*
|
|
20
|
+
* A URL spelled out at each call site drifts: one consumer writes `/wallet`,
|
|
21
|
+
* another `/api/wallet`, and the two disagree the day the server moves. Naming
|
|
22
|
+
* every route once, as an `as const` table the consumer reads, is how the method
|
|
23
|
+
* and path travel with the types they belong to — a change here is a change every
|
|
24
|
+
* consumer sees at compile time.
|
|
25
|
+
*
|
|
26
|
+
* This is a table, not a router. It carries no handler, no schema and no
|
|
27
|
+
* validator — those live in `apps/api`, which is the only module that serves
|
|
28
|
+
* these paths and the only one that may open a database connection (CLAUDE.md).
|
|
29
|
+
* The route for a purchase's result is keyed by `:purchaseId` because a purchase
|
|
30
|
+
* has a lifecycle a caller polls; a wallet read has none, so it takes no
|
|
31
|
+
* parameter.
|
|
32
|
+
*
|
|
33
|
+
* ## Why citizenship is TWO PATHS rather than one path with two methods
|
|
34
|
+
*
|
|
35
|
+
* `citizenship` reads whether the calling citizen has been provisioned;
|
|
36
|
+
* `citizenshipProvision` provisions them. `GET /api/citizenship` and
|
|
37
|
+
* `POST /api/citizenship` would have been the tidier pair of URLs and would have
|
|
38
|
+
* been wrong: the scope a credential must carry is registered PER PATH at
|
|
39
|
+
* CAY-0030's gate, so two operations sharing one path share one scope
|
|
40
|
+
* requirement — and the read would inherit the write's, or the write the read's.
|
|
41
|
+
* Neither is acceptable, and neither would be visible in this table. Distinct
|
|
42
|
+
* paths keep one path meaning one operation, which is the property the gate is
|
|
43
|
+
* entitled to assume (CAY-0031).
|
|
44
|
+
*
|
|
45
|
+
* ## Why cancelling is its OWN path, and a POST rather than a DELETE
|
|
46
|
+
*
|
|
47
|
+
* `purchaseCancel` sits under the purchase it cancels and adds a segment of its
|
|
48
|
+
* own rather than reusing `purchaseResult`'s path with a second method, for
|
|
49
|
+
* exactly the reason citizenship is two paths: one path carries one scope
|
|
50
|
+
* requirement at CAY-0030's gate, and a read that shared a path with a
|
|
51
|
+
* value-affecting write would share its scope (CAY-0032).
|
|
52
|
+
*
|
|
53
|
+
* It is a `POST` and not a `DELETE` because nothing is deleted. Cancelling moves a
|
|
54
|
+
* purchase to a terminal status and releases its reservation; the purchase row
|
|
55
|
+
* remains, readable at `purchaseResult` forever after. A `DELETE` would describe
|
|
56
|
+
* the removal of a resource, and a money record that can be removed is a money
|
|
57
|
+
* record nobody can audit.
|
|
58
|
+
*
|
|
59
|
+
* ## Why `organisationProvision` takes no parameter, and never will
|
|
60
|
+
*
|
|
61
|
+
* `POST /api/organisation/provision` carries NOTHING that names the organisation.
|
|
62
|
+
* No path parameter, no query, no body field. The Organisation ID comes from the
|
|
63
|
+
* organisation-authority claim on the verified assertion and from nowhere else
|
|
64
|
+
* (CAY-0059 §4), and a parameter here is precisely the wallet-creation oracle that
|
|
65
|
+
* decision removes rather than guards: an identifier a caller supplies is a
|
|
66
|
+
* request, never evidence (CAY-0057). A later edit adding `:organisationId` to
|
|
67
|
+
* this path would reintroduce the oracle in one line, so the absence is recorded
|
|
68
|
+
* where the path is written.
|
|
69
|
+
*
|
|
70
|
+
* ## Why `citizenshipFounderClaim` takes no parameter, and never will
|
|
71
|
+
*
|
|
72
|
+
* `POST /api/citizenship/founder-claim` carries NOTHING that names a Citizen, a
|
|
73
|
+
* wallet or an amount. The Citizen is the verified assertion's subject. The founder
|
|
74
|
+
* wallet is named by the founder claim reference, and that reference travels INSIDE
|
|
75
|
+
* the signed assertion, never in the path, a query or a body (CAY-0081 §2): a
|
|
76
|
+
* reference a caller supplies beside the credential would be the caller's claim about
|
|
77
|
+
* which allocation is theirs (CAY-0021). The amount is the allocation already in the
|
|
78
|
+
* wallet, and nothing about the claim moves it. It is its own path beside
|
|
79
|
+
* `citizenshipProvision`, for citizenship's reason above: scope is registered per
|
|
80
|
+
* path, and a credential minted to claim must not provision, nor the reverse.
|
|
81
|
+
*
|
|
82
|
+
* ## Why `commercialPayment` names both wallets in the BODY, and nobody in the path
|
|
83
|
+
*
|
|
84
|
+
* `POST /api/commercial-payments` is an Organisation paying a Citizen (CAY-0079). The
|
|
85
|
+
* paying wallet and the payee's wallet travel in the body as wallet ids, beside the
|
|
86
|
+
* cents, the idempotency key and the authority the caller invokes. Neither is a
|
|
87
|
+
* path parameter, because neither is a resource the caller owns and polls: a
|
|
88
|
+
* commercial payment has no lifecycle, so there is nothing under the path to read
|
|
89
|
+
* back. Nothing in the request names a Percayso identity. The acting party is the
|
|
90
|
+
* verified assertion's subject, resolved to their own wallet and never echoed
|
|
91
|
+
* (CAY-0079 §2). A path that took a payee's identity would make this route a
|
|
92
|
+
* directory of identities for anyone holding a mandate.
|
|
93
|
+
*
|
|
94
|
+
* ## Why the citizen market is FOUR PATHS, and none of them names a wallet
|
|
95
|
+
*
|
|
96
|
+
* `citizenMarket` reads the book, `placeQuote` places a quote, and `withdrawQuote`
|
|
97
|
+
* withdraws one (CAY-0080). They are three paths for citizenship's reason: one path
|
|
98
|
+
* carries one scope at CAY-0030's gate, and a read must not share the scope of a
|
|
99
|
+
* write that reserves cents, nor a withdrawal the scope of a placement.
|
|
100
|
+
*
|
|
101
|
+
* None takes a wallet or a holder. The holder is the verified subject, resolved
|
|
102
|
+
* inside `apps/api` from the holders mapping, and a parameter naming a wallet would
|
|
103
|
+
* be a claim about who the caller is (CAY-0021). The withdrawal names its quote in
|
|
104
|
+
* the body, as a purchase's cancellation cannot, because a quote is not a resource
|
|
105
|
+
* this surface lets a caller poll by id: its progress is on the book's read, among
|
|
106
|
+
* the caller's own quotes. It is a `POST` because nothing is deleted: a withdrawn
|
|
107
|
+
* quote leaves the book once, and its row stays.
|
|
108
|
+
*
|
|
109
|
+
* `requestBuyBack` asks the Treasury to buy back (CAY-0080 §2.2). It is a fourth path
|
|
110
|
+
* for the same reason: selling to the Treasury is not a quote (Open decision G), and a
|
|
111
|
+
* credential scoped to place one must not thereby cash out. It names no wallet either.
|
|
112
|
+
*/
|
|
113
|
+
export const ROUTES = {
|
|
114
|
+
wallet: { method: 'GET', path: '/api/wallet' },
|
|
115
|
+
citizenship: { method: 'GET', path: '/api/citizenship' },
|
|
116
|
+
citizenshipProvision: { method: 'POST', path: '/api/citizenship/provision' },
|
|
117
|
+
citizenshipFounderClaim: { method: 'POST', path: '/api/citizenship/founder-claim' },
|
|
118
|
+
organisationProvision: { method: 'POST', path: '/api/organisation/provision' },
|
|
119
|
+
purchaseAuthorise: { method: 'POST', path: '/api/purchases' },
|
|
120
|
+
purchaseResult: { method: 'GET', path: '/api/purchases/:purchaseId' },
|
|
121
|
+
purchaseCancel: { method: 'POST', path: '/api/purchases/:purchaseId/cancel' },
|
|
122
|
+
commercialPayment: { method: 'POST', path: '/api/commercial-payments' },
|
|
123
|
+
citizenMarket: { method: 'GET', path: '/api/citizen-market' },
|
|
124
|
+
placeQuote: { method: 'POST', path: '/api/quotes' },
|
|
125
|
+
withdrawQuote: { method: 'POST', path: '/api/quote-withdrawals' },
|
|
126
|
+
requestBuyBack: { method: 'POST', path: '/api/buy-backs' },
|
|
127
|
+
version: { method: 'GET', path: '/api/version' },
|
|
128
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of the machine-readable contract schema `GET /api/version/schema`
|
|
3
|
+
* serves — a structural description of the Piper-facing surface, one that a
|
|
4
|
+
* consumer (or the CI contract test) can read at runtime.
|
|
5
|
+
*
|
|
6
|
+
* ## Why this file is types-only and hand-written, while the schema VALUE is not
|
|
7
|
+
*
|
|
8
|
+
* This file declares what a schema descriptor *is*. The descriptor VALUE —
|
|
9
|
+
* `CONTRACT_SCHEMA` in `contract-schema.generated.ts` — is generated from the
|
|
10
|
+
* published types by `scripts/generate-contract-schema.ts`, never hand-written: a
|
|
11
|
+
* hand-maintained schema is a third artefact that silently goes stale (the issue's
|
|
12
|
+
* first Must-hold), and the whole point of the version endpoint is to catch a type
|
|
13
|
+
* that changed without the schema following. The generator reads the same source
|
|
14
|
+
* as `@percayso-community/cayso-contracts`, so the two cannot disagree, and a CI test regenerates and
|
|
15
|
+
* compares — a type changed without regeneration turns that test red.
|
|
16
|
+
*
|
|
17
|
+
* ## Why it describes the Piper-facing surface ONLY
|
|
18
|
+
*
|
|
19
|
+
* The descriptor covers the exported wire types, the route table, the capability
|
|
20
|
+
* names and the contract version — the surface a consumer of `@percayso-community/cayso-contracts`
|
|
21
|
+
* reads. It does NOT describe the internal routes (`INTERNAL_ROUTES` in
|
|
22
|
+
* `apps/api`: the operator, webhook and peer endpoints): those are not Piper's
|
|
23
|
+
* business, and a schema that enumerated the operator surface would have published
|
|
24
|
+
* it (CAY-0026, CLAUDE.md). The generator only ever sees this package, so the
|
|
25
|
+
* internal surface is absent by construction.
|
|
26
|
+
*
|
|
27
|
+
* ## What it deliberately omits
|
|
28
|
+
*
|
|
29
|
+
* `CaysoWallet` — the client capability interface carrying a `getMyWallet()`
|
|
30
|
+
* METHOD — is not a wire shape: a schema describes the data crossing the boundary,
|
|
31
|
+
* not a method a client happens to expose. The generator excludes it by name and
|
|
32
|
+
* refuses (throws) on any other method-bearing or otherwise unrecognised
|
|
33
|
+
* construct, so a new one cannot slip into the surface undescribed.
|
|
34
|
+
*
|
|
35
|
+
* This file carries no numbers: a contract version is a version string, not an
|
|
36
|
+
* economic amount, and no rate, tier, schedule or threshold appears here.
|
|
37
|
+
*/
|
|
38
|
+
/** A primitive leaf of the type language the descriptor speaks. */
|
|
39
|
+
export type ScalarKind = 'string' | 'number' | 'boolean';
|
|
40
|
+
/**
|
|
41
|
+
* One node in the structural description of a type. A closed union: a consumer
|
|
42
|
+
* switches on the present key rather than pattern-matching on prose, the same
|
|
43
|
+
* discipline the failure-reason union follows.
|
|
44
|
+
*
|
|
45
|
+
* - `{ scalar }` — a primitive: `string`, `number` or `boolean`.
|
|
46
|
+
* - `{ literal }` — a string-literal type, e.g. the `'cayso'` audience or a
|
|
47
|
+
* failure-reason member.
|
|
48
|
+
* - `{ null: true }` — the `null` type, as it appears in `T | null`.
|
|
49
|
+
* - `{ ref }` — a reference to another named type in {@link ContractSchema.types}.
|
|
50
|
+
* - `{ array, readonly }` — an array whose element is `array`; `readonly`
|
|
51
|
+
* records whether the source wrote `readonly T[]`.
|
|
52
|
+
* - `{ union }` — a union of the member nodes, in source order.
|
|
53
|
+
*/
|
|
54
|
+
export type SchemaTypeNode = {
|
|
55
|
+
readonly scalar: ScalarKind;
|
|
56
|
+
} | {
|
|
57
|
+
readonly literal: string;
|
|
58
|
+
} | {
|
|
59
|
+
readonly null: true;
|
|
60
|
+
} | {
|
|
61
|
+
readonly ref: string;
|
|
62
|
+
} | {
|
|
63
|
+
readonly array: SchemaTypeNode;
|
|
64
|
+
readonly readonly: boolean;
|
|
65
|
+
} | {
|
|
66
|
+
readonly union: readonly SchemaTypeNode[];
|
|
67
|
+
};
|
|
68
|
+
/** One property of an object type: its type, and whether it is `readonly`/optional. */
|
|
69
|
+
export interface SchemaProperty {
|
|
70
|
+
readonly type: SchemaTypeNode;
|
|
71
|
+
readonly readonly: boolean;
|
|
72
|
+
readonly optional: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A named type on the surface: either a type alias (`type Cents = number`) or an
|
|
76
|
+
* object type (an interface). `kind` discriminates the two.
|
|
77
|
+
*/
|
|
78
|
+
export type SchemaType = {
|
|
79
|
+
readonly kind: 'alias';
|
|
80
|
+
readonly of: SchemaTypeNode;
|
|
81
|
+
} | {
|
|
82
|
+
readonly kind: 'object';
|
|
83
|
+
readonly properties: Readonly<Record<string, SchemaProperty>>;
|
|
84
|
+
};
|
|
85
|
+
/** A route on the contract surface, as the descriptor reports it. */
|
|
86
|
+
export interface SchemaRoute {
|
|
87
|
+
readonly method: string;
|
|
88
|
+
readonly path: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The whole served schema: the contract version it describes, the package name,
|
|
92
|
+
* the capability names, the route table and every named wire type. Generated from
|
|
93
|
+
* `@percayso-community/cayso-contracts`' own source; served verbatim at `GET /api/version/schema`.
|
|
94
|
+
*/
|
|
95
|
+
export interface ContractSchema {
|
|
96
|
+
readonly contractVersion: string;
|
|
97
|
+
readonly packageName: string;
|
|
98
|
+
readonly capabilities: Readonly<Record<string, string>>;
|
|
99
|
+
readonly routes: Readonly<Record<string, SchemaRoute>>;
|
|
100
|
+
readonly types: Readonly<Record<string, SchemaType>>;
|
|
101
|
+
}
|
|
@@ -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/schema.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@percayso-community/cayso-contracts/schema` — the machine-readable contract schema, on its own
|
|
3
|
+
* subpath.
|
|
4
|
+
*
|
|
5
|
+
* This is the single value `GET /api/version/schema` serves and the CI contract
|
|
6
|
+
* test compares against: `CONTRACT_SCHEMA`, generated from the published types
|
|
7
|
+
* (never hand-written), plus the descriptor types that give it shape. It ships
|
|
8
|
+
* from a *separate* entry point — the way the client does — so a consumer wanting
|
|
9
|
+
* only the types (`@percayso-community/cayso-contracts`) or only the client (`./client`) pulls in
|
|
10
|
+
* none of this, and importing it does nothing but hand back a frozen data value
|
|
11
|
+
* and its types (CLAUDE.md: packages have no side effects on import).
|
|
12
|
+
*
|
|
13
|
+
* ## This is a diagnostic, NOT a replacement for depending on the package
|
|
14
|
+
*
|
|
15
|
+
* The schema exists so a running deployment can *report* the surface it was built
|
|
16
|
+
* against — for the version endpoint, for the contract test, for an operator
|
|
17
|
+
* eyeballing a mismatch. It is emphatically not a runtime type system: a consumer
|
|
18
|
+
* gets its compile-time guarantees by depending on `@percayso-community/cayso-contracts` and its
|
|
19
|
+
* types, which do not exist at runtime to be fetched. Fetching this schema to
|
|
20
|
+
* drive validation instead of depending on the package would re-implement, badly,
|
|
21
|
+
* the guarantee the package already gives for free (CAY-0026, and the README).
|
|
22
|
+
*/
|
|
23
|
+
export type { ContractSchema, ScalarKind, SchemaProperty, SchemaRoute, SchemaType, SchemaTypeNode, } from './schema-types.js';
|
|
24
|
+
export { CONTRACT_SCHEMA } from './contract-schema.generated.js';
|
package/dist/schema.js
ADDED
|
@@ -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 { CONTRACT_SCHEMA } from './contract-schema.generated.js';
|
package/dist/wallet.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cayso.wallet` — the read side of a wallet: the record, the balance, the
|
|
3
|
+
* allowances a holder carries, and the response that binds them.
|
|
4
|
+
*
|
|
5
|
+
* This is the pillar's first real contract. `wallet` is the noun and `cayso.*`
|
|
6
|
+
* is the namespace (CAY-0017); a `balance` is the derived sum of a wallet's own
|
|
7
|
+
* holdings and is never a stored figure (CAY-0019). A consumer of this contract
|
|
8
|
+
* reads a wallet — it never sees a holding, a carve or a movement, and there is
|
|
9
|
+
* no mutation here: crediting, debiting or transferring a wallet is not a read,
|
|
10
|
+
* and nothing on `CaysoWallet` can express one.
|
|
11
|
+
*
|
|
12
|
+
* ## Why balance and allowances are two answers, never one sum
|
|
13
|
+
*
|
|
14
|
+
* What a citizen *has* and what a citizen *may spend* are different questions,
|
|
15
|
+
* and this shape keeps them apart on purpose (CAY-0024).
|
|
16
|
+
*
|
|
17
|
+
* - `Balance` is the sum of the wallet's **own** holdings. It is the holder's
|
|
18
|
+
* service credit and nothing else.
|
|
19
|
+
* - An `Allowance` is a revocable authority to spend from an **organisation's**
|
|
20
|
+
* wallet. The cents stay the organisation's until they are spent; an
|
|
21
|
+
* allowance is not the holder's money and never becomes it.
|
|
22
|
+
*
|
|
23
|
+
* So `balance` and `allowances` are **never summed** — not here, not in any
|
|
24
|
+
* consumer. The reason is regulatory, not cosmetic: were an employer's allowance
|
|
25
|
+
* folded into the balance, then in every screen, statement and export it would
|
|
26
|
+
* read as the citizen's own service credit. That is remuneration in kind — an
|
|
27
|
+
* employer handing over something cashable — which is precisely the shape
|
|
28
|
+
* CAY-0024 exists to keep out of the model. Two fields that never add up is how
|
|
29
|
+
* the contract refuses to tell that lie.
|
|
30
|
+
*
|
|
31
|
+
* ## What this file must not decide
|
|
32
|
+
*
|
|
33
|
+
* - **No formatted money.** Every amount is an integer count of cents, paired
|
|
34
|
+
* with `publishedValuePence`; the consumer renders "₡500" or "£5.00". A
|
|
35
|
+
* server that returned `"₡500.00"` would have fixed the locale, the symbol
|
|
36
|
+
* and the rounding for every consumer forever.
|
|
37
|
+
* - **No numbers.** Amounts, rates, tiers and thresholds live in the canon PRD
|
|
38
|
+
* and are read from one place. `publishedValuePence` is a field, not a value.
|
|
39
|
+
* - `restrictedCents` and `unrestrictedCents` are reported figures. What a
|
|
40
|
+
* restricted lot may do is the capability policy's (CAY-0035, and CAY-0074's
|
|
41
|
+
* rule that a carve does not move restriction), not these fields'.
|
|
42
|
+
*/
|
|
43
|
+
/** The capability this contract surfaces. Matches the glossary exactly. */
|
|
44
|
+
export declare const WALLET_CAPABILITY: "cayso.wallet";
|
|
45
|
+
/** Who a wallet belongs to. */
|
|
46
|
+
export type HolderType = 'citizen' | 'organisation';
|
|
47
|
+
/** Unix epoch milliseconds. */
|
|
48
|
+
export type Millis = number;
|
|
49
|
+
/**
|
|
50
|
+
* An integer number of cents. **₡1 = 100 cents** — the permanent relationship, and the
|
|
51
|
+
* whole of the unit. Never fractional.
|
|
52
|
+
*
|
|
53
|
+
* The sterling value is NOT part of this type's meaning. ₡1 is £0.01 at tier 1 and the
|
|
54
|
+
* published value ratchets (CAY-0002), which is why every amount on this surface travels
|
|
55
|
+
* beside a `publishedValuePence`. **Render sterling from that figure, never from a
|
|
56
|
+
* constant**: a constant is correct until the first quickening and silently wrong after.
|
|
57
|
+
*/
|
|
58
|
+
export type Cents = number;
|
|
59
|
+
export interface WalletRecord {
|
|
60
|
+
/** This repository's opaque id, and the only handle an operator surface shows. */
|
|
61
|
+
readonly walletId: string;
|
|
62
|
+
readonly holderType: HolderType;
|
|
63
|
+
readonly createdAt: Millis;
|
|
64
|
+
}
|
|
65
|
+
/** The sum of a wallet's own holdings. Never includes an allowance. */
|
|
66
|
+
export interface Balance {
|
|
67
|
+
readonly cents: Cents;
|
|
68
|
+
readonly restrictedCents: Cents;
|
|
69
|
+
readonly unrestrictedCents: Cents;
|
|
70
|
+
/** What ₡1 is worth in sterling right now, in pence. */
|
|
71
|
+
readonly publishedValuePence: number;
|
|
72
|
+
/** When the figures were true. A lagging read is stale, never wrong. */
|
|
73
|
+
readonly asOf: Millis;
|
|
74
|
+
}
|
|
75
|
+
/** Authority to spend from an organisation's wallet. Not the holder's money. */
|
|
76
|
+
export interface Allowance {
|
|
77
|
+
readonly organisationWalletId: string;
|
|
78
|
+
readonly limitCents: Cents;
|
|
79
|
+
readonly spentCents: Cents;
|
|
80
|
+
readonly remainingCents: Cents;
|
|
81
|
+
}
|
|
82
|
+
export interface WalletView {
|
|
83
|
+
readonly wallet: WalletRecord;
|
|
84
|
+
readonly balance: Balance;
|
|
85
|
+
/** Allowances on OTHER wallets. Deliberately not summed into balance. */
|
|
86
|
+
readonly allowances: readonly Allowance[];
|
|
87
|
+
}
|
|
88
|
+
export interface CaysoWallet {
|
|
89
|
+
readonly capability: typeof WALLET_CAPABILITY;
|
|
90
|
+
readonly version: string;
|
|
91
|
+
getMyWallet(): Promise<WalletView | null>;
|
|
92
|
+
}
|
package/dist/wallet.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
* `cayso.wallet` — the read side of a wallet: the record, the balance, the
|
|
18
|
+
* allowances a holder carries, and the response that binds them.
|
|
19
|
+
*
|
|
20
|
+
* This is the pillar's first real contract. `wallet` is the noun and `cayso.*`
|
|
21
|
+
* is the namespace (CAY-0017); a `balance` is the derived sum of a wallet's own
|
|
22
|
+
* holdings and is never a stored figure (CAY-0019). A consumer of this contract
|
|
23
|
+
* reads a wallet — it never sees a holding, a carve or a movement, and there is
|
|
24
|
+
* no mutation here: crediting, debiting or transferring a wallet is not a read,
|
|
25
|
+
* and nothing on `CaysoWallet` can express one.
|
|
26
|
+
*
|
|
27
|
+
* ## Why balance and allowances are two answers, never one sum
|
|
28
|
+
*
|
|
29
|
+
* What a citizen *has* and what a citizen *may spend* are different questions,
|
|
30
|
+
* and this shape keeps them apart on purpose (CAY-0024).
|
|
31
|
+
*
|
|
32
|
+
* - `Balance` is the sum of the wallet's **own** holdings. It is the holder's
|
|
33
|
+
* service credit and nothing else.
|
|
34
|
+
* - An `Allowance` is a revocable authority to spend from an **organisation's**
|
|
35
|
+
* wallet. The cents stay the organisation's until they are spent; an
|
|
36
|
+
* allowance is not the holder's money and never becomes it.
|
|
37
|
+
*
|
|
38
|
+
* So `balance` and `allowances` are **never summed** — not here, not in any
|
|
39
|
+
* consumer. The reason is regulatory, not cosmetic: were an employer's allowance
|
|
40
|
+
* folded into the balance, then in every screen, statement and export it would
|
|
41
|
+
* read as the citizen's own service credit. That is remuneration in kind — an
|
|
42
|
+
* employer handing over something cashable — which is precisely the shape
|
|
43
|
+
* CAY-0024 exists to keep out of the model. Two fields that never add up is how
|
|
44
|
+
* the contract refuses to tell that lie.
|
|
45
|
+
*
|
|
46
|
+
* ## What this file must not decide
|
|
47
|
+
*
|
|
48
|
+
* - **No formatted money.** Every amount is an integer count of cents, paired
|
|
49
|
+
* with `publishedValuePence`; the consumer renders "₡500" or "£5.00". A
|
|
50
|
+
* server that returned `"₡500.00"` would have fixed the locale, the symbol
|
|
51
|
+
* and the rounding for every consumer forever.
|
|
52
|
+
* - **No numbers.** Amounts, rates, tiers and thresholds live in the canon PRD
|
|
53
|
+
* and are read from one place. `publishedValuePence` is a field, not a value.
|
|
54
|
+
* - `restrictedCents` and `unrestrictedCents` are reported figures. What a
|
|
55
|
+
* restricted lot may do is the capability policy's (CAY-0035, and CAY-0074's
|
|
56
|
+
* rule that a carve does not move restriction), not these fields'.
|
|
57
|
+
*/
|
|
58
|
+
/** The capability this contract surfaces. Matches the glossary exactly. */
|
|
59
|
+
export const WALLET_CAPABILITY = 'cayso.wallet';
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@percayso-community/cayso-contracts",
|
|
3
|
+
"version": "0.23.0",
|
|
4
|
+
"description": "The versioned capability contract surface through which anything outside this repository reads a wallet. Published; keep it the Piper-facing surface only.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Percayso-Community/cayso.platform.git",
|
|
9
|
+
"directory": "packages/contracts"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./client": {
|
|
21
|
+
"types": "./dist/client.d.ts",
|
|
22
|
+
"default": "./dist/client.js"
|
|
23
|
+
},
|
|
24
|
+
"./schema": {
|
|
25
|
+
"types": "./dist/schema.d.ts",
|
|
26
|
+
"default": "./dist/schema.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.build.json",
|
|
36
|
+
"generate-schema": "tsx scripts/generate-contract-schema.ts",
|
|
37
|
+
"lint": "eslint .",
|
|
38
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
39
|
+
"test": "vitest run"
|
|
40
|
+
}
|
|
41
|
+
}
|