@eco-foundation/api-schemas 0.12.0 → 0.13.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
@@ -4,7 +4,7 @@ Zod-first schemas, error catalog, golden fixtures, and generated OpenAPI 3.1 for
4
4
  API. The Zod schemas are the source of truth; everything else (OpenAPI, fixtures, plain types)
5
5
  is generated from or gated against them.
6
6
 
7
- Today the package covers **13 endpoints across 12 paths**, **39 error codes** (51 namespaced
7
+ Today the package covers **13 endpoints across 12 paths**, **40 error codes** (51 namespaced
8
8
  legacy references), and **21 golden fixtures**.
9
9
 
10
10
  ## Install
@@ -14,13 +14,13 @@ While the package is 0.x, pin the patch range or the exact version:
14
14
  ```json
15
15
  {
16
16
  "dependencies": {
17
- "@eco-foundation/api-schemas": "~0.10.0"
17
+ "@eco-foundation/api-schemas": "~0.13.0"
18
18
  }
19
19
  }
20
20
  ```
21
21
 
22
22
  **Never write `^0`.** npm resolves `^0.x` as `>=0.0.0 <1.0.0`, so it silently accepts every
23
- future breaking `0.y` — it is not a pin at all. Use `~0.10.0` or the exact `0.10.0`.
23
+ future breaking `0.y` — it is not a pin at all. Use `~0.13.0` or the exact `0.13.0`.
24
24
 
25
25
  A matching version string is not evidence of a matching artifact: also compare
26
26
  `V1_SCHEMA_DIGEST` (see [Invariants](#invariants-enforced-in-ci)). Two builds both called
@@ -30,6 +30,55 @@ A matching version string is not evidence of a matching artifact: also compare
30
30
  zod-bearing entry point. Which case you are in is decided entirely by your imports; see
31
31
  [Consumers](#consumers).
32
32
 
33
+ ## Migrating 0.12.0 -> 0.13.0
34
+
35
+ **BREAKING under 0.x semver only for exhaustive handling of `V1ErrorCode`.** Nothing on the wire
36
+ that `0.12.0` accepted is rejected and no response shape changes. The release adds one error code,
37
+ one config schema, and one fixture file for scheduled maintenance windows (PER-673).
38
+
39
+ **`V1_SCHEMA_DIGEST` MOVES** to
40
+ `e5a684f678f80135ddf7241029d965d2a206b3b573a9969ad1352610d1b4abc9`, because both digest inputs
41
+ changed (`errors/catalog.lock.json` gained one code and `openapi/v1.json` its `x-error-catalog`
42
+ entry). Every consumer running `assertNoSkew` moves in the same rollout.
43
+
44
+ **1. New error code `maintenance` (503, audience `all`, no legacy codes).** It is a planned stop,
45
+ distinct from `service-unavailable` (an outage). eco-router, eco-quotes and deposit-addresses
46
+ return it from every quote-creating and address-creating endpoint while a configured window is
47
+ active, with `startsAt`/`endsAt` in the body and a `Retry-After` header. `V1ProblemSchema` now
48
+ checks a `maintenance` body's `status`/`type` against the catalog, and an exhaustive `switch`
49
+ over `V1ErrorCode` stops compiling until it handles the new member. That is the breaking item.
50
+
51
+ **2. New config schemas `MaintenanceWindowSchema` and `MaintenanceConfigSchema`** (on the `v1`
52
+ namespace, with types `MaintenanceWindow`, `MaintenanceConfig`, `MaintenanceConfigInput`). A
53
+ window is `{ startsAt, endsAt, message }`: `startsAt` inclusive, `endsAt` exclusive, both
54
+ ISO-8601 UTC strings ending in `Z` with at most three fractional digits (offsets and impossible
55
+ dates such as `2026-02-30` are rejected), `message` 1-200 characters and not blank (whitespace-only
56
+ is rejected — it is echoed verbatim to partners), and `endsAt` must be after `startsAt`. `windows`
57
+ defaults to `[]`. Both schemas reject any key not in their shape (e.g. a `window` typo for
58
+ `windows`) at parse time, naming the key — this is scoped to the raw input's own enumerable
59
+ properties, so a config object read through node-config (whose hidden, non-enumerable `get`/`has`/
60
+ `util` helpers are unaffected) still parses. Overlapping windows are allowed; past windows are valid
61
+ (services drop them at boot). **Quote the timestamps in YAML**: an unquoted
62
+ `2026-10-04T02:00:00Z` is loaded as a `Date` and fails with "quote it in YAML". There is no
63
+ `./v1/maintenance` subpath, because the schemas need zod anyway, so import them from the root
64
+ `v1` namespace.
65
+
66
+ **3. New fixture `fixtures/maintenance-boundaries.json`** (served by the existing `./fixtures/*`
67
+ subpath). Each entry service implements its own resolver (the package deliberately ships none)
68
+ and asserts it against every vector: active when `startsAt <= now < endsAt`; with several
69
+ active, the latest `endsAt` wins and a tie goes to the first in the list;
70
+ `retryAfterSeconds = max(1, ceil((endsAt - now) / 1000))`.
71
+
72
+ **4. Consumers** pin `~0.13.0` (never `^0`) and re-run `assertNoSkew`. No golden fixture under
73
+ `src/fixtures/v1` changed.
74
+
75
+ **5. The `zod` peer range rises to `^4.4.0`** (from `^4.3.0`), still optional. The maintenance
76
+ schemas' exported types (`MaintenanceWindowSchema`, `MaintenanceConfigSchema`) use
77
+ `z.ZodPreprocess`, which zod only exports from 4.4 — on zod 4.3.x those `.d.ts` types silently
78
+ collapse to `any` under `skipLibCheck` (and fail `TS2724` without it). A consumer that imports
79
+ zod-bearing entry points must upgrade to zod `^4.4.0`+ before adopting `0.13.0`; the two
80
+ zod-free subpaths (`./v1/signature`, `./v1/types`) are unaffected.
81
+
33
82
  ## Migrating 0.11.0 -> 0.12.0
34
83
 
35
84
  **Non-breaking for callers, a new version for consumers.** `GET /v1/intents/status` now accepts
@@ -989,7 +1038,7 @@ must: it is the artifact identity, not the version string.
989
1038
  declared optional (`peerDependenciesMeta: { zod: { optional: true } }`), because for two of the
990
1039
  published entry points it is genuinely not needed:
991
1040
 
992
- - **Needs `zod` (`^4.3.0`):** the bare package name `.`, `./testing`, and anything reached
1041
+ - **Needs `zod` (`^4.4.0`):** the bare package name `.`, `./testing`, and anything reached
993
1042
  through the `v1` namespace on either. These load schema modules, and their `.d.ts` names zod
994
1043
  types.
995
1044
  - **Needs NO `zod`:** `./v1/signature` (runtime code importing `@noble/hashes` and nothing else)
@@ -998,8 +1047,8 @@ published entry points it is genuinely not needed:
998
1047
 
999
1048
  Read `optional` narrowly: it says zod **may be absent**, not that any zod version works. pnpm
1000
1049
  suppresses the unmet-peer warning only when zod is MISSING (verified against pnpm 10.16); a
1001
- consumer that HAS a zod outside `^4.3.0` — solver-v2, on zod 3 — still sees
1002
- `unmet peer zod@^4.3.0: found <version>`, since that is a version mismatch rather than a missing
1050
+ consumer that HAS a zod outside `^4.4.0` — solver-v2, on zod 3 — still sees
1051
+ `unmet peer zod@^4.4.0: found <version>`, since that is a version mismatch rather than a missing
1003
1052
  peer. Suppressing that one is the consumer's own call
1004
1053
  (`pnpm.peerDependencyRules.allowedVersions`), and it is safe exactly while its imports are
1005
1054
  limited to the two zod-free subpaths above. The range itself cannot be widened — the schemas
@@ -1007,14 +1056,16 @@ require Zod 4.
1007
1056
 
1008
1057
  Zod-major compatibility is the reason there are two interfaces, not one:
1009
1058
 
1010
- - **Zod-4 consumers** (eco-router `^4.4.3`, deposit-addresses `^4.3.5`) import the schemas
1011
- directly. Since `0.2.0`, `zod` is a **`peerDependency` with a range (`^4.3.0`)** — optional
1059
+ - **Zod-4 consumers** (eco-router `^4.4.3`) import the schemas directly. Since `0.2.0`, `zod` is
1060
+ a **`peerDependency` with a range (`^4.4.0` since `0.13.0`, raised from `^4.3.0`)** — optional
1012
1061
  since `0.3.1` — not a hard-pinned dependency: the consumer's own single copy satisfies it. For
1013
- these consumers optional changes nothing; they must still install zod. The previous exact
1014
- `dependencies` pin at `4.4.3` materialised a SECOND nested zod for every consumer not on that
1015
- exact version, and two zod instances in one type graph is the documented failure — `TS2345` on
1062
+ these consumers optional changes nothing; they must still install zod. deposit-addresses'
1063
+ prior `^4.3.5` install is now below the floor and must upgrade to `^4.4.0`+ to consume `0.13.0`
1064
+ — see [Migrating 0.12.0 -> 0.13.0](#migrating-0120---0130). The previous exact `dependencies`
1065
+ pin at `4.4.3` materialised a SECOND nested zod for every consumer not on that exact version,
1066
+ and two zod instances in one type graph is the documented failure — `TS2345` on
1016
1067
  `_zod.version.minor` when a package schema is passed to a local `z.ZodType<T>`, and `tsc`
1017
- OOMing at 4 GB with no message. A consumer therefore installs zod itself (any `4.3.x`+ `4.x`);
1068
+ OOMing at 4 GB with no message. A consumer therefore installs zod itself (any `4.4.x`+ `4.x`);
1018
1069
  the package keeps an exact `4.4.3` **devDependency** so its own build and its generated
1019
1070
  OpenAPI stay byte-stable.
1020
1071
  - **solver-v2 is on Zod 3 and must never be forced to Zod 4.** It consumes the zod-free
@@ -1041,6 +1092,7 @@ Zod-major compatibility is the reason there are two interfaces, not one:
1041
1092
  | `./openapi/v1.json` | the generated OpenAPI 3.1 document |
1042
1093
  | `./fixtures/signature-hash/vectors.json` | cross-repo `signatureHash` vectors |
1043
1094
  | `./fixtures/quote-signature/vector-*.json` | cross-repo F3 quote-signature (EIP-712) vectors |
1095
+ | `./fixtures/maintenance-boundaries.json` | cross-repo maintenance-window (PER-673) resolver boundary vectors |
1044
1096
 
1045
1097
  ## Fixtures
1046
1098
 
@@ -1057,7 +1109,10 @@ There are two fixture locations and they serve different jobs — do not conflat
1057
1109
  `fixtures/signature-hash/vectors.json` (the `signatureHash` canonicalization contract) and
1058
1110
  `fixtures/quote-signature/vector-*.json` (the F3 EIP-712 `EcoQuoteV1` vectors, each with its
1059
1111
  `digest`, `signature`, and `signer`; the signing key is derived from a public domain string,
1060
- so this repo ships no private-key bytes).
1112
+ so this repo ships no private-key bytes), and `fixtures/maintenance-boundaries.json` (the
1113
+ PER-673 maintenance-window boundary vectors: `{ name, windows, nowIso, expected }`, which each
1114
+ entry service's local resolver asserts; this package's own suite checks every vector against a
1115
+ reference resolver kept in the spec file, so a wrong `expected` fails here first).
1061
1116
 
1062
1117
  `routes.list` is the one fixture tagged `stability: 'draft'`. A live-replay consumer skips it
1063
1118
  with the reason `draft endpoint (x-stability: draft) — not served at launch` instead of
@@ -1,5 +1,5 @@
1
1
  {
2
- "digest": "85d2717c541b52f668574f045ddc3f3722813bbdcee9e8f5d15bed5cfc927561",
2
+ "digest": "e5a684f678f80135ddf7241029d965d2a206b3b573a9969ad1352610d1b4abc9",
3
3
  "inputs": [
4
4
  "errors/catalog.lock.json",
5
5
  "openapi/v1.json"
@@ -280,6 +280,13 @@ export declare const V1_ERROR_CATALOG: readonly [{
280
280
  readonly type: string;
281
281
  readonly audience: "read" | "write" | "all";
282
282
  readonly legacyCodes: readonly LegacyCodeRef[];
283
+ }, {
284
+ readonly code: "maintenance";
285
+ readonly status: number;
286
+ readonly title: string;
287
+ readonly type: string;
288
+ readonly audience: "read" | "write" | "all";
289
+ readonly legacyCodes: readonly LegacyCodeRef[];
283
290
  }];
284
291
  export type V1ErrorCode = (typeof V1_ERROR_CATALOG)[number]['code'];
285
292
  export declare function getErrorDefinition(code: V1ErrorCode): V1ErrorDefinition;
@@ -109,6 +109,7 @@ exports.V1_ERROR_CATALOG = [
109
109
  ]),
110
110
  define('solver-error', 502, 'An upstream solver rejected or failed the quote request', 'all'),
111
111
  define('service-unavailable', 503, 'Service temporarily unavailable', 'all'),
112
+ define('maintenance', 503, 'Scheduled maintenance: new quotes and deposit addresses are paused until the window ends', 'all'),
112
113
  ];
113
114
  const BY_CODE = new Map(exports.V1_ERROR_CATALOG.map((e) => [e.code, e]));
114
115
  function getErrorDefinition(code) {
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../src/v1/errors/catalog.ts"],"names":[],"mappings":";;;AAmSA,gDAIC;AAhRD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,iCAAiC,IAAI,EAAE,CAAC;AAE1E,MAAM,MAAM,GAAG,CACb,IAAO,EACP,MAAc,EACd,KAAa,EACb,QAAkC,EAClC,cAAwC,EAAE,EAC1C,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAU,CAAC;AAEvE,QAAA,gBAAgB,GAAG;IAC9B,MAAM,CAAC,iBAAiB,EAAE,GAAG,EAAE,2BAA2B,EAAE,KAAK,EAAE,CAAC,iBAAiB,CAAC,CAAC;IACvF,MAAM,CAAC,mBAAmB,EAAE,GAAG,EAAE,iCAAiC,EAAE,KAAK,EAAE,CAAC,iBAAiB,CAAC,CAAC;IAC/F,MAAM,CAAC,mBAAmB,EAAE,GAAG,EAAE,8BAA8B,EAAE,KAAK,EAAE;QACtE,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,wBAAwB;KACzB,CAAC;IACF,MAAM,CACJ,wBAAwB,EACxB,GAAG,EACH,yEAAyE;QACvE,+CAA+C,EACjD,KAAK,CACN;IASD,MAAM,CACJ,0BAA0B,EAC1B,GAAG,EACH,8DAA8D,EAC9D,OAAO,CACR;IAWD,MAAM,CACJ,gBAAgB,EAChB,GAAG,EACH,mFAAmF,EACnF,OAAO,CACR;IACD,MAAM,CAAC,mBAAmB,EAAE,GAAG,EAAE,+BAA+B,EAAE,OAAO,EAAE;QACzE,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,wBAAwB;QACxB,wBAAwB;KACzB,CAAC;IACF,MAAM,CAAC,uBAAuB,EAAE,GAAG,EAAE,6CAA6C,EAAE,OAAO,EAAE;QAC3F,iBAAiB;QACjB,wBAAwB;QACxB,wBAAwB;KACzB,CAAC;IACF,MAAM,CAAC,iBAAiB,EAAE,GAAG,EAAE,yCAAyC,EAAE,OAAO,CAAC;IAClF,MAAM,CAAC,iBAAiB,EAAE,GAAG,EAAE,qBAAqB,EAAE,OAAO,CAAC;IAC9D,MAAM,CAAC,mBAAmB,EAAE,GAAG,EAAE,2CAA2C,EAAE,KAAK,EAAE;QACnF,iBAAiB;KAClB,CAAC;IACF,MAAM,CAAC,iBAAiB,EAAE,GAAG,EAAE,sDAAsD,EAAE,OAAO,CAAC;IAC/F,MAAM,CAAC,iBAAiB,EAAE,GAAG,EAAE,yCAAyC,EAAE,KAAK,EAAE;QAC/E,iBAAiB;KAClB,CAAC;IAiCF,MAAM,CACJ,oBAAoB,EACpB,GAAG,EACH,8EAA8E,EAC9E,KAAK,CACN;IAID,MAAM,CACJ,oBAAoB,EACpB,GAAG,EACH,kFAAkF,EAClF,KAAK,CACN;IACD,MAAM,CACJ,yBAAyB,EACzB,GAAG,EACH,wDAAwD,EACxD,OAAO,CACR;IACD,MAAM,CAAC,YAAY,EAAE,GAAG,EAAE,wCAAwC,EAAE,OAAO,CAAC;IAC5E,MAAM,CAAC,eAAe,EAAE,GAAG,EAAE,gDAAgD,EAAE,OAAO,EAAE;QACtF,iBAAiB;KAClB,CAAC;IAQF,MAAM,CACJ,mBAAmB,EACnB,GAAG,EACH,mFAAmF;QACjF,YAAY,EACd,KAAK,CACN;IAKD,MAAM,CACJ,wBAAwB,EACxB,GAAG,EACH,+EAA+E;QAC7E,qCAAqC,EACvC,KAAK,CACN;IACD,MAAM,CAAC,gBAAgB,EAAE,GAAG,EAAE,yCAAyC,EAAE,KAAK,EAAE;QAC9E,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;KAClB,CAAC;IACF,MAAM,CAAC,qBAAqB,EAAE,GAAG,EAAE,2CAA2C,EAAE,KAAK,EAAE;QACrF,iBAAiB;QACjB,wBAAwB;KACzB,CAAC;IACF,MAAM,CACJ,mBAAmB,EACnB,GAAG,EACH,yDAAyD,EACzD,KAAK,CACN;IACD,MAAM,CAAC,oBAAoB,EAAE,GAAG,EAAE,8CAA8C,EAAE,OAAO,EAAE;QACzF,wBAAwB;KACzB,CAAC;IAOF,MAAM,CAAC,qBAAqB,EAAE,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE;QAClE,wBAAwB;QACxB,wBAAwB;KACzB,CAAC;IACF,MAAM,CAAC,wBAAwB,EAAE,GAAG,EAAE,+CAA+C,EAAE,KAAK,CAAC;IAC7F,MAAM,CAAC,gBAAgB,EAAE,GAAG,EAAE,gDAAgD,EAAE,KAAK,CAAC;IACtF,MAAM,CACJ,iBAAiB,EACjB,GAAG,EACH,gFAAgF;QAC9E,6BAA6B,EAC/B,KAAK,CACN;IACD,MAAM,CAAC,wBAAwB,EAAE,GAAG,EAAE,0CAA0C,EAAE,KAAK,CAAC;IACxF,MAAM,CAAC,sBAAsB,EAAE,GAAG,EAAE,4CAA4C,EAAE,KAAK,CAAC;IACxF,MAAM,CAAC,wBAAwB,EAAE,GAAG,EAAE,wCAAwC,EAAE,KAAK,CAAC;IACtF,MAAM,CAAC,kBAAkB,EAAE,GAAG,EAAE,sCAAsC,EAAE,KAAK,CAAC;IAC9E,MAAM,CACJ,2BAA2B,EAC3B,GAAG,EACH,oFAAoF,EACpF,OAAO,CACR;IACD,MAAM,CACJ,0BAA0B,EAC1B,GAAG,EACH,4DAA4D,EAC5D,OAAO,CACR;IACD,MAAM,CAAC,qBAAqB,EAAE,GAAG,EAAE,qBAAqB,EAAE,KAAK,CAAC;IAChE,MAAM,CAAC,gBAAgB,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE;QACrD,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,iBAAiB;QACjB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;KACzB,CAAC;IACF,MAAM,CAAC,gBAAgB,EAAE,GAAG,EAAE,yCAAyC,EAAE,KAAK,EAAE;QAC9E,iBAAiB;QACjB,iBAAiB;KAClB,CAAC;IAcF,MAAM,CAAC,cAAc,EAAE,GAAG,EAAE,yDAAyD,EAAE,KAAK,CAAC;IAG7F,MAAM,CAAC,qBAAqB,EAAE,GAAG,EAAE,iCAAiC,EAAE,KAAK,CAAC;CAC7B,CAAC;AAIlD,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,wBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAU,CAAC,CAClD,CAAC;AAEF,SAAgB,kBAAkB,CAAC,IAAiB;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IAC5D,OAAO,GAAG,CAAC;AACb,CAAC;AAMY,QAAA,qBAAqB,GAA6D;IAC7F,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,4CAA4C,EAAE;IACvF,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,4CAA4C,EAAE;IACvF,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,4CAA4C,EAAE;IACvF,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,4CAA4C,EAAE;IACvF,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,4CAA4C,EAAE;IACvF,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,kDAAkD,EAAE;IAC7F;QACE,UAAU,EAAE,wBAAwB;QACpC,MAAM,EAAE,8EAA8E;KACvF;CACF,CAAC"}
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../src/v1/errors/catalog.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,7 @@ export * from './endpoints/tokens';
9
9
  export * from './enums';
10
10
  export * from './errors/catalog';
11
11
  export * from './errors/problem';
12
+ export * from './maintenance/schema';
12
13
  export * from './primitives';
13
14
  export * from './signature';
14
15
  export declare const V1_API_VERSION: "v1";
package/dist/v1/index.js CHANGED
@@ -26,6 +26,7 @@ __exportStar(require("./endpoints/tokens"), exports);
26
26
  __exportStar(require("./enums"), exports);
27
27
  __exportStar(require("./errors/catalog"), exports);
28
28
  __exportStar(require("./errors/problem"), exports);
29
+ __exportStar(require("./maintenance/schema"), exports);
29
30
  __exportStar(require("./primitives"), exports);
30
31
  __exportStar(require("./signature"), exports);
31
32
  exports.V1_API_VERSION = 'v1';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/v1/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,gEAA8C;AAC9C,qDAAmC;AACnC,uDAAqC;AACrC,qDAAmC;AACnC,qDAAmC;AACnC,qDAAmC;AACnC,qDAAmC;AACnC,0CAAwB;AACxB,mDAAiC;AACjC,mDAAiC;AACjC,+CAA6B;AAC7B,8CAA4B;AAEf,QAAA,cAAc,GAAG,IAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/v1/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,gEAA8C;AAC9C,qDAAmC;AACnC,uDAAqC;AACrC,qDAAmC;AACnC,qDAAmC;AACnC,qDAAmC;AACnC,qDAAmC;AACnC,0CAAwB;AACxB,mDAAiC;AACjC,mDAAiC;AACjC,uDAAqC;AACrC,+CAA6B;AAC7B,8CAA4B;AAEf,QAAA,cAAc,GAAG,IAAa,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ export declare const MAINTENANCE_MESSAGE_MAX_LENGTH = 200;
3
+ declare const MaintenanceWindowShape: z.ZodObject<{
4
+ startsAt: z.ZodString;
5
+ endsAt: z.ZodString;
6
+ message: z.ZodString;
7
+ }, z.core.$strip>;
8
+ export declare const MaintenanceWindowSchema: z.ZodPreprocess<z.ZodObject<{
9
+ startsAt: z.ZodString;
10
+ endsAt: z.ZodString;
11
+ message: z.ZodString;
12
+ }, z.core.$strip>>;
13
+ export type MaintenanceWindow = z.infer<typeof MaintenanceWindowShape>;
14
+ declare const MaintenanceConfigShape: z.ZodObject<{
15
+ windows: z.ZodDefault<z.ZodArray<z.ZodPreprocess<z.ZodObject<{
16
+ startsAt: z.ZodString;
17
+ endsAt: z.ZodString;
18
+ message: z.ZodString;
19
+ }, z.core.$strip>>>>;
20
+ }, z.core.$strip>;
21
+ export declare const MaintenanceConfigSchema: z.ZodPreprocess<z.ZodObject<{
22
+ windows: z.ZodDefault<z.ZodArray<z.ZodPreprocess<z.ZodObject<{
23
+ startsAt: z.ZodString;
24
+ endsAt: z.ZodString;
25
+ message: z.ZodString;
26
+ }, z.core.$strip>>>>;
27
+ }, z.core.$strip>>;
28
+ export type MaintenanceConfig = z.infer<typeof MaintenanceConfigShape>;
29
+ export type MaintenanceConfigInput = z.input<typeof MaintenanceConfigShape>;
30
+ export {};
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MaintenanceConfigSchema = exports.MaintenanceWindowSchema = exports.MAINTENANCE_MESSAGE_MAX_LENGTH = void 0;
4
+ const zod_1 = require("zod");
5
+ const ISO_UTC_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
6
+ function isRealUtcInstant(value) {
7
+ const ms = Date.parse(value);
8
+ if (!Number.isFinite(ms))
9
+ return false;
10
+ return new Date(ms).toISOString().slice(0, 19) === value.slice(0, 19);
11
+ }
12
+ function utcMillis(value) {
13
+ return ISO_UTC_PATTERN.test(value) && isRealUtcInstant(value) ? Date.parse(value) : undefined;
14
+ }
15
+ const IsoUtcTimestamp = zod_1.z
16
+ .string({
17
+ error: 'must be an ISO-8601 UTC timestamp string; quote it in YAML',
18
+ })
19
+ .regex(ISO_UTC_PATTERN, 'must be an ISO-8601 UTC timestamp ending in Z, e.g. 2026-10-04T02:00:00Z')
20
+ .refine((value) => !ISO_UTC_PATTERN.test(value) || utcMillis(value) !== undefined, 'is not a real calendar instant');
21
+ exports.MAINTENANCE_MESSAGE_MAX_LENGTH = 200;
22
+ function rejectUnknownKeys(allowedKeys) {
23
+ const allowed = new Set(allowedKeys);
24
+ return (raw, ctx) => {
25
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
26
+ return raw;
27
+ const extra = Object.keys(raw).filter((key) => !allowed.has(key));
28
+ if (extra.length === 0)
29
+ return raw;
30
+ const quoted = extra.map((key) => `"${key}"`).join(', ');
31
+ ctx.addIssue({
32
+ code: 'custom',
33
+ message: extra.length === 1 ? `Unrecognized key: ${quoted}` : `Unrecognized keys: ${quoted}`,
34
+ });
35
+ return raw;
36
+ };
37
+ }
38
+ const MaintenanceWindowShape = zod_1.z
39
+ .object({
40
+ startsAt: IsoUtcTimestamp,
41
+ endsAt: IsoUtcTimestamp,
42
+ message: zod_1.z
43
+ .string()
44
+ .max(exports.MAINTENANCE_MESSAGE_MAX_LENGTH)
45
+ .refine((value) => value.trim().length > 0, 'must not be blank'),
46
+ })
47
+ .superRefine((window, ctx) => {
48
+ const startsMs = utcMillis(window.startsAt);
49
+ const endsMs = utcMillis(window.endsAt);
50
+ if (startsMs === undefined || endsMs === undefined)
51
+ return;
52
+ if (endsMs <= startsMs) {
53
+ ctx.addIssue({
54
+ code: 'custom',
55
+ path: ['endsAt'],
56
+ message: `endsAt ${window.endsAt} must be after startsAt ${window.startsAt}`,
57
+ });
58
+ }
59
+ });
60
+ exports.MaintenanceWindowSchema = zod_1.z.preprocess(rejectUnknownKeys(['startsAt', 'endsAt', 'message']), MaintenanceWindowShape);
61
+ const MaintenanceConfigShape = zod_1.z.object({
62
+ windows: zod_1.z.array(exports.MaintenanceWindowSchema).default([]),
63
+ });
64
+ exports.MaintenanceConfigSchema = zod_1.z.preprocess(rejectUnknownKeys(['windows']), MaintenanceConfigShape);
65
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/v1/maintenance/schema.ts"],"names":[],"mappings":"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"}
@@ -258,5 +258,10 @@
258
258
  "code": "service-unavailable",
259
259
  "status": 503,
260
260
  "legacyCodes": []
261
+ },
262
+ {
263
+ "code": "maintenance",
264
+ "status": 503,
265
+ "legacyCodes": []
261
266
  }
262
267
  ]
@@ -0,0 +1,405 @@
1
+ [
2
+ {
3
+ "name": "empty window list",
4
+ "windows": [],
5
+ "nowIso": "2026-10-04T03:00:00.000Z",
6
+ "expected": {
7
+ "active": false
8
+ }
9
+ },
10
+ {
11
+ "name": "one millisecond before startsAt",
12
+ "windows": [
13
+ {
14
+ "startsAt": "2026-10-04T02:00:00Z",
15
+ "endsAt": "2026-10-04T04:00:00Z",
16
+ "message": "Scheduled maintenance: database upgrade"
17
+ }
18
+ ],
19
+ "nowIso": "2026-10-04T01:59:59.999Z",
20
+ "expected": {
21
+ "active": false
22
+ }
23
+ },
24
+ {
25
+ "name": "exactly startsAt (start is inclusive)",
26
+ "windows": [
27
+ {
28
+ "startsAt": "2026-10-04T02:00:00Z",
29
+ "endsAt": "2026-10-04T04:00:00Z",
30
+ "message": "Scheduled maintenance: database upgrade"
31
+ }
32
+ ],
33
+ "nowIso": "2026-10-04T02:00:00.000Z",
34
+ "expected": {
35
+ "active": true,
36
+ "startsAt": "2026-10-04T02:00:00Z",
37
+ "endsAt": "2026-10-04T04:00:00Z",
38
+ "message": "Scheduled maintenance: database upgrade",
39
+ "retryAfterSeconds": 7200
40
+ }
41
+ },
42
+ {
43
+ "name": "mid-window",
44
+ "windows": [
45
+ {
46
+ "startsAt": "2026-10-04T02:00:00Z",
47
+ "endsAt": "2026-10-04T04:00:00Z",
48
+ "message": "Scheduled maintenance: database upgrade"
49
+ }
50
+ ],
51
+ "nowIso": "2026-10-04T03:00:00.000Z",
52
+ "expected": {
53
+ "active": true,
54
+ "startsAt": "2026-10-04T02:00:00Z",
55
+ "endsAt": "2026-10-04T04:00:00Z",
56
+ "message": "Scheduled maintenance: database upgrade",
57
+ "retryAfterSeconds": 3600
58
+ }
59
+ },
60
+ {
61
+ "name": "one millisecond before endsAt",
62
+ "windows": [
63
+ {
64
+ "startsAt": "2026-10-04T02:00:00Z",
65
+ "endsAt": "2026-10-04T04:00:00Z",
66
+ "message": "Scheduled maintenance: database upgrade"
67
+ }
68
+ ],
69
+ "nowIso": "2026-10-04T03:59:59.999Z",
70
+ "expected": {
71
+ "active": true,
72
+ "startsAt": "2026-10-04T02:00:00Z",
73
+ "endsAt": "2026-10-04T04:00:00Z",
74
+ "message": "Scheduled maintenance: database upgrade",
75
+ "retryAfterSeconds": 1
76
+ }
77
+ },
78
+ {
79
+ "name": "exactly endsAt (end is exclusive)",
80
+ "windows": [
81
+ {
82
+ "startsAt": "2026-10-04T02:00:00Z",
83
+ "endsAt": "2026-10-04T04:00:00Z",
84
+ "message": "Scheduled maintenance: database upgrade"
85
+ }
86
+ ],
87
+ "nowIso": "2026-10-04T04:00:00.000Z",
88
+ "expected": {
89
+ "active": false
90
+ }
91
+ },
92
+ {
93
+ "name": "fractional remainder rounds up: 1.5 s before endsAt",
94
+ "windows": [
95
+ {
96
+ "startsAt": "2026-10-04T02:00:00Z",
97
+ "endsAt": "2026-10-04T04:00:00Z",
98
+ "message": "Scheduled maintenance: database upgrade"
99
+ }
100
+ ],
101
+ "nowIso": "2026-10-04T03:59:58.500Z",
102
+ "expected": {
103
+ "active": true,
104
+ "startsAt": "2026-10-04T02:00:00Z",
105
+ "endsAt": "2026-10-04T04:00:00Z",
106
+ "message": "Scheduled maintenance: database upgrade",
107
+ "retryAfterSeconds": 2
108
+ }
109
+ },
110
+ {
111
+ "name": "whole-second remainder does not round up: 2 s before endsAt",
112
+ "windows": [
113
+ {
114
+ "startsAt": "2026-10-04T02:00:00Z",
115
+ "endsAt": "2026-10-04T04:00:00Z",
116
+ "message": "Scheduled maintenance: database upgrade"
117
+ }
118
+ ],
119
+ "nowIso": "2026-10-04T03:59:58.000Z",
120
+ "expected": {
121
+ "active": true,
122
+ "startsAt": "2026-10-04T02:00:00Z",
123
+ "endsAt": "2026-10-04T04:00:00Z",
124
+ "message": "Scheduled maintenance: database upgrade",
125
+ "retryAfterSeconds": 2
126
+ }
127
+ },
128
+ {
129
+ "name": "past-only window",
130
+ "windows": [
131
+ {
132
+ "startsAt": "2026-10-01T02:00:00Z",
133
+ "endsAt": "2026-10-01T04:00:00Z",
134
+ "message": "Last week's maintenance"
135
+ }
136
+ ],
137
+ "nowIso": "2026-10-04T03:00:00.000Z",
138
+ "expected": {
139
+ "active": false
140
+ }
141
+ },
142
+ {
143
+ "name": "future-only window",
144
+ "windows": [
145
+ {
146
+ "startsAt": "2026-10-11T02:00:00Z",
147
+ "endsAt": "2026-10-11T04:00:00Z",
148
+ "message": "Next week's maintenance"
149
+ }
150
+ ],
151
+ "nowIso": "2026-10-04T03:00:00.000Z",
152
+ "expected": {
153
+ "active": false
154
+ }
155
+ },
156
+ {
157
+ "name": "past and future windows, now between them",
158
+ "windows": [
159
+ {
160
+ "startsAt": "2026-10-01T02:00:00Z",
161
+ "endsAt": "2026-10-01T04:00:00Z",
162
+ "message": "Last week's maintenance"
163
+ },
164
+ {
165
+ "startsAt": "2026-10-11T02:00:00Z",
166
+ "endsAt": "2026-10-11T04:00:00Z",
167
+ "message": "Next week's maintenance"
168
+ }
169
+ ],
170
+ "nowIso": "2026-10-04T03:00:00.000Z",
171
+ "expected": {
172
+ "active": false
173
+ }
174
+ },
175
+ {
176
+ "name": "overlapping windows, only the first active",
177
+ "windows": [
178
+ {
179
+ "startsAt": "2026-10-04T02:00:00Z",
180
+ "endsAt": "2026-10-04T04:00:00Z",
181
+ "message": "A: database upgrade"
182
+ },
183
+ {
184
+ "startsAt": "2026-10-04T03:00:00Z",
185
+ "endsAt": "2026-10-04T05:00:00Z",
186
+ "message": "B: key rotation"
187
+ }
188
+ ],
189
+ "nowIso": "2026-10-04T02:30:00.000Z",
190
+ "expected": {
191
+ "active": true,
192
+ "startsAt": "2026-10-04T02:00:00Z",
193
+ "endsAt": "2026-10-04T04:00:00Z",
194
+ "message": "A: database upgrade",
195
+ "retryAfterSeconds": 5400
196
+ }
197
+ },
198
+ {
199
+ "name": "overlapping windows, both active: latest endsAt wins",
200
+ "windows": [
201
+ {
202
+ "startsAt": "2026-10-04T02:00:00Z",
203
+ "endsAt": "2026-10-04T04:00:00Z",
204
+ "message": "A: database upgrade"
205
+ },
206
+ {
207
+ "startsAt": "2026-10-04T03:00:00Z",
208
+ "endsAt": "2026-10-04T05:00:00Z",
209
+ "message": "B: key rotation"
210
+ }
211
+ ],
212
+ "nowIso": "2026-10-04T03:30:00.000Z",
213
+ "expected": {
214
+ "active": true,
215
+ "startsAt": "2026-10-04T03:00:00Z",
216
+ "endsAt": "2026-10-04T05:00:00Z",
217
+ "message": "B: key rotation",
218
+ "retryAfterSeconds": 5400
219
+ }
220
+ },
221
+ {
222
+ "name": "overlapping windows, both active, list reversed: latest endsAt still wins",
223
+ "windows": [
224
+ {
225
+ "startsAt": "2026-10-04T03:00:00Z",
226
+ "endsAt": "2026-10-04T05:00:00Z",
227
+ "message": "B: key rotation"
228
+ },
229
+ {
230
+ "startsAt": "2026-10-04T02:00:00Z",
231
+ "endsAt": "2026-10-04T04:00:00Z",
232
+ "message": "A: database upgrade"
233
+ }
234
+ ],
235
+ "nowIso": "2026-10-04T03:30:00.000Z",
236
+ "expected": {
237
+ "active": true,
238
+ "startsAt": "2026-10-04T03:00:00Z",
239
+ "endsAt": "2026-10-04T05:00:00Z",
240
+ "message": "B: key rotation",
241
+ "retryAfterSeconds": 5400
242
+ }
243
+ },
244
+ {
245
+ "name": "overlapping windows, after the first ends",
246
+ "windows": [
247
+ {
248
+ "startsAt": "2026-10-04T02:00:00Z",
249
+ "endsAt": "2026-10-04T04:00:00Z",
250
+ "message": "A: database upgrade"
251
+ },
252
+ {
253
+ "startsAt": "2026-10-04T03:00:00Z",
254
+ "endsAt": "2026-10-04T05:00:00Z",
255
+ "message": "B: key rotation"
256
+ }
257
+ ],
258
+ "nowIso": "2026-10-04T04:00:00.000Z",
259
+ "expected": {
260
+ "active": true,
261
+ "startsAt": "2026-10-04T03:00:00Z",
262
+ "endsAt": "2026-10-04T05:00:00Z",
263
+ "message": "B: key rotation",
264
+ "retryAfterSeconds": 3600
265
+ }
266
+ },
267
+ {
268
+ "name": "overlapping windows with equal endsAt: first in list wins",
269
+ "windows": [
270
+ {
271
+ "startsAt": "2026-10-04T02:00:00Z",
272
+ "endsAt": "2026-10-04T04:00:00Z",
273
+ "message": "A: database upgrade"
274
+ },
275
+ {
276
+ "startsAt": "2026-10-04T03:00:00Z",
277
+ "endsAt": "2026-10-04T04:00:00Z",
278
+ "message": "B: key rotation"
279
+ }
280
+ ],
281
+ "nowIso": "2026-10-04T03:30:00.000Z",
282
+ "expected": {
283
+ "active": true,
284
+ "startsAt": "2026-10-04T02:00:00Z",
285
+ "endsAt": "2026-10-04T04:00:00Z",
286
+ "message": "A: database upgrade",
287
+ "retryAfterSeconds": 1800
288
+ }
289
+ },
290
+ {
291
+ "name": "overlapping windows with equal endsAt, list reversed: first in list wins",
292
+ "windows": [
293
+ {
294
+ "startsAt": "2026-10-04T03:00:00Z",
295
+ "endsAt": "2026-10-04T04:00:00Z",
296
+ "message": "B: key rotation"
297
+ },
298
+ {
299
+ "startsAt": "2026-10-04T02:00:00Z",
300
+ "endsAt": "2026-10-04T04:00:00Z",
301
+ "message": "A: database upgrade"
302
+ }
303
+ ],
304
+ "nowIso": "2026-10-04T03:30:00.000Z",
305
+ "expected": {
306
+ "active": true,
307
+ "startsAt": "2026-10-04T03:00:00Z",
308
+ "endsAt": "2026-10-04T04:00:00Z",
309
+ "message": "B: key rotation",
310
+ "retryAfterSeconds": 1800
311
+ }
312
+ },
313
+ {
314
+ "name": "adjacent windows, one millisecond before the handoff",
315
+ "windows": [
316
+ {
317
+ "startsAt": "2026-10-04T02:00:00Z",
318
+ "endsAt": "2026-10-04T03:00:00Z",
319
+ "message": "A: database upgrade"
320
+ },
321
+ {
322
+ "startsAt": "2026-10-04T03:00:00Z",
323
+ "endsAt": "2026-10-04T04:00:00Z",
324
+ "message": "B: key rotation"
325
+ }
326
+ ],
327
+ "nowIso": "2026-10-04T02:59:59.999Z",
328
+ "expected": {
329
+ "active": true,
330
+ "startsAt": "2026-10-04T02:00:00Z",
331
+ "endsAt": "2026-10-04T03:00:00Z",
332
+ "message": "A: database upgrade",
333
+ "retryAfterSeconds": 1
334
+ }
335
+ },
336
+ {
337
+ "name": "adjacent windows at the handoff instant: the second window",
338
+ "windows": [
339
+ {
340
+ "startsAt": "2026-10-04T02:00:00Z",
341
+ "endsAt": "2026-10-04T03:00:00Z",
342
+ "message": "A: database upgrade"
343
+ },
344
+ {
345
+ "startsAt": "2026-10-04T03:00:00Z",
346
+ "endsAt": "2026-10-04T04:00:00Z",
347
+ "message": "B: key rotation"
348
+ }
349
+ ],
350
+ "nowIso": "2026-10-04T03:00:00.000Z",
351
+ "expected": {
352
+ "active": true,
353
+ "startsAt": "2026-10-04T03:00:00Z",
354
+ "endsAt": "2026-10-04T04:00:00Z",
355
+ "message": "B: key rotation",
356
+ "retryAfterSeconds": 3600
357
+ }
358
+ },
359
+ {
360
+ "name": "sub-second bounds, one millisecond before startsAt",
361
+ "windows": [
362
+ {
363
+ "startsAt": "2026-10-04T02:00:00.250Z",
364
+ "endsAt": "2026-10-04T02:00:01.750Z",
365
+ "message": "Sub-second window"
366
+ }
367
+ ],
368
+ "nowIso": "2026-10-04T02:00:00.249Z",
369
+ "expected": {
370
+ "active": false
371
+ }
372
+ },
373
+ {
374
+ "name": "sub-second bounds, exactly startsAt",
375
+ "windows": [
376
+ {
377
+ "startsAt": "2026-10-04T02:00:00.250Z",
378
+ "endsAt": "2026-10-04T02:00:01.750Z",
379
+ "message": "Sub-second window"
380
+ }
381
+ ],
382
+ "nowIso": "2026-10-04T02:00:00.250Z",
383
+ "expected": {
384
+ "active": true,
385
+ "startsAt": "2026-10-04T02:00:00.250Z",
386
+ "endsAt": "2026-10-04T02:00:01.750Z",
387
+ "message": "Sub-second window",
388
+ "retryAfterSeconds": 2
389
+ }
390
+ },
391
+ {
392
+ "name": "sub-second bounds, exactly endsAt",
393
+ "windows": [
394
+ {
395
+ "startsAt": "2026-10-04T02:00:00.250Z",
396
+ "endsAt": "2026-10-04T02:00:01.750Z",
397
+ "message": "Sub-second window"
398
+ }
399
+ ],
400
+ "nowIso": "2026-10-04T02:00:01.750Z",
401
+ "expected": {
402
+ "active": false
403
+ }
404
+ }
405
+ ]
package/openapi/v1.json CHANGED
@@ -6465,6 +6465,14 @@
6465
6465
  "type": "https://api.eco.com/v1/errors/service-unavailable",
6466
6466
  "audience": "all",
6467
6467
  "legacyCodes": []
6468
+ },
6469
+ {
6470
+ "code": "maintenance",
6471
+ "status": 503,
6472
+ "title": "Scheduled maintenance: new quotes and deposit addresses are paused until the window ends",
6473
+ "type": "https://api.eco.com/v1/errors/maintenance",
6474
+ "audience": "all",
6475
+ "legacyCodes": []
6468
6476
  }
6469
6477
  ]
6470
6478
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eco-foundation/api-schemas",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Zod-first schemas, error catalog, golden fixtures, and generated OpenAPI 3.1 for the Eco /v1 API",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -63,7 +63,7 @@
63
63
  "@scure/base": "^1.2.6"
64
64
  },
65
65
  "peerDependencies": {
66
- "zod": "^4.3.0"
66
+ "zod": "^4.4.0"
67
67
  },
68
68
  "peerDependenciesMeta": {
69
69
  "zod": {