@cat-factory/gatekeeper-worker 0.4.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -13
- package/dist/arguments.d.ts +18 -0
- package/dist/arguments.d.ts.map +1 -0
- package/dist/arguments.js +75 -0
- package/dist/arguments.js.map +1 -0
- package/dist/capability.d.ts +55 -0
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +99 -5
- package/dist/capability.js.map +1 -1
- package/dist/env.d.ts +10 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +12 -0
- package/dist/env.js.map +1 -1
- package/dist/errors.d.ts +32 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -1
- package/dist/gatekeeper.d.ts +78 -3
- package/dist/gatekeeper.d.ts.map +1 -1
- package/dist/gatekeeper.js +127 -13
- package/dist/gatekeeper.js.map +1 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +12 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +28 -0
- package/dist/markdown.js.map +1 -0
- package/dist/os/account.d.ts +27 -0
- package/dist/os/account.d.ts.map +1 -0
- package/dist/os/account.js +130 -0
- package/dist/os/account.js.map +1 -0
- package/dist/os/descriptions.d.ts +43 -0
- package/dist/os/descriptions.d.ts.map +1 -0
- package/dist/os/descriptions.js +100 -0
- package/dist/os/descriptions.js.map +1 -0
- package/dist/os/discoverability.d.ts +42 -0
- package/dist/os/discoverability.d.ts.map +1 -0
- package/dist/os/discoverability.js +63 -0
- package/dist/os/discoverability.js.map +1 -0
- package/dist/os/exports.d.ts +55 -0
- package/dist/os/exports.d.ts.map +1 -0
- package/dist/os/exports.js +93 -0
- package/dist/os/exports.js.map +1 -0
- package/dist/os/hook-controller.d.ts +14 -0
- package/dist/os/hook-controller.d.ts.map +1 -0
- package/dist/os/hook-controller.js +53 -0
- package/dist/os/hook-controller.js.map +1 -0
- package/dist/os/hooks.d.ts +200 -0
- package/dist/os/hooks.d.ts.map +1 -0
- package/dist/os/hooks.js +253 -0
- package/dist/os/hooks.js.map +1 -0
- package/dist/os/protocol.d.ts +193 -0
- package/dist/os/protocol.d.ts.map +1 -0
- package/dist/os/protocol.js +22 -0
- package/dist/os/protocol.js.map +1 -0
- package/dist/os/queue.d.ts +99 -0
- package/dist/os/queue.d.ts.map +1 -0
- package/dist/os/queue.js +240 -0
- package/dist/os/queue.js.map +1 -0
- package/dist/os/resource-core.d.ts +115 -0
- package/dist/os/resource-core.d.ts.map +1 -0
- package/dist/os/resource-core.js +172 -0
- package/dist/os/resource-core.js.map +1 -0
- package/dist/os/resource.d.ts +16 -0
- package/dist/os/resource.d.ts.map +1 -0
- package/dist/os/resource.js +70 -0
- package/dist/os/resource.js.map +1 -0
- package/dist/os/resources.d.ts +11 -0
- package/dist/os/resources.d.ts.map +1 -0
- package/dist/os/resources.js +26 -0
- package/dist/os/resources.js.map +1 -0
- package/dist/os/session-types.d.ts +6 -0
- package/dist/os/session-types.d.ts.map +1 -0
- package/dist/os/session-types.js +110 -0
- package/dist/os/session-types.js.map +1 -0
- package/dist/os/sharing.d.ts +35 -0
- package/dist/os/sharing.d.ts.map +1 -0
- package/dist/os/sharing.js +135 -0
- package/dist/os/sharing.js.map +1 -0
- package/dist/os/vendor.d.ts +14 -0
- package/dist/os/vendor.d.ts.map +1 -0
- package/dist/os/vendor.js +102 -0
- package/dist/os/vendor.js.map +1 -0
- package/dist/policy/compile.d.ts +38 -0
- package/dist/policy/compile.d.ts.map +1 -1
- package/dist/policy/compile.js +42 -4
- package/dist/policy/compile.js.map +1 -1
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/index.d.ts.map +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/policy/index.js.map +1 -1
- package/dist/state.d.ts +80 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +234 -6
- package/dist/state.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +28 -6
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// The handle a workspace turns one bound hook on and off by.
|
|
2
|
+
//
|
|
3
|
+
// It is a fifth named export for the same reason the verifier is a fourth: what the workspace has
|
|
4
|
+
// to hold is not what the workspace has to be able to DO. A controller carries no capability, no
|
|
5
|
+
// key and no session; it names one registration, and its two methods are the only two things
|
|
6
|
+
// anybody may do to that registration from outside. Handing over a session or an account instead
|
|
7
|
+
// would hand over everything they carry along with the ability to disable a hook.
|
|
8
|
+
//
|
|
9
|
+
// Its props ARE the registration's identity, which is what keeps this class stateless: the
|
|
10
|
+
// workspace persists the controller, so the id, the topic, the account and the tier ride back in
|
|
11
|
+
// on `enable` without this Gatekeeper keeping a table of hooks nobody has approved yet. That is
|
|
12
|
+
// also the contract's own instruction ("avoid storing any state until the hook is enabled") read
|
|
13
|
+
// literally rather than approximately.
|
|
14
|
+
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
15
|
+
import { stateFor } from '../env.js';
|
|
16
|
+
/**
|
|
17
|
+
* Build the hook controller class a deployment exports.
|
|
18
|
+
*
|
|
19
|
+
* No policy argument, like the verifier: nothing here resolves a tier or compiles a grant. The
|
|
20
|
+
* tier a delivery is described at was resolved by the session that bound the hook and rides the
|
|
21
|
+
* props, because re-resolving it here would silently re-tier a live hook the day an operator edits
|
|
22
|
+
* the policy, with the workspace still believing it enabled the one it was shown.
|
|
23
|
+
*/
|
|
24
|
+
export function createGatekeeperHookController() {
|
|
25
|
+
return class CatFactoryHookController extends WorkerEntrypoint {
|
|
26
|
+
/**
|
|
27
|
+
* The workspace approved the hook: record it, and hold the initiator it handed over.
|
|
28
|
+
*
|
|
29
|
+
* Re-enabling an already-enabled hook REPLACES the initiator, which the contract asks for, and
|
|
30
|
+
* the store keeps the counters this call zeroes (see `enableHook`): they are the
|
|
31
|
+
* registration's history rather than that stub's, so resetting them would erase the record of
|
|
32
|
+
* the deliveries a workspace missed at exactly the moment it is re-arming to stop missing them.
|
|
33
|
+
*/
|
|
34
|
+
async enable(initiator, target) {
|
|
35
|
+
const record = {
|
|
36
|
+
...this.ctx.props,
|
|
37
|
+
target,
|
|
38
|
+
enabledAt: Date.now(),
|
|
39
|
+
deliveries: 0,
|
|
40
|
+
missed: 0,
|
|
41
|
+
failures: 0,
|
|
42
|
+
lastDeliveryAt: null,
|
|
43
|
+
lastError: null,
|
|
44
|
+
};
|
|
45
|
+
await stateFor(this.env).enableHook(record, initiator);
|
|
46
|
+
}
|
|
47
|
+
/** The workspace withdrew the hook: the record and the live half both go, permanently. */
|
|
48
|
+
async disable() {
|
|
49
|
+
await stateFor(this.env).disableHook(this.ctx.props.hookId);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=hook-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-controller.js","sourceRoot":"","sources":["../../src/os/hook-controller.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,kGAAkG;AAClG,iGAAiG;AACjG,6FAA6F;AAC7F,iGAAiG;AACjG,kFAAkF;AAClF,EAAE;AACF,2FAA2F;AAC3F,iGAAiG;AACjG,gGAAgG;AAChG,iGAAiG;AACjG,uCAAuC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAsB,MAAM,WAAW,CAAA;AAMxD;;;;;;;GAOG;AACH,MAAM,UAAU,8BAA8B;IAI5C,OAAO,MAAM,wBAAyB,SAAQ,gBAG7C;QACC;;;;;;;WAOG;QACH,KAAK,CAAC,MAAM,CAAC,SAAwB,EAAE,MAA0B;YAC/D,MAAM,MAAM,GAAe;gBACzB,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK;gBACjB,MAAM;gBACN,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,UAAU,EAAE,CAAC;gBACb,MAAM,EAAE,CAAC;gBACT,QAAQ,EAAE,CAAC;gBACX,cAAc,EAAE,IAAI;gBACpB,SAAS,EAAE,IAAI;aAChB,CAAA;YACD,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;QACxD,CAAC;QAED,0FAA0F;QAC1F,KAAK,CAAC,OAAO;YACX,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QAC7D,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { ApprovalCard, RunState } from '../state.js';
|
|
2
|
+
import type { HookDescription, HookInitiator, HookTargetMetadata, ObservationDescription } from './protocol.js';
|
|
3
|
+
/** What a workspace can be pushed. */
|
|
4
|
+
export type HookTopic = 'approval_card' | 'run_event';
|
|
5
|
+
/**
|
|
6
|
+
* The two topics, as an exhaustive `Record`, so a third one cannot be half-added.
|
|
7
|
+
*
|
|
8
|
+
* A topic is a delivery FAMILY this Gatekeeper already receives and projects, never a new
|
|
9
|
+
* subscription upstream: what the platform sends is decided by `enroll()`, and a hook that pushed
|
|
10
|
+
* something the webhook does not carry would be a promise no delivery could keep.
|
|
11
|
+
*/
|
|
12
|
+
export declare const HOOK_TOPICS: {
|
|
13
|
+
readonly approval_card: {
|
|
14
|
+
readonly sessionMethod: 'approvals_subscribe';
|
|
15
|
+
readonly callbackMethod: 'onApprovalCard';
|
|
16
|
+
readonly title: 'Push cat-factory approval cards as they are raised';
|
|
17
|
+
readonly description: string;
|
|
18
|
+
};
|
|
19
|
+
readonly run_event: {
|
|
20
|
+
readonly sessionMethod: 'runs_subscribe';
|
|
21
|
+
readonly callbackMethod: 'onRunEvent';
|
|
22
|
+
readonly title: 'Push cat-factory run lifecycle transitions';
|
|
23
|
+
readonly description: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/** Every topic, for a caller that has to cover all of them. */
|
|
27
|
+
export declare const HOOK_TOPIC_NAMES: HookTopic[];
|
|
28
|
+
/** What a hook's controller is imbued with when a session binds it. */
|
|
29
|
+
export interface HookControllerProps {
|
|
30
|
+
hookId: string;
|
|
31
|
+
topic: HookTopic;
|
|
32
|
+
/** The account whose session bound it. A session reads back only its own account's hooks. */
|
|
33
|
+
accountId: string;
|
|
34
|
+
/** The policy tier that account resolved to, for the delivery's own description. */
|
|
35
|
+
tier: string;
|
|
36
|
+
/** The paired deployment, so the record can describe a delivery without resolving config. */
|
|
37
|
+
deployment: string;
|
|
38
|
+
}
|
|
39
|
+
/** One enabled hook, as it is stored. Written on `enable`, deleted on `disable`. */
|
|
40
|
+
export interface HookRecord extends HookControllerProps {
|
|
41
|
+
/** The workspace the hook was enabled from, and the gadget within it where there is one. */
|
|
42
|
+
target: HookTargetMetadata;
|
|
43
|
+
enabledAt: number;
|
|
44
|
+
/** Deliveries this hook has taken. */
|
|
45
|
+
deliveries: number;
|
|
46
|
+
/**
|
|
47
|
+
* Deliveries that found no live initiator, so nothing was pushed.
|
|
48
|
+
*
|
|
49
|
+
* The one number that must never be inferred from silence: it is what tells a workspace that
|
|
50
|
+
* its hook stopped receiving without anybody deciding it should.
|
|
51
|
+
*/
|
|
52
|
+
missed: number;
|
|
53
|
+
/** Deliveries the workspace's own side refused or failed. */
|
|
54
|
+
failures: number;
|
|
55
|
+
lastDeliveryAt: number | null;
|
|
56
|
+
/** The most recent failure, kept so a reader has something to act on beyond a count. */
|
|
57
|
+
lastError: string | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* What IDENTIFIES a registration in storage: the place its deliveries land.
|
|
61
|
+
*
|
|
62
|
+
* Not the hook id, which is the wrong key for the one lifecycle a workspace is documented to run.
|
|
63
|
+
* The live half of a registration cannot be stored, so a durable object evicted between two
|
|
64
|
+
* deliveries keeps the record and loses the stub, and the remedy is to bind again. A bind mints a
|
|
65
|
+
* FRESH hook id, so a hook-id-keyed row made every one of those cycles a new row: the dead
|
|
66
|
+
* registration stayed forever, never live, counting a `missed` against every delivery for the rest
|
|
67
|
+
* of the deployment's life, and no reader could tell it from a hook that had genuinely gone quiet.
|
|
68
|
+
*
|
|
69
|
+
* Keying on the target makes re-binding what it reads as: the same registration, re-armed. The row
|
|
70
|
+
* is replaced, its counters carry forward (the `missed` count is the evidence the workspace acted
|
|
71
|
+
* on, so re-arming must not erase it), and the set of rows stays bounded by the gadgets a
|
|
72
|
+
* workspace actually runs rather than by how many times one was re-armed.
|
|
73
|
+
*
|
|
74
|
+
* The account comes first and is never the caller's to assert, so no target a workspace names can
|
|
75
|
+
* collide with another account's row; the remaining parts are escaped so a separator inside one of
|
|
76
|
+
* them cannot make two different targets share a key.
|
|
77
|
+
*/
|
|
78
|
+
export declare function registrationKey(record: {
|
|
79
|
+
accountId: string;
|
|
80
|
+
topic: HookTopic;
|
|
81
|
+
target: HookTargetMetadata;
|
|
82
|
+
}): string;
|
|
83
|
+
/**
|
|
84
|
+
* One hook as a session reads it back, which is the record PLUS the fact it cannot store.
|
|
85
|
+
*
|
|
86
|
+
* `live` is computed at read time from whether the initiator is still held, never persisted: a
|
|
87
|
+
* stored flag would say "live" from before the eviction that made it false, which is the one
|
|
88
|
+
* reading that must not be available.
|
|
89
|
+
*/
|
|
90
|
+
export interface HookRegistration extends HookRecord {
|
|
91
|
+
live: boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* What one fan-out did, summed over every push it made.
|
|
95
|
+
*
|
|
96
|
+
* It carries no topic: a change can produce payloads for more than one (a terminal run event
|
|
97
|
+
* settles the run's open cards as it records the transition), and one number per outcome across
|
|
98
|
+
* all of them is what a caller acts on. WHICH topics were dispatched is the receiver's to report,
|
|
99
|
+
* because it is knowable before any push is made.
|
|
100
|
+
*/
|
|
101
|
+
export interface HookDispatchReport {
|
|
102
|
+
delivered: number;
|
|
103
|
+
/** Registrations whose live half was lost, so nothing was pushed and the record counted it. */
|
|
104
|
+
stale: number;
|
|
105
|
+
failed: number;
|
|
106
|
+
}
|
|
107
|
+
/** What a topic pushes, as the callback receives it. */
|
|
108
|
+
export type HookPayload = {
|
|
109
|
+
topic: 'approval_card';
|
|
110
|
+
card: ApprovalCard;
|
|
111
|
+
} | {
|
|
112
|
+
topic: 'run_event';
|
|
113
|
+
state: RunState;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* One thing a committed write left to push, named by its id rather than carried whole.
|
|
117
|
+
*
|
|
118
|
+
* The fan-out runs in the durable object and the write is reported out of it, so what crosses that
|
|
119
|
+
* boundary is a list of ids and the dispatcher reads each row back. Two reasons, and the second is
|
|
120
|
+
* the one that decides it. A `RunState` carries the run projection verbatim as an open record,
|
|
121
|
+
* which a Durable Object stub's serialization type will not carry (the same constraint
|
|
122
|
+
* `runs_watched()` annotates around), so a payload cannot make the trip. And re-reading is the
|
|
123
|
+
* more correct half anyway: a card that has been answered between the commit and the push is
|
|
124
|
+
* pushed as it now stands, rather than as the delivery left it.
|
|
125
|
+
*/
|
|
126
|
+
export type HookPushTarget = {
|
|
127
|
+
topic: 'approval_card';
|
|
128
|
+
cardId: string;
|
|
129
|
+
} | {
|
|
130
|
+
topic: 'run_event';
|
|
131
|
+
runId: string;
|
|
132
|
+
};
|
|
133
|
+
/** What the approver is asked when a workspace binds a hook. */
|
|
134
|
+
export declare function describeHook(topic: HookTopic, subject: {
|
|
135
|
+
accountId: string;
|
|
136
|
+
tier: string;
|
|
137
|
+
deployment: string;
|
|
138
|
+
}): HookDescription;
|
|
139
|
+
/**
|
|
140
|
+
* What the queue is asked before ONE delivery is pushed.
|
|
141
|
+
*
|
|
142
|
+
* A hook delivery is an observation and is authorized as one, which the contract asks for and
|
|
143
|
+
* which is also the only reading that holds: the bytes are a card the paired deployment raised,
|
|
144
|
+
* and that they arrived here over a webhook rather than over a read is an implementation detail of
|
|
145
|
+
* how they got here. The refusal path is what makes it matter: a workspace that has withdrawn a
|
|
146
|
+
* person's access can stop the push without this Gatekeeper having to be told.
|
|
147
|
+
*/
|
|
148
|
+
export declare function describeHookDelivery(payload: HookPayload, record: HookRecord): ObservationDescription;
|
|
149
|
+
/** The live half of a registration: the workspace's initiator, held past the call it arrived on. */
|
|
150
|
+
export type HookRegistry = Map<string, HookInitiator>;
|
|
151
|
+
/**
|
|
152
|
+
* Take a reference to a stub that outlives the call it arrived on.
|
|
153
|
+
*
|
|
154
|
+
* The same loan rule `holdQueue` documents, arriving on the other parameter the contract hands
|
|
155
|
+
* over: the runtime disposes a parameter stub when the method returns, and an initiator kept for
|
|
156
|
+
* the next delivery is used long after that. The optional call is the in-process case, where the
|
|
157
|
+
* "stub" is an ordinary object with nothing to duplicate.
|
|
158
|
+
*/
|
|
159
|
+
export declare function holdInitiator(initiator: HookInitiator): HookInitiator;
|
|
160
|
+
/** Give back a held reference. Paired with {@link holdInitiator} and kept beside it. */
|
|
161
|
+
export declare function releaseInitiator(initiator: HookInitiator): void;
|
|
162
|
+
/**
|
|
163
|
+
* What one push did, SEPARATE from the record it will be counted on.
|
|
164
|
+
*
|
|
165
|
+
* The split is the whole safety property of the fan-out. A push awaits a call into another Worker,
|
|
166
|
+
* which is not a storage operation, so the durable object's input gate is open across it and the
|
|
167
|
+
* record read before the push is a snapshot that may be stale by the time it resolves: a
|
|
168
|
+
* concurrent `disable` may have removed the registration, and a concurrent delivery may have moved
|
|
169
|
+
* the very counters this one is about to bump. Reporting the outcome and folding it onto whatever
|
|
170
|
+
* is stored AFTERWARDS is what keeps both of those true, where writing back a modified snapshot
|
|
171
|
+
* resurrected the withdrawn registration and lost the sibling's increments.
|
|
172
|
+
*/
|
|
173
|
+
export type HookPushOutcome = {
|
|
174
|
+
outcome: 'delivered';
|
|
175
|
+
}
|
|
176
|
+
/** The registration's live half was lost, so there was nothing to push through. */
|
|
177
|
+
| {
|
|
178
|
+
outcome: 'stale';
|
|
179
|
+
} | {
|
|
180
|
+
outcome: 'failed';
|
|
181
|
+
error: string;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Push one payload to one enabled hook, and report what that did.
|
|
185
|
+
*
|
|
186
|
+
* Every outcome is a REPORT rather than a throw, because the caller is a fan-out on behalf of a
|
|
187
|
+
* delivery whose record has already committed: a workspace that refuses, breaks or hangs costs a
|
|
188
|
+
* notification, and must never cost the acknowledgement the platform's retry is protecting.
|
|
189
|
+
*/
|
|
190
|
+
export declare function pushToHook(payload: HookPayload, record: HookRecord, initiator: HookInitiator | undefined): Promise<HookPushOutcome>;
|
|
191
|
+
/**
|
|
192
|
+
* Fold one push's outcome onto the record as it stands NOW.
|
|
193
|
+
*
|
|
194
|
+
* Pure, and takes the current record rather than the one the push was described from, so a caller
|
|
195
|
+
* can re-read between the two. `lastError` is cleared by a delivery on purpose: it is what a
|
|
196
|
+
* reader acts on beside `failures`, and a stale one beside a hook that has since recovered points
|
|
197
|
+
* at a fix nobody needs to make.
|
|
198
|
+
*/
|
|
199
|
+
export declare function applyPushOutcome(record: HookRecord, result: HookPushOutcome, now: number): HookRecord;
|
|
200
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/os/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/os/hooks.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// PUSHING what the inbox already holds: the approval cards and run transitions a workspace would
|
|
2
|
+
// otherwise have to ask for.
|
|
3
|
+
//
|
|
4
|
+
// Nothing here is a new source of truth, and that is the whole design. A delivery arrives on the
|
|
5
|
+
// webhook, is verified, deduped and written to durable state, and only THEN is anything pushed:
|
|
6
|
+
// `approvals_list()` and `runs_watched()` go on answering the same question for a workspace that
|
|
7
|
+
// never bound a hook, and for one whose hook missed an event. So a hook is an accelerator over a
|
|
8
|
+
// projection, never the projection itself, and every failure mode below is safe in exactly that
|
|
9
|
+
// way: what a hook loses, a read still has.
|
|
10
|
+
//
|
|
11
|
+
// The lifecycle is the published contract's, and each step exists for a reason worth keeping:
|
|
12
|
+
//
|
|
13
|
+
// 1. A session calls `approvals_subscribe(callback)`. We mint a hook id, build a CONTROLLER of
|
|
14
|
+
// our own, and hand both the controller and the callback to `queue.bindHook`. We store
|
|
15
|
+
// NOTHING: the workspace may still have to ask a person, and a gatekeeper that wrote a row
|
|
16
|
+
// here would be holding registrations for hooks nobody ever approved.
|
|
17
|
+
// 2. The workspace approves and calls `enable(initiator, target)` on that controller. NOW there
|
|
18
|
+
// is state: the durable record, plus the initiator held live.
|
|
19
|
+
// 3. Each event calls `initiator.startHook()` for a FRESH callback and the queue that governs
|
|
20
|
+
// it, authorizes the delivery as the observation it is, and delivers.
|
|
21
|
+
// 4. `disable()` removes both halves. The contract asks for a permanent clean-up and means it.
|
|
22
|
+
//
|
|
23
|
+
// THE CALLBACK IS NEVER STORED, at any point. It is a parameter stub, so the runtime disposes it
|
|
24
|
+
// when the call it arrived on returns; storing one would leave a registration that looks live and
|
|
25
|
+
// throws on the first delivery, months after the mistake.
|
|
26
|
+
//
|
|
27
|
+
// THE INITIATOR IS HELD IN MEMORY, and that is a real limit rather than an oversight. A stub is a
|
|
28
|
+
// reference into another Worker and there is no way to write one to storage, so a Durable Object
|
|
29
|
+
// that is evicted between two deliveries keeps the record and loses the live half. That is why
|
|
30
|
+
// every record COUNTS its missed deliveries and why `hooks_bound()` publishes the count beside
|
|
31
|
+
// `live`: a workspace can see that its hook went quiet, and reconcile from the projection that
|
|
32
|
+
// never did. A silent stop would be the one failure this design cannot tolerate, because a
|
|
33
|
+
// workspace has no way to tell it from a deployment where nothing happened.
|
|
34
|
+
//
|
|
35
|
+
// So RE-BINDING IS THE DOCUMENTED REMEDY, and a registration is therefore keyed on where its
|
|
36
|
+
// deliveries land rather than on the id one bind minted (`registrationKey`). Re-arming a hook is
|
|
37
|
+
// the same registration coming back, not a second one beside the first.
|
|
38
|
+
import { describeError } from '../errors.js';
|
|
39
|
+
import { fenced } from '../markdown.js';
|
|
40
|
+
/**
|
|
41
|
+
* The two topics, as an exhaustive `Record`, so a third one cannot be half-added.
|
|
42
|
+
*
|
|
43
|
+
* A topic is a delivery FAMILY this Gatekeeper already receives and projects, never a new
|
|
44
|
+
* subscription upstream: what the platform sends is decided by `enroll()`, and a hook that pushed
|
|
45
|
+
* something the webhook does not carry would be a promise no delivery could keep.
|
|
46
|
+
*/
|
|
47
|
+
export const HOOK_TOPICS = {
|
|
48
|
+
approval_card: {
|
|
49
|
+
sessionMethod: 'approvals_subscribe',
|
|
50
|
+
callbackMethod: 'onApprovalCard',
|
|
51
|
+
title: 'Push cat-factory approval cards as they are raised',
|
|
52
|
+
description: 'Each time the paired cat-factory deployment reports that a run has parked on a human ' +
|
|
53
|
+
'decision, or that a card it raised has been settled elsewhere, the card is pushed to this ' +
|
|
54
|
+
'callback. It carries exactly what `approvals_list()` answers with, which stays available ' +
|
|
55
|
+
'and stays authoritative: a delivery missed while this hook was not live is still readable ' +
|
|
56
|
+
'there.',
|
|
57
|
+
},
|
|
58
|
+
run_event: {
|
|
59
|
+
sessionMethod: 'runs_subscribe',
|
|
60
|
+
callbackMethod: 'onRunEvent',
|
|
61
|
+
title: 'Push cat-factory run lifecycle transitions',
|
|
62
|
+
description: 'Each time the paired cat-factory deployment reports that a run has started, completed or ' +
|
|
63
|
+
'failed, the run’s latest state is pushed to this callback. It carries exactly what ' +
|
|
64
|
+
'`runs_watched()` answers with, which stays available and stays authoritative.',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
/** Every topic, for a caller that has to cover all of them. */
|
|
68
|
+
export const HOOK_TOPIC_NAMES = Object.keys(HOOK_TOPICS);
|
|
69
|
+
/**
|
|
70
|
+
* What IDENTIFIES a registration in storage: the place its deliveries land.
|
|
71
|
+
*
|
|
72
|
+
* Not the hook id, which is the wrong key for the one lifecycle a workspace is documented to run.
|
|
73
|
+
* The live half of a registration cannot be stored, so a durable object evicted between two
|
|
74
|
+
* deliveries keeps the record and loses the stub, and the remedy is to bind again. A bind mints a
|
|
75
|
+
* FRESH hook id, so a hook-id-keyed row made every one of those cycles a new row: the dead
|
|
76
|
+
* registration stayed forever, never live, counting a `missed` against every delivery for the rest
|
|
77
|
+
* of the deployment's life, and no reader could tell it from a hook that had genuinely gone quiet.
|
|
78
|
+
*
|
|
79
|
+
* Keying on the target makes re-binding what it reads as: the same registration, re-armed. The row
|
|
80
|
+
* is replaced, its counters carry forward (the `missed` count is the evidence the workspace acted
|
|
81
|
+
* on, so re-arming must not erase it), and the set of rows stays bounded by the gadgets a
|
|
82
|
+
* workspace actually runs rather than by how many times one was re-armed.
|
|
83
|
+
*
|
|
84
|
+
* The account comes first and is never the caller's to assert, so no target a workspace names can
|
|
85
|
+
* collide with another account's row; the remaining parts are escaped so a separator inside one of
|
|
86
|
+
* them cannot make two different targets share a key.
|
|
87
|
+
*/
|
|
88
|
+
export function registrationKey(record) {
|
|
89
|
+
return [
|
|
90
|
+
record.accountId,
|
|
91
|
+
record.topic,
|
|
92
|
+
record.target.workspaceId,
|
|
93
|
+
record.target.gadgetId === undefined ? '' : String(record.target.gadgetId),
|
|
94
|
+
]
|
|
95
|
+
.map((part) => encodeURIComponent(part))
|
|
96
|
+
.join(':');
|
|
97
|
+
}
|
|
98
|
+
/** What the approver is asked when a workspace binds a hook. */
|
|
99
|
+
export function describeHook(topic, subject) {
|
|
100
|
+
const definition = HOOK_TOPICS[topic];
|
|
101
|
+
return {
|
|
102
|
+
title: `${definition.title} (${subject.deployment})`,
|
|
103
|
+
description: `${definition.description}\n\nDeliveries are made for account \`${subject.accountId}\` at ` +
|
|
104
|
+
`policy tier \`${subject.tier}\`, and each one is authorized as an observation before it ` +
|
|
105
|
+
'is pushed.',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* What the queue is asked before ONE delivery is pushed.
|
|
110
|
+
*
|
|
111
|
+
* A hook delivery is an observation and is authorized as one, which the contract asks for and
|
|
112
|
+
* which is also the only reading that holds: the bytes are a card the paired deployment raised,
|
|
113
|
+
* and that they arrived here over a webhook rather than over a read is an implementation detail of
|
|
114
|
+
* how they got here. The refusal path is what makes it matter: a workspace that has withdrawn a
|
|
115
|
+
* person's access can stop the push without this Gatekeeper having to be told.
|
|
116
|
+
*/
|
|
117
|
+
export function describeHookDelivery(payload, record) {
|
|
118
|
+
const subject = payload.topic === 'approval_card'
|
|
119
|
+
? `The approval card \`${payload.card.cardId}\` raised for run \`${payload.card.runId}\`, ` +
|
|
120
|
+
`titled:\n\n${fenced(payload.card.title, '')}`
|
|
121
|
+
: `Run \`${payload.state.runId}\` reported \`${payload.state.event}\`.`;
|
|
122
|
+
return {
|
|
123
|
+
title: `${HOOK_TOPICS[payload.topic].title} (${record.deployment})`,
|
|
124
|
+
description: `${subject}\n\nPushed to a hook this workspace enabled for account ` +
|
|
125
|
+
`\`${record.accountId}\` at policy tier \`${record.tier}\`, from the record of what the ` +
|
|
126
|
+
`paired deployment at ${record.deployment} delivered.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Take a reference to a stub that outlives the call it arrived on.
|
|
131
|
+
*
|
|
132
|
+
* The same loan rule `holdQueue` documents, arriving on the other parameter the contract hands
|
|
133
|
+
* over: the runtime disposes a parameter stub when the method returns, and an initiator kept for
|
|
134
|
+
* the next delivery is used long after that. The optional call is the in-process case, where the
|
|
135
|
+
* "stub" is an ordinary object with nothing to duplicate.
|
|
136
|
+
*/
|
|
137
|
+
export function holdInitiator(initiator) {
|
|
138
|
+
const stub = initiator;
|
|
139
|
+
return typeof stub.dup === 'function' ? stub.dup() : initiator;
|
|
140
|
+
}
|
|
141
|
+
/** Give back a held reference. Paired with {@link holdInitiator} and kept beside it. */
|
|
142
|
+
export function releaseInitiator(initiator) {
|
|
143
|
+
;
|
|
144
|
+
initiator[Symbol.dispose]?.();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* How long one workspace has to take one push before it is counted as a failure.
|
|
148
|
+
*
|
|
149
|
+
* A hook delivery is a call into somebody else's Worker, and the failure this bounds is the one
|
|
150
|
+
* with no natural end: a workspace that accepts the connection and never answers. Unbounded, one
|
|
151
|
+
* such hook holds the fan-out (and, on the receiver, the invocation carrying it) until the runtime
|
|
152
|
+
* kills it, and the counter update that would have SAID so never lands, so the registration reads
|
|
153
|
+
* as one nothing was ever pushed to.
|
|
154
|
+
*/
|
|
155
|
+
const PUSH_TIMEOUT_MS = 10_000;
|
|
156
|
+
/**
|
|
157
|
+
* Push one payload to one enabled hook, and report what that did.
|
|
158
|
+
*
|
|
159
|
+
* Every outcome is a REPORT rather than a throw, because the caller is a fan-out on behalf of a
|
|
160
|
+
* delivery whose record has already committed: a workspace that refuses, breaks or hangs costs a
|
|
161
|
+
* notification, and must never cost the acknowledgement the platform's retry is protecting.
|
|
162
|
+
*/
|
|
163
|
+
export async function pushToHook(payload, record, initiator) {
|
|
164
|
+
if (initiator === undefined)
|
|
165
|
+
return { outcome: 'stale' };
|
|
166
|
+
try {
|
|
167
|
+
await withDeadline(deliverThrough(initiator, payload, record), PUSH_TIMEOUT_MS);
|
|
168
|
+
return { outcome: 'delivered' };
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return { outcome: 'failed', error: describeError(error) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Fold one push's outcome onto the record as it stands NOW.
|
|
176
|
+
*
|
|
177
|
+
* Pure, and takes the current record rather than the one the push was described from, so a caller
|
|
178
|
+
* can re-read between the two. `lastError` is cleared by a delivery on purpose: it is what a
|
|
179
|
+
* reader acts on beside `failures`, and a stale one beside a hook that has since recovered points
|
|
180
|
+
* at a fix nobody needs to make.
|
|
181
|
+
*/
|
|
182
|
+
export function applyPushOutcome(record, result, now) {
|
|
183
|
+
if (result.outcome === 'stale')
|
|
184
|
+
return { ...record, missed: record.missed + 1 };
|
|
185
|
+
if (result.outcome === 'failed') {
|
|
186
|
+
return { ...record, failures: record.failures + 1, lastError: result.error };
|
|
187
|
+
}
|
|
188
|
+
return { ...record, deliveries: record.deliveries + 1, lastDeliveryAt: now, lastError: null };
|
|
189
|
+
}
|
|
190
|
+
/** One delivery, through a callback minted for it and released with it. */
|
|
191
|
+
async function deliverThrough(initiator, payload, record) {
|
|
192
|
+
const started = await initiator.startHook();
|
|
193
|
+
try {
|
|
194
|
+
await started.approvalQueue.authorizeObservation(describeHookDelivery(payload, record));
|
|
195
|
+
await callBack(started.callback, payload);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
releaseStarted(started);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Give up on a push that never answers, and say that is what happened.
|
|
203
|
+
*
|
|
204
|
+
* The timer is cleared on both paths: a Worker invocation stays alive while one is outstanding, so
|
|
205
|
+
* leaving them behind would hold every delivery's invocation open for the timeout even when every
|
|
206
|
+
* push answered at once.
|
|
207
|
+
*/
|
|
208
|
+
async function withDeadline(work, timeoutMs) {
|
|
209
|
+
let timer;
|
|
210
|
+
const expiry = new Promise((_resolve, reject) => {
|
|
211
|
+
timer = setTimeout(() => reject(new Error(`The workspace did not answer this hook delivery within ${timeoutMs}ms, so it was ` +
|
|
212
|
+
'counted as a failure rather than waited on.')), timeoutMs);
|
|
213
|
+
});
|
|
214
|
+
try {
|
|
215
|
+
return await Promise.race([work, expiry]);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
if (timer !== undefined)
|
|
219
|
+
clearTimeout(timer);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Call the topic's method on the workspace's callback.
|
|
224
|
+
*
|
|
225
|
+
* A callback with no such method is a FAILURE that says so, rather than a push that quietly does
|
|
226
|
+
* nothing: the workspace implements the interface this package publishes in the session types, and
|
|
227
|
+
* a mismatch between the two is exactly the thing a count of failures should make visible.
|
|
228
|
+
*/
|
|
229
|
+
async function callBack(callback, payload) {
|
|
230
|
+
const method = HOOK_TOPICS[payload.topic].callbackMethod;
|
|
231
|
+
if (callback === null || callback === undefined) {
|
|
232
|
+
throw new Error('This hook was started with no callback, so nothing could be delivered to it.');
|
|
233
|
+
}
|
|
234
|
+
const target = callback;
|
|
235
|
+
// Two things about a stub decide the shape of these three lines, and both read as pedantry until
|
|
236
|
+
// they bite. A stub answers EVERY property, so the absent-method check can only catch a plain
|
|
237
|
+
// object (which is the in-process case, and the one where a helpful message is worth having);
|
|
238
|
+
// for a real callback the missing method surfaces from the far side, as it should. And the
|
|
239
|
+
// invocation is DIRECT rather than through `Function.prototype.call`, because `deliver.call(…)`
|
|
240
|
+
// on a stub asks the far side for an operation named `onApprovalCard.call`.
|
|
241
|
+
if (target[method] === undefined) {
|
|
242
|
+
throw new Error(`The callback this hook was started with has no ${method}() method, so nothing could be ` +
|
|
243
|
+
'delivered to it.');
|
|
244
|
+
}
|
|
245
|
+
await target[method](payload.topic === 'approval_card' ? payload.card : payload.state);
|
|
246
|
+
}
|
|
247
|
+
/** Release both stubs one `startHook()` handed over. Neither outlives the delivery. */
|
|
248
|
+
function releaseStarted(started) {
|
|
249
|
+
;
|
|
250
|
+
started.callback?.[Symbol.dispose]?.();
|
|
251
|
+
started.approvalQueue?.[Symbol.dispose]?.();
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/os/hooks.ts"],"names":[],"mappings":"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"}
|