@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,551 @@
|
|
|
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
|
+
import { ROUTES } from './routes.js';
|
|
17
|
+
// --- The profile's version ---------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* The version of the assurance profile this deployment speaks — the one an issuer
|
|
20
|
+
* stamps on a claim it mints, as `AssertionClaims.assuranceProfileVersion`.
|
|
21
|
+
*
|
|
22
|
+
* It names the standard as well as the version, because the standard is Cayso's
|
|
23
|
+
* own (see the file docblock) and a bare `1` would not say whose. Adopting an
|
|
24
|
+
* external scheme later, or changing what any method asserts, is a NEW version
|
|
25
|
+
* with a new identifier — never a redefinition of this one. Redefining a version
|
|
26
|
+
* in place is how a claim minted last month silently comes to mean something else.
|
|
27
|
+
*
|
|
28
|
+
* It is deliberately NOT the contract version: the contract version describes the
|
|
29
|
+
* wire surface and moves when a type changes, while this moves when the MEANING of
|
|
30
|
+
* evidence changes. Two clocks, two numbers, for the same reason the contract
|
|
31
|
+
* version is not the schema version (CONTEXT.md, Contract version).
|
|
32
|
+
*/
|
|
33
|
+
export const ASSURANCE_PROFILE_VERSION = 'cayso-assurance-1';
|
|
34
|
+
/**
|
|
35
|
+
* Every assurance profile version this deployment recognises. A claim carrying
|
|
36
|
+
* anything else is refused ({@link assessAssurance}) rather than read as if it
|
|
37
|
+
* meant what this version means — fail closed, the posture every adapter in this
|
|
38
|
+
* tree takes (CLAUDE.md, Trust and authentication).
|
|
39
|
+
*
|
|
40
|
+
* A future version that is genuinely compatible is added here deliberately. It
|
|
41
|
+
* contains {@link ASSURANCE_PROFILE_VERSION} by construction: a deployment always
|
|
42
|
+
* recognises the profile it speaks.
|
|
43
|
+
*/
|
|
44
|
+
export const RECOGNISED_ASSURANCE_PROFILE_VERSIONS = [ASSURANCE_PROFILE_VERSION];
|
|
45
|
+
/**
|
|
46
|
+
* What each method asserts. These statements are the contract: a consumer reads
|
|
47
|
+
* them to decide whether its evidence qualifies, and an issuer is bound by them
|
|
48
|
+
* when it mints.
|
|
49
|
+
*/
|
|
50
|
+
export const EVIDENCE_STATEMENTS = {
|
|
51
|
+
'dev-bypass': {
|
|
52
|
+
method: 'dev-bypass',
|
|
53
|
+
evidence: {
|
|
54
|
+
identityDocumentChecked: false,
|
|
55
|
+
biometricCaptured: false,
|
|
56
|
+
biometricComparedToDocumentPortrait: false,
|
|
57
|
+
},
|
|
58
|
+
asserts: 'Asserts NOTHING. No identity document was examined, no biometric was captured, and no ' +
|
|
59
|
+
'comparison was performed. It marks an identity verified having checked nothing at all, ' +
|
|
60
|
+
'and an identity marked this way keeps the method permanently.',
|
|
61
|
+
},
|
|
62
|
+
document: {
|
|
63
|
+
method: 'document',
|
|
64
|
+
evidence: {
|
|
65
|
+
identityDocumentChecked: true,
|
|
66
|
+
biometricCaptured: false,
|
|
67
|
+
biometricComparedToDocumentPortrait: false,
|
|
68
|
+
},
|
|
69
|
+
asserts: 'Asserts that a government-issued identity document was checked for validity. It asserts ' +
|
|
70
|
+
'NO comparison between that document and the person presenting it: the document is sound, ' +
|
|
71
|
+
'and nothing here says who was holding it.',
|
|
72
|
+
},
|
|
73
|
+
'document-and-biometric': {
|
|
74
|
+
method: 'document-and-biometric',
|
|
75
|
+
evidence: {
|
|
76
|
+
identityDocumentChecked: true,
|
|
77
|
+
biometricCaptured: true,
|
|
78
|
+
biometricComparedToDocumentPortrait: true,
|
|
79
|
+
},
|
|
80
|
+
asserts: 'Asserts a valid government-issued identity document AND a live biometric capture compared ' +
|
|
81
|
+
"against that document's portrait, with a recorded match decision. The comparison is the " +
|
|
82
|
+
"whole of what distinguishes this from 'document' — a capture that was never compared is " +
|
|
83
|
+
"'document', not this.",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Every verification method, as a value. Derived from {@link EVIDENCE_STATEMENTS}
|
|
88
|
+
* rather than hand-listed: that table is a mapped type over the union, so its keys
|
|
89
|
+
* ARE the union and this list cannot drift from it. A hand-written array beside a
|
|
90
|
+
* union is a second home for the same fact, and the two disagree eventually.
|
|
91
|
+
*/
|
|
92
|
+
export const VERIFICATION_METHODS = Object.keys(EVIDENCE_STATEMENTS);
|
|
93
|
+
/**
|
|
94
|
+
* Whether `evidence` supports asserting `method` — the issuer's check, and the
|
|
95
|
+
* whole of what makes an overclaiming claim REFUSABLE rather than merely
|
|
96
|
+
* discouraged.
|
|
97
|
+
*
|
|
98
|
+
* True when everything the method asserts actually happened. Evidence may exceed
|
|
99
|
+
* what the method asserts (a compared biometric supports `'document'` perfectly
|
|
100
|
+
* well); it may never fall short of it.
|
|
101
|
+
*
|
|
102
|
+
* The case this exists for: an issuer that checked a document and captured a face
|
|
103
|
+
* WITHOUT comparing them gets `false` for `'document-and-biometric'`. It must then
|
|
104
|
+
* mint `'document'` or mint nothing — it must not assert a match decision that was
|
|
105
|
+
* never recorded.
|
|
106
|
+
*
|
|
107
|
+
* `'dev-bypass'` asserts nothing, so nothing can fall short of it and this is
|
|
108
|
+
* vacuously true for it. That is correct and harmless: a method that claims
|
|
109
|
+
* nothing cannot overclaim, and it is refused at every gated operation anyway.
|
|
110
|
+
*/
|
|
111
|
+
export function evidenceSupports(method, evidence) {
|
|
112
|
+
const asserted = EVIDENCE_STATEMENTS[method].evidence;
|
|
113
|
+
return ((!asserted.identityDocumentChecked || evidence.identityDocumentChecked) &&
|
|
114
|
+
(!asserted.biometricCaptured || evidence.biometricCaptured) &&
|
|
115
|
+
(!asserted.biometricComparedToDocumentPortrait || evidence.biometricComparedToDocumentPortrait));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The strongest method this evidence honestly supports, or `null` when it supports
|
|
119
|
+
* none. An issuer mints THIS, rather than the method it would like to have.
|
|
120
|
+
*
|
|
121
|
+
* `'dev-bypass'` is never returned. It is not a conclusion evidence supports — it
|
|
122
|
+
* is the marker of a verification that examined nothing, set by the bypass itself
|
|
123
|
+
* and never derived from evidence. Evidence that proves nothing yields `null`, and
|
|
124
|
+
* an issuer holding `null` has nothing to assert.
|
|
125
|
+
*/
|
|
126
|
+
export function methodForEvidence(evidence) {
|
|
127
|
+
if (evidenceSupports('document-and-biometric', evidence))
|
|
128
|
+
return 'document-and-biometric';
|
|
129
|
+
if (evidenceSupports('document', evidence))
|
|
130
|
+
return 'document';
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The acceptance policy: which methods each operation accepts. THE statement of
|
|
135
|
+
* record — a gate reads it rather than holding its own condition.
|
|
136
|
+
*
|
|
137
|
+
* `'dev-bypass'` is `'refused'` at every gated operation and appears in no
|
|
138
|
+
* accepted set (CAY-0021).
|
|
139
|
+
*/
|
|
140
|
+
export const ASSURANCE_POLICY = {
|
|
141
|
+
// A holder reading their own balance. No bar: nothing moves, the data is the
|
|
142
|
+
// holder's own, and the wallet surface deliberately serves a development-bypassed
|
|
143
|
+
// identity its own wallet while refusing it a purchase. Reading is not a lesser
|
|
144
|
+
// form of buying — it is a different act, and this entry says so rather than
|
|
145
|
+
// pretending the bypass cleared something.
|
|
146
|
+
'read-own-wallet': {
|
|
147
|
+
operation: 'read-own-wallet',
|
|
148
|
+
assurance: 'not-required',
|
|
149
|
+
rationale: 'A holder reading their own balance moves no value and reads nobody else, so no assurance ' +
|
|
150
|
+
'bar is applied. Authentication is still required; that is a different gate.',
|
|
151
|
+
},
|
|
152
|
+
// Polling a purchase the caller started. Same reasoning: a read of one's own
|
|
153
|
+
// record. The purchase it reports on was itself gated when it was authorised.
|
|
154
|
+
'read-purchase-result': {
|
|
155
|
+
operation: 'read-purchase-result',
|
|
156
|
+
assurance: 'not-required',
|
|
157
|
+
rationale: 'Polling the result of a purchase the caller already made moves no value; the assurance bar ' +
|
|
158
|
+
'was applied when that purchase was authorised, and applying it again at the read would ' +
|
|
159
|
+
'refuse a holder their own outcome.',
|
|
160
|
+
},
|
|
161
|
+
// The value-moving operation on this surface, and the bar the money gate reads.
|
|
162
|
+
'authorise-purchase': {
|
|
163
|
+
operation: 'authorise-purchase',
|
|
164
|
+
assurance: 'required',
|
|
165
|
+
decisions: {
|
|
166
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
167
|
+
'dev-bypass': 'refused',
|
|
168
|
+
// A checked document, with no comparison to the person presenting it. Accepted:
|
|
169
|
+
// this is the bar the money gate has enforced since it was written, and this
|
|
170
|
+
// slice lifts that policy into the contract without changing it.
|
|
171
|
+
document: 'accepted',
|
|
172
|
+
'document-and-biometric': 'accepted',
|
|
173
|
+
},
|
|
174
|
+
rationale: 'Buying moves value, so the identity must have been checked by something. A checked document ' +
|
|
175
|
+
'is the standing bar; it is stated here rather than left as a condition inside the gate.',
|
|
176
|
+
},
|
|
177
|
+
// Terminating a purchase the holder started, before anything is captured
|
|
178
|
+
// (CAY-0032). REQUIRED, and at the same bar as authorising: cancelling releases
|
|
179
|
+
// reserved headroom and closes a money-path record for good, and a bar lower than
|
|
180
|
+
// the one that admitted the authorisation would let a weaker credential undo what
|
|
181
|
+
// a stronger one did. It is its OWN operation rather than a second use of
|
|
182
|
+
// 'authorise-purchase' because the acts differ — a credential minted so a
|
|
183
|
+
// confirmation page can finish a purchase should not thereby be able to end one —
|
|
184
|
+
// and because scope is checked per operation at the gate, so two acts sharing one
|
|
185
|
+
// key share one authorisation.
|
|
186
|
+
'cancel-purchase': {
|
|
187
|
+
operation: 'cancel-purchase',
|
|
188
|
+
assurance: 'required',
|
|
189
|
+
decisions: {
|
|
190
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
191
|
+
'dev-bypass': 'refused',
|
|
192
|
+
document: 'accepted',
|
|
193
|
+
'document-and-biometric': 'accepted',
|
|
194
|
+
},
|
|
195
|
+
rationale: 'Cancelling releases reserved headroom and closes a purchase for good, so it is money-grade: ' +
|
|
196
|
+
'anything but the development bypass. The bar is deliberately the same as authorising — a ' +
|
|
197
|
+
'credential that could not have bought cannot undo a purchase either.',
|
|
198
|
+
},
|
|
199
|
+
// Asking whether one's own citizenship has been provisioned. No bar, and for
|
|
200
|
+
// the same reason reading one's own wallet has none: the question moves no
|
|
201
|
+
// value, reads nobody else, and — since CAY-0031 — provisions nothing by being
|
|
202
|
+
// asked. It is deliberately NOT folded into 'provision-citizenship': putting
|
|
203
|
+
// the write's bar in front of the read would mean a citizen could not find out
|
|
204
|
+
// whether they had been provisioned without clearing the bar for provisioning
|
|
205
|
+
// them, which is the question answering itself.
|
|
206
|
+
'read-own-citizenship': {
|
|
207
|
+
operation: 'read-own-citizenship',
|
|
208
|
+
assurance: 'not-required',
|
|
209
|
+
rationale: 'Asking whether your own citizenship has been provisioned moves no value, reads nobody ' +
|
|
210
|
+
'else and writes nothing, so no assurance bar is applied. Authentication is still ' +
|
|
211
|
+
'required; that is a different gate.',
|
|
212
|
+
},
|
|
213
|
+
// Binding a Percayso identity to a wallet for the first time, and granting the
|
|
214
|
+
// sign-up cents that arrive with it (CAY-0031). The policy below was decided in
|
|
215
|
+
// CAY-0029, before the route existed; CAY-0031 serves the route and reads this
|
|
216
|
+
// entry unchanged rather than reopening it.
|
|
217
|
+
'provision-citizenship': {
|
|
218
|
+
operation: 'provision-citizenship',
|
|
219
|
+
assurance: 'required',
|
|
220
|
+
decisions: {
|
|
221
|
+
'dev-bypass': 'refused',
|
|
222
|
+
// Accepted, and the reasoning is worth recording because the opposite is
|
|
223
|
+
// tempting. Provisioning IS the moment an identity becomes a holder, and it
|
|
224
|
+
// is the only moment at which "is this person the one the document
|
|
225
|
+
// describes" can be asked — so a bar requiring a recorded comparison is
|
|
226
|
+
// defensible on its merits. It is not taken, for a reason outside this
|
|
227
|
+
// file: the only issuer that exists asserts `'document'` and cannot assert
|
|
228
|
+
// more, so that bar would provision nobody. A wallet is a precondition of
|
|
229
|
+
// every other operation, so an unprovisionable citizen is not a narrow
|
|
230
|
+
// refusal — it closes the whole surface, including the purchase path this
|
|
231
|
+
// same table admits a checked document to. A gate stricter than the one
|
|
232
|
+
// behind it refuses everybody and protects no-one.
|
|
233
|
+
//
|
|
234
|
+
// Sybil farming of grants is the real risk this bar would have addressed,
|
|
235
|
+
// and it is addressed where the canon puts it — the Account Prohibitions —
|
|
236
|
+
// rather than by a bar that also excludes every honest citizen.
|
|
237
|
+
document: 'accepted',
|
|
238
|
+
'document-and-biometric': 'accepted',
|
|
239
|
+
},
|
|
240
|
+
rationale: 'Provisioning binds an identity to a wallet once and every later act inherits that binding, ' +
|
|
241
|
+
'so it is money-grade: anything but the development bypass. A recorded document-to-person ' +
|
|
242
|
+
'comparison is not required, because a wallet is a precondition of every other operation ' +
|
|
243
|
+
'and a bar no issuer can currently clear would provision nobody.',
|
|
244
|
+
},
|
|
245
|
+
// Binding an Organisation ID to an Organisation Wallet for the first time
|
|
246
|
+
// (CAY-0059). Its OWN operation rather than a second use of
|
|
247
|
+
// 'provision-citizenship': the two bind different holders from different facts —
|
|
248
|
+
// citizenship binds the authenticated `sub`, this binds the one Organisation ID
|
|
249
|
+
// the issuer attested — and scope is checked per operation at the gate, so one
|
|
250
|
+
// key covering both acts would let a credential minted to provision a citizen's
|
|
251
|
+
// own citizenship bring an Organisation Wallet into being.
|
|
252
|
+
//
|
|
253
|
+
// The bar is deliberately the SAME as provisioning a citizen, and for the same
|
|
254
|
+
// reason rather than a different one: the acting party is still a citizen, the
|
|
255
|
+
// only issuer that exists asserts 'document' and cannot assert more, and a bar
|
|
256
|
+
// no issuer can clear would provision nobody. It grants no cents — an
|
|
257
|
+
// organisation receives no sign-up grant — but it brings a wallet into being,
|
|
258
|
+
// and a wallet is the precondition of every later act against it.
|
|
259
|
+
'provision-organisation': {
|
|
260
|
+
operation: 'provision-organisation',
|
|
261
|
+
assurance: 'required',
|
|
262
|
+
decisions: {
|
|
263
|
+
'dev-bypass': 'refused',
|
|
264
|
+
document: 'accepted',
|
|
265
|
+
'document-and-biometric': 'accepted',
|
|
266
|
+
},
|
|
267
|
+
rationale: 'Provisioning an organisation binds an Organisation ID to a wallet once and every later act ' +
|
|
268
|
+
'against that wallet inherits the binding, so it is money-grade: anything but the development ' +
|
|
269
|
+
'bypass. The bar matches provisioning a citizen because the acting party is a citizen and a ' +
|
|
270
|
+
'stricter bar than the issuer can clear would provision nobody.',
|
|
271
|
+
},
|
|
272
|
+
// Binding a founder wallet, created at genesis and holding a founder's share of
|
|
273
|
+
// the founder pool, to the Citizen it was allocated to (CAY-0081). Its OWN
|
|
274
|
+
// operation rather than a second use of 'provision-citizenship': it delivers no
|
|
275
|
+
// sign-up grant, and a claimed founder must never receive one by any route.
|
|
276
|
+
//
|
|
277
|
+
// The bar is DELIBERATELY STRICTER than provisioning's, and the argument that
|
|
278
|
+
// keeps provisioning at 'document' does not carry over. A founder completes full
|
|
279
|
+
// identity proofing before claiming (CAY-0081 §9). 'Personally known' decides who
|
|
280
|
+
// is on the nomination list, not whether they are verified. Provisioning stays at
|
|
281
|
+
// 'document' because a bar no issuer can clear would provision nobody and close
|
|
282
|
+
// the whole surface. A bar no issuer can clear here closes the founder claim and
|
|
283
|
+
// nothing else. Until the issuer can assert a recorded document-to-person
|
|
284
|
+
// comparison, no founder can claim anywhere but a development stack, and that is
|
|
285
|
+
// correct. Whether 'document-and-biometric' is what Identity Platform's full
|
|
286
|
+
// proofing asserts is identity.platform#87's answer.
|
|
287
|
+
'claim-founder-wallet': {
|
|
288
|
+
operation: 'claim-founder-wallet',
|
|
289
|
+
assurance: 'required',
|
|
290
|
+
decisions: {
|
|
291
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
292
|
+
'dev-bypass': 'refused',
|
|
293
|
+
// A checked document with no comparison to the person presenting it. Refused:
|
|
294
|
+
// it does not show the person claiming is the person the document describes,
|
|
295
|
+
// and the founder's allocation is the largest figure a claim can reach.
|
|
296
|
+
document: 'refused',
|
|
297
|
+
'document-and-biometric': 'accepted',
|
|
298
|
+
},
|
|
299
|
+
rationale: 'Claiming binds a founder wallet, and the founder allocation it holds, to one Citizen for ' +
|
|
300
|
+
'good, with no operator path to correct a mis-claim. So a founder must have completed full ' +
|
|
301
|
+
'identity proofing: a checked document compared to the person presenting it. A bar the issuer ' +
|
|
302
|
+
'cannot yet clear closes the founder claim and nothing else.',
|
|
303
|
+
},
|
|
304
|
+
// An Organisation paying a Citizen (CAY-0079 §1). Money-grade, on both routes and
|
|
305
|
+
// for different reasons: the mandate holder because value leaves the
|
|
306
|
+
// organisation for an individual, and the Authorised User because CAY-0037
|
|
307
|
+
// already requires the money-grade profile at every use of an allowance. It is an
|
|
308
|
+
// allowlist, so a future method is admissible only when somebody decides it here.
|
|
309
|
+
//
|
|
310
|
+
// The bar is the purchase path's rather than the founder claim's. The acting
|
|
311
|
+
// party is a citizen, the only issuer that exists asserts 'document', and a bar no
|
|
312
|
+
// issuer can clear would close the payment on every deployment. What limits how
|
|
313
|
+
// much can leave is the mandate, the allowance's limit and the Signatory /
|
|
314
|
+
// Authorised-User threshold, and none of them is an assurance question.
|
|
315
|
+
'make-commercial-payment': {
|
|
316
|
+
operation: 'make-commercial-payment',
|
|
317
|
+
assurance: 'required',
|
|
318
|
+
decisions: {
|
|
319
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
320
|
+
'dev-bypass': 'refused',
|
|
321
|
+
document: 'accepted',
|
|
322
|
+
'document-and-biometric': 'accepted',
|
|
323
|
+
},
|
|
324
|
+
rationale: 'A commercial payment moves value out of an organisation to an individual, so it is ' +
|
|
325
|
+
'money-grade: anything but the development bypass. The bar applies on both routes, and to ' +
|
|
326
|
+
'an Authorised User it applies again at every payment, not only when the allowance was granted.',
|
|
327
|
+
},
|
|
328
|
+
// The citizen market's three acts (CAY-0080 §7, Open decision Q). All three are
|
|
329
|
+
// money-grade and all three are allowlists, at the purchase path's bar: the acting
|
|
330
|
+
// party is a Citizen, and a bar no issuer can yet clear would shut the market on
|
|
331
|
+
// every deployment. What limits the market is its figures, which are canon and
|
|
332
|
+
// unset until the canon carries them, and none of them is an assurance question.
|
|
333
|
+
//
|
|
334
|
+
// During B7's exit window an excluded holder places no new quote, and the market
|
|
335
|
+
// withdraws their resting quotes; the buy-back stays open as an exit route. That is
|
|
336
|
+
// eligibility rather than assurance, and no exclusion signal reaches Cayso yet, so
|
|
337
|
+
// it is recorded here and enforced by nothing until the slice that delivers the
|
|
338
|
+
// signal (`citizen-market.ts`).
|
|
339
|
+
'place-quote': {
|
|
340
|
+
operation: 'place-quote',
|
|
341
|
+
assurance: 'required',
|
|
342
|
+
decisions: {
|
|
343
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
344
|
+
'dev-bypass': 'refused',
|
|
345
|
+
document: 'accepted',
|
|
346
|
+
'document-and-biometric': 'accepted',
|
|
347
|
+
},
|
|
348
|
+
rationale: 'Placing a quote offers cents for sterling or bids sterling for cents, and an ask reserves ' +
|
|
349
|
+
'cents from entry, so it is money-grade: anything but the development bypass. During the ' +
|
|
350
|
+
'exclusion exit window no new quote is placed, because entering the market is not an exit.',
|
|
351
|
+
},
|
|
352
|
+
// Gated at the SAME bar as placing, so that a lower-assurance credential cannot
|
|
353
|
+
// interfere with a holder's quotes. A holder who no longer clears it has their
|
|
354
|
+
// quotes withdrawn by the market rather than left stranded behind a bar they
|
|
355
|
+
// cannot reach — which is the market's act, not this operation.
|
|
356
|
+
'withdraw-quote': {
|
|
357
|
+
operation: 'withdraw-quote',
|
|
358
|
+
assurance: 'required',
|
|
359
|
+
decisions: {
|
|
360
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
361
|
+
'dev-bypass': 'refused',
|
|
362
|
+
document: 'accepted',
|
|
363
|
+
'document-and-biometric': 'accepted',
|
|
364
|
+
},
|
|
365
|
+
rationale: 'Withdrawing a quote releases reserved cents and changes what the book offers, so it is held ' +
|
|
366
|
+
'to the same money-grade bar as placing one: a credential that could not have placed a quote ' +
|
|
367
|
+
'cannot interfere with one either.',
|
|
368
|
+
},
|
|
369
|
+
// Selling unrestricted cents to the Treasury at the floor reference. It pays
|
|
370
|
+
// sterling out, so it is money-grade. It stays open during B7's exit window:
|
|
371
|
+
// Simon James named it, with transfer, as the way an excluded holder's
|
|
372
|
+
// unrestricted cents may leave.
|
|
373
|
+
'request-buy-back': {
|
|
374
|
+
operation: 'request-buy-back',
|
|
375
|
+
assurance: 'required',
|
|
376
|
+
decisions: {
|
|
377
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
378
|
+
'dev-bypass': 'refused',
|
|
379
|
+
document: 'accepted',
|
|
380
|
+
'document-and-biometric': 'accepted',
|
|
381
|
+
},
|
|
382
|
+
rationale: 'A buy-back pays sterling for cents the Treasury cancels, so it is money-grade: anything but ' +
|
|
383
|
+
'the development bypass. It is an exit route during the exclusion exit window, and stays ' +
|
|
384
|
+
'open to an excluded holder while the window lasts.',
|
|
385
|
+
},
|
|
386
|
+
// Reading the book. No bar, for read-own-wallet's reason: nothing moves, and what
|
|
387
|
+
// the read shows is the published value, the two bounds, the best bid and ask with
|
|
388
|
+
// their sizes, and the caller's own quotes. Placing and withdrawing are gated; a
|
|
389
|
+
// look at the book is a different act, and this entry says so rather than letting
|
|
390
|
+
// the read borrow the market's bar.
|
|
391
|
+
'read-citizen-market': {
|
|
392
|
+
operation: 'read-citizen-market',
|
|
393
|
+
assurance: 'not-required',
|
|
394
|
+
rationale: 'Reading the book moves no value and names nobody: it shows the published value, the floor ' +
|
|
395
|
+
"reference, the best bid and ask with their sizes, and the caller's own quotes. " +
|
|
396
|
+
'Authentication is still required; that is a different gate.',
|
|
397
|
+
},
|
|
398
|
+
// The marketplace status read (CAY-0083 §2). Read-scope, not money-grade, because
|
|
399
|
+
// nothing moves: it answers the signed receipt of the movement a key landed for the
|
|
400
|
+
// buyer's own wallet, or "no movement for this key", and writes no row. The bar was
|
|
401
|
+
// applied when the settlement it reports on was presented, as `read-purchase-result`
|
|
402
|
+
// leaves it at the authorisation. Holding the read to the money-grade bar would
|
|
403
|
+
// refuse a buyer the one answer that tells them whether their cents moved, which is
|
|
404
|
+
// the answer that stops a retry under a new key.
|
|
405
|
+
'read-marketplace-receipt': {
|
|
406
|
+
operation: 'read-marketplace-receipt',
|
|
407
|
+
assurance: 'not-required',
|
|
408
|
+
rationale: "Asking what an idempotency key landed for the buyer's own wallet moves no value, reads " +
|
|
409
|
+
'nobody else and writes nothing, so no assurance bar is applied; the bar was applied when ' +
|
|
410
|
+
'the sale or refund was presented. Authentication is still required; that is a different gate.',
|
|
411
|
+
},
|
|
412
|
+
// The two marketplace operations Piper presents Store Platform's signed decisions
|
|
413
|
+
// under (CAY-0083 §3). Both money-grade, and both allowlists (CAY-0029), so a future
|
|
414
|
+
// method is admissible only when somebody decides it here.
|
|
415
|
+
//
|
|
416
|
+
// The bar is the purchase path's rather than the founder claim's, for the reason
|
|
417
|
+
// the commercial payment's is: the acting party is a Citizen, the only issuer that
|
|
418
|
+
// exists asserts 'document', and a bar no issuer can clear would close the
|
|
419
|
+
// marketplace on every deployment. What limits a sale is Store Platform's signed
|
|
420
|
+
// order and the buyer's unrestricted cents, and what limits a refund is Store
|
|
421
|
+
// Platform's signed decision and its refund policy. None of those is an assurance
|
|
422
|
+
// question.
|
|
423
|
+
'settle-marketplace-order': {
|
|
424
|
+
operation: 'settle-marketplace-order',
|
|
425
|
+
assurance: 'required',
|
|
426
|
+
decisions: {
|
|
427
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
428
|
+
'dev-bypass': 'refused',
|
|
429
|
+
document: 'accepted',
|
|
430
|
+
'document-and-biometric': 'accepted',
|
|
431
|
+
},
|
|
432
|
+
rationale: "Settling a marketplace sale moves the buyer's cents to a publisher and the Treasury, so it " +
|
|
433
|
+
'is money-grade: anything but the development bypass. The signed order says what is bought ' +
|
|
434
|
+
'and who is paid; the assertion says who is buying, and it is held to this bar.',
|
|
435
|
+
},
|
|
436
|
+
// A refund draws a publisher's cents back to the buyer on Store Platform's
|
|
437
|
+
// authority. The buyer is the payee, and their assertion shows who is present, so
|
|
438
|
+
// it is held to the SAME bar as the sale: a credential that could not have bought
|
|
439
|
+
// cannot be refunded either, and a lower bar here would let a weaker credential
|
|
440
|
+
// draw cents out of a publisher's wallet that a stronger one paid in.
|
|
441
|
+
'settle-marketplace-refund': {
|
|
442
|
+
operation: 'settle-marketplace-refund',
|
|
443
|
+
assurance: 'required',
|
|
444
|
+
decisions: {
|
|
445
|
+
// Asserts nothing; permanently refused wherever "identity-verified" is meant.
|
|
446
|
+
'dev-bypass': 'refused',
|
|
447
|
+
document: 'accepted',
|
|
448
|
+
'document-and-biometric': 'accepted',
|
|
449
|
+
},
|
|
450
|
+
rationale: "Settling a marketplace refund draws cents from a publisher's wallet and the Treasury to the " +
|
|
451
|
+
'buyer, so it is money-grade: anything but the development bypass. The authority is Store ' +
|
|
452
|
+
"Platform's signed refund decision; the assertion shows the buyer is present, and it is held " +
|
|
453
|
+
'to the same bar as the sale it refunds.',
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
/**
|
|
457
|
+
* Every operation, as a value. Derived from {@link ASSURANCE_POLICY} for the same
|
|
458
|
+
* reason {@link VERIFICATION_METHODS} is derived from the statements: the mapped
|
|
459
|
+
* type makes the table's keys the union, so this cannot drift.
|
|
460
|
+
*/
|
|
461
|
+
export const CONTRACT_OPERATIONS = Object.keys(ASSURANCE_POLICY);
|
|
462
|
+
/**
|
|
463
|
+
* Which operation each route on the contract surface exercises, or `null` for a
|
|
464
|
+
* route that carries no identity at all (the public version handshake — you cannot
|
|
465
|
+
* be assured before you can negotiate the contract).
|
|
466
|
+
*
|
|
467
|
+
* Adding a route to `ROUTES` is a compile error here until it is mapped, so "every
|
|
468
|
+
* operation on this surface is classified" is mechanical rather than asserted.
|
|
469
|
+
* The reverse direction is deliberately not total — an operation may be
|
|
470
|
+
* classified before it has a route, which is how `'provision-citizenship'` was
|
|
471
|
+
* decided here and then served, unchanged, by CAY-0031.
|
|
472
|
+
*
|
|
473
|
+
* The two citizenship routes map to two DIFFERENT operations, which is the whole
|
|
474
|
+
* reason they are two paths: one path carries one scope requirement at CAY-0030's
|
|
475
|
+
* gate, so a read sharing a path with a write would share its scope too.
|
|
476
|
+
*/
|
|
477
|
+
export const OPERATION_FOR_ROUTE = {
|
|
478
|
+
wallet: 'read-own-wallet',
|
|
479
|
+
citizenship: 'read-own-citizenship',
|
|
480
|
+
citizenshipProvision: 'provision-citizenship',
|
|
481
|
+
citizenshipFounderClaim: 'claim-founder-wallet',
|
|
482
|
+
organisationProvision: 'provision-organisation',
|
|
483
|
+
purchaseAuthorise: 'authorise-purchase',
|
|
484
|
+
purchaseResult: 'read-purchase-result',
|
|
485
|
+
purchaseCancel: 'cancel-purchase',
|
|
486
|
+
commercialPayment: 'make-commercial-payment',
|
|
487
|
+
citizenMarket: 'read-citizen-market',
|
|
488
|
+
placeQuote: 'place-quote',
|
|
489
|
+
withdrawQuote: 'withdraw-quote',
|
|
490
|
+
requestBuyBack: 'request-buy-back',
|
|
491
|
+
version: null,
|
|
492
|
+
};
|
|
493
|
+
/**
|
|
494
|
+
* The closed set of methods an operation accepts, or `null` when it applies no
|
|
495
|
+
* assurance bar. `null` and `[]` are different answers and the caller must tell
|
|
496
|
+
* them apart: `[]` would mean "accepts nothing", which is true of no operation
|
|
497
|
+
* here.
|
|
498
|
+
*
|
|
499
|
+
* Derived from the decisions, never authored beside them — one table, read two
|
|
500
|
+
* ways, so an accepted set cannot disagree with the decision it came from.
|
|
501
|
+
*/
|
|
502
|
+
export function acceptedMethods(operation) {
|
|
503
|
+
const policy = ASSURANCE_POLICY[operation];
|
|
504
|
+
if (policy.assurance === 'not-required')
|
|
505
|
+
return null;
|
|
506
|
+
return VERIFICATION_METHODS.filter((method) => policy.decisions[method] === 'accepted');
|
|
507
|
+
}
|
|
508
|
+
/** The single accepted value — one object, since it carries nothing. */
|
|
509
|
+
const ACCEPTED = { accepted: true };
|
|
510
|
+
/**
|
|
511
|
+
* May this claim perform this operation, on assurance grounds? The verifier half
|
|
512
|
+
* of the profile, and the function a gate calls instead of writing a condition.
|
|
513
|
+
*
|
|
514
|
+
* It reads exactly three fields — the profile version, the status and the method —
|
|
515
|
+
* and nothing else. Not `mandateFor`: whether a caller may act for a wallet is
|
|
516
|
+
* authority, not assurance, and the Signatory / Authorised-User threshold is a
|
|
517
|
+
* canon figure the gate for that operation reads from configuration. Not the reserved
|
|
518
|
+
* claims. Not the issuer or the key: whether the claim can be TRUSTED is the trust
|
|
519
|
+
* envelope's question, a separate gap, and a caller must answer it separately.
|
|
520
|
+
* This function assumes it has already been answered.
|
|
521
|
+
*
|
|
522
|
+
* The order of the checks is the order of the questions: do we understand this
|
|
523
|
+
* profile at all, does this operation apply a bar, was the identity verified, did
|
|
524
|
+
* it name a method, is that method accepted here.
|
|
525
|
+
*/
|
|
526
|
+
export function assessAssurance(operation, claims) {
|
|
527
|
+
// An unrecognised profile is refused before anything else and whatever the
|
|
528
|
+
// operation: a method name minted under a profile we do not hold means whatever
|
|
529
|
+
// that profile said it meant, and reading it as if it meant this one's is the
|
|
530
|
+
// failure the version exists to prevent.
|
|
531
|
+
if (!RECOGNISED_ASSURANCE_PROFILE_VERSIONS.includes(claims.assuranceProfileVersion)) {
|
|
532
|
+
return { accepted: false, refusalReason: 'assurance_profile_unrecognised' };
|
|
533
|
+
}
|
|
534
|
+
const policy = ASSURANCE_POLICY[operation];
|
|
535
|
+
if (policy.assurance === 'not-required')
|
|
536
|
+
return ACCEPTED;
|
|
537
|
+
if (claims.verificationStatus !== 'verified') {
|
|
538
|
+
return { accepted: false, refusalReason: 'identity_unverified' };
|
|
539
|
+
}
|
|
540
|
+
// Verified while naming no method is an incoherent claim, and it is exactly the
|
|
541
|
+
// trap CAY-0021 names in a different guise: asking for "verified" and inheriting
|
|
542
|
+
// whatever that includes. There is no evidence to assess, so there is nothing to
|
|
543
|
+
// accept.
|
|
544
|
+
if (claims.verificationMethod === null) {
|
|
545
|
+
return { accepted: false, refusalReason: 'verification_method_absent' };
|
|
546
|
+
}
|
|
547
|
+
if (policy.decisions[claims.verificationMethod] !== 'accepted') {
|
|
548
|
+
return { accepted: false, refusalReason: 'verification_method_not_accepted' };
|
|
549
|
+
}
|
|
550
|
+
return ACCEPTED;
|
|
551
|
+
}
|