@cat-factory/gatekeeper-worker 0.3.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +276 -0
  3. package/dist/approvals.d.ts +90 -0
  4. package/dist/approvals.d.ts.map +1 -0
  5. package/dist/approvals.js +193 -0
  6. package/dist/approvals.js.map +1 -0
  7. package/dist/capability.d.ts +61 -0
  8. package/dist/capability.d.ts.map +1 -0
  9. package/dist/capability.js +125 -0
  10. package/dist/capability.js.map +1 -0
  11. package/dist/env.d.ts +45 -0
  12. package/dist/env.d.ts.map +1 -0
  13. package/dist/env.js +90 -0
  14. package/dist/env.js.map +1 -0
  15. package/dist/errors.d.ts +22 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +30 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/gatekeeper.d.ts +78 -0
  20. package/dist/gatekeeper.d.ts.map +1 -0
  21. package/dist/gatekeeper.js +162 -0
  22. package/dist/gatekeeper.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +23 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/keys.d.ts +62 -0
  28. package/dist/keys.d.ts.map +1 -0
  29. package/dist/keys.js +152 -0
  30. package/dist/keys.js.map +1 -0
  31. package/dist/masking.d.ts +9 -0
  32. package/dist/masking.d.ts.map +1 -0
  33. package/dist/masking.js +43 -0
  34. package/dist/masking.js.map +1 -0
  35. package/dist/policy/compile.d.ts +88 -0
  36. package/dist/policy/compile.d.ts.map +1 -0
  37. package/dist/policy/compile.js +170 -0
  38. package/dist/policy/compile.js.map +1 -0
  39. package/dist/policy/decisions.d.ts +93 -0
  40. package/dist/policy/decisions.d.ts.map +1 -0
  41. package/dist/policy/decisions.js +659 -0
  42. package/dist/policy/decisions.js.map +1 -0
  43. package/dist/policy/index.d.ts +6 -0
  44. package/dist/policy/index.d.ts.map +1 -0
  45. package/dist/policy/index.js +17 -0
  46. package/dist/policy/index.js.map +1 -0
  47. package/dist/state.d.ts +150 -0
  48. package/dist/state.d.ts.map +1 -0
  49. package/dist/state.js +229 -0
  50. package/dist/state.js.map +1 -0
  51. package/dist/webhook/delivery.d.ts +102 -0
  52. package/dist/webhook/delivery.d.ts.map +1 -0
  53. package/dist/webhook/delivery.js +162 -0
  54. package/dist/webhook/delivery.js.map +1 -0
  55. package/dist/webhook/signature.d.ts +17 -0
  56. package/dist/webhook/signature.d.ts.map +1 -0
  57. package/dist/webhook/signature.js +73 -0
  58. package/dist/webhook/signature.js.map +1 -0
  59. package/dist/worker.d.ts +21 -0
  60. package/dist/worker.d.ts.map +1 -0
  61. package/dist/worker.js +155 -0
  62. package/dist/worker.js.map +1 -0
  63. package/package.json +62 -0
@@ -0,0 +1,61 @@
1
+ import { RpcTarget } from 'capnweb';
2
+ import type { Actor, KeyBroker } from './keys.js';
3
+ import { type CompiledTier } from './policy/compile.js';
4
+ import type { ApprovalCard, GatekeeperState } from './state.js';
5
+ export interface SessionDependencies {
6
+ actor: Actor;
7
+ tier: CompiledTier;
8
+ keys: KeyBroker;
9
+ state: DurableObjectStub<GatekeeperState>;
10
+ }
11
+ /** What `tier()` answers: enough for an OS Gadget to render who the caller is acting as. */
12
+ export interface TierSummary {
13
+ actorId: string;
14
+ tier: string;
15
+ description: string;
16
+ keyScope: string;
17
+ }
18
+ /**
19
+ * What `approvals_inspect()` answers: the card, what the run is ACTUALLY parked on now, and which
20
+ * verbs this tier can reach.
21
+ *
22
+ * The reason this exists beside `approvals_answer` is the same one `withheld()` exists beside
23
+ * `bindings()`: an agent composing an answer needs to know what the park takes and whether its own
24
+ * tier holds the operation, and deriving either from a doc is how an integration ends up posting a
25
+ * body the platform refuses with a 422 that names a field the agent never chose.
26
+ */
27
+ export interface CardInspection {
28
+ card: ApprovalCard;
29
+ /** The run's live decision list, verbatim. The webhook is a trigger; this is the truth. */
30
+ decisions: unknown;
31
+ /** What the run is parked on and can be answered from here, with the verbs each takes. */
32
+ parks: {
33
+ kind: string;
34
+ summary: string;
35
+ actions: {
36
+ action: string;
37
+ binding: string;
38
+ summary: string;
39
+ /** False when this tier was not granted the binding: the verb exists, this caller cannot use it. */
40
+ granted: boolean;
41
+ fields: readonly {
42
+ name: string;
43
+ required: boolean;
44
+ choices?: readonly string[];
45
+ detail: string;
46
+ }[];
47
+ }[];
48
+ }[];
49
+ /** Present only when nothing is answerable, saying which of the several reasons it is. */
50
+ stale?: string;
51
+ }
52
+ /**
53
+ * Build the capability for one connected actor.
54
+ *
55
+ * Returns an `RpcTarget` whose prototype carries exactly the granted operations plus the reserved
56
+ * methods above. Each call resolves the actor's own key (minted once, cached durably) and
57
+ * forwards through the binding's `invoke` thunk, so retry, encoding and error mapping stay the
58
+ * SDK's job and this layer only ever decides WHO and WHETHER.
59
+ */
60
+ export declare function buildCapability(deps: SessionDependencies): RpcTarget;
61
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAYnC,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAEjD,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAY,MAAM,YAAY,CAAA;AAoBzE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,YAAY,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAA;CAC1C;AAED,4FAA4F;AAC5F,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAA;IAClB,2FAA2F;IAC3F,SAAS,EAAE,OAAO,CAAA;IAClB,0FAA0F;IAC1F,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE;YACP,MAAM,EAAE,MAAM,CAAA;YACd,OAAO,EAAE,MAAM,CAAA;YACf,OAAO,EAAE,MAAM,CAAA;YACf,oGAAoG;YACpG,OAAO,EAAE,OAAO,CAAA;YAChB,MAAM,EAAE,SAAS;gBACf,IAAI,EAAE,MAAM,CAAA;gBACZ,QAAQ,EAAE,OAAO,CAAA;gBACjB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;gBAC3B,MAAM,EAAE,MAAM,CAAA;aACf,EAAE,CAAA;SACJ,EAAE,CAAA;KACJ,EAAE,CAAA;IACH,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,SAAS,CAwGpE"}
@@ -0,0 +1,125 @@
1
+ // The object-capability itself: what an OS workspace agent actually holds.
2
+ //
3
+ // The design rule that makes this a capability rather than a permission check is that the granted
4
+ // operations are the object's METHODS. A tier without `tasks_delete` does not get a `tasks_delete`
5
+ // that refuses; it gets an object with no such method, so the refusal is a `TypeError` from the
6
+ // caller's own runtime and there is no allow-list to get backwards. What policy CAN still get
7
+ // wrong (which names are granted) is checked once, at compile time, against the live operation
8
+ // table; what it cannot get wrong is checked by JavaScript.
9
+ //
10
+ // Methods live on a per-session PROTOTYPE, never on the instance. Cap'n Web deliberately refuses
11
+ // to serve an RpcTarget's own instance properties (they would leak private internals), so an
12
+ // instance-property capability would be a set of methods no caller can reach.
13
+ import { RpcTarget } from 'capnweb';
14
+ import { answerCard, assertAnswerable, pendingParks, describeStale, } from './approvals.js';
15
+ import { GatekeeperError, PolicyError } from './errors.js';
16
+ import { applyMask } from './masking.js';
17
+ import { describeBinding } from './policy/compile.js';
18
+ /**
19
+ * The methods a capability carries beyond its granted bindings.
20
+ *
21
+ * Reserved rather than merely used: a future `/api/v1` resource group called `approvals` would
22
+ * generate a binding named `approvals_list`, and silently overwriting one of these with it (or
23
+ * the other way round) is the kind of collision that reads as a missing feature. It is checked at
24
+ * build time and refused as a policy error, because the fix is a rename in this file.
25
+ */
26
+ const RESERVED_METHODS = [
27
+ 'tier',
28
+ 'bindings',
29
+ 'withheld',
30
+ 'approvals_list',
31
+ 'approvals_inspect',
32
+ 'approvals_answer',
33
+ 'runs_watched',
34
+ ];
35
+ /**
36
+ * Build the capability for one connected actor.
37
+ *
38
+ * Returns an `RpcTarget` whose prototype carries exactly the granted operations plus the reserved
39
+ * methods above. Each call resolves the actor's own key (minted once, cached durably) and
40
+ * forwards through the binding's `invoke` thunk, so retry, encoding and error mapping stay the
41
+ * SDK's job and this layer only ever decides WHO and WHETHER.
42
+ */
43
+ export function buildCapability(deps) {
44
+ const { tier } = deps;
45
+ const granted = new Map(tier.granted.map((binding) => [binding.name, binding]));
46
+ for (const reserved of RESERVED_METHODS) {
47
+ if (granted.has(reserved)) {
48
+ throw new PolicyError(`Binding '${reserved}' collides with a reserved capability method. Rename the reserved ` +
49
+ 'method in capability.ts; a granted operation must never be shadowed by one.');
50
+ }
51
+ }
52
+ const invoke = async (name, args) => {
53
+ const binding = granted.get(name);
54
+ if (binding === undefined) {
55
+ throw new GatekeeperError('binding_not_granted', `Tier '${tier.name}' does not grant '${name}'.`);
56
+ }
57
+ const result = await deps.keys.run(deps.actor, tier.keyScope, (client) => binding.invoke(client, args));
58
+ return applyMask(result, tier.mask);
59
+ };
60
+ class Capability extends RpcTarget {
61
+ }
62
+ const proto = Capability.prototype;
63
+ for (const binding of tier.granted) {
64
+ proto[binding.name] = (args = {}) => invoke(binding.name, args);
65
+ }
66
+ proto.tier = () => ({
67
+ actorId: deps.actor.id,
68
+ tier: tier.name,
69
+ description: tier.description,
70
+ keyScope: tier.keyScope,
71
+ });
72
+ // The two halves of "what can I do here", answered separately on purpose. `bindings()` carries
73
+ // the consequence annotations with the cautious default applied, so the OS can run its own
74
+ // approval governance over a call without re-deriving what "destructive" means.
75
+ proto.bindings = () => tier.granted.map(describeBinding);
76
+ // `withheld()` is the degrade-loudly half: an agent that cannot tell "your policy hides this"
77
+ // from "this deployment does not have it" reports the wrong one to whoever has to fix it.
78
+ proto.withheld = () => tier.withheld;
79
+ proto.approvals_list = () => deps.state.listCards();
80
+ // The lifecycle projection the `run.*` subscription feeds. Without it those deliveries would be
81
+ // verified, deduped and dropped, and a status Gadget would be back to polling `tasks_get_run`
82
+ // for a transition the platform already pushed.
83
+ proto.runs_watched = () => deps.state.listRunStates();
84
+ proto.approvals_inspect = async (cardId) => {
85
+ const card = await deps.state.getCard(cardId);
86
+ if (card === null) {
87
+ throw new GatekeeperError('card_not_found', `No approval card '${cardId}'. It may have been raised against a different paired ` +
88
+ 'workspace, or predate this Gatekeeper.');
89
+ }
90
+ const list = (await invoke('decisions_list', { runId: card.runId }));
91
+ const parks = pendingParks(list).map((park) => ({
92
+ kind: park.kind,
93
+ summary: park.summary,
94
+ actions: park.verbs.map((verb) => ({
95
+ action: verb.action,
96
+ binding: verb.binding,
97
+ summary: verb.summary,
98
+ granted: granted.has(verb.binding),
99
+ fields: verb.fields,
100
+ })),
101
+ }));
102
+ return {
103
+ card,
104
+ decisions: list,
105
+ parks,
106
+ ...(parks.length === 0 ? { stale: describeStale(list) } : {}),
107
+ };
108
+ };
109
+ proto.approvals_answer = async (cardId, input) => {
110
+ const card = assertAnswerable(await deps.state.getCard(cardId), cardId);
111
+ const outcome = await answerCard(card, input, invoke);
112
+ // Only an answer that left the run UNPARKED settles the card. An approval short of quorum, or
113
+ // a reply the incorporation has not folded in yet, leaves it open because the next answerer
114
+ // still has to see it. A `stale` answer settles nothing either: the run may be held by a wait
115
+ // a person has to clear elsewhere, and destroying the card would remove the one pointer to it
116
+ // that the inbox had. The platform re-delivers a card under a new notification id, so a
117
+ // wrongly settled one is never re-raised.
118
+ if (outcome.status === 'answered') {
119
+ await deps.state.resolveCard(cardId, `${outcome.kind}:${outcome.action}`, Date.now());
120
+ }
121
+ return outcome;
122
+ };
123
+ return new Capability();
124
+ }
125
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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"}
package/dist/env.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type { GatekeeperState } from './state.js';
2
+ export interface GatekeeperEnv {
3
+ /** The cat-factory deployment's origin, e.g. `https://cat-factory.example.com`. */
4
+ CAT_FACTORY_BASE_URL: string;
5
+ /**
6
+ * The webhook id this Gatekeeper enrols under. Caller-chosen and idempotent by design, so a
7
+ * cold-booting Worker writes its own well-known id with no create-or-discover round trip.
8
+ */
9
+ WEBHOOK_ID: string;
10
+ /** This Worker's own public origin, the URL the platform delivers to. */
11
+ PUBLIC_URL: string;
12
+ /** An `admin` cat-factory key. Mints per-actor keys and nothing else; never leaves the Worker. */
13
+ PROVISIONING_KEY: string;
14
+ /** The signing secret this Gatekeeper registers, and verifies every delivery against. */
15
+ WEBHOOK_SECRET: string;
16
+ /** The bearer token the paired Cloudflare OS deployment presents on every RPC call. */
17
+ OS_SHARED_TOKEN: string;
18
+ STATE: DurableObjectNamespace<GatekeeperState>;
19
+ }
20
+ /** How an operator supplies one binding. */
21
+ export type BindingKind = 'var' | 'secret' | 'durable-object';
22
+ type Binding = keyof GatekeeperEnv;
23
+ /** A configuration binding this deployment has not set. */
24
+ export declare class ConfigError extends Error {
25
+ readonly binding: Binding;
26
+ constructor(binding: Binding);
27
+ }
28
+ type StringBinding = Exclude<Binding, 'STATE'>;
29
+ /** Read a required string binding, or refuse naming it. */
30
+ export declare function requireVar(env: GatekeeperEnv, binding: StringBinding): string;
31
+ /**
32
+ * Every binding this deployment has not set, in ONE pass.
33
+ *
34
+ * `requireVar` answers the first binding the path a request happened to take reads, which is the
35
+ * right answer for that request and the wrong one for a health check: an operator wiring a
36
+ * deployment learns the next unset name only after fixing this one and redeploying. So the health
37
+ * check asks the whole table at once, and derives it from `GatekeeperEnv` rather than a
38
+ * transcribed list, because a binding this check silently passed over is a binding whose absence
39
+ * reads as a healthy Gatekeeper.
40
+ */
41
+ export declare function missingBindings(env: GatekeeperEnv): Binding[];
42
+ /** The refusal an operator can act on: every unset binding, each with how to set it. */
43
+ export declare function describeMissingBindings(missing: readonly Binding[]): string;
44
+ export {};
45
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,MAAM,WAAW,aAAa;IAC5B,mFAAmF;IACnF,oBAAoB,EAAE,MAAM,CAAA;IAC5B;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAA;IAClB,kGAAkG;IAClG,gBAAgB,EAAE,MAAM,CAAA;IACxB,yFAAyF;IACzF,cAAc,EAAE,MAAM,CAAA;IACtB,uFAAuF;IACvF,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,EAAE,sBAAsB,CAAC,eAAe,CAAC,CAAA;CAC/C;AAED,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,GAAG,gBAAgB,CAAA;AAsB7D,KAAK,OAAO,GAAG,MAAM,aAAa,CAAA;AAgBlC,2DAA2D;AAC3D,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IAEzB,YAAY,OAAO,EAAE,OAAO,EAI3B;CACF;AAED,KAAK,aAAa,GAAG,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AAE9C,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAI7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,EAAE,CAG7D;AAYD,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAG3E"}
package/dist/env.js ADDED
@@ -0,0 +1,90 @@
1
+ // The deployment's configuration, and the one rule about reading it: a missing value is a
2
+ // REFUSAL that names the binding, never a default.
3
+ //
4
+ // Every field below is either a credential or the identity of the thing it talks to, and there is
5
+ // no safe stand-in for either. A Gatekeeper that booted with an empty webhook secret would accept
6
+ // unsigned deliveries; one that booted with an empty shared token would serve capabilities to
7
+ // anybody who found the route. So configuration is validated at the edge of each request and
8
+ // answered as a 503 naming the binding an operator has to set, which is the same shape the
9
+ // platform's own unwired-capability refusals take.
10
+ //
11
+ // A request reads the bindings its own path needs, so it can only name the first one it trips on.
12
+ // `/health` is the other question ("could this deployment serve at all"), and it takes the other
13
+ // route through this file: `missingBindings` over the whole table in one pass.
14
+ /**
15
+ * Which mechanism each binding arrives through, and the one place that fact is stated.
16
+ *
17
+ * A var and a Durable Object namespace are written in `wrangler.toml`; a secret is put with
18
+ * `wrangler secret put` and belongs in no file the repository carries. That distinction is the one
19
+ * an operator gets wrong, and "set it in wrangler.toml OR with `wrangler secret put`" invites the
20
+ * wrong half, so every refusal names the mechanism that binding actually takes. It is a
21
+ * `Record` over `GatekeeperEnv`'s own keys, so a binding added above fails to compile until it
22
+ * says how it is supplied.
23
+ */
24
+ const BINDING_KINDS = {
25
+ CAT_FACTORY_BASE_URL: 'var',
26
+ WEBHOOK_ID: 'var',
27
+ PUBLIC_URL: 'var',
28
+ PROVISIONING_KEY: 'secret',
29
+ WEBHOOK_SECRET: 'secret',
30
+ OS_SHARED_TOKEN: 'secret',
31
+ STATE: 'durable-object',
32
+ };
33
+ /** Where an operator sets one binding, phrased as the remedy a refusal ends on. */
34
+ function howToSet(binding) {
35
+ // The declared return type is the exhaustiveness guard: a `BindingKind` added with no case here
36
+ // widens this to `string | undefined` and fails the build.
37
+ switch (BINDING_KINDS[binding]) {
38
+ case 'var':
39
+ return 'set it as a var in wrangler.toml';
40
+ case 'secret':
41
+ return `put it with \`wrangler secret put ${binding}\`, never in wrangler.toml`;
42
+ case 'durable-object':
43
+ return 'bind it in wrangler.toml as a durable_objects binding to the GatekeeperState class';
44
+ }
45
+ }
46
+ /** A configuration binding this deployment has not set. */
47
+ export class ConfigError extends Error {
48
+ binding;
49
+ constructor(binding) {
50
+ super(`${binding} is not configured on this Gatekeeper: ${howToSet(binding)}.`);
51
+ this.name = 'ConfigError';
52
+ this.binding = binding;
53
+ }
54
+ }
55
+ /** Read a required string binding, or refuse naming it. */
56
+ export function requireVar(env, binding) {
57
+ const value = env[binding];
58
+ if (typeof value !== 'string' || value.length === 0)
59
+ throw new ConfigError(binding);
60
+ return value;
61
+ }
62
+ /**
63
+ * Every binding this deployment has not set, in ONE pass.
64
+ *
65
+ * `requireVar` answers the first binding the path a request happened to take reads, which is the
66
+ * right answer for that request and the wrong one for a health check: an operator wiring a
67
+ * deployment learns the next unset name only after fixing this one and redeploying. So the health
68
+ * check asks the whole table at once, and derives it from `GatekeeperEnv` rather than a
69
+ * transcribed list, because a binding this check silently passed over is a binding whose absence
70
+ * reads as a healthy Gatekeeper.
71
+ */
72
+ export function missingBindings(env) {
73
+ const bindings = Object.keys(BINDING_KINDS);
74
+ return bindings.filter((binding) => !isSet(env, binding));
75
+ }
76
+ function isSet(env, binding) {
77
+ if (BINDING_KINDS[binding] === 'durable-object') {
78
+ // A namespace this Worker can reach, rather than merely a truthy property: an unbound
79
+ // `durable_objects` entry is a plain `undefined`, and a bound one always answers `idFromName`.
80
+ return typeof env.STATE?.idFromName === 'function';
81
+ }
82
+ const value = env[binding];
83
+ return typeof value === 'string' && value.length > 0;
84
+ }
85
+ /** The refusal an operator can act on: every unset binding, each with how to set it. */
86
+ export function describeMissingBindings(missing) {
87
+ const remedies = missing.map((binding) => `${binding} (${howToSet(binding)})`).join('; ');
88
+ return `This Gatekeeper is not fully configured, so it can accept no traffic. Unset: ${remedies}.`;
89
+ }
90
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,mDAAmD;AACnD,EAAE;AACF,kGAAkG;AAClG,kGAAkG;AAClG,8FAA8F;AAC9F,6FAA6F;AAC7F,2FAA2F;AAC3F,mDAAmD;AACnD,EAAE;AACF,kGAAkG;AAClG,iGAAiG;AACjG,+EAA+E;AA0B/E;;;;;;;;;GASG;AACH,MAAM,aAAa,GAAG;IACpB,oBAAoB,EAAE,KAAK;IAC3B,UAAU,EAAE,KAAK;IACjB,UAAU,EAAE,KAAK;IACjB,gBAAgB,EAAE,QAAQ;IAC1B,cAAc,EAAE,QAAQ;IACxB,eAAe,EAAE,QAAQ;IACzB,KAAK,EAAE,gBAAgB;CACoC,CAAA;AAI7D,mFAAmF;AACnF,SAAS,QAAQ,CAAC,OAAgB;IAChC,gGAAgG;IAChG,2DAA2D;IAC3D,QAAQ,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/B,KAAK,KAAK;YACR,OAAO,kCAAkC,CAAA;QAC3C,KAAK,QAAQ;YACX,OAAO,qCAAqC,OAAO,4BAA4B,CAAA;QACjF,KAAK,gBAAgB;YACnB,OAAO,oFAAoF,CAAA;IAC/F,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,OAAO,CAAS;IAEzB,YAAY,OAAgB;QAC1B,KAAK,CAAC,GAAG,OAAO,0CAA0C,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC/E,IAAI,CAAC,IAAI,GAAG,aAAa,CAAA;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;CACF;AAID,2DAA2D;AAC3D,MAAM,UAAU,UAAU,CAAC,GAAkB,EAAE,OAAsB;IACnE,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;IAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,WAAW,CAAC,OAAO,CAAC,CAAA;IACnF,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,GAAkB;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAc,CAAA;IACxD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;AAC3D,CAAC;AAED,SAAS,KAAK,CAAC,GAAkB,EAAE,OAAgB;IACjD,IAAI,aAAa,CAAC,OAAO,CAAC,KAAK,gBAAgB,EAAE,CAAC;QAChD,sFAAsF;QACtF,+FAA+F;QAC/F,OAAO,OAAO,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,UAAU,CAAA;IACpD,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;IAC1B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;AACtD,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,uBAAuB,CAAC,OAA2B;IACjE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzF,OAAO,gFAAgF,QAAQ,GAAG,CAAA;AACpG,CAAC"}
@@ -0,0 +1,22 @@
1
+ /** A refusal the operator fixes, raised while compiling the policy. */
2
+ export declare class PolicyError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /** Every machine-readable cause a live Gatekeeper answers with. */
6
+ export type GatekeeperReason = 'unknown_actor' | 'unknown_tier' | 'binding_not_granted' | 'card_not_found' | 'card_already_resolved' | 'unsupported_action'
7
+ /** The verb needs a field the caller did not send. Named rather than defaulted. */
8
+ | 'invalid_answer'
9
+ /** The run holds no pending decision of the kind the caller named. */
10
+ | 'no_such_park'
11
+ /** The run is parked on more than one thing and the caller named none. */
12
+ | 'ambiguous_park'
13
+ /** The platform sent a parked decision without the id every answer to it addresses. */
14
+ | 'malformed_decision'
15
+ /** A minted credential was refused upstream and re-minting it was refused too. */
16
+ | 'credential_rejected';
17
+ /** A refusal the caller (or the OS deployment operating it) acts on. */
18
+ export declare class GatekeeperError extends Error {
19
+ readonly reason: GatekeeperReason;
20
+ constructor(reason: GatekeeperReason, message: string);
21
+ }
22
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAcA,uEAAuE;AACvE,qBAAa,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED,mEAAmE;AACnE,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,cAAc,GACd,qBAAqB,GACrB,gBAAgB,GAChB,uBAAuB,GACvB,oBAAoB;AACtB,mFAAmF;GACjF,gBAAgB;AAClB,sEAAsE;GACpE,cAAc;AAChB,0EAA0E;GACxE,gBAAgB;AAClB,uFAAuF;GACrF,oBAAoB;AACtB,kFAAkF;GAChF,qBAAqB,CAAA;AAEzB,wEAAwE;AACxE,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;IAEjC,YAAY,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAIpD;CACF"}
package/dist/errors.js ADDED
@@ -0,0 +1,30 @@
1
+ // The two refusals a Gatekeeper makes, kept apart because they need different fixes.
2
+ //
3
+ // A `PolicyError` is the OPERATOR's: the policy this deployment handed the Gatekeeper names
4
+ // something the surface does not have, or grants something its key could never call. (Named by
5
+ // what it IS, never by the file it arrived in: that file is the operator's to place, and the
6
+ // template's `policy.config.ts` is one choice of name rather than this package's to assume.) It is
7
+ // raised while the policy is compiled, before any capability exists, so a misconfigured Gatekeeper
8
+ // serves nothing rather than serving a capability whose every method 403s.
9
+ //
10
+ // A `GatekeeperError` is the CALLER's: a request that reached a live Gatekeeper and was refused
11
+ // by it (an unknown actor, an approval card that no longer exists). It carries a machine-readable
12
+ // `reason` for the same purpose the platform's own `details.reason` serves: an OS Gadget maps it
13
+ // to copy and a remedy, and prose is what a human reads afterwards.
14
+ /** A refusal the operator fixes, raised while compiling the policy. */
15
+ export class PolicyError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = 'PolicyError';
19
+ }
20
+ }
21
+ /** A refusal the caller (or the OS deployment operating it) acts on. */
22
+ export class GatekeeperError extends Error {
23
+ reason;
24
+ constructor(reason, message) {
25
+ super(message);
26
+ this.name = 'GatekeeperError';
27
+ this.reason = reason;
28
+ }
29
+ }
30
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,EAAE;AACF,4FAA4F;AAC5F,+FAA+F;AAC/F,6FAA6F;AAC7F,mGAAmG;AACnG,mGAAmG;AACnG,2EAA2E;AAC3E,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,iGAAiG;AACjG,oEAAoE;AAEpE,uEAAuE;AACvE,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,aAAa,CAAA;IAC3B,CAAC;CACF;AAqBD,wEAAwE;AACxE,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,MAAM,CAAkB;IAEjC,YAAY,MAAwB,EAAE,OAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;CACF"}
@@ -0,0 +1,78 @@
1
+ import { type GatekeeperEnv } from './env.js';
2
+ import { type Actor } from './keys.js';
3
+ import { type GatekeeperPolicy } from './policy/compile.js';
4
+ import { type VerificationResult } from './webhook/signature.js';
5
+ /** Why a delivery was refused, drawn from the verifier so the two cannot drift. */
6
+ type RejectionReason = Extract<VerificationResult, {
7
+ ok: false;
8
+ }>['reason'];
9
+ /** What taking delivery of one webhook POST did. */
10
+ export type DeliveryOutcome = {
11
+ handled: 'rejected';
12
+ reason: RejectionReason;
13
+ } | {
14
+ handled: 'unparseable';
15
+ } | {
16
+ handled: 'duplicate';
17
+ deliveryId: string;
18
+ } | {
19
+ handled: 'accepted';
20
+ deliveryId: string;
21
+ effect: 'opened' | 'superseded' | 'run-event' | 'none';
22
+ };
23
+ export declare class Gatekeeper {
24
+ #private;
25
+ private constructor();
26
+ /**
27
+ * Assemble against the deployment's own policy, compiling it.
28
+ *
29
+ * Throws a `PolicyError` on a policy an operator has to fix, and a `ConfigError` on a binding
30
+ * they have not set. Both are refusals the request path answers as a 503 naming the cause, which
31
+ * is why the compile happens per assembly rather than once at module load: a Worker whose policy
32
+ * threw while its module evaluated serves nothing at all, not even the refusal that says why.
33
+ */
34
+ static create(env: GatekeeperEnv, policy: GatekeeperPolicy): Gatekeeper;
35
+ /** Whether the presented bearer token is this Gatekeeper's own. */
36
+ authorize(header: string | null): boolean;
37
+ /**
38
+ * The capability for one actor the OS has authenticated.
39
+ *
40
+ * The tier is resolved from the Gatekeeper's OWN policy, never from anything the caller sent:
41
+ * an agent that could name its tier would be its own authorization.
42
+ */
43
+ capabilityFor(actor: Actor): import("capnweb").RpcTarget;
44
+ /**
45
+ * Retire an actor: revoke every key this Gatekeeper minted for them, upstream and here.
46
+ *
47
+ * Exposed on the ADMIN surface rather than on a capability, because it is the OS deployment's
48
+ * offboarding action and not something an agent acting AS someone should be able to do to them.
49
+ */
50
+ retire(actorId: string): Promise<{
51
+ revoked: string[];
52
+ remaining: string[];
53
+ }>;
54
+ /**
55
+ * Register this Worker as a named outbound webhook endpoint, idempotently.
56
+ *
57
+ * Safe to call on every cron tick and on demand: the route is keyed on the caller-chosen id, so
58
+ * re-asserting the registration heals a deployment whose endpoint was edited or removed without
59
+ * ever displacing a sibling integration's slot.
60
+ */
61
+ enroll(): Promise<{
62
+ webhookId: string;
63
+ url: string;
64
+ }>;
65
+ /**
66
+ * Take delivery of one webhook POST.
67
+ *
68
+ * Order matters and is the whole security story of this method: verify the MAC over the RAW
69
+ * bytes FIRST, parse SECOND, and hand the dedupe and the effect to durable state as ONE call.
70
+ * Parsing before verifying would run this deployment's JSON decoder over unauthenticated input.
71
+ * Deduping in a call of its own would be worse than useless: a marker committed before the card
72
+ * write turns a failed write into a `duplicate` on the platform's retry, so the approval never
73
+ * reaches the inbox and nothing anywhere reports that it did not.
74
+ */
75
+ takeDelivery(request: Request, now: number): Promise<DeliveryOutcome>;
76
+ }
77
+ export {};
78
+ //# sourceMappingURL=gatekeeper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatekeeper.d.ts","sourceRoot":"","sources":["../src/gatekeeper.ts"],"names":[],"mappings":"AAYA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,UAAU,CAAA;AAEzD,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAA;AAG5B,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAKhF,mFAAmF;AACnF,KAAK,eAAe,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAA;AAE3E,oDAAoD;AACpD,MAAM,MAAM,eAAe,GACvB;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAChD;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,GAC1B;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC5C;IACE,OAAO,EAAE,UAAU,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,GAAG,YAAY,GAAG,WAAW,GAAG,MAAM,CAAA;CACvD,CAAA;AAEL,qBAAa,UAAU;;IAMrB,OAAO,eAiBN;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAEtE;IAED,mEAAmE;IACnE,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAIxC;IAED;;;;;OAKG;IACH,aAAa,CAAC,KAAK,EAAE,KAAK,+BAUzB;IAED;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAEjF;IAED;;;;;;OAMG;IACG,MAAM,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CA2B1D;IAED;;;;;;;;;OASG;IACG,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA0B1E;CACF"}