@cat-factory/gatekeeper-worker 0.4.3 → 0.5.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 +58 -12
- package/dist/capability.d.ts +45 -0
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +60 -4
- package/dist/capability.js.map +1 -1
- package/dist/errors.d.ts +13 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/gatekeeper.d.ts +47 -2
- package/dist/gatekeeper.d.ts.map +1 -1
- package/dist/gatekeeper.js +89 -5
- package/dist/gatekeeper.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -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 +30 -0
- package/dist/os/discoverability.d.ts.map +1 -0
- package/dist/os/discoverability.js +46 -0
- package/dist/os/discoverability.js.map +1 -0
- package/dist/os/exports.d.ts +28 -0
- package/dist/os/exports.d.ts.map +1 -0
- package/dist/os/exports.js +68 -0
- package/dist/os/exports.js.map +1 -0
- package/dist/os/protocol.d.ts +150 -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 +90 -0
- package/dist/os/queue.d.ts.map +1 -0
- package/dist/os/queue.js +197 -0
- package/dist/os/queue.js.map +1 -0
- package/dist/os/resource-core.d.ts +100 -0
- package/dist/os/resource-core.d.ts.map +1 -0
- package/dist/os/resource-core.js +155 -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 +64 -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 +82 -0
- package/dist/os/session-types.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 +95 -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/worker.d.ts.map +1 -1
- package/dist/worker.js +24 -5
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAaA;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAS,GAAG,MAAM,CAOjE"}
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// The one Markdown-safety primitive this Worker needs.
|
|
2
|
+
//
|
|
3
|
+
// Every description this Gatekeeper composes is MARKDOWN rendered to a person who is about to
|
|
4
|
+
// decide something, and several of the holes in it carry text an agent or a task author wrote: the
|
|
5
|
+
// argument bag of a call, the title of an approval card. Those are parsed surfaces, not inert
|
|
6
|
+
// string sinks, and the failure is not cosmetic: an unbalanced fence swallows everything after it,
|
|
7
|
+
// so a payload closing the block early makes the rest of OUR prose render as the payload's, right
|
|
8
|
+
// where "here is what you are approving" is being said.
|
|
9
|
+
//
|
|
10
|
+
// It sits beside `masking.ts` rather than inside `os/` because both doors compose descriptions and
|
|
11
|
+
// neither owns the rule. It is the same sizing rule the platform's own `fencedOutput` uses for
|
|
12
|
+
// captured command output reaching a model.
|
|
13
|
+
/**
|
|
14
|
+
* Fence a payload so it cannot break out of the code block that holds it.
|
|
15
|
+
*
|
|
16
|
+
* Sized one backtick longer than the longest run the payload contains, which is what makes it
|
|
17
|
+
* total: a payload holding ```` closes a ``` fence and everything after it, including the rest of
|
|
18
|
+
* the description, renders as prose the reader takes for ours.
|
|
19
|
+
*
|
|
20
|
+
* The language tag is the caller's, because a JSON argument bag and a free-text title are both
|
|
21
|
+
* fenced and only one of them is JSON.
|
|
22
|
+
*/
|
|
23
|
+
export function fenced(payload, language = 'json') {
|
|
24
|
+
const longestRun = [...payload.matchAll(/`+/g)].reduce((longest, match) => Math.max(longest, match[0].length), 0);
|
|
25
|
+
const fence = '`'.repeat(Math.max(3, longestRun + 1));
|
|
26
|
+
return `${fence}${language}\n${payload}\n${fence}`;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,8FAA8F;AAC9F,mGAAmG;AACnG,8FAA8F;AAC9F,mGAAmG;AACnG,kGAAkG;AAClG,wDAAwD;AACxD,EAAE;AACF,mGAAmG;AACnG,+FAA+F;AAC/F,4CAA4C;AAE5C;;;;;;;;;GASG;AACH,MAAM,UAAU,MAAM,CAAC,OAAe,EAAE,QAAQ,GAAG,MAAM;IACvD,MAAM,UAAU,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CACpD,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EACtD,CAAC,CACF,CAAA;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC,CAAA;IACrD,OAAO,GAAG,KAAK,GAAG,QAAQ,KAAK,OAAO,KAAK,KAAK,EAAE,CAAA;AACpD,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GatekeeperEnv } from '../env.js';
|
|
2
|
+
import type { GatekeeperPolicy } from '../policy/compile.js';
|
|
3
|
+
import type { AccountEntrypoint, VerifierEntrypoint } from './protocol.js';
|
|
4
|
+
/** What the vendor imbues an account with. The id IS the account; nothing else is stored. */
|
|
5
|
+
export interface AccountProps {
|
|
6
|
+
accountId: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The account entrypoint over a deployment's policy.
|
|
10
|
+
*
|
|
11
|
+
* Stateless by construction: the workspace holds the stub, the stub holds the id, and everything
|
|
12
|
+
* else is resolved from the policy and the paired deployment on each call. There is no account
|
|
13
|
+
* table to fall out of step with the workspace's own.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createGatekeeperAccount(options: {
|
|
16
|
+
policy: GatekeeperPolicy;
|
|
17
|
+
}): new (ctx: ExecutionContext<AccountProps>, env: GatekeeperEnv) => AccountEntrypoint;
|
|
18
|
+
/**
|
|
19
|
+
* The verifier: an identity token with no authority.
|
|
20
|
+
*
|
|
21
|
+
* The contract's own interface is empty, because what is being passed IS the identity: another
|
|
22
|
+
* gatekeeper asks the workspace whether the holder of this stub may see something, and never calls
|
|
23
|
+
* a method on it. `describe()` is here so a human looking at a share prompt sees which account is
|
|
24
|
+
* being named rather than an opaque stub.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createGatekeeperVerifier(): new (ctx: ExecutionContext<AccountProps>, env: GatekeeperEnv) => VerifierEntrypoint;
|
|
27
|
+
//# sourceMappingURL=account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/os/account.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAG9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,OAAO,KAAK,EAEV,iBAAiB,EAEjB,kBAAkB,EACnB,MAAM,eAAe,CAAA;AAItB,6FAA6F;AAC7F,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,gBAAgB,CAAA;CACzB,GAAG,KAAK,GAAG,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,aAAa,KAAK,iBAAiB,CA8FrF;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,KAC1C,GAAG,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACnC,GAAG,EAAE,aAAa,KACf,kBAAkB,CAMtB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// One connected account, and the identity story behind it.
|
|
2
|
+
//
|
|
3
|
+
// The published contract's normal path is OAuth: a user connects their own vendor account through a
|
|
4
|
+
// nonce-protected flow, and identity thereafter travels as a props-imbued stub the workspace
|
|
5
|
+
// persists. cat-factory has no per-user OAuth, so this vendor takes the contract's own escape
|
|
6
|
+
// hatch: `autoProvisionsAccount`, where the workspace mints one account per user with no flow at
|
|
7
|
+
// all. That is a recognised low-trust variant rather than a violation, and it is worth being exact
|
|
8
|
+
// about what it does and does not buy.
|
|
9
|
+
//
|
|
10
|
+
// `createAccount()` "takes no arguments, so it carries no user identity" (the spec's own words). So
|
|
11
|
+
// the account's identity is one this Gatekeeper MINTS, and the workspace's persistence of the
|
|
12
|
+
// returned stub is what makes it stable and per-user. Two consequences follow, and both are better
|
|
13
|
+
// stated than discovered:
|
|
14
|
+
//
|
|
15
|
+
// - The actor is stronger here than on `/rpc`, not weaker. `connect({ actorId })` trusts a string
|
|
16
|
+
// the caller sent; an account id was never in anyone's hands to assert.
|
|
17
|
+
// - A policy's `grants` map, keyed on an OS user identity an operator typed, cannot match an id
|
|
18
|
+
// nobody had yet when the policy was written. So an auto-provisioned account resolves to the
|
|
19
|
+
// policy's `autoProvisionedTier`, which is deliberately NOT `defaultTier` and does not inherit
|
|
20
|
+
// from it: naming one is how a deployment opts into Cloudflare OS discovery, and a policy that
|
|
21
|
+
// names none refuses, loudly, saying so. An operator who wants ONE account raised above it
|
|
22
|
+
// reads that account's id off `describe()` (`uniqueName`) and grants THAT id directly.
|
|
23
|
+
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
24
|
+
import { GatekeeperError } from '../errors.js';
|
|
25
|
+
import { Gatekeeper } from '../gatekeeper.js';
|
|
26
|
+
import { loopbackExport } from './exports.js';
|
|
27
|
+
import { supportedResourceFor } from './resources.js';
|
|
28
|
+
/**
|
|
29
|
+
* The account entrypoint over a deployment's policy.
|
|
30
|
+
*
|
|
31
|
+
* Stateless by construction: the workspace holds the stub, the stub holds the id, and everything
|
|
32
|
+
* else is resolved from the policy and the paired deployment on each call. There is no account
|
|
33
|
+
* table to fall out of step with the workspace's own.
|
|
34
|
+
*/
|
|
35
|
+
export function createGatekeeperAccount(options) {
|
|
36
|
+
const { policy } = options;
|
|
37
|
+
return class CatFactoryAccount extends WorkerEntrypoint {
|
|
38
|
+
#gatekeeper() {
|
|
39
|
+
return Gatekeeper.create(this.env, policy);
|
|
40
|
+
}
|
|
41
|
+
async describe() {
|
|
42
|
+
const gatekeeper = this.#gatekeeper();
|
|
43
|
+
const accountId = this.ctx.props.accountId;
|
|
44
|
+
const resource = supportedResourceFor(gatekeeper.deployment);
|
|
45
|
+
return {
|
|
46
|
+
// The id is surfaced as the canonical name because it is the value an operator needs in
|
|
47
|
+
// hand to grant this account a tier above the default, and the only place it is visible.
|
|
48
|
+
uniqueName: accountId,
|
|
49
|
+
displayName: `cat-factory (${gatekeeper.tierForAccount(accountId).name})`,
|
|
50
|
+
avatar: { url: new URL('/favicon.ico', gatekeeper.deployment).toString() },
|
|
51
|
+
grantedResourceUrlPatterns: [resource.urlPattern],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async getSupportedResources() {
|
|
55
|
+
return [supportedResourceFor(this.#gatekeeper().deployment)];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The resource object for a URL, imbued with this account's identity.
|
|
59
|
+
*
|
|
60
|
+
* The account id rides `ctx.props` onto the resource rather than being passed per call, which is
|
|
61
|
+
* what makes it unforgeable from the session side: an agent holding a session has no argument
|
|
62
|
+
* with which to name a different account.
|
|
63
|
+
*/
|
|
64
|
+
async getGatekeeperClassFor(url) {
|
|
65
|
+
const gatekeeper = this.#gatekeeper();
|
|
66
|
+
const resource = supportedResourceFor(gatekeeper.deployment);
|
|
67
|
+
if (!new URLPattern(resource.urlPattern).test(url)) {
|
|
68
|
+
throw new GatekeeperError('no_such_resource', `This Gatekeeper serves ${resource.urlPattern} and nothing else, so it cannot bind ` +
|
|
69
|
+
`${url}. It is paired with one cat-factory workspace by the provisioning key it ` +
|
|
70
|
+
'holds; a second workspace takes a second Gatekeeper deployment.');
|
|
71
|
+
}
|
|
72
|
+
// The account, and nothing else: the URL is what was CHECKED, not what has to be carried.
|
|
73
|
+
// Every URL this pattern matches binds the same paired workspace, so recording which one a
|
|
74
|
+
// caller happened to name would be state no reader could act on (`ResourceProps`).
|
|
75
|
+
const props = { accountId: this.ctx.props.accountId };
|
|
76
|
+
return { class: loopbackExport(this.ctx.exports, 'resource', props), resource };
|
|
77
|
+
}
|
|
78
|
+
/** Nothing to grant: this vendor's single resource comes with the account. */
|
|
79
|
+
async ensureResources(_resourceUrlPatterns) {
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
/** Offboarding: revoke every cat-factory key this Gatekeeper minted for the account. */
|
|
83
|
+
async revoke() {
|
|
84
|
+
await this.#gatekeeper().retire(this.ctx.props.accountId);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Identity carried into another vendor's gatekeeper, and NOTHING else.
|
|
88
|
+
*
|
|
89
|
+
* A separate export rather than `this`, because the holder of a verifier is a third party's
|
|
90
|
+
* gatekeeper: handing it this object would hand it `revoke()` and the resource classes too.
|
|
91
|
+
*/
|
|
92
|
+
async getVerifier() {
|
|
93
|
+
return loopbackExport(this.ctx.exports, 'verifier', {
|
|
94
|
+
accountId: this.ctx.props.accountId,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** This vendor does not authenticate anybody: it auto-provisions, and says so in `describe()`. */
|
|
98
|
+
async getAuthenticatedEmail() {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* There is no connect flow to re-run.
|
|
103
|
+
*
|
|
104
|
+
* An account's credentials are the per-account cat-factory keys this Gatekeeper mints, and a
|
|
105
|
+
* rejected one is already re-minted automatically on the next call. So the honest answer is a
|
|
106
|
+
* refusal naming the real remedy rather than a URL that would open nothing.
|
|
107
|
+
*/
|
|
108
|
+
async reconnect() {
|
|
109
|
+
throw new GatekeeperError('unsupported_action', 'This vendor auto-provisions accounts and runs no connect flow, so there is nothing to ' +
|
|
110
|
+
'reconnect. If calls are failing on credentials, the deployment-level provisioning key ' +
|
|
111
|
+
'is the one to check; a per-account key is re-minted automatically when it is refused.');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The verifier: an identity token with no authority.
|
|
117
|
+
*
|
|
118
|
+
* The contract's own interface is empty, because what is being passed IS the identity: another
|
|
119
|
+
* gatekeeper asks the workspace whether the holder of this stub may see something, and never calls
|
|
120
|
+
* a method on it. `describe()` is here so a human looking at a share prompt sees which account is
|
|
121
|
+
* being named rather than an opaque stub.
|
|
122
|
+
*/
|
|
123
|
+
export function createGatekeeperVerifier() {
|
|
124
|
+
return class CatFactoryVerifier extends WorkerEntrypoint {
|
|
125
|
+
async describe() {
|
|
126
|
+
return { accountId: this.ctx.props.accountId };
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/os/account.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type GatekeeperBinding } from '@cat-factory/gatekeeper-bindings';
|
|
2
|
+
import type { ActionDescription, ActionKind, ObservationDescription } from './protocol.js';
|
|
3
|
+
/** Who a described call is made as, for the approver reading it. */
|
|
4
|
+
export interface CallSubject {
|
|
5
|
+
/** The account this session was opened for. */
|
|
6
|
+
accountId: string;
|
|
7
|
+
/** The policy tier that account resolved to. */
|
|
8
|
+
tier: string;
|
|
9
|
+
/** The paired deployment the call lands on. */
|
|
10
|
+
deployment: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Describe a read.
|
|
14
|
+
*
|
|
15
|
+
* `prohibitAllSharing` is set for exactly the operations the table marks as reading a telemetry
|
|
16
|
+
* sink, which are the ones that serve CAPTURED TEXT: model prompts and replies, tool arguments,
|
|
17
|
+
* agent search terms, provisioning output. Every one of those sits inside a `read` key's floor, so
|
|
18
|
+
* without this the platform's most sensitive reads would be shared onward on the same terms as a
|
|
19
|
+
* task list. The annotation already exists for the policy layer; this is the second consumer it
|
|
20
|
+
* was always going to have.
|
|
21
|
+
*/
|
|
22
|
+
export declare function describeObservation(binding: GatekeeperBinding, args: Record<string, unknown>, subject: CallSubject): ObservationDescription;
|
|
23
|
+
/** The stable tag an OS pre-approval rule matches this operation by. */
|
|
24
|
+
export declare function actionKindOf(binding: GatekeeperBinding): ActionKind;
|
|
25
|
+
/**
|
|
26
|
+
* Describe a side effect.
|
|
27
|
+
*
|
|
28
|
+
* Three fields are fixed for every action this Gatekeeper submits, and each is a statement about
|
|
29
|
+
* what this implementation does rather than a default:
|
|
30
|
+
*
|
|
31
|
+
* - `implementsRevert: false`. Reverting a started run, a merged pull request or a stopped job is
|
|
32
|
+
* not something this Worker can do on the caller's behalf, and the flag exists precisely so the
|
|
33
|
+
* UI does not offer an undo that would not work.
|
|
34
|
+
* - `awaitDecision: true`. This Gatekeeper does not SIMULATE: there is no provisional run id to
|
|
35
|
+
* hand an agent so it can keep working while a human decides. The spec's alternative for a
|
|
36
|
+
* non-simulating gatekeeper is to suspend the agent's turn, which is what the flag asks for.
|
|
37
|
+
* - `autoApprovable` follows the table's own consequence reading, with the cautious default
|
|
38
|
+
* applied by `resolveConsequence`: an unannotated mutation is destructive, so it is NOT
|
|
39
|
+
* offered for auto-approval. Being auto-approvable is still only permission for a user who
|
|
40
|
+
* opted into this action's kind; it never approves anything by itself.
|
|
41
|
+
*/
|
|
42
|
+
export declare function describeAction(binding: GatekeeperBinding, args: Record<string, unknown>, subject: CallSubject): ActionDescription;
|
|
43
|
+
//# sourceMappingURL=descriptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptions.d.ts","sourceRoot":"","sources":["../../src/os/descriptions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AAE7F,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAE1F,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAA;CACnB;AAsBD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,WAAW,GACnB,sBAAsB,CAexB;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAEnE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,WAAW,GACnB,iBAAiB,CAoBnB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// What the OS is told about a call, derived from the operation table rather than authored.
|
|
2
|
+
//
|
|
3
|
+
// The bindings already carry every fact an approver needs: what the operation does, the route it
|
|
4
|
+
// is, the scope floor behind it, whether it mutates, whether the platform annotated it as
|
|
5
|
+
// destructive, and whether it reads captured telemetry. Deriving the descriptions from those is
|
|
6
|
+
// the same no-drift rule the table itself exists for: a hand-written description per operation
|
|
7
|
+
// would be forty strings that stop matching the surface the day an operation changes, and the
|
|
8
|
+
// place the staleness lands is an approval prompt, which is the worst possible place for it.
|
|
9
|
+
//
|
|
10
|
+
// The description is MARKDOWN rendered to a human, and the argument bag inside it is agent-authored
|
|
11
|
+
// text, so every hole carrying one goes through `fenced` (`../markdown.ts`), which is where that
|
|
12
|
+
// rule and the reason for it live.
|
|
13
|
+
import { resolveConsequence } from '@cat-factory/gatekeeper-bindings';
|
|
14
|
+
import { fenced } from '../markdown.js';
|
|
15
|
+
/** The arguments as an approver should see them: the bag that will actually be forwarded. */
|
|
16
|
+
function renderArguments(args) {
|
|
17
|
+
const keys = Object.keys(args);
|
|
18
|
+
if (keys.length === 0)
|
|
19
|
+
return 'This call carries no arguments.';
|
|
20
|
+
let payload;
|
|
21
|
+
try {
|
|
22
|
+
payload = JSON.stringify(args, null, 2) ?? String(args);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// A cycle or a value JSON cannot carry. The call would fail downstream anyway, and an
|
|
26
|
+
// approver told nothing about the arguments is being asked to approve an unknown.
|
|
27
|
+
payload = '"<arguments could not be rendered>"';
|
|
28
|
+
}
|
|
29
|
+
return fenced(payload);
|
|
30
|
+
}
|
|
31
|
+
/** The route line both description kinds open their detail with. */
|
|
32
|
+
function routeLine(binding) {
|
|
33
|
+
return `\`${binding.httpMethod} ${binding.path}\` on ${'`'}${binding.operationId}${'`'}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Describe a read.
|
|
37
|
+
*
|
|
38
|
+
* `prohibitAllSharing` is set for exactly the operations the table marks as reading a telemetry
|
|
39
|
+
* sink, which are the ones that serve CAPTURED TEXT: model prompts and replies, tool arguments,
|
|
40
|
+
* agent search terms, provisioning output. Every one of those sits inside a `read` key's floor, so
|
|
41
|
+
* without this the platform's most sensitive reads would be shared onward on the same terms as a
|
|
42
|
+
* task list. The annotation already exists for the policy layer; this is the second consumer it
|
|
43
|
+
* was always going to have.
|
|
44
|
+
*/
|
|
45
|
+
export function describeObservation(binding, args, subject) {
|
|
46
|
+
const captured = binding.telemetrySink !== undefined
|
|
47
|
+
? `\n\nThis read serves CAPTURED TEXT from the \`${binding.telemetrySink}\` telemetry sink: ` +
|
|
48
|
+
'model prompts and replies, tool arguments, or agent search terms. It is withheld from ' +
|
|
49
|
+
'sharing for that reason.'
|
|
50
|
+
: '';
|
|
51
|
+
return {
|
|
52
|
+
title: `${binding.summary} (${subject.deployment})`,
|
|
53
|
+
description: `Read ${routeLine(binding)} on the cat-factory deployment at ${subject.deployment}, as ` +
|
|
54
|
+
`account \`${subject.accountId}\` at policy tier \`${subject.tier}\`.\n\n` +
|
|
55
|
+
`${renderArguments(args)}${captured}`,
|
|
56
|
+
...(binding.telemetrySink !== undefined ? { prohibitAllSharing: true } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** The stable tag an OS pre-approval rule matches this operation by. */
|
|
60
|
+
export function actionKindOf(binding) {
|
|
61
|
+
return { tag: binding.name, label: binding.summary };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Describe a side effect.
|
|
65
|
+
*
|
|
66
|
+
* Three fields are fixed for every action this Gatekeeper submits, and each is a statement about
|
|
67
|
+
* what this implementation does rather than a default:
|
|
68
|
+
*
|
|
69
|
+
* - `implementsRevert: false`. Reverting a started run, a merged pull request or a stopped job is
|
|
70
|
+
* not something this Worker can do on the caller's behalf, and the flag exists precisely so the
|
|
71
|
+
* UI does not offer an undo that would not work.
|
|
72
|
+
* - `awaitDecision: true`. This Gatekeeper does not SIMULATE: there is no provisional run id to
|
|
73
|
+
* hand an agent so it can keep working while a human decides. The spec's alternative for a
|
|
74
|
+
* non-simulating gatekeeper is to suspend the agent's turn, which is what the flag asks for.
|
|
75
|
+
* - `autoApprovable` follows the table's own consequence reading, with the cautious default
|
|
76
|
+
* applied by `resolveConsequence`: an unannotated mutation is destructive, so it is NOT
|
|
77
|
+
* offered for auto-approval. Being auto-approvable is still only permission for a user who
|
|
78
|
+
* opted into this action's kind; it never approves anything by itself.
|
|
79
|
+
*/
|
|
80
|
+
export function describeAction(binding, args, subject) {
|
|
81
|
+
const consequence = resolveConsequence(binding);
|
|
82
|
+
const stakes = consequence.destructive
|
|
83
|
+
? 'This operation is annotated DESTRUCTIVE: its effects are not safely repeatable, and this ' +
|
|
84
|
+
'Gatekeeper cannot revert it.'
|
|
85
|
+
: 'This operation is annotated non-destructive' +
|
|
86
|
+
(consequence.idempotent ? ' and idempotent.' : '.');
|
|
87
|
+
return {
|
|
88
|
+
title: `${binding.summary} (${subject.deployment})`,
|
|
89
|
+
description: `Call ${routeLine(binding)} on the cat-factory deployment at ${subject.deployment}, as ` +
|
|
90
|
+
`account \`${subject.accountId}\` at policy tier \`${subject.tier}\`. The call is made with ` +
|
|
91
|
+
`a per-account API key at scope \`${binding.minScope}\` or above, minted for and stamped ` +
|
|
92
|
+
'with that account.\n\n' +
|
|
93
|
+
`${renderArguments(args)}\n\n${stakes}`,
|
|
94
|
+
implementsRevert: false,
|
|
95
|
+
awaitDecision: true,
|
|
96
|
+
autoApprovable: !consequence.destructive,
|
|
97
|
+
actionKind: actionKindOf(binding),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=descriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptions.js","sourceRoot":"","sources":["../../src/os/descriptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Why a Cloudflare OS deployment could not finish installing this Gatekeeper. */
|
|
2
|
+
export type DiscoveryBlockerReason = 'missing_exports' | 'no_auto_provisioned_tier';
|
|
3
|
+
/** One thing standing between this Worker and a workspace that could use it. */
|
|
4
|
+
export interface DiscoveryBlocker {
|
|
5
|
+
reason: DiscoveryBlockerReason;
|
|
6
|
+
detail: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* What `/health` says about the Cloudflare OS door, beside what it says about liveness.
|
|
10
|
+
*
|
|
11
|
+
* `discoverable` is derived from `blockers` rather than reported alongside it, so the two cannot
|
|
12
|
+
* disagree.
|
|
13
|
+
*/
|
|
14
|
+
export interface DiscoverabilityReport {
|
|
15
|
+
discoverable: boolean;
|
|
16
|
+
blockers: DiscoveryBlocker[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Ask both questions in ONE pass.
|
|
20
|
+
*
|
|
21
|
+
* The same rule the binding check follows: an operator who learns the next missing piece only
|
|
22
|
+
* after redeploying wires a deployment one restart at a time.
|
|
23
|
+
*/
|
|
24
|
+
export declare function describeDiscoverability(deps: {
|
|
25
|
+
/** The Worker's own exports, as the object model reaches them (`ctx.exports`). */
|
|
26
|
+
exports: unknown;
|
|
27
|
+
/** The tier name the policy nominates for auto-provisioned accounts, or `null`. */
|
|
28
|
+
autoProvisionedTier: string | null;
|
|
29
|
+
}): DiscoverabilityReport;
|
|
30
|
+
//# sourceMappingURL=discoverability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discoverability.d.ts","sourceRoot":"","sources":["../../src/os/discoverability.ts"],"names":[],"mappings":"AAmBA,kFAAkF;AAClF,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG,0BAA0B,CAAA;AAEnF,gFAAgF;AAChF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,sBAAsB,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,OAAO,CAAA;IACrB,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,kFAAkF;IAClF,OAAO,EAAE,OAAO,CAAA;IAChB,mFAAmF;IACnF,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC,GAAG,qBAAqB,CA0BxB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Whether a Cloudflare OS deployment could discover and install this Gatekeeper, as a REPORT.
|
|
2
|
+
//
|
|
3
|
+
// Two things have to be true and neither has a request path of its own: the entry module must
|
|
4
|
+
// carry the four exports the object model resolves by name, and the policy must name an
|
|
5
|
+
// `autoProvisionedTier`, since every account this door mints falls to it. A workspace that finds
|
|
6
|
+
// either missing does not get an error anyone monitors; it simply never finishes installing.
|
|
7
|
+
//
|
|
8
|
+
// It is a report rather than a refusal because a Gatekeeper serving `/rpc` and nothing else is a
|
|
9
|
+
// SUPPORTED deployment, not a broken one: the HTTP door is what a consumer that is not a
|
|
10
|
+
// Cloudflare OS speaks, and this package promises to go on serving it. Turning that deployment's
|
|
11
|
+
// liveness red the day it takes a version bump would be this check answering a question nobody
|
|
12
|
+
// asked it, which is the same failure as the health route that reported only the bindings its
|
|
13
|
+
// request path happened to read: a monitor saying something the operator cannot act on.
|
|
14
|
+
//
|
|
15
|
+
// So the two facts are reported side by side, each naming its own remedy, and a deployment that
|
|
16
|
+
// wants discovery keys a monitor on `os.discoverable` while one that does not ignores it.
|
|
17
|
+
import { missingOsExports, OS_EXPORTS } from './exports.js';
|
|
18
|
+
/**
|
|
19
|
+
* Ask both questions in ONE pass.
|
|
20
|
+
*
|
|
21
|
+
* The same rule the binding check follows: an operator who learns the next missing piece only
|
|
22
|
+
* after redeploying wires a deployment one restart at a time.
|
|
23
|
+
*/
|
|
24
|
+
export function describeDiscoverability(deps) {
|
|
25
|
+
const blockers = [];
|
|
26
|
+
const missing = missingOsExports(deps.exports);
|
|
27
|
+
if (missing.length > 0) {
|
|
28
|
+
blockers.push({
|
|
29
|
+
reason: 'missing_exports',
|
|
30
|
+
detail: `This Worker's entry module does not export ${missing.map((role) => OS_EXPORTS[role]).join(', ')}. ` +
|
|
31
|
+
'The Cloudflare OS object model resolves each by name against this Worker ' +
|
|
32
|
+
'(deploy/gatekeeper/src/index.ts is the template).',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (deps.autoProvisionedTier === null) {
|
|
36
|
+
blockers.push({
|
|
37
|
+
reason: 'no_auto_provisioned_tier',
|
|
38
|
+
detail: "This deployment's policy names no autoProvisionedTier, so a workspace can discover this " +
|
|
39
|
+
'Gatekeeper and never open a session through it: an account minted by the object model ' +
|
|
40
|
+
'carries no identity a grants entry could match, and every one falls to that tier. It is ' +
|
|
41
|
+
'deliberately separate from defaultTier, which governs the /rpc door.',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return { discoverable: blockers.length === 0, blockers };
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=discoverability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discoverability.js","sourceRoot":"","sources":["../../src/os/discoverability.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,8FAA8F;AAC9F,wFAAwF;AACxF,iGAAiG;AACjG,6FAA6F;AAC7F,EAAE;AACF,iGAAiG;AACjG,yFAAyF;AACzF,iGAAiG;AACjG,+FAA+F;AAC/F,8FAA8F;AAC9F,wFAAwF;AACxF,EAAE;AACF,gGAAgG;AAChG,0FAA0F;AAE1F,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAsB3D;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAKvC;IACC,MAAM,QAAQ,GAAuB,EAAE,CAAA;IAEvC,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC9C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,iBAAiB;YACzB,MAAM,EACJ,8CAA8C,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBACpG,2EAA2E;gBAC3E,mDAAmD;SACtD,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,mBAAmB,KAAK,IAAI,EAAE,CAAC;QACtC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,0BAA0B;YAClC,MAAM,EACJ,0FAA0F;gBAC1F,wFAAwF;gBACxF,0FAA0F;gBAC1F,sEAAsE;SACzE,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;AAC1D,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** The export names the OS object model resolves, keyed by the role each plays. */
|
|
2
|
+
export declare const OS_EXPORTS: {
|
|
3
|
+
/** The entrypoint a `GATEKEEPER_CAT_FACTORY` service binding targets. Pinned by the OS. */
|
|
4
|
+
readonly vendor: 'GatekeeperVendor';
|
|
5
|
+
/** One connected account: what `createAccount()` hands back for the workspace to persist. */
|
|
6
|
+
readonly account: 'CatFactoryAccount';
|
|
7
|
+
/** The per-resource Durable Object a bound workspace's session comes from. */
|
|
8
|
+
readonly resource: 'CatFactoryResource';
|
|
9
|
+
/**
|
|
10
|
+
* The bearer-of-identity another vendor's gatekeeper is handed when this account is added as an
|
|
11
|
+
* observer there. It is a separate export because it must carry NO authority: handing over the
|
|
12
|
+
* account itself would hand over `revoke()` and the resource classes with it.
|
|
13
|
+
*/
|
|
14
|
+
readonly verifier: 'CatFactoryVerifier';
|
|
15
|
+
};
|
|
16
|
+
/** A role in the object model, spelled as {@link OS_EXPORTS} keys it. */
|
|
17
|
+
export type OsExportRole = keyof typeof OS_EXPORTS;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve one of this Worker's own exports, imbued with the props it should carry.
|
|
20
|
+
*
|
|
21
|
+
* The cast is the boundary between "what wrangler resolved" and "what this package needs", and the
|
|
22
|
+
* refusal below is what makes it safe: an export the deployment did not write is named here rather
|
|
23
|
+
* than reaching a caller as a missing method on a stub.
|
|
24
|
+
*/
|
|
25
|
+
export declare function loopbackExport<T>(exports: unknown, role: OsExportRole, props: unknown): T;
|
|
26
|
+
/** Every export role the OS object model needs that this Worker does not carry. */
|
|
27
|
+
export declare function missingOsExports(exports: unknown): OsExportRole[];
|
|
28
|
+
//# sourceMappingURL=exports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../../src/os/exports.ts"],"names":[],"mappings":"AAkBA,mFAAmF;AACnF,eAAO,MAAM,UAAU;IACrB,2FAA2F;qBACnF,kBAAkB;IAC1B,6FAA6F;sBACpF,mBAAmB;IAC5B,8EAA8E;uBACpE,oBAAoB;IAC9B;;;;OAIG;uBACO,oBAAoB;CACW,CAAA;AAE3C,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,UAAU,CAAA;AAmBlD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,CAYzF;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,EAAE,CAGjE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// The named exports the OS object model reaches through, and the one place they are stated.
|
|
2
|
+
//
|
|
3
|
+
// A Worker's own entrypoints and Durable Object classes are reachable at runtime as
|
|
4
|
+
// `ctx.exports.<Name>`, resolved against the DEPLOYMENT's entry module rather than this package's.
|
|
5
|
+
// So the base cannot hold the names: it can only agree with the template about them, and this is
|
|
6
|
+
// the file that agreement lives in. Both `deploy/gatekeeper/src/index.ts` and the suite's
|
|
7
|
+
// `test/worker.ts` export exactly these, which is the same "the template's entry point and the
|
|
8
|
+
// suite's are the same lines" check the base/template split already leans on.
|
|
9
|
+
//
|
|
10
|
+
// `GATEKEEPER_VENDOR` is additionally the name the OS itself pins: a `GATEKEEPER_*` service binding
|
|
11
|
+
// targets a `WorkerEntrypoint` export called `GatekeeperVendor`, so that one is not ours to choose.
|
|
12
|
+
//
|
|
13
|
+
// A missing export is REFUSED by name rather than surfacing as `undefined is not a function`
|
|
14
|
+
// several calls later, and `/health` asks about all of them in one pass, so an operator wiring a
|
|
15
|
+
// deployment learns every missing line at once rather than one redeploy at a time.
|
|
16
|
+
import { GatekeeperError } from '../errors.js';
|
|
17
|
+
/** The export names the OS object model resolves, keyed by the role each plays. */
|
|
18
|
+
export const OS_EXPORTS = {
|
|
19
|
+
/** The entrypoint a `GATEKEEPER_CAT_FACTORY` service binding targets. Pinned by the OS. */
|
|
20
|
+
vendor: 'GatekeeperVendor',
|
|
21
|
+
/** One connected account: what `createAccount()` hands back for the workspace to persist. */
|
|
22
|
+
account: 'CatFactoryAccount',
|
|
23
|
+
/** The per-resource Durable Object a bound workspace's session comes from. */
|
|
24
|
+
resource: 'CatFactoryResource',
|
|
25
|
+
/**
|
|
26
|
+
* The bearer-of-identity another vendor's gatekeeper is handed when this account is added as an
|
|
27
|
+
* observer there. It is a separate export because it must carry NO authority: handing over the
|
|
28
|
+
* account itself would hand over `revoke()` and the resource classes with it.
|
|
29
|
+
*/
|
|
30
|
+
verifier: 'CatFactoryVerifier',
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Read one factory out of a bag that may not be one.
|
|
34
|
+
*
|
|
35
|
+
* Total against an ABSENT bag as well as an incomplete one, because the two are the same fact here
|
|
36
|
+
* (nothing is reachable) and only one of them would otherwise be reported: a caller with no
|
|
37
|
+
* `ctx.exports` at all would get a `TypeError` from the read rather than the refusal that names
|
|
38
|
+
* what is missing, which is the failure this whole module exists to turn into a sentence.
|
|
39
|
+
*/
|
|
40
|
+
function factoryFor(exports, name) {
|
|
41
|
+
if (typeof exports !== 'object' || exports === null)
|
|
42
|
+
return undefined;
|
|
43
|
+
const factory = exports[name];
|
|
44
|
+
return typeof factory === 'function' ? factory : undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve one of this Worker's own exports, imbued with the props it should carry.
|
|
48
|
+
*
|
|
49
|
+
* The cast is the boundary between "what wrangler resolved" and "what this package needs", and the
|
|
50
|
+
* refusal below is what makes it safe: an export the deployment did not write is named here rather
|
|
51
|
+
* than reaching a caller as a missing method on a stub.
|
|
52
|
+
*/
|
|
53
|
+
export function loopbackExport(exports, role, props) {
|
|
54
|
+
const name = OS_EXPORTS[role];
|
|
55
|
+
const factory = factoryFor(exports, name);
|
|
56
|
+
if (factory === undefined) {
|
|
57
|
+
throw new GatekeeperError('missing_export', `This Worker's entry module does not export '${name}'. The Cloudflare OS object model ` +
|
|
58
|
+
`reaches it as ctx.exports.${name}; add it to your entry module (deploy/gatekeeper/src/` +
|
|
59
|
+
'index.ts is the template) and redeploy.');
|
|
60
|
+
}
|
|
61
|
+
return factory({ props });
|
|
62
|
+
}
|
|
63
|
+
/** Every export role the OS object model needs that this Worker does not carry. */
|
|
64
|
+
export function missingOsExports(exports) {
|
|
65
|
+
const roles = Object.keys(OS_EXPORTS);
|
|
66
|
+
return roles.filter((role) => factoryFor(exports, OS_EXPORTS[role]) === undefined);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=exports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exports.js","sourceRoot":"","sources":["../../src/os/exports.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,EAAE;AACF,oFAAoF;AACpF,mGAAmG;AACnG,iGAAiG;AACjG,0FAA0F;AAC1F,+FAA+F;AAC/F,8EAA8E;AAC9E,EAAE;AACF,oGAAoG;AACpG,oGAAoG;AACpG,EAAE;AACF,6FAA6F;AAC7F,iGAAiG;AACjG,mFAAmF;AAEnF,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE9C,mFAAmF;AACnF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,2FAA2F;IAC3F,MAAM,EAAE,kBAAkB;IAC1B,6FAA6F;IAC7F,OAAO,EAAE,mBAAmB;IAC5B,8EAA8E;IAC9E,QAAQ,EAAE,oBAAoB;IAC9B;;;;OAIG;IACH,QAAQ,EAAE,oBAAoB;CACW,CAAA;AAQ3C;;;;;;;GAOG;AACH,SAAS,UAAU,CAAC,OAAgB,EAAE,IAAY;IAChD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACrE,MAAM,OAAO,GAAI,OAA2B,CAAC,IAAI,CAAC,CAAA;IAClD,OAAO,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAI,OAAgB,EAAE,IAAkB,EAAE,KAAc;IACpF,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAC7B,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IACzC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,eAAe,CACvB,gBAAgB,EAChB,+CAA+C,IAAI,oCAAoC;YACrF,6BAA6B,IAAI,uDAAuD;YACxF,yCAAyC,CAC5C,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAC,EAAE,KAAK,EAAE,CAAM,CAAA;AAChC,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAmB,CAAA;IACvD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,SAAS,CAAC,CAAA;AACpF,CAAC"}
|