@percayso-community/cayso-contracts 0.30.0 → 1.0.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/README.md +33 -13
- package/dist/assertion.d.ts +43 -0
- package/dist/authority.d.ts +221 -0
- package/dist/authority.js +49 -0
- package/dist/client.d.ts +44 -3
- package/dist/client.js +17 -4
- package/dist/cloud-settlement.d.ts +8 -5
- package/dist/contract-schema.generated.js +567 -1
- package/dist/errors.d.ts +13 -2
- package/dist/index.d.ts +58 -4
- package/dist/index.js +57 -4
- package/dist/operator.d.ts +136 -5
- package/dist/operator.js +6 -1
- package/dist/schema-types.d.ts +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,20 +26,32 @@ Cloud verifies Cayso's authorisation acknowledgement under a key it reads from
|
|
|
26
26
|
`ACKNOWLEDGEMENT_KEYS_ROUTE`, an unauthenticated read of public keys, published
|
|
27
27
|
apart from both.
|
|
28
28
|
|
|
29
|
-
**
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
**Percayso BizID's authority assertion** (CAY-0100) is published ahead of the
|
|
30
|
+
binding that accepts it. `AuthorityClaims` is its own type, under the identity
|
|
31
|
+
assertion's `AssertionHeader`, with no verification or assurance fields. It travels
|
|
32
|
+
in the `AUTHORITY_ASSERTION_HEADER` header, beside `Authorization`, and the thin
|
|
33
|
+
client sends it only on an Organisation operation given a per-request provider for
|
|
34
|
+
it. The binding's refusals are the closed `AuthorityBindingRefusal`.
|
|
35
|
+
`AssertionClaims` declares `auth_time`, which Percayso ID mints and the binding
|
|
36
|
+
compares. `organisationAuthority` and `mandateFor` are retired and still published,
|
|
37
|
+
until the breaking step that removes them.
|
|
38
|
+
|
|
39
|
+
**The operator read surface, on its own entry point** (CAY-0026, Amendments 4 and 5).
|
|
40
|
+
Four reads an operator app's *server* calls — the movement list, the purchase list, the
|
|
41
|
+
quickening ladder and the economy summary — are published at `@percayso-community/cayso-contracts/operator`:
|
|
32
42
|
their paths and scopes, query inputs (wallet id, a half-open `[from, to)` window, a
|
|
33
43
|
keyset cursor), rows and pages, and their closed refusal and not-configured bodies.
|
|
34
44
|
Money is `Cents`, a wallet is named by its wallet id, and no Percayso identity
|
|
35
45
|
appears on any row. It is not re-exported from the barrel, so Piper and the Cloud
|
|
36
46
|
never import an operator vocabulary by accident, and the schema describes it under
|
|
37
47
|
its own `operator` key rather than among the person-facing types. Cayso sends no
|
|
38
|
-
CORS headers: these reads are for a server, never a browser.
|
|
39
|
-
|
|
48
|
+
CORS headers: these reads are for a server, never a browser. The economy summary
|
|
49
|
+
(Amendment 5) answers aggregates only: whole-number counts and cent totals, with no
|
|
50
|
+
wallet id, identity or row of any kind. Widening the entry point beyond these four
|
|
51
|
+
needs a further amendment.
|
|
40
52
|
|
|
41
53
|
**Deliberately not published:** peer types, holdings and carves, and every operator
|
|
42
|
-
route but those
|
|
54
|
+
route but those four reads. They are not a consumer's business, and putting them on
|
|
43
55
|
the public surface widens it for nothing.
|
|
44
56
|
|
|
45
57
|
**This package carries no numbers.** Amounts, rates, tiers, schedules and
|
|
@@ -53,7 +65,7 @@ restated here.
|
|
|
53
65
|
| `@percayso-community/cayso-contracts` | Types and constants. No runtime. |
|
|
54
66
|
| `@percayso-community/cayso-contracts/client` | The thin client — fetch, assertion header, idempotent retry. |
|
|
55
67
|
| `@percayso-community/cayso-contracts/schema` | A machine-readable descriptor of the published surface: the published, versioned surface a consumer in another language generates its types from. |
|
|
56
|
-
| `@percayso-community/cayso-contracts/operator` | The
|
|
68
|
+
| `@percayso-community/cayso-contracts/operator` | The four operator reads' types and route constants, for an operator app's server. |
|
|
57
69
|
|
|
58
70
|
The split is the point: a consumer wanting only the types takes on no runtime at
|
|
59
71
|
all. The client owns the fetch, the assertion header and the idempotent retry.
|
|
@@ -84,8 +96,10 @@ see the version endpoint, below.
|
|
|
84
96
|
## Independent versioning
|
|
85
97
|
|
|
86
98
|
`CONTRACT_VERSION` is exported from the barrel and is **independent** — its own
|
|
87
|
-
semantic version,
|
|
88
|
-
|
|
99
|
+
semantic version, not a repo-wide constant shared with anything else. It started at
|
|
100
|
+
`0.1.0`, and from `1.0.0` it follows ordinary semantic versioning, Cayso's adoption of CMTY-0032
|
|
101
|
+
([CAY-0101](../../docs/architectural-decision-record/CAY-0101-the-contracts-follow-ordinary-semantic-versioning-from-1-0-0.md)):
|
|
102
|
+
a minor is additive and drops nothing, and a major is the only breaking kind. Piper's own contracts share one global version bumped across all of them
|
|
89
103
|
together; independent versioning across a service boundary is exactly what this
|
|
90
104
|
money surface gains that theirs lack (CAY-0026). The constant and the package's
|
|
91
105
|
`version` field must agree, and a test fails when they drift.
|
|
@@ -103,10 +117,16 @@ describes. The thin client's `getVersion()` reads it and warns once, per client
|
|
|
103
117
|
instance, when the server's contract *major* differs from the one the client was
|
|
104
118
|
built against.
|
|
105
119
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
A deployment keeps serving every earlier minor of its major and lists them in
|
|
121
|
+
`supportedContractVersions`, so a consumer pinned to any of them keeps working; a
|
|
122
|
+
consumer's handshake accepts a Cayso if `contractVersion` or any entry of
|
|
123
|
+
`supportedContractVersions` is on its own line: from `1.0.0` the same major, below it the
|
|
124
|
+
same minor. Only a
|
|
125
|
+
major breaks, and its first release lists nothing from an older major and says so in
|
|
126
|
+
the bump's changelog. An earlier major is listed only during a declared transition:
|
|
127
|
+
`1.0.0` keeps `0.32.0`, `0.31.0`, `0.30.0` and `0.29.0`, and the next major drops them
|
|
128
|
+
([CAY-0101](../../docs/architectural-decision-record/CAY-0101-the-contracts-follow-ordinary-semantic-versioning-from-1-0-0.md),
|
|
129
|
+
amending CAY-0086).
|
|
110
130
|
|
|
111
131
|
Alongside it, `GET /api/version/schema` serves the same machine-readable
|
|
112
132
|
descriptor as `@percayso-community/cayso-contracts/schema` — the surface outside consumers read, with
|
package/dist/assertion.d.ts
CHANGED
|
@@ -756,6 +756,15 @@ export interface AssertionClaims {
|
|
|
756
756
|
* THIS operation — the organisation-authority claim (CAY-0059 §3). OPTIONAL on
|
|
757
757
|
* the wire, and its absence provisions nothing.
|
|
758
758
|
*
|
|
759
|
+
* ## RETIRED by CAY-0100, and kept until #419
|
|
760
|
+
*
|
|
761
|
+
* BizID holds the Organisation, so the evidence for an act on an Organisation
|
|
762
|
+
* Wallet becomes BizID's `AuthorityClaims.organisationId`, and Percayso ID stops
|
|
763
|
+
* minting this claim. It stays declared, and read, until the previous `cayso-api`
|
|
764
|
+
* pin already runs the binding, because a revert must not bring back a reader of a
|
|
765
|
+
* claim nobody mints (CAY-0100 §7, Consequences). Its removal is the breaking
|
|
766
|
+
* contract step, #419. Nothing new may read it.
|
|
767
|
+
*
|
|
759
768
|
* ## It is a second fact beside `sub`, and it replaces nothing
|
|
760
769
|
*
|
|
761
770
|
* `sub` stays what it has always been: the citizen Percayso ID authenticated
|
|
@@ -907,6 +916,14 @@ export interface AssertionClaims {
|
|
|
907
916
|
* displays — never the Organisation ID of the holder behind it, and never an
|
|
908
917
|
* Instance ID. Present when the holder may act for one or more organisations.
|
|
909
918
|
*
|
|
919
|
+
* ## RETIRED by CAY-0100 §4a, and kept until #419
|
|
920
|
+
*
|
|
921
|
+
* The commercial payment's mandate route, the delegated purchase and an allowance
|
|
922
|
+
* grant on an Organisation Wallet authorise from BizID's `AuthorityClaims`, not from
|
|
923
|
+
* this claim. Percayso ID stops sending it only once no reader of it is left at the
|
|
924
|
+
* previous `cayso-api` pin (CAY-0100, Consequences), and its removal is the breaking
|
|
925
|
+
* contract step, #419. Nothing new may read it.
|
|
926
|
+
*
|
|
910
927
|
* ## THIS IS NOT THE ORGANISATION-AUTHORITY CLAIM, AND THE TWO MUST NOT BE
|
|
911
928
|
* OVERLOADED
|
|
912
929
|
*
|
|
@@ -944,4 +961,30 @@ export interface AssertionClaims {
|
|
|
944
961
|
* session, in seconds. Nothing in Phase A may branch on it (CAY-0008).
|
|
945
962
|
*/
|
|
946
963
|
readonly sessionAgeSeconds?: number;
|
|
964
|
+
/**
|
|
965
|
+
* When the Citizen tapped their passkey for this assertion, in **seconds** since the
|
|
966
|
+
* Unix epoch: the issuer's convention, not `Millis`, as `exp` and `iat` are.
|
|
967
|
+
* OPTIONAL on the wire, declared ahead of its first reader (CAY-0100).
|
|
968
|
+
*
|
|
969
|
+
* Percayso ID mints it. BizID's authority assertion carries the tap it saw as
|
|
970
|
+
* `AuthorityClaims.auth_time`, and the binding (#417) refuses the two unless they
|
|
971
|
+
* are equal, so one tap serves both (CAY-0100 §4). Spelled `auth_time`, as OpenID
|
|
972
|
+
* Connect and that claim spell it, rather than in this surface's camel case, so the
|
|
973
|
+
* two sides of the comparison carry one name.
|
|
974
|
+
*
|
|
975
|
+
* ## Declared before Percayso ID mints it, and why that order is the point
|
|
976
|
+
*
|
|
977
|
+
* A verifier refuses any claim key it does not know. If Percayso ID minted
|
|
978
|
+
* `auth_time` before the `cayso-api` pin declared it, a Cloud revert to the previous
|
|
979
|
+
* pin would refuse every identity assertion. So Cayso declares it first, and
|
|
980
|
+
* Percayso ID mints it only once the previous pin already does (CAY-0100,
|
|
981
|
+
* Consequences). A verifier admits a non-negative integer here and refuses any other
|
|
982
|
+
* value as malformed.
|
|
983
|
+
*
|
|
984
|
+
* ## Nothing may branch on it yet
|
|
985
|
+
*
|
|
986
|
+
* Like {@link sessionAgeSeconds}, it is declared and depended on by nothing until the
|
|
987
|
+
* binding is built. Its absence refuses nothing, and its presence admits nothing.
|
|
988
|
+
*/
|
|
989
|
+
readonly auth_time?: number;
|
|
947
990
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `authority` — the shape of Percayso BizID's authority assertion, and the closed
|
|
3
|
+
* reasons the binding refuses one for (CAY-0100 §2, §4).
|
|
4
|
+
*
|
|
5
|
+
* BizID holds the Organisation and Percayso ID holds people (CMTY-0031, BIZ-0010).
|
|
6
|
+
* So the evidence that a Citizen may act for an Organisation comes from BizID, a
|
|
7
|
+
* second issuer, in a credential of its own: {@link AuthorityClaims}. It says that
|
|
8
|
+
* this Citizen may do this operation for this Organisation, under its formed
|
|
9
|
+
* mandate. It is not a second identity assertion and shares no type with
|
|
10
|
+
* `AssertionClaims`.
|
|
11
|
+
*
|
|
12
|
+
* This file is the SHAPE ALONE, published so that Piper can build against it
|
|
13
|
+
* before Cayso accepts it. Nothing here reads, verifies or binds a credential.
|
|
14
|
+
* BizID's registration as an authority issuer, its verifier and the binding to
|
|
15
|
+
* Percayso ID's assertion are later slices (#415–#417), and until they are served
|
|
16
|
+
* an Organisation operation answers exactly as it did.
|
|
17
|
+
*
|
|
18
|
+
* ## The envelope is the identity assertion's
|
|
19
|
+
*
|
|
20
|
+
* Compact JWS, `EdDSA` over Ed25519 (CAY-0072), with a header of exactly
|
|
21
|
+
* `{alg, kid}`: the same {@link AssertionHeader}, not a second header type, so the
|
|
22
|
+
* one rule about what a header may say is written once. `iss` is BizID, `aud` is
|
|
23
|
+
* `'cayso'` and `jti` is fresh. `iat` and `exp` are in seconds, and `exp` sits
|
|
24
|
+
* within `MAX_ASSERTION_LIFETIME_SECONDS` (`assertion.ts`), the ceiling an identity
|
|
25
|
+
* assertion is held to. That ceiling is not restated here. Replay protection is on
|
|
26
|
+
* `(iss, jti)`, exactly as for identity assertions (CAY-0030).
|
|
27
|
+
*
|
|
28
|
+
* ## No verification or assurance fields
|
|
29
|
+
*
|
|
30
|
+
* BizID verifies no people. Money-grade assurance comes from Percayso ID's
|
|
31
|
+
* assertion, under the per-operation acceptance policy (CAY-0029), and nothing
|
|
32
|
+
* here can stand in for it. An `AuthorityClaims` alone authenticates no route: it
|
|
33
|
+
* is accepted only beside a Percayso ID assertion it binds to (CAY-0100 §3, §4).
|
|
34
|
+
*
|
|
35
|
+
* ## It travels in its own header, beside the identity assertion
|
|
36
|
+
*
|
|
37
|
+
* See {@link AUTHORITY_ASSERTION_HEADER}. The thin client sends it on the
|
|
38
|
+
* Organisation operations alone, fetched for each request and never cached, under
|
|
39
|
+
* the discipline the identity assertion is fetched under.
|
|
40
|
+
*/
|
|
41
|
+
import type { AssertionHeader, ContractOperation } from './assertion.js';
|
|
42
|
+
/**
|
|
43
|
+
* The request header an authority assertion travels in, beside the identity
|
|
44
|
+
* assertion's `Authorization: Bearer` (CAY-0100 §4).
|
|
45
|
+
*
|
|
46
|
+
* ## Why this name
|
|
47
|
+
*
|
|
48
|
+
* - **Not `Authorization`.** That header carries Percayso ID's assertion and only
|
|
49
|
+
* it. A request has one `Authorization`, and the two credentials are verified by
|
|
50
|
+
* two verifiers against two registrations, so neither may be mistaken for the
|
|
51
|
+
* other by where it arrived.
|
|
52
|
+
* - **No `X-` prefix.** RFC 6648 retired it, and the one header this contract
|
|
53
|
+
* already names beside `Authorization` is `Idempotency-Key`, which has none. This
|
|
54
|
+
* follows that spelling: two words, each capitalised, hyphenated.
|
|
55
|
+
* - **The glossary's noun.** CAY-0100 calls the credential BizID's *authority
|
|
56
|
+
* assertion*, so the header says that and nothing more. It does not name BizID,
|
|
57
|
+
* for the reason `Authorization` does not name Percayso ID: who minted it is the
|
|
58
|
+
* credential's `iss`, checked against a registration, never a header's say-so.
|
|
59
|
+
*
|
|
60
|
+
* The value is the compact JWS alone, with no `Bearer` scheme: the scheme belongs to
|
|
61
|
+
* `Authorization`'s grammar, and this header has no other value to tell it from.
|
|
62
|
+
*
|
|
63
|
+
* Published as a value so a consumer that does not use the thin client spells it
|
|
64
|
+
* from the contract rather than from a retyped copy, and so the service that reads
|
|
65
|
+
* it reads the same string.
|
|
66
|
+
*/
|
|
67
|
+
export declare const AUTHORITY_ASSERTION_HEADER: "Authority-Assertion";
|
|
68
|
+
/**
|
|
69
|
+
* A minted authority assertion as a whole: the identity assertion's header, and the
|
|
70
|
+
* claims BizID asserts (CAY-0100 §2).
|
|
71
|
+
*
|
|
72
|
+
* The header is {@link AssertionHeader} itself, exactly `{alg, kid}`, read first to
|
|
73
|
+
* choose a check and believed never. The split is the one `AssertionEnvelope` makes,
|
|
74
|
+
* for the same reason.
|
|
75
|
+
*/
|
|
76
|
+
export interface AuthorityEnvelope {
|
|
77
|
+
/** How to check this credential. Read first, believed never. */
|
|
78
|
+
readonly header: AssertionHeader;
|
|
79
|
+
/** What BizID asserts, established only once the header's check has passed. */
|
|
80
|
+
readonly claims: AuthorityClaims;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The claims carried by BizID's authority assertion (CAY-0100 §2): this Citizen may
|
|
84
|
+
* perform this operation for this Organisation, under this version of its formed
|
|
85
|
+
* mandate.
|
|
86
|
+
*
|
|
87
|
+
* CLOSED. These ten keys and no others: a verifier refuses a key it does not know, as
|
|
88
|
+
* it does for an identity assertion. There are no verification or assurance fields,
|
|
89
|
+
* because BizID does not verify people.
|
|
90
|
+
*
|
|
91
|
+
* A claim to be CHECKED, never a claim to be trusted. Cayso verifies a minted
|
|
92
|
+
* assertion and never takes a caller's word for who they are, nor for whom they act
|
|
93
|
+
* (CLAUDE.md, Trust and authentication).
|
|
94
|
+
*/
|
|
95
|
+
export interface AuthorityClaims {
|
|
96
|
+
/**
|
|
97
|
+
* Who minted this: Percayso BizID, registered as an authority issuer for this type
|
|
98
|
+
* alone (CAY-0100 §3). Typed `string` rather than a literal for the reason
|
|
99
|
+
* `AssertionClaims.iss` is: an unrecognised issuer must be representable in order
|
|
100
|
+
* to be refused.
|
|
101
|
+
*/
|
|
102
|
+
readonly iss: string;
|
|
103
|
+
/** The intended recipient. Exactly `'cayso'`, and checked, not merely present. */
|
|
104
|
+
readonly aud: 'cayso';
|
|
105
|
+
/**
|
|
106
|
+
* This assertion's unique identity, fresh for each one. Replay is refused on
|
|
107
|
+
* `(iss, jti)`, exactly as for identity assertions (CAY-0030). It never names the
|
|
108
|
+
* identity assertion's `jti`, and the identity assertion never names it (CAY-0100
|
|
109
|
+
* §4): each is consumed as its own single use.
|
|
110
|
+
*/
|
|
111
|
+
readonly jti: string;
|
|
112
|
+
/**
|
|
113
|
+
* Issued-at, in **seconds** since the Unix epoch, the issuer's convention and not
|
|
114
|
+
* `Millis`, as on `AssertionClaims`.
|
|
115
|
+
*/
|
|
116
|
+
readonly iat: number;
|
|
117
|
+
/**
|
|
118
|
+
* Expiry, in **seconds** since the Unix epoch. No further ahead than
|
|
119
|
+
* `MAX_ASSERTION_LIFETIME_SECONDS`, the ceiling an identity assertion is held to
|
|
120
|
+
* (CAY-0100 §2). At or before now is expired, with no skew grace added.
|
|
121
|
+
*/
|
|
122
|
+
readonly exp: number;
|
|
123
|
+
/**
|
|
124
|
+
* The Citizen this authority is for: the subject Percayso ID's assertion already
|
|
125
|
+
* named. **NEVER taken as an identity** (CAY-0100 §3). BizID attests no person, so
|
|
126
|
+
* this is read only as the value the binding compares with the identity assertion's
|
|
127
|
+
* `sub`, and a mismatch is `subject_mismatch`. Like that `sub`, it never reaches
|
|
128
|
+
* the chain (CAY-0006).
|
|
129
|
+
*/
|
|
130
|
+
readonly sub: string;
|
|
131
|
+
/**
|
|
132
|
+
* The ONE operation this authority was minted for. SINGULAR, never a list: a list
|
|
133
|
+
* would be a menu, and a credential carrying a menu lets whatever holds it choose.
|
|
134
|
+
* It must be in Percayso ID's `scope` and be the operation of the route it is
|
|
135
|
+
* presented on, so an authority for one act cannot be spent on another (CAY-0100
|
|
136
|
+
* §4).
|
|
137
|
+
*/
|
|
138
|
+
readonly operation: ContractOperation;
|
|
139
|
+
/**
|
|
140
|
+
* The Organisation ID Percayso BizID mints: the permanent external identity of the
|
|
141
|
+
* Organisation Wallet's holder (CAY-0100 §1).
|
|
142
|
+
*
|
|
143
|
+
* It reaches Cayso from THIS claim and from nowhere else: not a path, a body or a
|
|
144
|
+
* query, and not Percayso ID's assertion, which does not echo it because Percayso
|
|
145
|
+
* ID holds people, not Organisations (CAY-0100 §4). Nothing compares it against a
|
|
146
|
+
* Percayso ID claim.
|
|
147
|
+
*
|
|
148
|
+
* A plain `string`, for the reason `AssertionClaims.organisationAuthority` is one:
|
|
149
|
+
* this is the wire, JSON carries no brands, and a published type a consumer cannot
|
|
150
|
+
* build from a string it holds is a distinction that consumer cannot observe. The
|
|
151
|
+
* branded `OrganisationId`, which refuses an Instance ID at compile time, lives on
|
|
152
|
+
* the write path in `apps/api`, where the holder key is formed (CAY-0059 §5).
|
|
153
|
+
*/
|
|
154
|
+
readonly organisationId: string;
|
|
155
|
+
/**
|
|
156
|
+
* The version of the Organisation's formed mandate BizID decided this authority
|
|
157
|
+
* under (CAY-0100 §5). Cayso records it with the movement, or in the act's audit
|
|
158
|
+
* row where the act writes no movement, and refuses an `AuthorityClaims` without
|
|
159
|
+
* one. It checks only that it is present and well formed: what the mandate allows is
|
|
160
|
+
* BizID's to decide, and Cayso does not interpret it.
|
|
161
|
+
*
|
|
162
|
+
* A JSON **integer, at least 1**: the mandate's version counter, which starts at 1 and
|
|
163
|
+
* rises by one each time the mandate is re-formed. The JWS payload carries a bare
|
|
164
|
+
* number (`"mandateVersion":1`), as `bizid-contracts` declares it and BIZ-0010 records
|
|
165
|
+
* it. {@link isMandateVersion} is the one check of that shape.
|
|
166
|
+
*/
|
|
167
|
+
readonly mandateVersion: number;
|
|
168
|
+
/**
|
|
169
|
+
* When the Citizen tapped their Percayso ID passkey, in **seconds** since the Unix
|
|
170
|
+
* epoch: the tap BizID saw (CAY-0100 §2). Percayso ID's assertion carries the same
|
|
171
|
+
* `auth_time`, so one tap serves both, and the binding refuses two that differ as
|
|
172
|
+
* `auth_time_mismatch` (§4). Spelled `auth_time`, as OpenID Connect spells it and as
|
|
173
|
+
* BIZ-0010 does, rather than in this surface's camel case.
|
|
174
|
+
*/
|
|
175
|
+
readonly auth_time: number;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Why the binding refused an Organisation operation: CAY-0100 §4's working shape,
|
|
179
|
+
* closed and spelled here.
|
|
180
|
+
*
|
|
181
|
+
* Published before anything produces it, as `founder_wallet_lapsed` was, so a
|
|
182
|
+
* consumer exhausting `CaysoApiErrorCode` renders it from the version that declared
|
|
183
|
+
* it. A new reason is a new member and a contract version, never a free-text field.
|
|
184
|
+
* Every member means the same thing: the operation did not happen.
|
|
185
|
+
*
|
|
186
|
+
* It is the BINDING's refusal, not the operation's. Each operation keeps its own
|
|
187
|
+
* union for what it refuses once the binding holds, and the identity assertion
|
|
188
|
+
* keeps the gate's codes.
|
|
189
|
+
*/
|
|
190
|
+
export type AuthorityBindingRefusal =
|
|
191
|
+
/**
|
|
192
|
+
* An Organisation operation was presented with no authority assertion. Refused,
|
|
193
|
+
* and never read as "act as the Citizen alone" (CAY-0100 §4).
|
|
194
|
+
*/
|
|
195
|
+
'authority_assertion_missing'
|
|
196
|
+
/**
|
|
197
|
+
* The authority assertion did not verify: malformed, an issuer or key not
|
|
198
|
+
* registered for this type, a bad signature, expired, or already used. One member
|
|
199
|
+
* for all of them, so the refusal describes no part of the check to whoever is
|
|
200
|
+
* probing it.
|
|
201
|
+
*/
|
|
202
|
+
| 'authority_assertion_invalid'
|
|
203
|
+
/** The two assertions name different subjects. */
|
|
204
|
+
| 'subject_mismatch'
|
|
205
|
+
/** The authority's `operation` is not in Percayso ID's `scope`. */
|
|
206
|
+
| 'operation_not_in_scope'
|
|
207
|
+
/**
|
|
208
|
+
* The authority's `operation` is not the operation of the route it was presented
|
|
209
|
+
* on, so an authority minted for one act cannot be spent on another.
|
|
210
|
+
*/
|
|
211
|
+
| 'operation_not_the_routes'
|
|
212
|
+
/** The two assertions carry different `auth_time`s, so no one passkey tap serves both. */
|
|
213
|
+
| 'auth_time_mismatch'
|
|
214
|
+
/** The two assertions are not both unexpired at the same moment. */
|
|
215
|
+
| 'not_jointly_current';
|
|
216
|
+
/**
|
|
217
|
+
* Whether `value` is a well-formed {@link AuthorityClaims.mandateVersion}: a JSON integer
|
|
218
|
+
* of at least 1. The one statement of the shape, so the verifier (#416) and any consumer
|
|
219
|
+
* check it the same way. It says nothing about what the mandate allows.
|
|
220
|
+
*/
|
|
221
|
+
export declare function isMandateVersion(value: unknown): value is number;
|
|
@@ -0,0 +1,49 @@
|
|
|
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 request header an authority assertion travels in, beside the identity
|
|
18
|
+
* assertion's `Authorization: Bearer` (CAY-0100 §4).
|
|
19
|
+
*
|
|
20
|
+
* ## Why this name
|
|
21
|
+
*
|
|
22
|
+
* - **Not `Authorization`.** That header carries Percayso ID's assertion and only
|
|
23
|
+
* it. A request has one `Authorization`, and the two credentials are verified by
|
|
24
|
+
* two verifiers against two registrations, so neither may be mistaken for the
|
|
25
|
+
* other by where it arrived.
|
|
26
|
+
* - **No `X-` prefix.** RFC 6648 retired it, and the one header this contract
|
|
27
|
+
* already names beside `Authorization` is `Idempotency-Key`, which has none. This
|
|
28
|
+
* follows that spelling: two words, each capitalised, hyphenated.
|
|
29
|
+
* - **The glossary's noun.** CAY-0100 calls the credential BizID's *authority
|
|
30
|
+
* assertion*, so the header says that and nothing more. It does not name BizID,
|
|
31
|
+
* for the reason `Authorization` does not name Percayso ID: who minted it is the
|
|
32
|
+
* credential's `iss`, checked against a registration, never a header's say-so.
|
|
33
|
+
*
|
|
34
|
+
* The value is the compact JWS alone, with no `Bearer` scheme: the scheme belongs to
|
|
35
|
+
* `Authorization`'s grammar, and this header has no other value to tell it from.
|
|
36
|
+
*
|
|
37
|
+
* Published as a value so a consumer that does not use the thin client spells it
|
|
38
|
+
* from the contract rather than from a retyped copy, and so the service that reads
|
|
39
|
+
* it reads the same string.
|
|
40
|
+
*/
|
|
41
|
+
export const AUTHORITY_ASSERTION_HEADER = 'Authority-Assertion';
|
|
42
|
+
/**
|
|
43
|
+
* Whether `value` is a well-formed {@link AuthorityClaims.mandateVersion}: a JSON integer
|
|
44
|
+
* of at least 1. The one statement of the shape, so the verifier (#416) and any consumer
|
|
45
|
+
* check it the same way. It says nothing about what the mandate allows.
|
|
46
|
+
*/
|
|
47
|
+
export function isMandateVersion(value) {
|
|
48
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 1;
|
|
49
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -24,6 +24,23 @@
|
|
|
24
24
|
* so each attempt gets a fresh one. That the retry re-fetches is a decision, not
|
|
25
25
|
* an accident.
|
|
26
26
|
*
|
|
27
|
+
* ## A second assertion, on the Organisation operations alone
|
|
28
|
+
*
|
|
29
|
+
* An act on an Organisation Wallet needs BizID's authority assertion beside Percayso
|
|
30
|
+
* ID's (CAY-0100 §4). The three methods that can be one take an optional
|
|
31
|
+
* {@link AuthorityAssertionProvider} as their last argument, and nothing else does.
|
|
32
|
+
* Its `getAuthorityAssertion()` is called under exactly the discipline above: inside
|
|
33
|
+
* each attempt, beside `getAssertion()`, never cached, and re-fetched on a retry,
|
|
34
|
+
* because the authority assertion is short-lived and single-use too. It travels in
|
|
35
|
+
* its own header, {@link AUTHORITY_ASSERTION_HEADER}, never in `Authorization`.
|
|
36
|
+
*
|
|
37
|
+
* It is per CALL rather than a client option, and that is the point. BizID mints an
|
|
38
|
+
* authority for one operation and one Organisation, and these methods name no
|
|
39
|
+
* Organisation, so only the caller knows which Organisation this act is for. A
|
|
40
|
+
* client-wide provider would have to guess, or would hand every Organisation
|
|
41
|
+
* operation the same authority. Passing none sends no header, and the request is
|
|
42
|
+
* exactly the request a `0.30.0` client sent.
|
|
43
|
+
*
|
|
27
44
|
* ## One method per route, no method that is not
|
|
28
45
|
*
|
|
29
46
|
* Every method reads its method and path from `ROUTES` — no URL is hand-written
|
|
@@ -107,6 +124,19 @@ export interface CaysoClientOptions {
|
|
|
107
124
|
*/
|
|
108
125
|
warn?: (message: string) => void;
|
|
109
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Where an Organisation operation gets BizID's authority assertion for this one act
|
|
129
|
+
* (CAY-0100 §4): the second credential, beside the one `getAssertion` supplies.
|
|
130
|
+
*
|
|
131
|
+
* Called before EVERY attempt, never once and never cached, for the reason
|
|
132
|
+
* `getAssertion` is: the credential is short-lived and single-use, so one minted for
|
|
133
|
+
* a first attempt must not be presented again by a retry. The value is the compact
|
|
134
|
+
* JWS BizID minted, sent in {@link AUTHORITY_ASSERTION_HEADER} exactly as returned.
|
|
135
|
+
*/
|
|
136
|
+
export interface AuthorityAssertionProvider {
|
|
137
|
+
/** Called before every attempt. Authority assertions are short-lived, so never cache one. */
|
|
138
|
+
getAuthorityAssertion(): Promise<string>;
|
|
139
|
+
}
|
|
110
140
|
/**
|
|
111
141
|
* One method per entry in `ROUTES`, and no method that is not. `getMyWallet`
|
|
112
142
|
* answers `null` where the contract says a wallet may not exist yet, rather than
|
|
@@ -166,8 +196,12 @@ export interface CaysoClient {
|
|
|
166
196
|
* carries no idempotency key and a repeat is the same act rather than a second
|
|
167
197
|
* one. A credential carrying no organisation authority is refused; absence
|
|
168
198
|
* provisions nothing.
|
|
199
|
+
*
|
|
200
|
+
* `authority` supplies BizID's authority assertion for `provision-organisation`
|
|
201
|
+
* (CAY-0100 §4). Optional, and sent only when given: no route reads it until the
|
|
202
|
+
* binding is served, and from then an Organisation operation without it is refused.
|
|
169
203
|
*/
|
|
170
|
-
provisionOrganisation(): Promise<OrganisationView>;
|
|
204
|
+
provisionOrganisation(authority?: AuthorityAssertionProvider): Promise<OrganisationView>;
|
|
171
205
|
/**
|
|
172
206
|
* Authorise a purchase — or REPLAY one (CAY-0032). Posting the same
|
|
173
207
|
* `idempotencyKey` twice returns the ORIGINAL authorisation, at the original
|
|
@@ -184,8 +218,12 @@ export interface CaysoClient {
|
|
|
184
218
|
* `'terminal'` means the key's purchase has finished and will not change: a
|
|
185
219
|
* repeat of it will keep answering `'terminal'` forever, and the remedy is a new
|
|
186
220
|
* purchase under a NEW key.
|
|
221
|
+
*
|
|
222
|
+
* `authority` supplies BizID's authority assertion for a purchase on behalf of an
|
|
223
|
+
* Organisation Wallet, when `onBehalfOfWalletId` names one (CAY-0100 §4a). A
|
|
224
|
+
* Citizen's own purchase needs none and passes none.
|
|
187
225
|
*/
|
|
188
|
-
authorisePurchase(req: PurchaseRequest): Promise<PurchaseAuthorised | PurchaseTerminal>;
|
|
226
|
+
authorisePurchase(req: PurchaseRequest, authority?: AuthorityAssertionProvider): Promise<PurchaseAuthorised | PurchaseTerminal>;
|
|
189
227
|
getPurchase(purchaseId: string): Promise<PurchaseResult>;
|
|
190
228
|
/**
|
|
191
229
|
* Cancel a purchase the caller started, releasing its reservation (CAY-0032).
|
|
@@ -213,8 +251,11 @@ export interface CaysoClient {
|
|
|
213
251
|
* thrown {@link CaysoApiError} whose `code` is a `CommercialPaymentRefusal`
|
|
214
252
|
* member, one of the gate's codes, or a seam code, as {@link authorisePurchase}'s
|
|
215
253
|
* refusals do. A refusal means nothing moved.
|
|
254
|
+
*
|
|
255
|
+
* `authority` supplies BizID's authority assertion on the `'mandate'` route
|
|
256
|
+
* (CAY-0100 §4a). The `'payment-allowance'` route needs none and passes none.
|
|
216
257
|
*/
|
|
217
|
-
makeCommercialPayment(req: CommercialPaymentRequest): Promise<CommercialPaymentMade>;
|
|
258
|
+
makeCommercialPayment(req: CommercialPaymentRequest, authority?: AuthorityAssertionProvider): Promise<CommercialPaymentMade>;
|
|
218
259
|
/**
|
|
219
260
|
* Transfer unrestricted cents from the caller's own Citizen Wallet to another
|
|
220
261
|
* Citizen Wallet — or REPLAY a transfer already made under the same key (CAY-0088).
|
package/dist/client.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { CONTRACT_VERSION, NOT_PROVISIONED } from './index.js';
|
|
16
|
+
import { AUTHORITY_ASSERTION_HEADER, CONTRACT_VERSION, NOT_PROVISIONED } from './index.js';
|
|
17
17
|
import { ROUTES } from './routes.js';
|
|
18
18
|
// --- The retry mechanism -----------------------------------------------------
|
|
19
19
|
// These are this slice's own mechanism, not economics: an attempt count, a
|
|
@@ -110,6 +110,16 @@ function buildClient(options, internals) {
|
|
|
110
110
|
if (spec.idempotencyKey !== undefined && spec.idempotencyKey.length > 0) {
|
|
111
111
|
headers['Idempotency-Key'] = spec.idempotencyKey;
|
|
112
112
|
}
|
|
113
|
+
// The second credential, under the first one's discipline: per attempt, never
|
|
114
|
+
// cached, and in a header of its own (CAY-0100 §4). Absent unless the caller
|
|
115
|
+
// passed a provider, so every other request is unchanged. An empty value is sent
|
|
116
|
+
// as no header, as an empty key is above, so the server reads it as missing
|
|
117
|
+
// rather than as present and malformed.
|
|
118
|
+
if (spec.authority !== undefined) {
|
|
119
|
+
const authority = await spec.authority.getAuthorityAssertion();
|
|
120
|
+
if (authority.length > 0)
|
|
121
|
+
headers[AUTHORITY_ASSERTION_HEADER] = authority;
|
|
122
|
+
}
|
|
113
123
|
const init = { method: spec.method, headers };
|
|
114
124
|
if (spec.body !== undefined)
|
|
115
125
|
init.body = JSON.stringify(spec.body);
|
|
@@ -190,7 +200,7 @@ function buildClient(options, internals) {
|
|
|
190
200
|
idempotent: true,
|
|
191
201
|
});
|
|
192
202
|
},
|
|
193
|
-
async provisionOrganisation() {
|
|
203
|
+
async provisionOrganisation(authority) {
|
|
194
204
|
return requestJson({
|
|
195
205
|
method: ROUTES.organisationProvision.method,
|
|
196
206
|
path: ROUTES.organisationProvision.path,
|
|
@@ -198,9 +208,10 @@ function buildClient(options, internals) {
|
|
|
198
208
|
// is: the operation is idempotent on the attested Organisation ID, so a
|
|
199
209
|
// repeat is the same act rather than a second one.
|
|
200
210
|
idempotent: true,
|
|
211
|
+
authority,
|
|
201
212
|
});
|
|
202
213
|
},
|
|
203
|
-
async authorisePurchase(req) {
|
|
214
|
+
async authorisePurchase(req, authority) {
|
|
204
215
|
return requestJson({
|
|
205
216
|
method: ROUTES.purchaseAuthorise.method,
|
|
206
217
|
path: ROUTES.purchaseAuthorise.path,
|
|
@@ -208,9 +219,10 @@ function buildClient(options, internals) {
|
|
|
208
219
|
idempotent: typeof req.idempotencyKey === 'string' && req.idempotencyKey.length > 0,
|
|
209
220
|
body: req,
|
|
210
221
|
idempotencyKey: req.idempotencyKey,
|
|
222
|
+
authority,
|
|
211
223
|
});
|
|
212
224
|
},
|
|
213
|
-
async makeCommercialPayment(req) {
|
|
225
|
+
async makeCommercialPayment(req, authority) {
|
|
214
226
|
return requestJson({
|
|
215
227
|
method: ROUTES.commercialPayment.method,
|
|
216
228
|
path: ROUTES.commercialPayment.path,
|
|
@@ -219,6 +231,7 @@ function buildClient(options, internals) {
|
|
|
219
231
|
idempotent: typeof req.idempotencyKey === 'string' && req.idempotencyKey.length > 0,
|
|
220
232
|
body: req,
|
|
221
233
|
idempotencyKey: req.idempotencyKey,
|
|
234
|
+
authority,
|
|
222
235
|
});
|
|
223
236
|
},
|
|
224
237
|
async makeCitizenTransfer(req) {
|
|
@@ -98,9 +98,11 @@
|
|
|
98
98
|
* A **withheld** contribution row is carried and settles nothing: it writes no
|
|
99
99
|
* movement and does not refuse the period. How a later period pays what was withheld
|
|
100
100
|
* is open (CAY-0096 §5). A row of nought cents writes no movement either, though every
|
|
101
|
-
* check on it still runs. A **Core Node**'s qualified row
|
|
102
|
-
* member, `contribution_node_is_a_core_node`: a Core Node's owner is
|
|
103
|
-
* where its contribution settles, if anywhere, is not designed
|
|
101
|
+
* check on it still runs. A **Core Node**'s qualified row of any amount but nought is
|
|
102
|
+
* refused with its own member, `contribution_node_is_a_core_node`: a Core Node's owner is
|
|
103
|
+
* the Operator, and where its contribution settles, if anywhere, is not designed
|
|
104
|
+
* (CAY-0096 §4). A Core Node row of nought cents is skipped and settles nothing, an
|
|
105
|
+
* interim rule pending that design (30 October 2026, cloud.platform#357).
|
|
104
106
|
*
|
|
105
107
|
* ## What is not here
|
|
106
108
|
*
|
|
@@ -734,8 +736,9 @@ export type ContributionRowRefusal =
|
|
|
734
736
|
*/
|
|
735
737
|
'contribution_node_not_bound'
|
|
736
738
|
/**
|
|
737
|
-
* A qualified row names a Node this deployment is told is a
|
|
738
|
-
* Node's owner is the Operator (CMTY-0018 §4), so it settles to no
|
|
739
|
+
* A qualified row of more than nought cents names a Node this deployment is told is a
|
|
740
|
+
* Core Node. A Core Node's owner is the Operator (CMTY-0018 §4), so it settles to no
|
|
741
|
+
* Citizen Wallet. A nought row for a Core Node is skipped instead (interim, #357).
|
|
739
742
|
*/
|
|
740
743
|
| 'contribution_node_is_a_core_node'
|
|
741
744
|
/** The Treasury holds too few unrestricted cents to pay this row. */
|