@greatwait/contracts 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/authority.d.ts +217 -0
- package/dist/authority.js +261 -0
- package/dist/callback-ownership.d.ts +58 -0
- package/dist/callback-ownership.js +71 -0
- package/dist/callback-safety.d.ts +38 -0
- package/dist/callback-safety.js +403 -0
- package/dist/compatibility.d.ts +82 -0
- package/dist/compatibility.js +115 -0
- package/dist/delivery.d.ts +401 -0
- package/dist/delivery.js +311 -0
- package/dist/endpoints.d.ts +305 -0
- package/dist/endpoints.js +179 -0
- package/dist/events.d.ts +62 -0
- package/dist/events.js +38 -0
- package/dist/first-value-journey.d.ts +305 -0
- package/dist/first-value-journey.js +466 -0
- package/dist/forecast.d.ts +443 -0
- package/dist/forecast.js +729 -0
- package/dist/identifiers.d.ts +109 -0
- package/dist/identifiers.js +131 -0
- package/dist/identity.d.ts +196 -0
- package/dist/identity.js +182 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/mcp.d.ts +51 -0
- package/dist/mcp.js +56 -0
- package/dist/oauth.d.ts +796 -0
- package/dist/oauth.js +708 -0
- package/dist/operations.d.ts +3985 -0
- package/dist/operations.js +1837 -0
- package/dist/partition.d.ts +771 -0
- package/dist/partition.js +206 -0
- package/dist/policy.d.ts +93 -0
- package/dist/policy.js +180 -0
- package/dist/problems.d.ts +158 -0
- package/dist/problems.js +104 -0
- package/dist/protocol.d.ts +308 -0
- package/dist/protocol.js +515 -0
- package/dist/scheduling-records.d.ts +395 -0
- package/dist/scheduling-records.js +284 -0
- package/dist/scheduling.d.ts +1098 -0
- package/dist/scheduling.js +1116 -0
- package/dist/secrets.d.ts +383 -0
- package/dist/secrets.js +272 -0
- package/dist/signatures.d.ts +704 -0
- package/dist/signatures.js +867 -0
- package/dist/tenancy.d.ts +178 -0
- package/dist/tenancy.js +145 -0
- package/dist/test-sink.d.ts +986 -0
- package/dist/test-sink.js +755 -0
- package/dist/time.d.ts +45 -0
- package/dist/time.js +128 -0
- package/dist/usage.d.ts +121 -0
- package/dist/usage.js +200 -0
- package/dist/webhooks.d.ts +116 -0
- package/dist/webhooks.js +161 -0
- package/dist/workspace.d.ts +92 -0
- package/dist/workspace.js +59 -0
- package/generated/v1/callback-delivery-signature.json +467 -0
- package/generated/v1/callback-endpoint-verification.json +59 -0
- package/generated/v1/callback-receiver.json +241 -0
- package/generated/v1/examples/attempts.list.response.json +24 -0
- package/generated/v1/examples/attempts.read.response.json +20 -0
- package/generated/v1/examples/credentials.create.request.json +5 -0
- package/generated/v1/examples/credentials.create.response.json +10 -0
- package/generated/v1/examples/credentials.list.response.json +17 -0
- package/generated/v1/examples/credentials.revoke.response.json +11 -0
- package/generated/v1/examples/credentials.rotate.request.json +3 -0
- package/generated/v1/examples/credentials.rotate.response.json +22 -0
- package/generated/v1/examples/endpoints.create.request.json +3 -0
- package/generated/v1/examples/endpoints.create.response.json +16 -0
- package/generated/v1/examples/endpoints.disable.response.json +15 -0
- package/generated/v1/examples/endpoints.list.response.json +21 -0
- package/generated/v1/examples/endpoints.read.response.json +14 -0
- package/generated/v1/examples/endpoints.rotateSigningKey.request.json +3 -0
- package/generated/v1/examples/endpoints.rotateSigningKey.response.json +23 -0
- package/generated/v1/examples/endpoints.update.request.json +3 -0
- package/generated/v1/examples/endpoints.update.response.json +13 -0
- package/generated/v1/examples/endpoints.verify.response.json +15 -0
- package/generated/v1/examples/forecast.read.response.json +28 -0
- package/generated/v1/examples/forecast.windows.response.json +21 -0
- package/generated/v1/examples/grants.list.response.json +17 -0
- package/generated/v1/examples/grants.revoke.response.json +16 -0
- package/generated/v1/examples/meta.scaffold.response.json +5 -0
- package/generated/v1/examples/meta.version.response.json +5 -0
- package/generated/v1/examples/occurrences.list.response.json +17 -0
- package/generated/v1/examples/occurrences.read.response.json +15 -0
- package/generated/v1/examples/occurrences.redeliver.response.json +16 -0
- package/generated/v1/examples/projects.create.request.json +3 -0
- package/generated/v1/examples/projects.create.response.json +5 -0
- package/generated/v1/examples/projects.list.response.json +9 -0
- package/generated/v1/examples/schedules.archive.response.json +25 -0
- package/generated/v1/examples/schedules.cancel.response.json +24 -0
- package/generated/v1/examples/schedules.create.request.json +9 -0
- package/generated/v1/examples/schedules.create.response.json +25 -0
- package/generated/v1/examples/schedules.list.response.json +23 -0
- package/generated/v1/examples/schedules.pause.response.json +23 -0
- package/generated/v1/examples/schedules.read.response.json +25 -0
- package/generated/v1/examples/schedules.replace.request.json +8 -0
- package/generated/v1/examples/schedules.replace.response.json +25 -0
- package/generated/v1/examples/schedules.resume.response.json +23 -0
- package/generated/v1/examples/sinkdeliveries.list.response.json +19 -0
- package/generated/v1/examples/sinkdeliveries.read.response.json +16 -0
- package/generated/v1/examples/testsinks.configure.request.json +3 -0
- package/generated/v1/examples/testsinks.configure.response.json +13 -0
- package/generated/v1/examples/testsinks.create.request.json +8 -0
- package/generated/v1/examples/testsinks.create.response.json +13 -0
- package/generated/v1/examples/testsinks.delete.response.json +13 -0
- package/generated/v1/examples/testsinks.list.response.json +20 -0
- package/generated/v1/examples/testsinks.read.response.json +14 -0
- package/generated/v1/examples/usage.read.response.json +20 -0
- package/generated/v1/first-value-journey.json +436 -0
- package/generated/v1/manifest.json +1037 -0
- package/generated/v1/openapi.json +23665 -0
- package/generated/v1/policy-matrix.json +305 -0
- package/generated/v1/schemas/attempts.list.query.json +16 -0
- package/generated/v1/schemas/attempts.list.response.json +175 -0
- package/generated/v1/schemas/attempts.read.response.json +168 -0
- package/generated/v1/schemas/credentials.create.request.json +39 -0
- package/generated/v1/schemas/credentials.create.response.json +88 -0
- package/generated/v1/schemas/credentials.list.query.json +16 -0
- package/generated/v1/schemas/credentials.list.response.json +124 -0
- package/generated/v1/schemas/credentials.revoke.response.json +84 -0
- package/generated/v1/schemas/credentials.rotate.request.json +11 -0
- package/generated/v1/schemas/credentials.rotate.response.json +171 -0
- package/generated/v1/schemas/endpoints.create.request.json +12 -0
- package/generated/v1/schemas/endpoints.create.response.json +115 -0
- package/generated/v1/schemas/endpoints.disable.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.disable.response.json +100 -0
- package/generated/v1/schemas/endpoints.list.query.json +16 -0
- package/generated/v1/schemas/endpoints.list.response.json +146 -0
- package/generated/v1/schemas/endpoints.read.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.read.response.json +100 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.request.json +11 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.response.json +118 -0
- package/generated/v1/schemas/endpoints.update.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.update.request.json +12 -0
- package/generated/v1/schemas/endpoints.update.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.update.response.json +100 -0
- package/generated/v1/schemas/endpoints.verify.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.verify.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.verify.response.json +100 -0
- package/generated/v1/schemas/forecast.read.query.json +26 -0
- package/generated/v1/schemas/forecast.read.response.json +100 -0
- package/generated/v1/schemas/forecast.windows.query.json +26 -0
- package/generated/v1/schemas/forecast.windows.response.json +94 -0
- package/generated/v1/schemas/grants.list.response.json +98 -0
- package/generated/v1/schemas/grants.revoke.response.json +95 -0
- package/generated/v1/schemas/meta.scaffold.response.json +36 -0
- package/generated/v1/schemas/meta.version.response.json +19 -0
- package/generated/v1/schemas/occurrences.list.query.json +16 -0
- package/generated/v1/schemas/occurrences.list.response.json +83 -0
- package/generated/v1/schemas/occurrences.read.response.json +76 -0
- package/generated/v1/schemas/occurrences.redeliver.request-headers.json +13 -0
- package/generated/v1/schemas/occurrences.redeliver.response-headers.json +10 -0
- package/generated/v1/schemas/occurrences.redeliver.response.json +168 -0
- package/generated/v1/schemas/projects.create.request.json +12 -0
- package/generated/v1/schemas/projects.create.response.json +25 -0
- package/generated/v1/schemas/projects.list.response.json +35 -0
- package/generated/v1/schemas/schedules.archive.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.archive.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.archive.response.json +189 -0
- package/generated/v1/schemas/schedules.cancel.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.cancel.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.cancel.response.json +189 -0
- package/generated/v1/schemas/schedules.create.request-headers.json +12 -0
- package/generated/v1/schemas/schedules.create.request.json +99 -0
- package/generated/v1/schemas/schedules.create.response-headers.json +15 -0
- package/generated/v1/schemas/schedules.create.response.json +189 -0
- package/generated/v1/schemas/schedules.list.query.json +16 -0
- package/generated/v1/schemas/schedules.list.response.json +170 -0
- package/generated/v1/schemas/schedules.pause.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.pause.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.pause.response.json +189 -0
- package/generated/v1/schemas/schedules.read.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.read.response.json +189 -0
- package/generated/v1/schemas/schedules.replace.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.replace.request.json +99 -0
- package/generated/v1/schemas/schedules.replace.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.replace.response.json +189 -0
- package/generated/v1/schemas/schedules.resume.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.resume.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.resume.response.json +189 -0
- package/generated/v1/schemas/sinkdeliveries.list.query.json +16 -0
- package/generated/v1/schemas/sinkdeliveries.list.response.json +126 -0
- package/generated/v1/schemas/sinkdeliveries.read.response.json +98 -0
- package/generated/v1/schemas/testsinks.configure.request.json +66 -0
- package/generated/v1/schemas/testsinks.configure.response.json +115 -0
- package/generated/v1/schemas/testsinks.create.request.json +76 -0
- package/generated/v1/schemas/testsinks.create.response.json +115 -0
- package/generated/v1/schemas/testsinks.delete.response.json +115 -0
- package/generated/v1/schemas/testsinks.list.query.json +16 -0
- package/generated/v1/schemas/testsinks.list.response.json +161 -0
- package/generated/v1/schemas/testsinks.read.response.json +115 -0
- package/generated/v1/schemas/usage.read.query.json +15 -0
- package/generated/v1/schemas/usage.read.response.json +71 -0
- package/generated/v1/snippets/attempts.list.json +5 -0
- package/generated/v1/snippets/attempts.read.json +5 -0
- package/generated/v1/snippets/credentials.create.json +5 -0
- package/generated/v1/snippets/credentials.list.json +5 -0
- package/generated/v1/snippets/credentials.revoke.json +5 -0
- package/generated/v1/snippets/credentials.rotate.json +5 -0
- package/generated/v1/snippets/endpoints.create.json +5 -0
- package/generated/v1/snippets/endpoints.disable.json +5 -0
- package/generated/v1/snippets/endpoints.list.json +5 -0
- package/generated/v1/snippets/endpoints.read.json +5 -0
- package/generated/v1/snippets/endpoints.rotateSigningKey.json +5 -0
- package/generated/v1/snippets/endpoints.update.json +5 -0
- package/generated/v1/snippets/endpoints.verify.json +5 -0
- package/generated/v1/snippets/forecast.read.json +5 -0
- package/generated/v1/snippets/forecast.windows.json +5 -0
- package/generated/v1/snippets/grants.list.json +5 -0
- package/generated/v1/snippets/grants.revoke.json +5 -0
- package/generated/v1/snippets/meta.scaffold.json +5 -0
- package/generated/v1/snippets/meta.version.json +5 -0
- package/generated/v1/snippets/occurrences.list.json +5 -0
- package/generated/v1/snippets/occurrences.read.json +5 -0
- package/generated/v1/snippets/occurrences.redeliver.json +5 -0
- package/generated/v1/snippets/projects.create.json +5 -0
- package/generated/v1/snippets/projects.list.json +5 -0
- package/generated/v1/snippets/schedules.archive.json +5 -0
- package/generated/v1/snippets/schedules.cancel.json +5 -0
- package/generated/v1/snippets/schedules.create.json +5 -0
- package/generated/v1/snippets/schedules.list.json +5 -0
- package/generated/v1/snippets/schedules.pause.json +5 -0
- package/generated/v1/snippets/schedules.read.json +5 -0
- package/generated/v1/snippets/schedules.replace.json +5 -0
- package/generated/v1/snippets/schedules.resume.json +5 -0
- package/generated/v1/snippets/sinkdeliveries.list.json +5 -0
- package/generated/v1/snippets/sinkdeliveries.read.json +5 -0
- package/generated/v1/snippets/testsinks.configure.json +5 -0
- package/generated/v1/snippets/testsinks.create.json +5 -0
- package/generated/v1/snippets/testsinks.delete.json +5 -0
- package/generated/v1/snippets/testsinks.list.json +5 -0
- package/generated/v1/snippets/testsinks.read.json +5 -0
- package/generated/v1/snippets/usage.read.json +5 -0
- package/package.json +153 -0
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Inbound webhook authenticity.
|
|
4
|
+
*
|
|
5
|
+
* Clerk delivers organization and membership changes through Svix, which signs
|
|
6
|
+
* `<svix-id>.<svix-timestamp>.<raw body>` with HMAC-SHA256 under a secret minted
|
|
7
|
+
* per endpoint. This module owns the encoding and the header vocabulary; the
|
|
8
|
+
* adapter owns the key material and the constant-time comparison, exactly as
|
|
9
|
+
* `signatures.ts` splits outbound callback signing.
|
|
10
|
+
*
|
|
11
|
+
* The split matters. Keeping the signed-byte construction in the contract layer
|
|
12
|
+
* means every verifier — the Worker, a test, a future replay tool — signs the
|
|
13
|
+
* same bytes, and the reasoning below is stated once instead of being
|
|
14
|
+
* rediscovered by whoever writes the second one.
|
|
15
|
+
*/
|
|
16
|
+
export const SVIX_HEADERS = {
|
|
17
|
+
id: "svix-id",
|
|
18
|
+
signature: "svix-signature",
|
|
19
|
+
timestamp: "svix-timestamp",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Where Clerk delivers organization and membership changes.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately outside `/v1`. W1-07 enforces REST/MCP parity over every public
|
|
25
|
+
* `/v1` operation, and a webhook ingress is not a customer-facing operation —
|
|
26
|
+
* putting it there would either break that check or require an exception to it.
|
|
27
|
+
* Follows the internal-route convention set by `/__greatwait/version`.
|
|
28
|
+
*
|
|
29
|
+
* **Here rather than in the Worker that serves it, since W4-08.** The smoke
|
|
30
|
+
* asks a hosted stage whether this route is configured to accept a delivery,
|
|
31
|
+
* and a script cannot import the API application. A second spelling of an
|
|
32
|
+
* address Clerk is pointed at is one that can drift while both read as
|
|
33
|
+
* correct.
|
|
34
|
+
*/
|
|
35
|
+
export const CLERK_WEBHOOK_PATH = "/__greatwait/webhooks/clerk";
|
|
36
|
+
/** Only `v1` exists. A new scheme is added alongside, never in place of. */
|
|
37
|
+
export const WEBHOOK_SIGNATURE_SCHEME = "v1";
|
|
38
|
+
/**
|
|
39
|
+
* How far Clerk's clock may differ from ours before a delivery is stale.
|
|
40
|
+
*
|
|
41
|
+
* Svix's own guidance is five minutes, and matching it is deliberate: a tighter
|
|
42
|
+
* window would reject legitimate deliveries during a retry burst, and a wider
|
|
43
|
+
* one lengthens the replay window for a captured request.
|
|
44
|
+
*/
|
|
45
|
+
export const WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
46
|
+
/**
|
|
47
|
+
* A Svix message id. Deliberately a restricted charset rather than
|
|
48
|
+
* `z.string().min(1)` — see `webhookSigningInput` for why the absence of `.`
|
|
49
|
+
* here is a security property and not a formatting preference.
|
|
50
|
+
*/
|
|
51
|
+
export const svixIdSchema = z
|
|
52
|
+
.string()
|
|
53
|
+
.regex(/^msg_[A-Za-z0-9]{1,64}$/u, 'must be "msg_" followed by up to 64 alphanumeric characters');
|
|
54
|
+
/**
|
|
55
|
+
* Seconds since the Unix epoch, as digits. No sign, no decimal point, no
|
|
56
|
+
* exponent — `Number.parseInt` would happily accept `+1e9` and `12.5`, and both
|
|
57
|
+
* would reach the signed bytes in a form we did not intend.
|
|
58
|
+
*/
|
|
59
|
+
export const svixTimestampSchema = z
|
|
60
|
+
.string()
|
|
61
|
+
.regex(/^[0-9]{1,15}$/u, "must be a whole number of seconds since the epoch");
|
|
62
|
+
/**
|
|
63
|
+
* One `v1,<base64>` pair. Svix sends a space-delimited list so an endpoint can
|
|
64
|
+
* be verified under both keys during rotation, which is why the verifier must
|
|
65
|
+
* accept a match against *any* entry rather than parsing a single value.
|
|
66
|
+
*/
|
|
67
|
+
export const svixSignatureEntryPattern = /^v1,[A-Za-z0-9+/]+={0,2}$/u;
|
|
68
|
+
export const svixSignatureHeaderSchema = z
|
|
69
|
+
.string()
|
|
70
|
+
.min(1)
|
|
71
|
+
.refine((header) => header
|
|
72
|
+
.split(" ")
|
|
73
|
+
.filter((entry) => entry.length > 0)
|
|
74
|
+
.some((entry) => svixSignatureEntryPattern.test(entry)), "must contain at least one v1,<base64> signature");
|
|
75
|
+
export const svixHeadersSchema = z.object({
|
|
76
|
+
[SVIX_HEADERS.id]: svixIdSchema,
|
|
77
|
+
[SVIX_HEADERS.signature]: svixSignatureHeaderSchema,
|
|
78
|
+
[SVIX_HEADERS.timestamp]: svixTimestampSchema,
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* Every `v1` signature offered in the header, base64 still encoded.
|
|
82
|
+
*
|
|
83
|
+
* Entries in other schemes are dropped rather than rejected: Svix may add a
|
|
84
|
+
* scheme before we support it, and a delivery that carries both `v1` and a
|
|
85
|
+
* future `v2` must still verify under `v1` instead of failing closed on the
|
|
86
|
+
* presence of something newer.
|
|
87
|
+
*/
|
|
88
|
+
export function webhookSignatureCandidates(header) {
|
|
89
|
+
return header
|
|
90
|
+
.split(" ")
|
|
91
|
+
.filter((entry) => svixSignatureEntryPattern.test(entry))
|
|
92
|
+
.map((entry) => entry.slice(`${WEBHOOK_SIGNATURE_SCHEME},`.length));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The exact bytes Svix signs: `<id>.<timestamp>.` followed by the raw body,
|
|
96
|
+
* with no re-serialization anywhere in the path.
|
|
97
|
+
*
|
|
98
|
+
* Both prefix fields are validated rather than trusted, and — as with
|
|
99
|
+
* `signingInput` for outbound callbacks — that is a security property. The
|
|
100
|
+
* delimiter is `.`, so if either field could itself contain a `.` the encoding
|
|
101
|
+
* would not be injective: an attacker holding one legitimate signature could
|
|
102
|
+
* shift bytes across a delimiter, lengthening the id header while shortening
|
|
103
|
+
* the body, and still verify.
|
|
104
|
+
*
|
|
105
|
+
* The argument here is slightly different from the outbound one, which relies
|
|
106
|
+
* on both fields being fixed-width. `svixIdSchema` and `svixTimestampSchema`
|
|
107
|
+
* admit no `.` at all, so splitting the signed content on its first two dots
|
|
108
|
+
* recovers the id and the timestamp uniquely whatever their lengths. Variable
|
|
109
|
+
* width is fine; a dot would not be.
|
|
110
|
+
*
|
|
111
|
+
* **Throws** on malformed input. Use `tryWebhookSigningInput` when verifying
|
|
112
|
+
* headers off the wire, or a malformed-header probe becomes a 500.
|
|
113
|
+
*/
|
|
114
|
+
export function webhookSigningInput(svixId, svixTimestamp, rawBody) {
|
|
115
|
+
const checkedId = svixIdSchema.parse(svixId);
|
|
116
|
+
const checkedTimestamp = svixTimestampSchema.parse(svixTimestamp);
|
|
117
|
+
const prefix = `${checkedId}.${checkedTimestamp}.`;
|
|
118
|
+
// Both fields are ASCII by construction, now that both have been validated, so
|
|
119
|
+
// UTF-8 encoding them is byte-identical to their code points.
|
|
120
|
+
const prefixBytes = new TextEncoder().encode(prefix);
|
|
121
|
+
const input = new Uint8Array(prefixBytes.length + rawBody.length);
|
|
122
|
+
input.set(prefixBytes, 0);
|
|
123
|
+
input.set(rawBody, prefixBytes.length);
|
|
124
|
+
return input;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* `webhookSigningInput` for untrusted input: `undefined` instead of a throw when
|
|
128
|
+
* the headers are malformed.
|
|
129
|
+
*/
|
|
130
|
+
export function tryWebhookSigningInput(svixId, svixTimestamp, rawBody) {
|
|
131
|
+
try {
|
|
132
|
+
return webhookSigningInput(svixId, svixTimestamp, rawBody);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
// Only a validation failure means "malformed headers". Anything else — an
|
|
136
|
+
// allocation failure on an oversized body, say — is a real fault and must
|
|
137
|
+
// not be reported to the caller as a bad signature.
|
|
138
|
+
if (error instanceof z.ZodError) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Whether a delivery's timestamp is close enough to now.
|
|
146
|
+
*
|
|
147
|
+
* Rejects the future as firmly as the past. A signature dated an hour ahead is
|
|
148
|
+
* not a clock-skew inconvenience — accepting it would hand an attacker who
|
|
149
|
+
* captures one request an hour of replay window, so the comparison is on
|
|
150
|
+
* absolute difference.
|
|
151
|
+
*
|
|
152
|
+
* Replay protection beyond this window belongs to the receiver, keyed on the
|
|
153
|
+
* Svix message id; freshness alone does not make a delivery unique.
|
|
154
|
+
*/
|
|
155
|
+
export function isWebhookTimestampFresh(svixTimestamp, nowMs, toleranceSeconds = WEBHOOK_TOLERANCE_SECONDS) {
|
|
156
|
+
if (!svixTimestampSchema.safeParse(svixTimestamp).success) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
const signedMs = Number(svixTimestamp) * 1000;
|
|
160
|
+
return Math.abs(nowMs - signedMs) <= toleranceSeconds * 1000;
|
|
161
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Repository scaffold contracts.
|
|
4
|
+
*
|
|
5
|
+
* These schemas describe the workspace itself — the layer a package occupies
|
|
6
|
+
* and the scaffold responses the apps expose. The public V1 product contract
|
|
7
|
+
* (IDs, timestamps, pagination, errors, idempotency, events, signatures) is
|
|
8
|
+
* defined by W0-02 and does not belong here.
|
|
9
|
+
*/
|
|
10
|
+
export declare const WORKSPACE_LAYERS: readonly ["contract", "platform", "domain", "application", "adapter", "test-support"];
|
|
11
|
+
export declare const workspaceLayerSchema: z.ZodEnum<{
|
|
12
|
+
contract: "contract";
|
|
13
|
+
platform: "platform";
|
|
14
|
+
domain: "domain";
|
|
15
|
+
application: "application";
|
|
16
|
+
adapter: "adapter";
|
|
17
|
+
"test-support": "test-support";
|
|
18
|
+
}>;
|
|
19
|
+
export type WorkspaceLayer = z.infer<typeof workspaceLayerSchema>;
|
|
20
|
+
export declare const workspaceIdentitySchema: z.ZodObject<{
|
|
21
|
+
layer: z.ZodEnum<{
|
|
22
|
+
contract: "contract";
|
|
23
|
+
platform: "platform";
|
|
24
|
+
domain: "domain";
|
|
25
|
+
application: "application";
|
|
26
|
+
adapter: "adapter";
|
|
27
|
+
"test-support": "test-support";
|
|
28
|
+
}>;
|
|
29
|
+
workspace: z.ZodString;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
export type WorkspaceIdentity = z.infer<typeof workspaceIdentitySchema>;
|
|
32
|
+
/** Shape of `GET /__greatwait/version` on every Greatwait Worker. */
|
|
33
|
+
export declare const scaffoldVersionSchema: z.ZodObject<{
|
|
34
|
+
buildSha: z.ZodString;
|
|
35
|
+
environment: z.ZodString;
|
|
36
|
+
service: z.ZodString;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export type ScaffoldVersion = z.infer<typeof scaffoldVersionSchema>;
|
|
39
|
+
/** Shape of the scaffold description an adapter reads from the application. */
|
|
40
|
+
export declare const workspaceScaffoldSchema: z.ZodObject<{
|
|
41
|
+
dependsOn: z.ZodArray<z.ZodEnum<{
|
|
42
|
+
contract: "contract";
|
|
43
|
+
platform: "platform";
|
|
44
|
+
domain: "domain";
|
|
45
|
+
application: "application";
|
|
46
|
+
adapter: "adapter";
|
|
47
|
+
"test-support": "test-support";
|
|
48
|
+
}>>;
|
|
49
|
+
layer: z.ZodEnum<{
|
|
50
|
+
contract: "contract";
|
|
51
|
+
platform: "platform";
|
|
52
|
+
domain: "domain";
|
|
53
|
+
application: "application";
|
|
54
|
+
adapter: "adapter";
|
|
55
|
+
"test-support": "test-support";
|
|
56
|
+
}>;
|
|
57
|
+
service: z.ZodString;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
export type WorkspaceScaffold = z.infer<typeof workspaceScaffoldSchema>;
|
|
60
|
+
/**
|
|
61
|
+
* The minimal refusal body the scaffold routes return.
|
|
62
|
+
*
|
|
63
|
+
* Superseded by `problems.ts`, which W0-02 defined. This remains only for the
|
|
64
|
+
* scaffold routes in `apps/api` and `apps/contract-mock`; those move to the
|
|
65
|
+
* real problem document when W1-06 replaces the scaffold with the REST
|
|
66
|
+
* control plane. New code uses `problem()`.
|
|
67
|
+
*/
|
|
68
|
+
export declare const scaffoldProblemSchema: z.ZodObject<{
|
|
69
|
+
title: z.ZodString;
|
|
70
|
+
type: z.ZodLiteral<"about:blank">;
|
|
71
|
+
}, z.core.$strip>;
|
|
72
|
+
export type ScaffoldProblem = z.infer<typeof scaffoldProblemSchema>;
|
|
73
|
+
/**
|
|
74
|
+
* A transport-neutral tool descriptor. REST and MCP publish the same shape, so
|
|
75
|
+
* the two surfaces cannot drift into describing tools differently.
|
|
76
|
+
*/
|
|
77
|
+
export declare const toolDescriptorSchema: z.ZodObject<{
|
|
78
|
+
description: z.ZodString;
|
|
79
|
+
name: z.ZodString;
|
|
80
|
+
readOnly: z.ZodBoolean;
|
|
81
|
+
title: z.ZodString;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
export type ToolDescriptor = z.infer<typeof toolDescriptorSchema>;
|
|
84
|
+
export declare const toolListSchema: z.ZodObject<{
|
|
85
|
+
tools: z.ZodArray<z.ZodObject<{
|
|
86
|
+
description: z.ZodString;
|
|
87
|
+
name: z.ZodString;
|
|
88
|
+
readOnly: z.ZodBoolean;
|
|
89
|
+
title: z.ZodString;
|
|
90
|
+
}, z.core.$strip>>;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
export type ToolList = z.infer<typeof toolListSchema>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Repository scaffold contracts.
|
|
4
|
+
*
|
|
5
|
+
* These schemas describe the workspace itself — the layer a package occupies
|
|
6
|
+
* and the scaffold responses the apps expose. The public V1 product contract
|
|
7
|
+
* (IDs, timestamps, pagination, errors, idempotency, events, signatures) is
|
|
8
|
+
* defined by W0-02 and does not belong here.
|
|
9
|
+
*/
|
|
10
|
+
export const WORKSPACE_LAYERS = [
|
|
11
|
+
"contract",
|
|
12
|
+
"platform",
|
|
13
|
+
"domain",
|
|
14
|
+
"application",
|
|
15
|
+
"adapter",
|
|
16
|
+
"test-support",
|
|
17
|
+
];
|
|
18
|
+
export const workspaceLayerSchema = z.enum(WORKSPACE_LAYERS);
|
|
19
|
+
export const workspaceIdentitySchema = z.object({
|
|
20
|
+
layer: workspaceLayerSchema,
|
|
21
|
+
workspace: z.string().min(1),
|
|
22
|
+
});
|
|
23
|
+
/** Shape of `GET /__greatwait/version` on every Greatwait Worker. */
|
|
24
|
+
export const scaffoldVersionSchema = z.object({
|
|
25
|
+
buildSha: z.string().min(1),
|
|
26
|
+
environment: z.string().min(1),
|
|
27
|
+
service: z.string().min(1),
|
|
28
|
+
});
|
|
29
|
+
/** Shape of the scaffold description an adapter reads from the application. */
|
|
30
|
+
export const workspaceScaffoldSchema = z.object({
|
|
31
|
+
dependsOn: z.array(workspaceLayerSchema),
|
|
32
|
+
layer: workspaceLayerSchema,
|
|
33
|
+
service: z.string().min(1),
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* The minimal refusal body the scaffold routes return.
|
|
37
|
+
*
|
|
38
|
+
* Superseded by `problems.ts`, which W0-02 defined. This remains only for the
|
|
39
|
+
* scaffold routes in `apps/api` and `apps/contract-mock`; those move to the
|
|
40
|
+
* real problem document when W1-06 replaces the scaffold with the REST
|
|
41
|
+
* control plane. New code uses `problem()`.
|
|
42
|
+
*/
|
|
43
|
+
export const scaffoldProblemSchema = z.object({
|
|
44
|
+
title: z.string().min(1),
|
|
45
|
+
type: z.literal("about:blank"),
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* A transport-neutral tool descriptor. REST and MCP publish the same shape, so
|
|
49
|
+
* the two surfaces cannot drift into describing tools differently.
|
|
50
|
+
*/
|
|
51
|
+
export const toolDescriptorSchema = z.object({
|
|
52
|
+
description: z.string().min(1),
|
|
53
|
+
name: z.string().min(1),
|
|
54
|
+
readOnly: z.boolean(),
|
|
55
|
+
title: z.string().min(1),
|
|
56
|
+
});
|
|
57
|
+
export const toolListSchema = z.object({
|
|
58
|
+
tools: z.array(toolDescriptorSchema),
|
|
59
|
+
});
|