@percayso-community/cayso-contracts 0.27.0 → 0.30.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 CHANGED
@@ -21,7 +21,10 @@ Percayso Cloud is the second: its Contribution Store presents the settlement
21
21
  assertion to Cayso directly, and the Cloud generates its types from the `schema`
22
22
  entry point rather than typing Cayso's half by hand. The Cloud's two seams live in
23
23
  `cloud-settlement`, and the settlement path, which a registered issuer calls and
24
- no person does, is `CLOUD_SETTLEMENT_ROUTE`, published apart from `ROUTES`.
24
+ no person does, is `CLOUD_SETTLEMENT_ROUTE`, published apart from `ROUTES`. The
25
+ Cloud verifies Cayso's authorisation acknowledgement under a key it reads from
26
+ `ACKNOWLEDGEMENT_KEYS_ROUTE`, an unauthenticated read of public keys, published
27
+ apart from both.
25
28
 
26
29
  **The operator read surface, on its own entry point** (CAY-0026, Amendment 4). Three
27
30
  reads an operator app's *server* calls — the movement list, the purchase list and the
@@ -49,16 +52,34 @@ restated here.
49
52
  |---|---|
50
53
  | `@percayso-community/cayso-contracts` | Types and constants. No runtime. |
51
54
  | `@percayso-community/cayso-contracts/client` | The thin client — fetch, assertion header, idempotent retry. |
52
- | `@percayso-community/cayso-contracts/schema` | A machine-readable descriptor of the published surface. |
55
+ | `@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. |
53
56
  | `@percayso-community/cayso-contracts/operator` | The three operator reads' types and route constants, for an operator app's server. |
54
57
 
55
58
  The split is the point: a consumer wanting only the types takes on no runtime at
56
59
  all. The client owns the fetch, the assertion header and the idempotent retry.
57
60
 
58
61
  The `schema` entry point serves a data description of the same surface —
59
- generated from these very types, never hand-written — for tooling that needs the
60
- shape at runtime (a diff, a doc generator, a compatibility check). It is a
61
- diagnostic, **not** a stand-in for depending on the package: see below.
62
+ generated from these very types, never hand-written. It is a published, versioned
63
+ surface in its own right, and there are two ways to depend on this package through
64
+ it, by language:
65
+
66
+ 1. **A TypeScript consumer imports the types**, and the compiler holds the surface.
67
+ Piper does this. For such a consumer the schema is a tool *about* the contract —
68
+ a diff, a doc generator, a compatibility check — and never a replacement for the
69
+ import.
70
+ 2. **A consumer in another language generates from the `schema` entry point of the
71
+ published tarball at a pinned version**, and treats a drift between its generated
72
+ types and the schema as a build failure. That *is* depending on the package: the
73
+ pin is the lockfile entry and the regeneration is the compile. The Percayso Cloud
74
+ is this consumer (`CAY-0026` Amendment 3; `CMTY-0018` §3 as amended): it is written
75
+ in Go, it cannot import a TypeScript type, and its generator reads the npm tarball
76
+ at a pinned version so that its Go types for the two Cloud seams in
77
+ `cloud-settlement` are never typed by hand. The package ships the schema as a JS
78
+ module only, which is why the generator reads the tarball and not a registry
79
+ endpoint.
80
+
81
+ What stays refused is reading the schema off the wire as a substitute for either:
82
+ see the version endpoint, below.
62
83
 
63
84
  ## Independent versioning
64
85
 
@@ -91,15 +112,18 @@ Alongside it, `GET /api/version/schema` serves the same machine-readable
91
112
  descriptor as `@percayso-community/cayso-contracts/schema` — the surface outside consumers read, with
92
113
  no operator, webhook or peer route. It exists for tooling and diagnostics.
93
114
 
94
- **It is not a substitute for depending on this package.** The descriptor is
95
- data; the guarantee is the *types*, and the types do not exist at runtime — they
96
- are erased when TypeScript compiles. A consumer that reads the schema off the
97
- wire and validates against it has re-created Piper's original mistake by another
98
- route: hand-rolling the shape instead of importing it, and losing every
99
- compile-time guarantee the moment the wire description and the code drift. Import
100
- `@percayso-community/cayso-contracts` and let the compiler hold the surface. Read the schema
101
- endpoint when you are building a tool *about* the contract, never to stand in for
102
- having it.
115
+ **The wire is not a substitute for depending on this package.** The descriptor
116
+ served here is data about *this deployment*; the guarantee is the published
117
+ package at a pinned version. A consumer that reads the schema off the wire and
118
+ validates or generates against it has re-created Piper's original mistake by
119
+ another route: it couples its build to a deployment's uptime, replaces a pinned
120
+ version with whatever happens to be deployed at that moment, and loses every
121
+ guarantee the moment the wire description and its code drift. Import
122
+ `@percayso-community/cayso-contracts` and let the compiler hold the surface, or —
123
+ in another language — generate from the `schema` entry point of the published
124
+ tarball at a pinned version, as the Cloud does. Read the schema *endpoint* when
125
+ you are building a tool *about* a running deployment, never to stand in for
126
+ having the package.
103
127
 
104
128
  ## No dependencies
105
129
 
@@ -326,6 +326,21 @@
326
326
  * credential minted to place a quote must not thereby move cents to somebody without
327
327
  * a match, nor the reverse. It is served at `ROUTES.citizenTransfer`.
328
328
  *
329
+ * `'change-handle'` is a Citizen's Handle change, charged by Cayso (CAY-0091). It is
330
+ * not a second use of `'make-citizen-transfer'`, which pays another Citizen, nor of
331
+ * `'settle-marketplace-order'`, which presents Store Platform's signed order: a Handle
332
+ * change charges the Citizen for a Community service on the authority of a change
333
+ * reference Percayso ID minted. A credential minted for any other operation must not
334
+ * authorise a Handle change, and the reverse. It is served at `ROUTES.handleChange`,
335
+ * and it charges for the change reference named by
336
+ * {@link AssertionClaims.handleChangeReference} and no other.
337
+ *
338
+ * `'read-handle-change-receipt'` asks whether a change reference was charged
339
+ * (CAY-0091), as `'read-marketplace-receipt'` asks of a checkout key. It is its own
340
+ * operation because it is a read beside the money-grade charge it reports on: a
341
+ * credential minted to ask what happened must not thereby be able to make something
342
+ * happen. It is served at `ROUTES.handleChangeReceipt`.
343
+ *
329
344
  * `'place-quote'`, `'withdraw-quote'` and `'request-buy-back'` are the citizen
330
345
  * market's three acts (CAY-0080, Open decision Q). They are three operations
331
346
  * because they are three acts: a credential minted to withdraw a quote must not
@@ -368,9 +383,19 @@
368
383
  * It is not a second use of `'make-commercial-payment'`: nothing moves when it is
369
384
  * presented, and what it authorises is spending in later periods, settled by the
370
385
  * Contribution Store's assertion, which no person presents and which therefore has
371
- * no operation here. It is classified before it is served.
386
+ * no operation here.
387
+ *
388
+ * `'revoke-standing-authorisation'` is the owner withdrawing that delegation from
389
+ * their Instance (CAY-0094). It is its own operation rather than a mode of
390
+ * presenting, because one path carries one scope at CAY-0030's gate: a credential
391
+ * minted to revoke must not thereby delegate, nor the reverse.
392
+ *
393
+ * `'bind-node'` is the owner's Instance binding a Node to the wallet its Instance key
394
+ * is already bound to (CMTY-0018 §4, CAY-0095). Its own operation, because it decides
395
+ * where a Node's earnings land and binds no key: a credential minted to delegate
396
+ * spending must not thereby direct earnings, nor the reverse.
372
397
  */
373
- export type ContractOperation = 'read-own-wallet' | 'read-purchase-result' | 'authorise-purchase' | 'cancel-purchase' | 'provision-citizenship' | 'read-own-citizenship' | 'provision-organisation' | 'claim-founder-wallet' | 'make-commercial-payment' | 'make-citizen-transfer' | 'place-quote' | 'withdraw-quote' | 'request-buy-back' | 'read-citizen-market' | 'read-marketplace-receipt' | 'settle-marketplace-order' | 'settle-marketplace-refund' | 'present-standing-authorisation';
398
+ export type ContractOperation = 'read-own-wallet' | 'read-purchase-result' | 'authorise-purchase' | 'cancel-purchase' | 'provision-citizenship' | 'read-own-citizenship' | 'provision-organisation' | 'claim-founder-wallet' | 'make-commercial-payment' | 'make-citizen-transfer' | 'change-handle' | 'read-handle-change-receipt' | 'place-quote' | 'withdraw-quote' | 'request-buy-back' | 'read-citizen-market' | 'read-marketplace-receipt' | 'settle-marketplace-order' | 'settle-marketplace-refund' | 'present-standing-authorisation' | 'revoke-standing-authorisation' | 'bind-node';
374
399
  /**
375
400
  * An operation key belonging to a deployment's OWN surfaces rather than to this
376
401
  * contract — operator work, above all. Deliberately not enumerated: this package
@@ -839,6 +864,42 @@ export interface AssertionClaims {
839
864
  * of work.
840
865
  */
841
866
  readonly founderClaimReference?: string;
867
+ /**
868
+ * The change reference: the single-use, opaque value Percayso ID issued when it
869
+ * reserved the Citizen's proposed Handle (CAY-0091). OPTIONAL on the wire, and read by
870
+ * `'change-handle'` and `'read-handle-change-receipt'` and by no other operation.
871
+ *
872
+ * ## It travels with its operations, in both directions
873
+ *
874
+ * As {@link founderClaimReference} does. A credential whose `scope` carries
875
+ * `'change-handle'` or `'read-handle-change-receipt'` must carry this claim, and a
876
+ * credential that carries this claim must be scoped to one of them. A verifier refuses
877
+ * either half missing as a malformed credential, before any gate reads it: a Handle
878
+ * change with no reference charges for nothing, and a reference carried to any other
879
+ * operation is half of a Handle change put in front of a route with no business
880
+ * reading it.
881
+ *
882
+ * ## It is carried here and never in a request
883
+ *
884
+ * The reference fixes the new Handle BEFORE the charge, so a receipt cannot be spent
885
+ * on a different name. A reference named in a body would be the caller's claim about
886
+ * which Handle change is theirs (CAY-0021); inside the signed assertion it is Percayso
887
+ * ID's statement. Cayso charges at most once per reference, keys the charge by it, and
888
+ * names it in the signed receipt.
889
+ *
890
+ * ## What it is not
891
+ *
892
+ * It is NOT the Handle. Cayso never learns the Handle: the reference is opaque, and
893
+ * Cayso compares it and never parses it. It is not derived from a Percayso identity,
894
+ * and it carries no `sub`, no name and no email. This contract asks for a non-empty
895
+ * string and nothing more.
896
+ *
897
+ * ## It is as trustworthy as the verifier, and no more
898
+ *
899
+ * As with every claim: declaring it makes the promise structural, and a real
900
+ * signature check is what makes it trustworthy.
901
+ */
902
+ readonly handleChangeReference?: string;
842
903
  /**
843
904
  * The Organisation **Wallet IDs** this identity holds the mandate for
844
905
  * (CAY-0024, CAY-0059 §7). Each entry is a wallet id — the opaque identifier
package/dist/assurance.js CHANGED
@@ -343,6 +343,33 @@ export const ASSURANCE_POLICY = {
343
343
  'anything but the development bypass. An unverified holder must never be able to send, ' +
344
344
  'because sending is how value reaches somebody else.',
345
345
  },
346
+ // A Citizen paying Cayso to change their Handle (CAY-0091). Money-grade, at the
347
+ // Citizen transfer's bar: value leaves the holder, and the payment is what lets
348
+ // Percayso ID change a public name, so an unverified holder must not be able to
349
+ // make it. An allowlist, so a future method is admissible only when somebody
350
+ // decides it here.
351
+ 'change-handle': {
352
+ operation: 'change-handle',
353
+ assurance: 'required',
354
+ decisions: {
355
+ // Asserts nothing; permanently refused wherever "identity-verified" is meant.
356
+ 'dev-bypass': 'refused',
357
+ document: 'accepted',
358
+ 'document-and-biometric': 'accepted',
359
+ },
360
+ rationale: "A Handle change charges the Citizen's own Citizen Wallet, so it is money-grade: anything " +
361
+ 'but the development bypass. The receipt it answers with lets Percayso ID change a ' +
362
+ 'Handle, which an unverified holder must never be able to buy.',
363
+ },
364
+ // Asking whether a change reference was charged (CAY-0091). A read: nothing moves and
365
+ // nothing is reserved, so it applies no bar, as the marketplace receipt read applies
366
+ // none. A credential scoped to it cannot charge anything.
367
+ 'read-handle-change-receipt': {
368
+ operation: 'read-handle-change-receipt',
369
+ assurance: 'not-required',
370
+ rationale: 'Asking whether a change reference was charged moves nothing and reserves nothing. It ' +
371
+ 'answers a receipt Cayso already signed, or that there was no charge, and nothing more.',
372
+ },
346
373
  // The citizen market's three acts (CAY-0080 §7, Open decision Q). All three are
347
374
  // money-grade and all three are allowlists, at the purchase path's bar: the acting
348
375
  // party is a Citizen, and a bar no issuer can yet clear would shut the market on
@@ -491,6 +518,41 @@ export const ASSURANCE_POLICY = {
491
518
  'bypass. Nothing moves when it is presented; what it authorises moves later, without the ' +
492
519
  'holder present, which is why the bar is applied here.',
493
520
  },
521
+ // The owner withdrawing that delegation from their Instance (CAY-0094). Held to the
522
+ // bar of the act it undoes: a credential that could not have delegated must not be
523
+ // able to withdraw a delegation either, or a weaker assertion becomes a way to stop
524
+ // an owner's Instance spending what they authorised.
525
+ 'revoke-standing-authorisation': {
526
+ operation: 'revoke-standing-authorisation',
527
+ assurance: 'required',
528
+ decisions: {
529
+ // Asserts nothing; permanently refused wherever "identity-verified" is meant.
530
+ 'dev-bypass': 'refused',
531
+ document: 'accepted',
532
+ 'document-and-biometric': 'accepted',
533
+ },
534
+ rationale: 'Revoking a standing authorisation withdraws a delegation of spending from the ' +
535
+ "holder's wallet, so it is held to the bar of presenting one: anything but the " +
536
+ 'development bypass. A credential that could not delegate cannot undo a delegation.',
537
+ },
538
+ // The owner's Instance binding a Node to their wallet (CMTY-0018 §4, CAY-0095). Held to
539
+ // the bar of presenting a standing authorisation: a bound Node's earnings are paid into
540
+ // the wallet at every later settlement, without the holder present, so choosing where
541
+ // they land is a decision about somebody's money.
542
+ 'bind-node': {
543
+ operation: 'bind-node',
544
+ assurance: 'required',
545
+ decisions: {
546
+ // Asserts nothing; permanently refused wherever "identity-verified" is meant.
547
+ 'dev-bypass': 'refused',
548
+ document: 'accepted',
549
+ 'document-and-biometric': 'accepted',
550
+ },
551
+ rationale: "Binding a Node decides which wallet the Node's earnings are paid into at every later " +
552
+ 'settlement, without the holder present, and a binding is never re-pointed, so it is ' +
553
+ 'held to the bar of presenting a standing authorisation: anything but the development ' +
554
+ 'bypass.',
555
+ },
494
556
  };
495
557
  /**
496
558
  * Every operation, as a value. Derived from {@link ASSURANCE_POLICY} for the same
@@ -524,12 +586,16 @@ export const OPERATION_FOR_ROUTE = {
524
586
  purchaseCancel: 'cancel-purchase',
525
587
  commercialPayment: 'make-commercial-payment',
526
588
  citizenTransfer: 'make-citizen-transfer',
589
+ handleChange: 'change-handle',
590
+ handleChangeReceipt: 'read-handle-change-receipt',
527
591
  citizenMarket: 'read-citizen-market',
528
592
  placeQuote: 'place-quote',
529
593
  withdrawQuote: 'withdraw-quote',
530
594
  requestBuyBack: 'request-buy-back',
531
595
  marketplaceOrder: 'settle-marketplace-order',
532
596
  standingAuthorisation: 'present-standing-authorisation',
597
+ revokeStandingAuthorisation: 'revoke-standing-authorisation',
598
+ bindNode: 'bind-node',
533
599
  version: null,
534
600
  };
535
601
  /**
package/dist/client.d.ts CHANGED
@@ -69,9 +69,10 @@
69
69
  */
70
70
  import type { BuyBackMade, BuyBackRequest, CitizenMarketViewed, QuotePlaced, QuoteRequest, QuoteWithdrawalRequest, QuoteWithdrawn } from './citizen-market.js';
71
71
  import type { CitizenshipFounderClaimed, CitizenshipView } from './citizenship.js';
72
- import type { StandingAuthorisationAcknowledged, StandingAuthorisationRequest } from './cloud-settlement.js';
72
+ import type { StandingAuthorisationAcknowledged, StandingAuthorisationRequest, StandingAuthorisationRevocationRequest, StandingAuthorisationRevoked, NodeBindingRequest, NodeBound } from './cloud-settlement.js';
73
73
  import type { CitizenTransferMade, CitizenTransferRequest } from './citizen-transfer.js';
74
74
  import type { CommercialPaymentMade, CommercialPaymentRequest } from './commercial-payment.js';
75
+ import type { HandleChangeCharged, HandleChangeReceiptReadResponse } from './handle-change.js';
75
76
  import type { MarketplaceOrderRequest, MarketplaceOrderSettled } from './marketplace-settlement.js';
76
77
  import type { OrganisationView } from './organisation.js';
77
78
  import type { PurchaseAuthorised, PurchaseCancelled, PurchaseRequest, PurchaseResult, PurchaseTerminal } from './purchase.js';
@@ -225,6 +226,24 @@ export interface CaysoClient {
225
226
  * refusal means nothing moved.
226
227
  */
227
228
  makeCitizenTransfer(req: CitizenTransferRequest): Promise<CitizenTransferMade>;
229
+ /**
230
+ * Charge the caller's own Citizen Wallet for a Handle change, and receive the signed
231
+ * receipt Percayso ID relies on (CAY-0091) — or, for a change reference already
232
+ * charged, the receipt of that charge, with nothing charged again.
233
+ *
234
+ * There is nothing to send: the change reference is inside the assertion, the wallet
235
+ * is the verified holder's own, and the price is Cayso's. A refusal arrives as a thrown
236
+ * {@link CaysoApiError} whose `code` is a `HandleChangeRefusal` member, one of the
237
+ * gate's codes, or a seam code. A refusal means nothing moved.
238
+ */
239
+ changeHandle(): Promise<HandleChangeCharged>;
240
+ /**
241
+ * Ask whether the change reference inside the assertion was charged for the caller
242
+ * (CAY-0091): its receipt, or `no-charge`. Never "pending". A refusal arrives as a
243
+ * thrown {@link CaysoApiError} whose `code` is a `HandleChangeReceiptReadRefusal`
244
+ * member, one of the gate's codes, or a seam code — and is never "no charge".
245
+ */
246
+ readHandleChangeReceipt(): Promise<HandleChangeReceiptReadResponse>;
228
247
  /**
229
248
  * Read the citizen market as the caller sees it (CAY-0080 §1.4): the published
230
249
  * value and its band, both bounds per quote unit, the best bid and the best ask with
@@ -285,10 +304,31 @@ export interface CaysoClient {
285
304
  * Safe to repeat: the authorisation's `jti` is its own key, and presenting the same
286
305
  * authorisation again binds nothing twice. A refusal arrives as a thrown
287
306
  * {@link CaysoApiError} whose `code` is a `StandingAuthorisationRefusal` member, one
288
- * of the gate's codes, or a seam code, and means nothing was bound. Published before
289
- * the route is served.
307
+ * of the gate's codes, or a seam code, and means nothing was bound. Each
308
+ * presentation answers a freshly signed acknowledgement.
290
309
  */
291
310
  presentStandingAuthorisation(req: StandingAuthorisationRequest): Promise<StandingAuthorisationAcknowledged>;
311
+ /**
312
+ * Revoke a standing authorisation the caller's wallet holds, named by its `jti`,
313
+ * under the owner's assertion (`revoke-standing-authorisation`, CAY-0094).
314
+ *
315
+ * Safe to repeat: revoking a revoked authorisation answers `revoked` again and
316
+ * writes nothing. A refusal arrives as a thrown {@link CaysoApiError} whose `code`
317
+ * is a `StandingAuthorisationRevocationRefusal` member, one of the gate's codes, or
318
+ * a seam code, and means nothing was written.
319
+ */
320
+ revokeStandingAuthorisation(req: StandingAuthorisationRevocationRequest): Promise<StandingAuthorisationRevoked>;
321
+ /**
322
+ * Bind a Node to the wallet the presenting Instance key is already bound to, under
323
+ * the owner's assertion (`bind-node`, CAY-0095). The key must have been bound to the
324
+ * caller's wallet by a standing authorisation it presented; this binds no key.
325
+ *
326
+ * Safe to repeat: binding a bound Node to the same wallet answers `bound` again and
327
+ * writes nothing. A refusal arrives as a thrown {@link CaysoApiError} whose `code` is
328
+ * a `NodeBindingRefusal` member, one of the gate's codes, or a seam code, and means
329
+ * nothing was bound.
330
+ */
331
+ bindNode(req: NodeBindingRequest): Promise<NodeBound>;
292
332
  getVersion(): Promise<CaysoVersion>;
293
333
  }
294
334
  /**
package/dist/client.js CHANGED
@@ -231,6 +231,25 @@ function buildClient(options, internals) {
231
231
  idempotencyKey: req.idempotencyKey,
232
232
  });
233
233
  },
234
+ async changeHandle() {
235
+ return requestJson({
236
+ method: ROUTES.handleChange.method,
237
+ path: ROUTES.handleChange.path,
238
+ // Safe to retry with no key: the charge is keyed by the change reference
239
+ // inside the assertion, so a repeat answers the charge already made, and each
240
+ // attempt mints a fresh assertion, so the single-use `jti` is never presented
241
+ // twice.
242
+ idempotent: true,
243
+ });
244
+ },
245
+ async readHandleChangeReceipt() {
246
+ return requestJson({
247
+ method: ROUTES.handleChangeReceipt.method,
248
+ path: ROUTES.handleChangeReceipt.path,
249
+ // A read: it moves nothing, so a retry is always safe.
250
+ idempotent: true,
251
+ });
252
+ },
234
253
  async getCitizenMarket() {
235
254
  return requestJson({
236
255
  method: ROUTES.citizenMarket.method,
@@ -290,6 +309,24 @@ function buildClient(options, internals) {
290
309
  body: req,
291
310
  });
292
311
  },
312
+ async revokeStandingAuthorisation(req) {
313
+ return requestJson({
314
+ method: ROUTES.revokeStandingAuthorisation.method,
315
+ path: ROUTES.revokeStandingAuthorisation.path,
316
+ // Safe to repeat: a revoked authorisation revoked again answers the same.
317
+ idempotent: true,
318
+ body: req,
319
+ });
320
+ },
321
+ async bindNode(req) {
322
+ return requestJson({
323
+ method: ROUTES.bindNode.method,
324
+ path: ROUTES.bindNode.path,
325
+ // Safe to repeat: the same Node bound to the same wallet answers the same.
326
+ idempotent: true,
327
+ body: req,
328
+ });
329
+ },
293
330
  async getPurchase(purchaseId) {
294
331
  return requestJson({
295
332
  method: ROUTES.purchaseResult.method,