@greatwait/contracts 0.1.0-alpha.1 → 0.1.0-alpha.2
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/dist/callback-receiver.d.ts +50 -0
- package/dist/callback-receiver.js +51 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/operations.d.ts +2 -2
- package/dist/scheduling.d.ts +2 -2
- package/dist/test-sink.d.ts +24 -4
- package/dist/test-sink.js +13 -2
- package/generated/v1/callback-receiver.json +1 -1
- package/generated/v1/openapi.json +1 -1
- package/package.json +6 -1
- package/dist/mcp.d.ts +0 -51
- package/dist/mcp.js +0 -56
- package/dist/webhooks.d.ts +0 -116
- package/dist/webhooks.js +0 -161
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds a receiver applies to the deliveries this service sends it.
|
|
3
|
+
*
|
|
4
|
+
* **A receiver's bounds are not the sender's, and the shipped SDK used the
|
|
5
|
+
* sender's.** `createNodeCallbackMiddleware` and the receiver conformance suite
|
|
6
|
+
* both defaulted their body limit to `TEST_SINK_BOUNDS.maxRequestBodyBytes` —
|
|
7
|
+
* a number whose every neighbour is justified by the test sink running *inside
|
|
8
|
+
* the API Worker's request budget, on the product's own account*. None of that
|
|
9
|
+
* reasoning is about a customer's own server, and the value it produced was
|
|
10
|
+
* wrong for one.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Headroom for the envelope wrapped around a payload.
|
|
14
|
+
*
|
|
15
|
+
* A receiver is sent an envelope, not a payload: version, type, schedule and
|
|
16
|
+
* occurrence identifiers, the planned time, the attempt ordinal, the trigger
|
|
17
|
+
* that says why the delivery exists, and in `callback` mode a completion URL,
|
|
18
|
+
* bearer token and deadline. All of that is additive to `payloadBytes`.
|
|
19
|
+
*
|
|
20
|
+
* **Measured, not chosen.** Serializing every published delivery test vector
|
|
21
|
+
* around a payload at exactly `SCHEDULING_BOUNDS.payloadBytes`, with a 180-
|
|
22
|
+
* character completion host and a 512-character token — both far past anything
|
|
23
|
+
* the service mints — the largest envelope exceeded the payload maximum by
|
|
24
|
+
* 1,097 bytes. `callback-receiver.test.ts` re-derives that rather than trusting
|
|
25
|
+
* this sentence, so an envelope that grows past the headroom fails a test
|
|
26
|
+
* instead of silently costing a customer their largest deliveries.
|
|
27
|
+
*
|
|
28
|
+
* Four kibibytes is roughly four times the measured worst case, which is slack
|
|
29
|
+
* enough to absorb a new envelope field and tight enough that this is still a
|
|
30
|
+
* bound rather than a shrug.
|
|
31
|
+
*/
|
|
32
|
+
export declare const CALLBACK_ENVELOPE_HEADROOM_BYTES = 4096;
|
|
33
|
+
/**
|
|
34
|
+
* The largest callback body a conforming receiver reads before refusing.
|
|
35
|
+
*
|
|
36
|
+
* Derived rather than written down, because the thing that must stay true is
|
|
37
|
+
* the *relationship*: a receiver has to accept the largest payload this service
|
|
38
|
+
* will accept, plus whatever the envelope adds. A literal here would be a
|
|
39
|
+
* second statement of `payloadBytes` and would go stale the first time that
|
|
40
|
+
* moved.
|
|
41
|
+
*
|
|
42
|
+
* **The defect this replaces was live in `0.1.0-alpha.1`.** The old default was
|
|
43
|
+
* 65,536 — exactly `payloadBytes`, and therefore below every envelope carrying
|
|
44
|
+
* a maximum payload. A customer scheduling their largest legal payload against
|
|
45
|
+
* a receiver built with the SDK's defaults got `413 body_too_large`, which the
|
|
46
|
+
* sender classifies as terminal: the occurrence fails and is not retried. The
|
|
47
|
+
* bound was reachable by anyone using both the documented maximum and the
|
|
48
|
+
* documented defaults.
|
|
49
|
+
*/
|
|
50
|
+
export declare const CALLBACK_RECEIVER_MAX_BODY_BYTES: number;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { SCHEDULING_BOUNDS } from "./scheduling.js";
|
|
2
|
+
/**
|
|
3
|
+
* Bounds a receiver applies to the deliveries this service sends it.
|
|
4
|
+
*
|
|
5
|
+
* **A receiver's bounds are not the sender's, and the shipped SDK used the
|
|
6
|
+
* sender's.** `createNodeCallbackMiddleware` and the receiver conformance suite
|
|
7
|
+
* both defaulted their body limit to `TEST_SINK_BOUNDS.maxRequestBodyBytes` —
|
|
8
|
+
* a number whose every neighbour is justified by the test sink running *inside
|
|
9
|
+
* the API Worker's request budget, on the product's own account*. None of that
|
|
10
|
+
* reasoning is about a customer's own server, and the value it produced was
|
|
11
|
+
* wrong for one.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Headroom for the envelope wrapped around a payload.
|
|
15
|
+
*
|
|
16
|
+
* A receiver is sent an envelope, not a payload: version, type, schedule and
|
|
17
|
+
* occurrence identifiers, the planned time, the attempt ordinal, the trigger
|
|
18
|
+
* that says why the delivery exists, and in `callback` mode a completion URL,
|
|
19
|
+
* bearer token and deadline. All of that is additive to `payloadBytes`.
|
|
20
|
+
*
|
|
21
|
+
* **Measured, not chosen.** Serializing every published delivery test vector
|
|
22
|
+
* around a payload at exactly `SCHEDULING_BOUNDS.payloadBytes`, with a 180-
|
|
23
|
+
* character completion host and a 512-character token — both far past anything
|
|
24
|
+
* the service mints — the largest envelope exceeded the payload maximum by
|
|
25
|
+
* 1,097 bytes. `callback-receiver.test.ts` re-derives that rather than trusting
|
|
26
|
+
* this sentence, so an envelope that grows past the headroom fails a test
|
|
27
|
+
* instead of silently costing a customer their largest deliveries.
|
|
28
|
+
*
|
|
29
|
+
* Four kibibytes is roughly four times the measured worst case, which is slack
|
|
30
|
+
* enough to absorb a new envelope field and tight enough that this is still a
|
|
31
|
+
* bound rather than a shrug.
|
|
32
|
+
*/
|
|
33
|
+
export const CALLBACK_ENVELOPE_HEADROOM_BYTES = 4_096;
|
|
34
|
+
/**
|
|
35
|
+
* The largest callback body a conforming receiver reads before refusing.
|
|
36
|
+
*
|
|
37
|
+
* Derived rather than written down, because the thing that must stay true is
|
|
38
|
+
* the *relationship*: a receiver has to accept the largest payload this service
|
|
39
|
+
* will accept, plus whatever the envelope adds. A literal here would be a
|
|
40
|
+
* second statement of `payloadBytes` and would go stale the first time that
|
|
41
|
+
* moved.
|
|
42
|
+
*
|
|
43
|
+
* **The defect this replaces was live in `0.1.0-alpha.1`.** The old default was
|
|
44
|
+
* 65,536 — exactly `payloadBytes`, and therefore below every envelope carrying
|
|
45
|
+
* a maximum payload. A customer scheduling their largest legal payload against
|
|
46
|
+
* a receiver built with the SDK's defaults got `413 body_too_large`, which the
|
|
47
|
+
* sender classifies as terminal: the occurrence fails and is not retried. The
|
|
48
|
+
* bound was reachable by anyone using both the documented maximum and the
|
|
49
|
+
* documented defaults.
|
|
50
|
+
*/
|
|
51
|
+
export const CALLBACK_RECEIVER_MAX_BODY_BYTES = SCHEDULING_BOUNDS.payloadBytes + CALLBACK_ENVELOPE_HEADROOM_BYTES;
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,6 @@ export { CREDENTIAL_PREFIX_LENGTH, CREDENTIAL_PREFIX_PATTERN, CREDENTIAL_ROTATIO
|
|
|
8
8
|
export { MAX_PARTITION_SHARDS, PROJECTION_DRAIN_LIMIT, SINGLE_SHARD, partitionInstanceName, partitionShardCount, partitionShardFor, partitionShardForIdempotencyKey, type CreateScheduleCommand, type CreateScheduleOutcome, type AttemptPagePosition, type DrainProjectionsOutcome, type DrainProjectionsQuery, type ForecastOutcome, type ForecastPartitionBucket, type ForecastQuery, type IdempotencyDisposition, type ListSchedulesOutcome, type ListSchedulesQuery, type ListAttemptsOutcome, type ListAttemptsQuery, type ListOccurrencesOutcome, type ListOccurrencesQuery, type OccurrencePagePosition, type PartitionOutcome, type PartitionRefusal, type PartitionRefused, type PartitionRpc, type ProjectionIntent, type ReadScheduleQuery, type ReadAttemptOutcome, type ReadAttemptQuery, type ReadOccurrenceOutcome, type ReadOccurrenceQuery, type RedeliverOccurrenceCommand, type RedeliverOccurrenceOutcome, type ReplaceScheduleCommand, type RetainedProjections, type ScheduleDefinition, type ScheduleWriteCommand, type ScheduleWriteOutcome, type SettleProjectionsCommand, type SettleProjectionsOutcome, } from "./partition.js";
|
|
9
9
|
export { EVENT_TYPES, eventEnvelopeSchema, eventTypeSchema, genericEventSchema, type EventType, type GenericEvent, } from "./events.js";
|
|
10
10
|
export { MEMBERSHIP_ROLES, MEMBERSHIP_STATUSES, externalMembershipIdSchema, externalOrganizationIdSchema, externalUserIdSchema, grantsAccess, leastRole, leastStatus, membershipChangeSchema, membershipRoleSchema, membershipSchema, membershipStatusSchema, organizationTombstoneSchema, sessionMembershipSchema, type ExternalMembershipId, type ExternalOrganizationId, type ExternalUserId, type Membership, type MembershipChange, type MembershipRole, type MembershipStatus, type OrganizationTombstone, type SessionMembership, } from "./identity.js";
|
|
11
|
-
export { CLERK_WEBHOOK_PATH, SVIX_HEADERS, WEBHOOK_SIGNATURE_SCHEME, WEBHOOK_TOLERANCE_SECONDS, isWebhookTimestampFresh, svixHeadersSchema, svixIdSchema, svixSignatureHeaderSchema, svixTimestampSchema, tryWebhookSigningInput, webhookSignatureCandidates, webhookSigningInput, } from "./webhooks.js";
|
|
12
11
|
export { CHANGE_KINDS, CHANGE_OPERATIONS, CHANGE_SUBJECTS, CONTRACT_VERSION, assess, changeKindSchema, classifyChange, contractVersionSchema, type ChangeKind, type ChangeOperation, type ChangeSubject, type CompatibilityVerdict, type ContractChange, } from "./compatibility.js";
|
|
13
12
|
export { WORKSPACE_LAYERS, scaffoldProblemSchema, scaffoldVersionSchema, toolDescriptorSchema, toolListSchema, workspaceIdentitySchema, workspaceLayerSchema, workspaceScaffoldSchema, type ScaffoldProblem, type ScaffoldVersion, type ToolDescriptor, type ToolList, type WorkspaceIdentity, type WorkspaceLayer, type WorkspaceScaffold, } from "./workspace.js";
|
|
14
13
|
export { MOCK_BEHAVIORS, MOCK_HEADERS, OPERATIONS, UNIVERSAL_PROBLEMS, AUTHENTICATED_PROBLEMS, findOperation, fixtureListSchema, mockBehaviorSchema, mockScenarioRequestSchema, mockScenarioResponseSchema, operationsFor, problemsFor, requestSchemaFor, responseSchemaFor, successStatusFor, type FixtureList, type McpDisposition, type MockBehavior, type MockScenarioRequest, type MockScenarioResponse, type OperationDefinition, type OperationId, type OperationSurface, } from "./operations.js";
|
|
@@ -17,7 +16,7 @@ export { ATTEMPT_DURATION_MAX_MS, ATTEMPT_FAILURE_SNIPPET_MAX_LENGTH, ATTEMPT_RE
|
|
|
17
16
|
export { portableAttemptRecordSchema, portableEncryptedPayloadRecordSchema, portableOccurrenceRecordSchema, portableReceiptRecordSchema, portableScheduleRecordSchema, portableSchedulingRecordSetSchema, type PortableAttemptRecord, type PortableEncryptedPayloadRecord, type PortableOccurrenceRecord, type PortableReceiptRecord, type PortableScheduleRecord, type PortableSchedulingRecordSet, } from "./scheduling-records.js";
|
|
18
17
|
export { ROLE_SCOPE_CEILINGS, capScopesToRole, ceilingForRole, credentialScopes, grantableScopes, policyMatrix, roleAllows, roleCanPerform, type PolicyMatrixRow, } from "./policy.js";
|
|
19
18
|
export { ACCESS_TOKEN_PATTERN, ACCESS_TOKEN_TTL_SECONDS, AUTHORIZATION_CODE_PATTERN, AUTHORIZATION_CODE_TTL_SECONDS, AUTHORIZATION_REQUEST_TTL_SECONDS, MAX_REDIRECT_URIS, OAUTH_ERRORS, OAUTH_ERROR_STATUS, OAUTH_GRANT_STATUSES, OAUTH_GRANT_TYPES, OAUTH_RESPONSE_TYPES, OAUTH_REVOCATION_REASONS, OAUTH_ROUTES, OAUTH_SECRET_PATTERN, OAUTH_TOKEN_NAMESPACE_LENGTH, OAUTH_TOKEN_SECRET_LENGTH, PKCE_CHALLENGE_PATTERN, PKCE_METHODS, PKCE_VERIFIER_PATTERN, REFRESH_TOKEN_PATTERN, REFRESH_TOKEN_TTL_SECONDS, TOKEN_ENDPOINT_AUTH_METHODS, authorizationParametersSchema, authorizationServerMetadataSchema, bearerChallenge, clientRegistrationRequestSchema, clientRegistrationSchema, consentDecisionSchema, consentOutcomeSchema, consentPromptSchema, formatScopeString, isLoopbackRedirect, oauthError, oauthErrorCodeSchema, oauthErrorSchema, oauthGrantListSchema, oauthGrantRevokedSchema, oauthGrantSchema, oauthGrantStatusSchema, oauthGrantTypeSchema, oauthRevocationReasonSchema, parseScopeString, pkceMethodSchema, protectedResourceMetadataSchema, redirectUriSchema, revocationRequestSchema, tokenRequestSchema, tokenResponseSchema, type AuthorizationParameters, type AuthorizationServerMetadata, type ClientRegistration, type ClientRegistrationRequest, type ConsentDecision, type ConsentOutcome, type ConsentPrompt, type OAuthErrorBody, type OAuthErrorCode, type OAuthGrant, type OAuthGrantList, type OAuthGrantRevoked, type OAuthGrantStatus, type OAuthGrantType, type OAuthRevocationReason, type ProtectedResourceMetadata, type RevocationRequest, type TokenRequest, type TokenResponse, } from "./oauth.js";
|
|
20
|
-
export {
|
|
19
|
+
export { CALLBACK_ENVELOPE_HEADROOM_BYTES, CALLBACK_RECEIVER_MAX_BODY_BYTES, } from "./callback-receiver.js";
|
|
21
20
|
export { CALLBACK_OWNERSHIP_POLICIES, DEFAULT_CALLBACK_OWNERSHIP_POLICY, readCallbackOwnershipPolicy, sendableStatuses, type CallbackOwnershipPolicy, } from "./callback-ownership.js";
|
|
22
21
|
export { CALLBACK_ENDPOINT_SECRET_PATTERN, CALLBACK_URL_REJECTION_REASONS, evaluateCallbackUrl, isPublicCallbackAddress, type CallbackUrlRejectionReason, } from "./callback-safety.js";
|
|
23
22
|
export { CALLBACK_ENDPOINT_LIMIT_PER_ENVIRONMENT, CALLBACK_ENDPOINT_STATUSES, CALLBACK_SIGNING_KEY_ROTATION_OVERLAP_SECONDS, CALLBACK_VERIFICATION_FAILURES, CALLBACK_VERIFICATION_HEADERS, CALLBACK_VERIFICATION_PROTOCOL, CALLBACK_VERIFICATION_PROTOCOL_VERSION, CALLBACK_VERIFICATION_RATE_LIMITS, CALLBACK_VERIFICATION_RESPONSE_BYTES, CALLBACK_VERIFICATION_TIMEOUT_MILLISECONDS, CALLBACK_VERIFICATION_TYPE, callbackEndpointCreatedSchema, callbackEndpointPageSchema, callbackEndpointRequestSchema, callbackEndpointRotatedSchema, callbackEndpointSchema, callbackEndpointStatusSchema, callbackSigningKeyRotationRequestSchema, callbackSigningKeysSchema, callbackVerificationChallengeSchema, callbackVerificationFailureSchema, type CallbackEndpoint, type CallbackEndpointCreated, type CallbackEndpointPage, type CallbackEndpointRequest, type CallbackEndpointRotated, type CallbackEndpointStatus, type CallbackSigningKeyRotationRequest, type CallbackSigningKeys, type CallbackVerificationChallenge, type CallbackVerificationFailure, } from "./endpoints.js";
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ export { CREDENTIAL_PREFIX_LENGTH, CREDENTIAL_PREFIX_PATTERN, CREDENTIAL_ROTATIO
|
|
|
8
8
|
export { MAX_PARTITION_SHARDS, PROJECTION_DRAIN_LIMIT, SINGLE_SHARD, partitionInstanceName, partitionShardCount, partitionShardFor, partitionShardForIdempotencyKey, } from "./partition.js";
|
|
9
9
|
export { EVENT_TYPES, eventEnvelopeSchema, eventTypeSchema, genericEventSchema, } from "./events.js";
|
|
10
10
|
export { MEMBERSHIP_ROLES, MEMBERSHIP_STATUSES, externalMembershipIdSchema, externalOrganizationIdSchema, externalUserIdSchema, grantsAccess, leastRole, leastStatus, membershipChangeSchema, membershipRoleSchema, membershipSchema, membershipStatusSchema, organizationTombstoneSchema, sessionMembershipSchema, } from "./identity.js";
|
|
11
|
-
export { CLERK_WEBHOOK_PATH, SVIX_HEADERS, WEBHOOK_SIGNATURE_SCHEME, WEBHOOK_TOLERANCE_SECONDS, isWebhookTimestampFresh, svixHeadersSchema, svixIdSchema, svixSignatureHeaderSchema, svixTimestampSchema, tryWebhookSigningInput, webhookSignatureCandidates, webhookSigningInput, } from "./webhooks.js";
|
|
12
11
|
export { CHANGE_KINDS, CHANGE_OPERATIONS, CHANGE_SUBJECTS, CONTRACT_VERSION, assess, changeKindSchema, classifyChange, contractVersionSchema, } from "./compatibility.js";
|
|
13
12
|
export { WORKSPACE_LAYERS, scaffoldProblemSchema, scaffoldVersionSchema, toolDescriptorSchema, toolListSchema, workspaceIdentitySchema, workspaceLayerSchema, workspaceScaffoldSchema, } from "./workspace.js";
|
|
14
13
|
export { MOCK_BEHAVIORS, MOCK_HEADERS, OPERATIONS, UNIVERSAL_PROBLEMS, AUTHENTICATED_PROBLEMS, findOperation, fixtureListSchema, mockBehaviorSchema, mockScenarioRequestSchema, mockScenarioResponseSchema, operationsFor, problemsFor, requestSchemaFor, responseSchemaFor, successStatusFor, } from "./operations.js";
|
|
@@ -17,7 +16,7 @@ export { ATTEMPT_DURATION_MAX_MS, ATTEMPT_FAILURE_SNIPPET_MAX_LENGTH, ATTEMPT_RE
|
|
|
17
16
|
export { portableAttemptRecordSchema, portableEncryptedPayloadRecordSchema, portableOccurrenceRecordSchema, portableReceiptRecordSchema, portableScheduleRecordSchema, portableSchedulingRecordSetSchema, } from "./scheduling-records.js";
|
|
18
17
|
export { ROLE_SCOPE_CEILINGS, capScopesToRole, ceilingForRole, credentialScopes, grantableScopes, policyMatrix, roleAllows, roleCanPerform, } from "./policy.js";
|
|
19
18
|
export { ACCESS_TOKEN_PATTERN, ACCESS_TOKEN_TTL_SECONDS, AUTHORIZATION_CODE_PATTERN, AUTHORIZATION_CODE_TTL_SECONDS, AUTHORIZATION_REQUEST_TTL_SECONDS, MAX_REDIRECT_URIS, OAUTH_ERRORS, OAUTH_ERROR_STATUS, OAUTH_GRANT_STATUSES, OAUTH_GRANT_TYPES, OAUTH_RESPONSE_TYPES, OAUTH_REVOCATION_REASONS, OAUTH_ROUTES, OAUTH_SECRET_PATTERN, OAUTH_TOKEN_NAMESPACE_LENGTH, OAUTH_TOKEN_SECRET_LENGTH, PKCE_CHALLENGE_PATTERN, PKCE_METHODS, PKCE_VERIFIER_PATTERN, REFRESH_TOKEN_PATTERN, REFRESH_TOKEN_TTL_SECONDS, TOKEN_ENDPOINT_AUTH_METHODS, authorizationParametersSchema, authorizationServerMetadataSchema, bearerChallenge, clientRegistrationRequestSchema, clientRegistrationSchema, consentDecisionSchema, consentOutcomeSchema, consentPromptSchema, formatScopeString, isLoopbackRedirect, oauthError, oauthErrorCodeSchema, oauthErrorSchema, oauthGrantListSchema, oauthGrantRevokedSchema, oauthGrantSchema, oauthGrantStatusSchema, oauthGrantTypeSchema, oauthRevocationReasonSchema, parseScopeString, pkceMethodSchema, protectedResourceMetadataSchema, redirectUriSchema, revocationRequestSchema, tokenRequestSchema, tokenResponseSchema, } from "./oauth.js";
|
|
20
|
-
export {
|
|
19
|
+
export { CALLBACK_ENVELOPE_HEADROOM_BYTES, CALLBACK_RECEIVER_MAX_BODY_BYTES, } from "./callback-receiver.js";
|
|
21
20
|
export { CALLBACK_OWNERSHIP_POLICIES, DEFAULT_CALLBACK_OWNERSHIP_POLICY, readCallbackOwnershipPolicy, sendableStatuses, } from "./callback-ownership.js";
|
|
22
21
|
export { CALLBACK_ENDPOINT_SECRET_PATTERN, CALLBACK_URL_REJECTION_REASONS, evaluateCallbackUrl, isPublicCallbackAddress, } from "./callback-safety.js";
|
|
23
22
|
export { CALLBACK_ENDPOINT_LIMIT_PER_ENVIRONMENT, CALLBACK_ENDPOINT_STATUSES, CALLBACK_SIGNING_KEY_ROTATION_OVERLAP_SECONDS, CALLBACK_VERIFICATION_FAILURES, CALLBACK_VERIFICATION_HEADERS, CALLBACK_VERIFICATION_PROTOCOL, CALLBACK_VERIFICATION_PROTOCOL_VERSION, CALLBACK_VERIFICATION_RATE_LIMITS, CALLBACK_VERIFICATION_RESPONSE_BYTES, CALLBACK_VERIFICATION_TIMEOUT_MILLISECONDS, CALLBACK_VERIFICATION_TYPE, callbackEndpointCreatedSchema, callbackEndpointPageSchema, callbackEndpointRequestSchema, callbackEndpointRotatedSchema, callbackEndpointSchema, callbackEndpointStatusSchema, callbackSigningKeyRotationRequestSchema, callbackSigningKeysSchema, callbackVerificationChallengeSchema, callbackVerificationFailureSchema, } from "./endpoints.js";
|
package/dist/operations.d.ts
CHANGED
|
@@ -2379,7 +2379,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
2379
2379
|
response: "response";
|
|
2380
2380
|
callback: "callback";
|
|
2381
2381
|
}>>;
|
|
2382
|
-
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<
|
|
2382
|
+
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<300 | 5 | 30 | 900 | 1800 | 3600 | 43200 | 86400>>;
|
|
2383
2383
|
}, z.core.$strict>>;
|
|
2384
2384
|
everySeconds: z.ZodOptional<z.ZodNumber>;
|
|
2385
2385
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
@@ -3360,7 +3360,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
3360
3360
|
response: "response";
|
|
3361
3361
|
callback: "callback";
|
|
3362
3362
|
}>>;
|
|
3363
|
-
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<
|
|
3363
|
+
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<300 | 5 | 30 | 900 | 1800 | 3600 | 43200 | 86400>>;
|
|
3364
3364
|
}, z.core.$strict>>;
|
|
3365
3365
|
everySeconds: z.ZodOptional<z.ZodNumber>;
|
|
3366
3366
|
endpoint: z.ZodOptional<z.ZodString>;
|
package/dist/scheduling.d.ts
CHANGED
|
@@ -127,7 +127,7 @@ export declare const FREE_DELIVERY_DURATION_SECONDS = 30;
|
|
|
127
127
|
* than thirty minutes has to do is answer immediately and call us back.
|
|
128
128
|
*/
|
|
129
129
|
export declare const RESPONSE_DELIVERY_MAX_DURATION_SECONDS = 1800;
|
|
130
|
-
export declare const deliveryDurationSecondsSchema: z.ZodLiteral<
|
|
130
|
+
export declare const deliveryDurationSecondsSchema: z.ZodLiteral<300 | 5 | 30 | 900 | 1800 | 3600 | 43200 | 86400>;
|
|
131
131
|
export type DeliveryDurationSeconds = z.infer<typeof deliveryDurationSecondsSchema>;
|
|
132
132
|
/** Whether a duration is past the point where it needs a plan that allows it. */
|
|
133
133
|
export declare function isLongRunningDuration(seconds: number): boolean;
|
|
@@ -1041,7 +1041,7 @@ export declare const scheduleRequestSchema: z.ZodObject<{
|
|
|
1041
1041
|
response: "response";
|
|
1042
1042
|
callback: "callback";
|
|
1043
1043
|
}>>;
|
|
1044
|
-
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<
|
|
1044
|
+
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<300 | 5 | 30 | 900 | 1800 | 3600 | 43200 | 86400>>;
|
|
1045
1045
|
}, z.core.$strict>>;
|
|
1046
1046
|
everySeconds: z.ZodOptional<z.ZodNumber>;
|
|
1047
1047
|
endpoint: z.ZodOptional<z.ZodString>;
|
package/dist/test-sink.d.ts
CHANGED
|
@@ -100,8 +100,18 @@ export declare const TEST_SINK_BOUNDS: {
|
|
|
100
100
|
readonly maxRetainedDeliveries: 50;
|
|
101
101
|
/** Sinks per project/environment. */
|
|
102
102
|
readonly maxSinksPerEnvironment: 10;
|
|
103
|
-
/**
|
|
104
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Largest request body a sink will read. Larger is refused unread.
|
|
105
|
+
*
|
|
106
|
+
* **A sink is a receiver, so this is the receiver's bound rather than one of
|
|
107
|
+
* its own.** It used to be a literal 65,536, which is exactly
|
|
108
|
+
* `SCHEDULING_BOUNDS.payloadBytes` — and therefore below every envelope
|
|
109
|
+
* carrying a maximum payload, so a customer rehearsing their largest legal
|
|
110
|
+
* delivery against a sink got the 413 they were rehearsing to avoid. The
|
|
111
|
+
* neighbours here are all justified by the sink running inside the API
|
|
112
|
+
* Worker's budget; this one never was.
|
|
113
|
+
*/
|
|
114
|
+
readonly maxRequestBodyBytes: number;
|
|
105
115
|
/** Captured body bytes retained on a receipt; beyond this it is truncated. */
|
|
106
116
|
readonly maxRetainedBodyBytes: 4096;
|
|
107
117
|
};
|
|
@@ -967,8 +977,18 @@ export declare const CALLBACK_RECEIVER_PROTOCOL: {
|
|
|
967
977
|
readonly maxRetainedDeliveries: 50;
|
|
968
978
|
/** Sinks per project/environment. */
|
|
969
979
|
readonly maxSinksPerEnvironment: 10;
|
|
970
|
-
/**
|
|
971
|
-
|
|
980
|
+
/**
|
|
981
|
+
* Largest request body a sink will read. Larger is refused unread.
|
|
982
|
+
*
|
|
983
|
+
* **A sink is a receiver, so this is the receiver's bound rather than one of
|
|
984
|
+
* its own.** It used to be a literal 65,536, which is exactly
|
|
985
|
+
* `SCHEDULING_BOUNDS.payloadBytes` — and therefore below every envelope
|
|
986
|
+
* carrying a maximum payload, so a customer rehearsing their largest legal
|
|
987
|
+
* delivery against a sink got the 413 they were rehearsing to avoid. The
|
|
988
|
+
* neighbours here are all justified by the sink running inside the API
|
|
989
|
+
* Worker's budget; this one never was.
|
|
990
|
+
*/
|
|
991
|
+
readonly maxRequestBodyBytes: number;
|
|
972
992
|
/** Captured body bytes retained on a receipt; beyond this it is truncated. */
|
|
973
993
|
readonly maxRetainedBodyBytes: 4096;
|
|
974
994
|
};
|
package/dist/test-sink.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CALLBACK_DEFERRAL_STATUS, CALLBACK_PENDING_DISCRIMINATOR, } from "./delivery.js";
|
|
3
3
|
import { callbackSigningKeyIdSchema, deliveryIdSchema, endpointIdSchema, occurrenceIdSchema, projectIdSchema, scheduleIdSchema, testSinkDeliveryIdSchema, testSinkIdSchema, } from "./identifiers.js";
|
|
4
|
+
import { CALLBACK_RECEIVER_MAX_BODY_BYTES } from "./callback-receiver.js";
|
|
4
5
|
import { pageSchema } from "./protocol.js";
|
|
5
6
|
import { CALLBACK_HEADERS, signatureHeaderSchema } from "./signatures.js";
|
|
6
7
|
import { timestampSchema } from "./time.js";
|
|
@@ -106,8 +107,18 @@ export const TEST_SINK_BOUNDS = {
|
|
|
106
107
|
maxRetainedDeliveries: 50,
|
|
107
108
|
/** Sinks per project/environment. */
|
|
108
109
|
maxSinksPerEnvironment: 10,
|
|
109
|
-
/**
|
|
110
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Largest request body a sink will read. Larger is refused unread.
|
|
112
|
+
*
|
|
113
|
+
* **A sink is a receiver, so this is the receiver's bound rather than one of
|
|
114
|
+
* its own.** It used to be a literal 65,536, which is exactly
|
|
115
|
+
* `SCHEDULING_BOUNDS.payloadBytes` — and therefore below every envelope
|
|
116
|
+
* carrying a maximum payload, so a customer rehearsing their largest legal
|
|
117
|
+
* delivery against a sink got the 413 they were rehearsing to avoid. The
|
|
118
|
+
* neighbours here are all justified by the sink running inside the API
|
|
119
|
+
* Worker's budget; this one never was.
|
|
120
|
+
*/
|
|
121
|
+
maxRequestBodyBytes: CALLBACK_RECEIVER_MAX_BODY_BYTES,
|
|
111
122
|
/** Captured body bytes retained on a receipt; beyond this it is truncated. */
|
|
112
123
|
maxRetainedBodyBytes: 4_096,
|
|
113
124
|
};
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"maxHoldMs": 45000,
|
|
133
133
|
"maxRetainedDeliveries": 50,
|
|
134
134
|
"maxSinksPerEnvironment": 10,
|
|
135
|
-
"maxRequestBodyBytes":
|
|
135
|
+
"maxRequestBodyBytes": 69632,
|
|
136
136
|
"maxRetainedBodyBytes": 4096
|
|
137
137
|
},
|
|
138
138
|
"clientErrorStatuses": [400, 401, 403, 404, 409, 410, 422],
|
|
@@ -23555,7 +23555,7 @@
|
|
|
23555
23555
|
"maxHoldMs": 45000,
|
|
23556
23556
|
"maxRetainedDeliveries": 50,
|
|
23557
23557
|
"maxSinksPerEnvironment": 10,
|
|
23558
|
-
"maxRequestBodyBytes":
|
|
23558
|
+
"maxRequestBodyBytes": 69632,
|
|
23559
23559
|
"maxRetainedBodyBytes": 4096
|
|
23560
23560
|
},
|
|
23561
23561
|
"clientErrorStatuses": [400, 401, 403, 404, 409, 410, 422],
|
package/package.json
CHANGED
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"import": "./dist/callback-ownership.js",
|
|
26
26
|
"default": "./dist/callback-ownership.js"
|
|
27
27
|
},
|
|
28
|
+
"./callback-receiver": {
|
|
29
|
+
"types": "./dist/callback-receiver.d.ts",
|
|
30
|
+
"import": "./dist/callback-receiver.js",
|
|
31
|
+
"default": "./dist/callback-receiver.js"
|
|
32
|
+
},
|
|
28
33
|
"./callback-safety": {
|
|
29
34
|
"types": "./dist/callback-safety.d.ts",
|
|
30
35
|
"import": "./dist/callback-safety.js",
|
|
@@ -149,5 +154,5 @@
|
|
|
149
154
|
},
|
|
150
155
|
"sideEffects": false,
|
|
151
156
|
"type": "module",
|
|
152
|
-
"version": "0.1.0-alpha.
|
|
157
|
+
"version": "0.1.0-alpha.2"
|
|
153
158
|
}
|
package/dist/mcp.d.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Where the remote MCP surface lives, and what it tells an agent about itself.
|
|
3
|
-
*
|
|
4
|
-
* Stated once for the same reason `OAUTH_ROUTES` is: two readers must agree.
|
|
5
|
-
* The router serves these paths and the protected-resource metadata document
|
|
6
|
-
* advertises them, and a path that drifts from its advertisement is not a 404 —
|
|
7
|
-
* it is a client that cannot authenticate and cannot say why.
|
|
8
|
-
*
|
|
9
|
-
* Outside `/v1`, following the convention `CLERK_WEBHOOK_PATH` and
|
|
10
|
-
* `OAUTH_ROUTES` set. The `/v1` surface is the operation registry, every entry
|
|
11
|
-
* of which is projected into OpenAPI, the SDK, an MCP tool and the contract
|
|
12
|
-
* mock; the MCP endpoint is the transport those tools are served over, and a
|
|
13
|
-
* transport that appeared in its own tool list would be a tool an agent could
|
|
14
|
-
* point at itself.
|
|
15
|
-
*/
|
|
16
|
-
export declare const MCP_ROUTES: {
|
|
17
|
-
/**
|
|
18
|
-
* The single Streamable HTTP endpoint. POST only; `GET` and `DELETE` answer
|
|
19
|
-
* `405`, because this endpoint opens no standalone event stream and mints no
|
|
20
|
-
* session to terminate.
|
|
21
|
-
*
|
|
22
|
-
* Served at this path **and** at the same path with a trailing slash, without
|
|
23
|
-
* a redirect between them. A `301`/`302` on a POST is rewritten by the fetch
|
|
24
|
-
* standard into a GET with the body dropped, so a client configured with the
|
|
25
|
-
* other spelling would have its opening request silently become the GET this
|
|
26
|
-
* endpoint refuses — and read that as "no MCP server here".
|
|
27
|
-
*/
|
|
28
|
-
readonly endpoint: "/mcp";
|
|
29
|
-
/**
|
|
30
|
-
* RFC 9728 metadata for the MCP endpoint specifically.
|
|
31
|
-
*
|
|
32
|
-
* Served *beside* the origin's own document rather than instead of it. RFC
|
|
33
|
-
* 9728 §3.1 places the metadata for a resource with a path component at
|
|
34
|
-
* `/.well-known/oauth-protected-resource` followed by that path, so a client
|
|
35
|
-
* that treats `https://host/mcp` as the resource it is authorizing for
|
|
36
|
-
* constructs exactly this URL — and one that follows the `resource_metadata`
|
|
37
|
-
* link out of the `401` is sent here too. Both arrive at a document naming
|
|
38
|
-
* the same authorization server.
|
|
39
|
-
*/
|
|
40
|
-
readonly protectedResourceMetadata: "/.well-known/oauth-protected-resource/mcp";
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* What the server tells a model about itself, once, before any tool call.
|
|
44
|
-
*
|
|
45
|
-
* MCP calls this `instructions` and hands it to the model as context. It is
|
|
46
|
-
* kept short and factual: an agent that has read the tool descriptions knows
|
|
47
|
-
* what each one does, and what it cannot work out from them is the shape of the
|
|
48
|
-
* authority it is acting under — which is the one thing that decides whether an
|
|
49
|
-
* instruction like "create this in production" is even expressible.
|
|
50
|
-
*/
|
|
51
|
-
export declare const MCP_INSTRUCTIONS: string;
|
package/dist/mcp.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Where the remote MCP surface lives, and what it tells an agent about itself.
|
|
3
|
-
*
|
|
4
|
-
* Stated once for the same reason `OAUTH_ROUTES` is: two readers must agree.
|
|
5
|
-
* The router serves these paths and the protected-resource metadata document
|
|
6
|
-
* advertises them, and a path that drifts from its advertisement is not a 404 —
|
|
7
|
-
* it is a client that cannot authenticate and cannot say why.
|
|
8
|
-
*
|
|
9
|
-
* Outside `/v1`, following the convention `CLERK_WEBHOOK_PATH` and
|
|
10
|
-
* `OAUTH_ROUTES` set. The `/v1` surface is the operation registry, every entry
|
|
11
|
-
* of which is projected into OpenAPI, the SDK, an MCP tool and the contract
|
|
12
|
-
* mock; the MCP endpoint is the transport those tools are served over, and a
|
|
13
|
-
* transport that appeared in its own tool list would be a tool an agent could
|
|
14
|
-
* point at itself.
|
|
15
|
-
*/
|
|
16
|
-
export const MCP_ROUTES = {
|
|
17
|
-
/**
|
|
18
|
-
* The single Streamable HTTP endpoint. POST only; `GET` and `DELETE` answer
|
|
19
|
-
* `405`, because this endpoint opens no standalone event stream and mints no
|
|
20
|
-
* session to terminate.
|
|
21
|
-
*
|
|
22
|
-
* Served at this path **and** at the same path with a trailing slash, without
|
|
23
|
-
* a redirect between them. A `301`/`302` on a POST is rewritten by the fetch
|
|
24
|
-
* standard into a GET with the body dropped, so a client configured with the
|
|
25
|
-
* other spelling would have its opening request silently become the GET this
|
|
26
|
-
* endpoint refuses — and read that as "no MCP server here".
|
|
27
|
-
*/
|
|
28
|
-
endpoint: "/mcp",
|
|
29
|
-
/**
|
|
30
|
-
* RFC 9728 metadata for the MCP endpoint specifically.
|
|
31
|
-
*
|
|
32
|
-
* Served *beside* the origin's own document rather than instead of it. RFC
|
|
33
|
-
* 9728 §3.1 places the metadata for a resource with a path component at
|
|
34
|
-
* `/.well-known/oauth-protected-resource` followed by that path, so a client
|
|
35
|
-
* that treats `https://host/mcp` as the resource it is authorizing for
|
|
36
|
-
* constructs exactly this URL — and one that follows the `resource_metadata`
|
|
37
|
-
* link out of the `401` is sent here too. Both arrive at a document naming
|
|
38
|
-
* the same authorization server.
|
|
39
|
-
*/
|
|
40
|
-
protectedResourceMetadata: "/.well-known/oauth-protected-resource/mcp",
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* What the server tells a model about itself, once, before any tool call.
|
|
44
|
-
*
|
|
45
|
-
* MCP calls this `instructions` and hands it to the model as context. It is
|
|
46
|
-
* kept short and factual: an agent that has read the tool descriptions knows
|
|
47
|
-
* what each one does, and what it cannot work out from them is the shape of the
|
|
48
|
-
* authority it is acting under — which is the one thing that decides whether an
|
|
49
|
-
* instruction like "create this in production" is even expressible.
|
|
50
|
-
*/
|
|
51
|
-
export const MCP_INSTRUCTIONS = [
|
|
52
|
-
"Greatwait schedules work and delivers it to an endpoint at the time asked for.",
|
|
53
|
-
"Every tool acts inside one organization, project and environment, fixed by the credential this connection was authorized with; no argument can name another, and there is no tool that moves between them.",
|
|
54
|
-
"Reads are safe to repeat. Writes are annotated, and the ones marked destructive settle or discard an occurrence that would otherwise have fired.",
|
|
55
|
-
"A refusal comes back as a problem document with a stable `code`: act on the code, not on the wording.",
|
|
56
|
-
].join(" ");
|
package/dist/webhooks.d.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
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 declare const SVIX_HEADERS: {
|
|
17
|
-
readonly id: "svix-id";
|
|
18
|
-
readonly signature: "svix-signature";
|
|
19
|
-
readonly 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 declare const CLERK_WEBHOOK_PATH = "/__greatwait/webhooks/clerk";
|
|
36
|
-
/** Only `v1` exists. A new scheme is added alongside, never in place of. */
|
|
37
|
-
export declare 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 declare 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 declare const svixIdSchema: z.ZodString;
|
|
52
|
-
/**
|
|
53
|
-
* Seconds since the Unix epoch, as digits. No sign, no decimal point, no
|
|
54
|
-
* exponent — `Number.parseInt` would happily accept `+1e9` and `12.5`, and both
|
|
55
|
-
* would reach the signed bytes in a form we did not intend.
|
|
56
|
-
*/
|
|
57
|
-
export declare const svixTimestampSchema: z.ZodString;
|
|
58
|
-
/**
|
|
59
|
-
* One `v1,<base64>` pair. Svix sends a space-delimited list so an endpoint can
|
|
60
|
-
* be verified under both keys during rotation, which is why the verifier must
|
|
61
|
-
* accept a match against *any* entry rather than parsing a single value.
|
|
62
|
-
*/
|
|
63
|
-
export declare const svixSignatureEntryPattern: RegExp;
|
|
64
|
-
export declare const svixSignatureHeaderSchema: z.ZodString;
|
|
65
|
-
export declare const svixHeadersSchema: z.ZodObject<{
|
|
66
|
-
"svix-id": z.ZodString;
|
|
67
|
-
"svix-signature": z.ZodString;
|
|
68
|
-
"svix-timestamp": z.ZodString;
|
|
69
|
-
}, z.core.$strip>;
|
|
70
|
-
/**
|
|
71
|
-
* Every `v1` signature offered in the header, base64 still encoded.
|
|
72
|
-
*
|
|
73
|
-
* Entries in other schemes are dropped rather than rejected: Svix may add a
|
|
74
|
-
* scheme before we support it, and a delivery that carries both `v1` and a
|
|
75
|
-
* future `v2` must still verify under `v1` instead of failing closed on the
|
|
76
|
-
* presence of something newer.
|
|
77
|
-
*/
|
|
78
|
-
export declare function webhookSignatureCandidates(header: string): readonly string[];
|
|
79
|
-
/**
|
|
80
|
-
* The exact bytes Svix signs: `<id>.<timestamp>.` followed by the raw body,
|
|
81
|
-
* with no re-serialization anywhere in the path.
|
|
82
|
-
*
|
|
83
|
-
* Both prefix fields are validated rather than trusted, and — as with
|
|
84
|
-
* `signingInput` for outbound callbacks — that is a security property. The
|
|
85
|
-
* delimiter is `.`, so if either field could itself contain a `.` the encoding
|
|
86
|
-
* would not be injective: an attacker holding one legitimate signature could
|
|
87
|
-
* shift bytes across a delimiter, lengthening the id header while shortening
|
|
88
|
-
* the body, and still verify.
|
|
89
|
-
*
|
|
90
|
-
* The argument here is slightly different from the outbound one, which relies
|
|
91
|
-
* on both fields being fixed-width. `svixIdSchema` and `svixTimestampSchema`
|
|
92
|
-
* admit no `.` at all, so splitting the signed content on its first two dots
|
|
93
|
-
* recovers the id and the timestamp uniquely whatever their lengths. Variable
|
|
94
|
-
* width is fine; a dot would not be.
|
|
95
|
-
*
|
|
96
|
-
* **Throws** on malformed input. Use `tryWebhookSigningInput` when verifying
|
|
97
|
-
* headers off the wire, or a malformed-header probe becomes a 500.
|
|
98
|
-
*/
|
|
99
|
-
export declare function webhookSigningInput(svixId: string, svixTimestamp: string, rawBody: Uint8Array): Uint8Array;
|
|
100
|
-
/**
|
|
101
|
-
* `webhookSigningInput` for untrusted input: `undefined` instead of a throw when
|
|
102
|
-
* the headers are malformed.
|
|
103
|
-
*/
|
|
104
|
-
export declare function tryWebhookSigningInput(svixId: string, svixTimestamp: string, rawBody: Uint8Array): Uint8Array | undefined;
|
|
105
|
-
/**
|
|
106
|
-
* Whether a delivery's timestamp is close enough to now.
|
|
107
|
-
*
|
|
108
|
-
* Rejects the future as firmly as the past. A signature dated an hour ahead is
|
|
109
|
-
* not a clock-skew inconvenience — accepting it would hand an attacker who
|
|
110
|
-
* captures one request an hour of replay window, so the comparison is on
|
|
111
|
-
* absolute difference.
|
|
112
|
-
*
|
|
113
|
-
* Replay protection beyond this window belongs to the receiver, keyed on the
|
|
114
|
-
* Svix message id; freshness alone does not make a delivery unique.
|
|
115
|
-
*/
|
|
116
|
-
export declare function isWebhookTimestampFresh(svixTimestamp: string, nowMs: number, toleranceSeconds?: number): boolean;
|
package/dist/webhooks.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
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
|
-
}
|