@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,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 {};
@@ -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';
@@ -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
+ }