@greatwait/contracts 0.1.0-alpha.1 → 0.1.0-alpha.3
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/authority.d.ts +57 -0
- package/dist/authority.js +35 -0
- package/dist/callback-receiver.d.ts +50 -0
- package/dist/callback-receiver.js +51 -0
- package/dist/first-value-journey.d.ts +156 -0
- package/dist/first-value-journey.js +213 -0
- package/dist/identity.d.ts +4 -4
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -5
- package/dist/mcp.d.ts +148 -0
- package/dist/mcp.js +158 -0
- package/dist/oauth.js +83 -7
- package/dist/operations.d.ts +157 -3
- package/dist/operations.js +104 -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/examples/context.read.response.json +6 -0
- package/generated/v1/examples/credentials.connection.response.json +16 -0
- package/generated/v1/manifest.json +53 -0
- package/generated/v1/openapi.json +690 -1
- package/generated/v1/policy-matrix.json +14 -0
- package/generated/v1/schemas/context.read.response.json +43 -0
- package/generated/v1/schemas/credentials.connection.response.json +64 -0
- package/generated/v1/snippets/attempts.list.json +1 -0
- package/generated/v1/snippets/attempts.read.json +1 -0
- package/generated/v1/snippets/context.read.json +6 -0
- package/generated/v1/snippets/credentials.connection.json +6 -0
- package/generated/v1/snippets/credentials.create.json +1 -0
- package/generated/v1/snippets/credentials.list.json +1 -0
- package/generated/v1/snippets/credentials.revoke.json +1 -0
- package/generated/v1/snippets/credentials.rotate.json +1 -0
- package/generated/v1/snippets/endpoints.create.json +1 -0
- package/generated/v1/snippets/endpoints.disable.json +1 -0
- package/generated/v1/snippets/endpoints.list.json +1 -0
- package/generated/v1/snippets/endpoints.read.json +1 -0
- package/generated/v1/snippets/endpoints.rotateSigningKey.json +1 -0
- package/generated/v1/snippets/endpoints.update.json +1 -0
- package/generated/v1/snippets/endpoints.verify.json +1 -0
- package/generated/v1/snippets/forecast.read.json +1 -0
- package/generated/v1/snippets/forecast.windows.json +1 -0
- package/generated/v1/snippets/grants.list.json +1 -0
- package/generated/v1/snippets/grants.revoke.json +1 -0
- package/generated/v1/snippets/journey/await-occurrence.json +6 -0
- package/generated/v1/snippets/journey/check-credential.json +6 -0
- package/generated/v1/snippets/journey/check-service.json +6 -0
- package/generated/v1/snippets/journey/clean-up.json +6 -0
- package/generated/v1/snippets/journey/create-destination.json +6 -0
- package/generated/v1/snippets/journey/create-one-shot.json +6 -0
- package/generated/v1/snippets/journey/read-attempt.json +6 -0
- package/generated/v1/snippets/journey/read-schedule.json +6 -0
- package/generated/v1/snippets/journey/replay-one-shot.json +6 -0
- package/generated/v1/snippets/meta.scaffold.json +1 -0
- package/generated/v1/snippets/meta.version.json +1 -0
- package/generated/v1/snippets/occurrences.list.json +1 -0
- package/generated/v1/snippets/occurrences.read.json +1 -0
- package/generated/v1/snippets/occurrences.redeliver.json +1 -0
- package/generated/v1/snippets/projects.create.json +1 -0
- package/generated/v1/snippets/projects.list.json +1 -0
- package/generated/v1/snippets/schedules.archive.json +1 -0
- package/generated/v1/snippets/schedules.cancel.json +1 -0
- package/generated/v1/snippets/schedules.create.json +1 -0
- package/generated/v1/snippets/schedules.list.json +1 -0
- package/generated/v1/snippets/schedules.pause.json +1 -0
- package/generated/v1/snippets/schedules.read.json +1 -0
- package/generated/v1/snippets/schedules.replace.json +1 -0
- package/generated/v1/snippets/schedules.resume.json +1 -0
- package/generated/v1/snippets/sinkdeliveries.list.json +1 -0
- package/generated/v1/snippets/sinkdeliveries.read.json +1 -0
- package/generated/v1/snippets/testsinks.configure.json +1 -0
- package/generated/v1/snippets/testsinks.create.json +1 -0
- package/generated/v1/snippets/testsinks.delete.json +1 -0
- package/generated/v1/snippets/testsinks.list.json +1 -0
- package/generated/v1/snippets/testsinks.read.json +1 -0
- package/generated/v1/snippets/usage.read.json +1 -0
- package/package.json +7 -2
- package/dist/webhooks.d.ts +0 -116
- package/dist/webhooks.js +0 -161
package/dist/operations.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type { PrincipalKind, Scope } from "./authority.js";
|
|
2
|
+
import type { EnvironmentKind, PrincipalKind, Scope } from "./authority.js";
|
|
3
3
|
import type { ProblemCode } from "./problems.js";
|
|
4
4
|
/**
|
|
5
5
|
* The operation registry: one declaration per public operation, and the single
|
|
@@ -100,6 +100,29 @@ export interface OperationDefinition {
|
|
|
100
100
|
* teaches a shape the service will reject.
|
|
101
101
|
*/
|
|
102
102
|
readonly example: unknown;
|
|
103
|
+
/**
|
|
104
|
+
* The environment kinds this operation is available in. Absent means both.
|
|
105
|
+
*
|
|
106
|
+
* **Declared because the tool listing has to know, and could not.**
|
|
107
|
+
* `tools/list` is filtered by `toolsVisibleTo`, which asks `authorizeOperation`
|
|
108
|
+
* — a decision about *scopes*. The environment restriction lived only in the
|
|
109
|
+
* routes and in prose, so a `live` credential holding `endpoints:read` was
|
|
110
|
+
* offered `testsinks_list`, `testsinks_read` and `sinkdeliveries_list`: three
|
|
111
|
+
* tools whose refusal tells it to "act in that environment instead", which a
|
|
112
|
+
* credential cannot do, because its authority is fixed when it is issued.
|
|
113
|
+
*
|
|
114
|
+
* Absent rather than required on all forty-one, because a restriction is the
|
|
115
|
+
* exception and a field repeated identically on thirty-four entries is one
|
|
116
|
+
* nobody reads. What stops the next restricted operation being forgotten is
|
|
117
|
+
* not this declaration but the case that drives every operation with both
|
|
118
|
+
* principals and compares the refusal against it — see
|
|
119
|
+
* `v1-environment-restriction.worker.test.ts`.
|
|
120
|
+
*
|
|
121
|
+
* **The filter is still a courtesy and the route is still the enforcement.**
|
|
122
|
+
* Nothing here is a permission check: a tool omitted from a listing is
|
|
123
|
+
* dispatched and refused exactly as before if a client calls it anyway.
|
|
124
|
+
*/
|
|
125
|
+
readonly environments?: readonly EnvironmentKind[];
|
|
103
126
|
/** Stable machine name, also the MCP tool name and the SDK method name. */
|
|
104
127
|
readonly id: string;
|
|
105
128
|
/**
|
|
@@ -1061,6 +1084,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
1061
1084
|
readonly updatedAt: "2026-08-07T15:00:00.000Z";
|
|
1062
1085
|
readonly url: "https://sink.greatwait.dev/__greatwait/test-sinks/tsk_01h8xyz9k2m4n6p8r0t2v4w6y8";
|
|
1063
1086
|
};
|
|
1087
|
+
readonly environments: readonly ["test"];
|
|
1064
1088
|
readonly id: "testsinks.create";
|
|
1065
1089
|
readonly idempotent: false;
|
|
1066
1090
|
readonly mcp: {
|
|
@@ -1189,6 +1213,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
1189
1213
|
readonly kind: "authoritative";
|
|
1190
1214
|
};
|
|
1191
1215
|
};
|
|
1216
|
+
readonly environments: readonly ["test"];
|
|
1192
1217
|
readonly id: "testsinks.list";
|
|
1193
1218
|
readonly idempotent: true;
|
|
1194
1219
|
readonly mcp: {
|
|
@@ -1281,6 +1306,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
1281
1306
|
readonly updatedAt: "2026-08-07T15:02:00.000Z";
|
|
1282
1307
|
readonly url: "https://sink.greatwait.dev/__greatwait/test-sinks/tsk_01h8xyz9k2m4n6p8r0t2v4w6y8";
|
|
1283
1308
|
};
|
|
1309
|
+
readonly environments: readonly ["test"];
|
|
1284
1310
|
readonly id: "testsinks.read";
|
|
1285
1311
|
readonly idempotent: true;
|
|
1286
1312
|
readonly mcp: {
|
|
@@ -1358,6 +1384,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
1358
1384
|
readonly updatedAt: "2026-08-07T15:03:00.000Z";
|
|
1359
1385
|
readonly url: "https://sink.greatwait.dev/__greatwait/test-sinks/tsk_01h8xyz9k2m4n6p8r0t2v4w6y8";
|
|
1360
1386
|
};
|
|
1387
|
+
readonly environments: readonly ["test"];
|
|
1361
1388
|
readonly id: "testsinks.configure";
|
|
1362
1389
|
readonly idempotent: true;
|
|
1363
1390
|
readonly mcp: {
|
|
@@ -1477,6 +1504,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
1477
1504
|
readonly kind: "authoritative";
|
|
1478
1505
|
};
|
|
1479
1506
|
};
|
|
1507
|
+
readonly environments: readonly ["test"];
|
|
1480
1508
|
readonly id: "sinkdeliveries.list";
|
|
1481
1509
|
readonly idempotent: true;
|
|
1482
1510
|
readonly mcp: {
|
|
@@ -1563,6 +1591,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
1563
1591
|
readonly signature: "v1=11135fdfc0bc5d49ebcc9d3a0d8df1116179097cefe961c6eca281339e5cfab3";
|
|
1564
1592
|
readonly sink: "tsk_01h8xyz9k2m4n6p8r0t2v4w6y8";
|
|
1565
1593
|
};
|
|
1594
|
+
readonly environments: readonly ["test"];
|
|
1566
1595
|
readonly id: "sinkdeliveries.read";
|
|
1567
1596
|
readonly idempotent: true;
|
|
1568
1597
|
readonly mcp: {
|
|
@@ -1635,6 +1664,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
1635
1664
|
readonly updatedAt: "2026-08-07T15:04:00.000Z";
|
|
1636
1665
|
readonly url: "https://sink.greatwait.dev/__greatwait/test-sinks/tsk_01h8xyz9k2m4n6p8r0t2v4w6y8";
|
|
1637
1666
|
};
|
|
1667
|
+
readonly environments: readonly ["test"];
|
|
1638
1668
|
readonly id: "testsinks.delete";
|
|
1639
1669
|
readonly idempotent: true;
|
|
1640
1670
|
readonly mcp: {
|
|
@@ -1746,6 +1776,57 @@ export declare const OPERATIONS: readonly [{
|
|
|
1746
1776
|
readonly scopes: readonly [];
|
|
1747
1777
|
readonly summary: "Set the mock scenario";
|
|
1748
1778
|
readonly surfaces: readonly ["mock"];
|
|
1779
|
+
}, {
|
|
1780
|
+
readonly creates: false;
|
|
1781
|
+
readonly description: "Reports the authority the presented credential carries: the project it acts in, which of that project's two environments, and the scopes the service will enforce for it. The answer comes entirely from the verified principal, so there is no parameter that could name another project and no way for the reply to describe anything but the caller itself. It requires a principal and no scope, because every value in it is a property of the credential being presented rather than of anything the credential may reach: a caller that could not read this could still act, and would simply be acting without being able to say where. A signed-in member is not accepted — a session's authority is an organization and it names its project per request, so there is no single answer to give one.";
|
|
1782
|
+
readonly example: {
|
|
1783
|
+
readonly environmentKind: "test";
|
|
1784
|
+
readonly principal: "api-credential";
|
|
1785
|
+
readonly project: "proj_01h8xyz9k2m4n6p8r0t2v4w6y8";
|
|
1786
|
+
readonly scopes: readonly ["schedules:read", "schedules:write"];
|
|
1787
|
+
};
|
|
1788
|
+
readonly id: "context.read";
|
|
1789
|
+
readonly idempotent: true;
|
|
1790
|
+
readonly mcp: {
|
|
1791
|
+
readonly kind: "resource";
|
|
1792
|
+
readonly uri: "greatwait://context";
|
|
1793
|
+
};
|
|
1794
|
+
readonly method: "GET";
|
|
1795
|
+
readonly mutates: false;
|
|
1796
|
+
readonly path: "/v1/context";
|
|
1797
|
+
readonly principals: readonly ["api-credential", "oauth"];
|
|
1798
|
+
readonly problems: readonly [];
|
|
1799
|
+
readonly response: z.ZodObject<{
|
|
1800
|
+
environmentKind: z.ZodEnum<{
|
|
1801
|
+
test: "test";
|
|
1802
|
+
live: "live";
|
|
1803
|
+
}>;
|
|
1804
|
+
principal: z.ZodEnum<{
|
|
1805
|
+
"api-credential": "api-credential";
|
|
1806
|
+
oauth: "oauth";
|
|
1807
|
+
}>;
|
|
1808
|
+
project: z.ZodString;
|
|
1809
|
+
scopes: z.ZodArray<z.ZodEnum<{
|
|
1810
|
+
"credentials:administer": "credentials:administer";
|
|
1811
|
+
"credentials:read": "credentials:read";
|
|
1812
|
+
"credentials:write": "credentials:write";
|
|
1813
|
+
"endpoints:read": "endpoints:read";
|
|
1814
|
+
"endpoints:write": "endpoints:write";
|
|
1815
|
+
"forecast:read": "forecast:read";
|
|
1816
|
+
"grants:read": "grants:read";
|
|
1817
|
+
"grants:write": "grants:write";
|
|
1818
|
+
"occurrences:read": "occurrences:read";
|
|
1819
|
+
"occurrences:redeliver": "occurrences:redeliver";
|
|
1820
|
+
"projects:read": "projects:read";
|
|
1821
|
+
"projects:write": "projects:write";
|
|
1822
|
+
"schedules:read": "schedules:read";
|
|
1823
|
+
"schedules:write": "schedules:write";
|
|
1824
|
+
"usage:read": "usage:read";
|
|
1825
|
+
}>>;
|
|
1826
|
+
}, z.core.$strip>;
|
|
1827
|
+
readonly scopes: readonly [];
|
|
1828
|
+
readonly summary: "Read this credential's authority";
|
|
1829
|
+
readonly surfaces: readonly ["api", "mock"];
|
|
1749
1830
|
}, {
|
|
1750
1831
|
readonly creates: true;
|
|
1751
1832
|
readonly description: "Issues an API credential bound to one project and environment, with scopes capped by the issuing member's current role. The full secret is in this response and in no other, is not recoverable afterwards, and there is deliberately no reveal endpoint. The value is the sixteen-character display prefix followed immediately by thirty-two more characters; the example below is a visible placeholder rather than that shape, because a realistic credential in a committed file is one the repository's own secret scanner refuses.";
|
|
@@ -1917,6 +1998,62 @@ export declare const OPERATIONS: readonly [{
|
|
|
1917
1998
|
readonly sessionAuthority: "headers";
|
|
1918
1999
|
readonly summary: "List API credentials";
|
|
1919
2000
|
readonly surfaces: readonly ["api", "mock"];
|
|
2001
|
+
}, {
|
|
2002
|
+
readonly creates: false;
|
|
2003
|
+
readonly description: "Reads what a key this project issued should be able to do over MCP: the endpoint to point a client at, the discovery document beside it, the scopes fixed into the key when it was made, and the MCP tool names those scopes authorize. It returns no secret and cannot: the transient value is shown once at creation and is never sent here. The credential is addressed by identifier and re-resolved against the signed-in member's own project and environment, so a key belonging to another project answers exactly as one that does not exist. `state` distinguishes a key that still verifies from one that was revoked and one whose rotation overlap has run out, because a client configured with either fails identically and the remedies differ.";
|
|
2004
|
+
readonly example: {
|
|
2005
|
+
readonly discovery: "https://api.greatwait.dev/.well-known/oauth-protected-resource/mcp";
|
|
2006
|
+
readonly displayPrefix: "pt_test_a1b2c3d4";
|
|
2007
|
+
readonly endpoint: "https://api.greatwait.dev/mcp";
|
|
2008
|
+
readonly id: "ak_01h8xyz9k2m4n6p8r0t2v4w6y8";
|
|
2009
|
+
readonly scopes: readonly ["occurrences:read", "schedules:read", "schedules:write"];
|
|
2010
|
+
readonly state: "usable";
|
|
2011
|
+
readonly tools: readonly ["occurrences_list", "occurrences_read", "schedules_cancel", "schedules_create", "schedules_list", "schedules_read"];
|
|
2012
|
+
};
|
|
2013
|
+
readonly id: "credentials.connection";
|
|
2014
|
+
readonly idempotent: true;
|
|
2015
|
+
readonly mcp: {
|
|
2016
|
+
readonly kind: "unavailable";
|
|
2017
|
+
readonly reason: "A signed-in member checking a key whose secret is still on screen. MCP carries API credentials and OAuth tokens, neither of which holds a transient secret or a session to bind one to.";
|
|
2018
|
+
};
|
|
2019
|
+
readonly method: "GET";
|
|
2020
|
+
readonly mutates: false;
|
|
2021
|
+
readonly path: "/v1/credentials/{credentialId}/connection";
|
|
2022
|
+
readonly principals: readonly ["session"];
|
|
2023
|
+
readonly problems: readonly ["not_found"];
|
|
2024
|
+
readonly response: z.ZodObject<{
|
|
2025
|
+
discovery: z.ZodString;
|
|
2026
|
+
displayPrefix: z.ZodString;
|
|
2027
|
+
endpoint: z.ZodString;
|
|
2028
|
+
id: z.ZodString;
|
|
2029
|
+
scopes: z.ZodArray<z.ZodEnum<{
|
|
2030
|
+
"credentials:administer": "credentials:administer";
|
|
2031
|
+
"credentials:read": "credentials:read";
|
|
2032
|
+
"credentials:write": "credentials:write";
|
|
2033
|
+
"endpoints:read": "endpoints:read";
|
|
2034
|
+
"endpoints:write": "endpoints:write";
|
|
2035
|
+
"forecast:read": "forecast:read";
|
|
2036
|
+
"grants:read": "grants:read";
|
|
2037
|
+
"grants:write": "grants:write";
|
|
2038
|
+
"occurrences:read": "occurrences:read";
|
|
2039
|
+
"occurrences:redeliver": "occurrences:redeliver";
|
|
2040
|
+
"projects:read": "projects:read";
|
|
2041
|
+
"projects:write": "projects:write";
|
|
2042
|
+
"schedules:read": "schedules:read";
|
|
2043
|
+
"schedules:write": "schedules:write";
|
|
2044
|
+
"usage:read": "usage:read";
|
|
2045
|
+
}>>;
|
|
2046
|
+
state: z.ZodEnum<{
|
|
2047
|
+
revoked: "revoked";
|
|
2048
|
+
usable: "usable";
|
|
2049
|
+
expired: "expired";
|
|
2050
|
+
}>;
|
|
2051
|
+
tools: z.ZodArray<z.ZodString>;
|
|
2052
|
+
}, z.core.$strip>;
|
|
2053
|
+
readonly scopes: readonly ["credentials:read"];
|
|
2054
|
+
readonly sessionAuthority: "headers";
|
|
2055
|
+
readonly summary: "Read a credential's MCP connection";
|
|
2056
|
+
readonly surfaces: readonly ["api", "mock"];
|
|
1920
2057
|
}, {
|
|
1921
2058
|
readonly creates: false;
|
|
1922
2059
|
readonly description: "Lists the projects in the signed-in member's organization that they may act in, with the environment kinds each one has. This is what a browser asks before it can address anything else: an API credential and an OAuth token each carry one project intrinsically, but a member belongs to an organization and chooses. Archived and deleted projects are omitted, because a member cannot act in one. The result is a display aid rather than an authorization control — naming a project on a later request re-resolves it against the member's own organization, and a project that is not theirs answers exactly as one that does not exist.";
|
|
@@ -2379,7 +2516,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
2379
2516
|
response: "response";
|
|
2380
2517
|
callback: "callback";
|
|
2381
2518
|
}>>;
|
|
2382
|
-
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<
|
|
2519
|
+
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<300 | 5 | 30 | 900 | 1800 | 3600 | 43200 | 86400>>;
|
|
2383
2520
|
}, z.core.$strict>>;
|
|
2384
2521
|
everySeconds: z.ZodOptional<z.ZodNumber>;
|
|
2385
2522
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
@@ -3360,7 +3497,7 @@ export declare const OPERATIONS: readonly [{
|
|
|
3360
3497
|
response: "response";
|
|
3361
3498
|
callback: "callback";
|
|
3362
3499
|
}>>;
|
|
3363
|
-
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<
|
|
3500
|
+
maxDurationSeconds: z.ZodOptional<z.ZodLiteral<300 | 5 | 30 | 900 | 1800 | 3600 | 43200 | 86400>>;
|
|
3364
3501
|
}, z.core.$strict>>;
|
|
3365
3502
|
everySeconds: z.ZodOptional<z.ZodNumber>;
|
|
3366
3503
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
@@ -3982,4 +4119,21 @@ export declare const UNIVERSAL_PROBLEMS: readonly ProblemCode[];
|
|
|
3982
4119
|
* whose rotation window has closed.
|
|
3983
4120
|
*/
|
|
3984
4121
|
export declare const AUTHENTICATED_PROBLEMS: readonly ProblemCode[];
|
|
4122
|
+
/**
|
|
4123
|
+
* The problem an operation returns when the principal is verified and its scope
|
|
4124
|
+
* list falls short.
|
|
4125
|
+
*
|
|
4126
|
+
* **Separated from the list above at W10-01, because the two were keyed on the
|
|
4127
|
+
* wrong field.** `problemsFor` read `scopes.length > 0` and published all four
|
|
4128
|
+
* problems from it, which was exact only while "requires a scope" and "requires
|
|
4129
|
+
* a credential" were the same sentence. `context.read` is the operation that
|
|
4130
|
+
* needs a credential and no scope, and under the old rule it would have been
|
|
4131
|
+
* published with no authentication problems and — through
|
|
4132
|
+
* `securityRequirements` — no security requirement either: a route that refuses
|
|
4133
|
+
* an anonymous caller, documented as open to one. Keying authentication on
|
|
4134
|
+
* `principals` and this one on `scopes` says what each actually depends on, and
|
|
4135
|
+
* it also stops publishing a `scope_missing` that no request to such an
|
|
4136
|
+
* operation could ever produce.
|
|
4137
|
+
*/
|
|
4138
|
+
export declare const SCOPED_PROBLEMS: readonly ProblemCode[];
|
|
3985
4139
|
export declare function problemsFor(operation: OperationDefinition): readonly ProblemCode[];
|
package/dist/operations.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { credentialContextSchema } from "./authority.js";
|
|
2
3
|
import { callbackEndpointCreatedSchema, callbackEndpointPageSchema, callbackEndpointRequestSchema, callbackEndpointRotatedSchema, callbackEndpointSchema, callbackSigningKeyRotationRequestSchema, } from "./endpoints.js";
|
|
3
4
|
import { FORECAST_WINDOW_VERSION, forecastRequestSchema, forecastSchema, forecastWindowsSchema, } from "./forecast.js";
|
|
4
5
|
import { usageRequestSchema, usageSchema } from "./usage.js";
|
|
@@ -8,6 +9,7 @@ import { credentialCreatedSchema, credentialPageSchema, credentialRequestSchema,
|
|
|
8
9
|
import { attemptPageSchema, attemptViewSchema, occurrencePageSchema, occurrenceViewSchema, schedulePageSchema, scheduleRequestSchema, scheduleViewSchema, } from "./scheduling.js";
|
|
9
10
|
import { testSinkCreateRequestSchema, testSinkDeliveryBodySchema, testSinkDeliveryPageSchema, testSinkPageSchema, testSinkProgramSchema, testSinkSchema, } from "./test-sink.js";
|
|
10
11
|
import { projectCreateRequestSchema, projectListSchema, projectSummarySchema, } from "./tenancy.js";
|
|
12
|
+
import { mcpConnectionSchema } from "./mcp.js";
|
|
11
13
|
import { scaffoldVersionSchema, workspaceScaffoldSchema } from "./workspace.js";
|
|
12
14
|
import { timestampSchema } from "./time.js";
|
|
13
15
|
/**
|
|
@@ -424,6 +426,7 @@ export const OPERATIONS = [
|
|
|
424
426
|
updatedAt: "2026-08-07T15:00:00.000Z",
|
|
425
427
|
url: "https://sink.greatwait.dev/__greatwait/test-sinks/tsk_01h8xyz9k2m4n6p8r0t2v4w6y8",
|
|
426
428
|
},
|
|
429
|
+
environments: ["test"],
|
|
427
430
|
id: "testsinks.create",
|
|
428
431
|
idempotent: false,
|
|
429
432
|
mcp: { destructive: false, kind: "tool", openWorld: false },
|
|
@@ -475,6 +478,7 @@ export const OPERATIONS = [
|
|
|
475
478
|
],
|
|
476
479
|
freshness: { kind: "authoritative" },
|
|
477
480
|
},
|
|
481
|
+
environments: ["test"],
|
|
478
482
|
id: "testsinks.list",
|
|
479
483
|
idempotent: true,
|
|
480
484
|
mcp: { destructive: false, kind: "tool", openWorld: false },
|
|
@@ -504,6 +508,7 @@ export const OPERATIONS = [
|
|
|
504
508
|
updatedAt: "2026-08-07T15:02:00.000Z",
|
|
505
509
|
url: "https://sink.greatwait.dev/__greatwait/test-sinks/tsk_01h8xyz9k2m4n6p8r0t2v4w6y8",
|
|
506
510
|
},
|
|
511
|
+
environments: ["test"],
|
|
507
512
|
id: "testsinks.read",
|
|
508
513
|
idempotent: true,
|
|
509
514
|
mcp: { destructive: false, kind: "tool", openWorld: false },
|
|
@@ -532,6 +537,7 @@ export const OPERATIONS = [
|
|
|
532
537
|
updatedAt: "2026-08-07T15:03:00.000Z",
|
|
533
538
|
url: "https://sink.greatwait.dev/__greatwait/test-sinks/tsk_01h8xyz9k2m4n6p8r0t2v4w6y8",
|
|
534
539
|
},
|
|
540
|
+
environments: ["test"],
|
|
535
541
|
id: "testsinks.configure",
|
|
536
542
|
idempotent: true,
|
|
537
543
|
mcp: { destructive: false, kind: "tool", openWorld: false },
|
|
@@ -568,6 +574,7 @@ export const OPERATIONS = [
|
|
|
568
574
|
],
|
|
569
575
|
freshness: { kind: "authoritative" },
|
|
570
576
|
},
|
|
577
|
+
environments: ["test"],
|
|
571
578
|
id: "sinkdeliveries.list",
|
|
572
579
|
idempotent: true,
|
|
573
580
|
mcp: { destructive: false, kind: "tool", openWorld: false },
|
|
@@ -602,6 +609,7 @@ export const OPERATIONS = [
|
|
|
602
609
|
signature: "v1=11135fdfc0bc5d49ebcc9d3a0d8df1116179097cefe961c6eca281339e5cfab3",
|
|
603
610
|
sink: "tsk_01h8xyz9k2m4n6p8r0t2v4w6y8",
|
|
604
611
|
},
|
|
612
|
+
environments: ["test"],
|
|
605
613
|
id: "sinkdeliveries.read",
|
|
606
614
|
idempotent: true,
|
|
607
615
|
mcp: {
|
|
@@ -633,6 +641,7 @@ export const OPERATIONS = [
|
|
|
633
641
|
updatedAt: "2026-08-07T15:04:00.000Z",
|
|
634
642
|
url: "https://sink.greatwait.dev/__greatwait/test-sinks/tsk_01h8xyz9k2m4n6p8r0t2v4w6y8",
|
|
635
643
|
},
|
|
644
|
+
environments: ["test"],
|
|
636
645
|
id: "testsinks.delete",
|
|
637
646
|
idempotent: true,
|
|
638
647
|
mcp: { destructive: true, kind: "tool", openWorld: false },
|
|
@@ -673,6 +682,40 @@ export const OPERATIONS = [
|
|
|
673
682
|
summary: "Set the mock scenario",
|
|
674
683
|
surfaces: ["mock"],
|
|
675
684
|
},
|
|
685
|
+
{
|
|
686
|
+
creates: false,
|
|
687
|
+
description: "Reports the authority the presented credential carries: the project it acts in, which of that project's two environments, and the scopes the service will enforce for it. The answer comes entirely from the verified principal, so there is no parameter that could name another project and no way for the reply to describe anything but the caller itself. It requires a principal and no scope, because every value in it is a property of the credential being presented rather than of anything the credential may reach: a caller that could not read this could still act, and would simply be acting without being able to say where. A signed-in member is not accepted — a session's authority is an organization and it names its project per request, so there is no single answer to give one.",
|
|
688
|
+
example: {
|
|
689
|
+
environmentKind: "test",
|
|
690
|
+
principal: "api-credential",
|
|
691
|
+
project: "proj_01h8xyz9k2m4n6p8r0t2v4w6y8",
|
|
692
|
+
scopes: ["schedules:read", "schedules:write"],
|
|
693
|
+
},
|
|
694
|
+
id: "context.read",
|
|
695
|
+
idempotent: true,
|
|
696
|
+
// A resource rather than a tool: it takes no arguments, it reports a fact
|
|
697
|
+
// about the caller, and an agent choosing between tools should not have to
|
|
698
|
+
// step past "what am I" to find the operations that schedule anything.
|
|
699
|
+
// Being scopeless is what makes a resource legal here — `check:mcp-parity`
|
|
700
|
+
// refuses a resource that requires one, because a resource carries no
|
|
701
|
+
// scope declaration for a host to gate on.
|
|
702
|
+
mcp: { kind: "resource", uri: "greatwait://context" },
|
|
703
|
+
method: "GET",
|
|
704
|
+
mutates: false,
|
|
705
|
+
path: "/v1/context",
|
|
706
|
+
principals: ["api-credential", "oauth"],
|
|
707
|
+
// No `not_found`: the authority tuple exists by the time a principal is
|
|
708
|
+
// verified, so there is nothing here that can be absent.
|
|
709
|
+
problems: [],
|
|
710
|
+
response: credentialContextSchema,
|
|
711
|
+
// **Empty, and that is the point of the operation.** Every other
|
|
712
|
+
// authenticated operation names the scope it consumes; this one consumes
|
|
713
|
+
// none, so `scope_missing` is a refusal it can never produce and
|
|
714
|
+
// `problemsFor` does not publish one for it.
|
|
715
|
+
scopes: [],
|
|
716
|
+
summary: "Read this credential's authority",
|
|
717
|
+
surfaces: ["api", "mock"],
|
|
718
|
+
},
|
|
676
719
|
{
|
|
677
720
|
creates: true,
|
|
678
721
|
description: "Issues an API credential bound to one project and environment, with scopes capped by the issuing member's current role. The full secret is in this response and in no other, is not recoverable afterwards, and there is deliberately no reveal endpoint. The value is the sixteen-character display prefix followed immediately by thirty-two more characters; the example below is a visible placeholder rather than that shape, because a realistic credential in a committed file is one the repository's own secret scanner refuses.",
|
|
@@ -776,6 +819,48 @@ export const OPERATIONS = [
|
|
|
776
819
|
summary: "List API credentials",
|
|
777
820
|
surfaces: ["api", "mock"],
|
|
778
821
|
},
|
|
822
|
+
{
|
|
823
|
+
creates: false,
|
|
824
|
+
description: "Reads what a key this project issued should be able to do over MCP: the endpoint to point a client at, the discovery document beside it, the scopes fixed into the key when it was made, and the MCP tool names those scopes authorize. It returns no secret and cannot: the transient value is shown once at creation and is never sent here. The credential is addressed by identifier and re-resolved against the signed-in member's own project and environment, so a key belonging to another project answers exactly as one that does not exist. `state` distinguishes a key that still verifies from one that was revoked and one whose rotation overlap has run out, because a client configured with either fails identically and the remedies differ.",
|
|
825
|
+
example: {
|
|
826
|
+
discovery: "https://api.greatwait.dev/.well-known/oauth-protected-resource/mcp",
|
|
827
|
+
displayPrefix: "pt_test_a1b2c3d4",
|
|
828
|
+
endpoint: "https://api.greatwait.dev/mcp",
|
|
829
|
+
id: "ak_01h8xyz9k2m4n6p8r0t2v4w6y8",
|
|
830
|
+
scopes: ["occurrences:read", "schedules:read", "schedules:write"],
|
|
831
|
+
state: "usable",
|
|
832
|
+
tools: [
|
|
833
|
+
"occurrences_list",
|
|
834
|
+
"occurrences_read",
|
|
835
|
+
"schedules_cancel",
|
|
836
|
+
"schedules_create",
|
|
837
|
+
"schedules_list",
|
|
838
|
+
"schedules_read",
|
|
839
|
+
],
|
|
840
|
+
},
|
|
841
|
+
id: "credentials.connection",
|
|
842
|
+
idempotent: true,
|
|
843
|
+
// **Withheld, and the reason is the same one that withholds issuance.** A
|
|
844
|
+
// member asks this about a key they are holding in a browser, in the window
|
|
845
|
+
// where its secret is still on screen. An agent is already connected: it has
|
|
846
|
+
// no transient secret to check and no session to bind one to, so a tool here
|
|
847
|
+
// would answer `authentication_required` to every caller that could reach
|
|
848
|
+
// it. Publishing the exclusion is the honest version of that.
|
|
849
|
+
mcp: {
|
|
850
|
+
kind: "unavailable",
|
|
851
|
+
reason: "A signed-in member checking a key whose secret is still on screen. MCP carries API credentials and OAuth tokens, neither of which holds a transient secret or a session to bind one to.",
|
|
852
|
+
},
|
|
853
|
+
method: "GET",
|
|
854
|
+
mutates: false,
|
|
855
|
+
path: "/v1/credentials/{credentialId}/connection",
|
|
856
|
+
principals: ["session"],
|
|
857
|
+
problems: ["not_found"],
|
|
858
|
+
response: mcpConnectionSchema,
|
|
859
|
+
scopes: ["credentials:read"],
|
|
860
|
+
sessionAuthority: "headers",
|
|
861
|
+
summary: "Read a credential's MCP connection",
|
|
862
|
+
surfaces: ["api", "mock"],
|
|
863
|
+
},
|
|
779
864
|
{
|
|
780
865
|
creates: false,
|
|
781
866
|
description: "Lists the projects in the signed-in member's organization that they may act in, with the environment kinds each one has. This is what a browser asks before it can address anything else: an API credential and an OAuth token each carry one project intrinsically, but a member belongs to an organization and chooses. Archived and deleted projects are omitted, because a member cannot act in one. The result is a display aid rather than an authorization control — naming a project on a later request re-resolves it against the member's own organization, and a project that is not theirs answers exactly as one that does not exist.",
|
|
@@ -1824,14 +1909,31 @@ export const AUTHENTICATED_PROBLEMS = [
|
|
|
1824
1909
|
"authentication_required",
|
|
1825
1910
|
"credential_expired",
|
|
1826
1911
|
"credential_revoked",
|
|
1827
|
-
"scope_missing",
|
|
1828
1912
|
];
|
|
1913
|
+
/**
|
|
1914
|
+
* The problem an operation returns when the principal is verified and its scope
|
|
1915
|
+
* list falls short.
|
|
1916
|
+
*
|
|
1917
|
+
* **Separated from the list above at W10-01, because the two were keyed on the
|
|
1918
|
+
* wrong field.** `problemsFor` read `scopes.length > 0` and published all four
|
|
1919
|
+
* problems from it, which was exact only while "requires a scope" and "requires
|
|
1920
|
+
* a credential" were the same sentence. `context.read` is the operation that
|
|
1921
|
+
* needs a credential and no scope, and under the old rule it would have been
|
|
1922
|
+
* published with no authentication problems and — through
|
|
1923
|
+
* `securityRequirements` — no security requirement either: a route that refuses
|
|
1924
|
+
* an anonymous caller, documented as open to one. Keying authentication on
|
|
1925
|
+
* `principals` and this one on `scopes` says what each actually depends on, and
|
|
1926
|
+
* it also stops publishing a `scope_missing` that no request to such an
|
|
1927
|
+
* operation could ever produce.
|
|
1928
|
+
*/
|
|
1929
|
+
export const SCOPED_PROBLEMS = ["scope_missing"];
|
|
1829
1930
|
export function problemsFor(operation) {
|
|
1830
1931
|
return [
|
|
1831
1932
|
...new Set([
|
|
1832
1933
|
...operation.problems,
|
|
1833
1934
|
...UNIVERSAL_PROBLEMS,
|
|
1834
|
-
...(operation.
|
|
1935
|
+
...(operation.principals.length > 0 ? AUTHENTICATED_PROBLEMS : []),
|
|
1936
|
+
...(operation.scopes.length > 0 ? SCOPED_PROBLEMS : []),
|
|
1835
1937
|
]),
|
|
1836
1938
|
].sort();
|
|
1837
1939
|
}
|
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],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"discovery": "https://api.greatwait.dev/.well-known/oauth-protected-resource/mcp",
|
|
3
|
+
"displayPrefix": "pt_test_a1b2c3d4",
|
|
4
|
+
"endpoint": "https://api.greatwait.dev/mcp",
|
|
5
|
+
"id": "ak_01h8xyz9k2m4n6p8r0t2v4w6y8",
|
|
6
|
+
"scopes": ["occurrences:read", "schedules:read", "schedules:write"],
|
|
7
|
+
"state": "usable",
|
|
8
|
+
"tools": [
|
|
9
|
+
"occurrences_list",
|
|
10
|
+
"occurrences_read",
|
|
11
|
+
"schedules_cancel",
|
|
12
|
+
"schedules_create",
|
|
13
|
+
"schedules_list",
|
|
14
|
+
"schedules_read"
|
|
15
|
+
]
|
|
16
|
+
}
|