@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,155 @@
|
|
|
1
|
+
// What a bound resource DOES, separated from the Durable Object it lives in.
|
|
2
|
+
//
|
|
3
|
+
// The split is not ceremony. A Durable Object can only be constructed by workerd from a real
|
|
4
|
+
// `DurableObjectState`, and the props-imbued class this Gatekeeper hands the workspace is opaque by
|
|
5
|
+
// design (the workspace instantiates it, through machinery that is its own). So a suite that could
|
|
6
|
+
// only reach this behaviour through the object would be reduced to asserting that a class was
|
|
7
|
+
// returned. Everything worth getting wrong lives here instead, where it is ordinary code with
|
|
8
|
+
// ordinary dependencies, and `resource.ts` is the shell that supplies `ctx.props` and holds one of
|
|
9
|
+
// these for the object's lifetime.
|
|
10
|
+
//
|
|
11
|
+
// Holding ONE per object is what the action ledger needs: the workspace settles an action by
|
|
12
|
+
// calling `applyAction` on the resource, so a core rebuilt per call would lose the pending action
|
|
13
|
+
// the decision is about.
|
|
14
|
+
import { GatekeeperError } from '../errors.js';
|
|
15
|
+
import { Gatekeeper } from '../gatekeeper.js';
|
|
16
|
+
import { actionKindOf } from './descriptions.js';
|
|
17
|
+
import { ActionLedger, queueGovernance } from './queue.js';
|
|
18
|
+
import { renderTierSessionTypes, SESSION_INTERFACE_NAME } from './session-types.js';
|
|
19
|
+
/** One bound resource: the session it opens, its types, and the action lifecycle behind it. */
|
|
20
|
+
export class ResourceCore {
|
|
21
|
+
#env;
|
|
22
|
+
#policy;
|
|
23
|
+
#props;
|
|
24
|
+
#ledger = new ActionLedger();
|
|
25
|
+
constructor(env, policy, props) {
|
|
26
|
+
this.#env = env;
|
|
27
|
+
this.#policy = policy;
|
|
28
|
+
this.#props = props;
|
|
29
|
+
}
|
|
30
|
+
async describe() {
|
|
31
|
+
const tier = this.#tier();
|
|
32
|
+
return {
|
|
33
|
+
url: this.#gatekeeper().deployment,
|
|
34
|
+
title: 'cat-factory workspace',
|
|
35
|
+
snippet: `File work, start runs and answer what they park on, at policy tier '${tier.name}': ` +
|
|
36
|
+
`${tier.description}`,
|
|
37
|
+
suggestedBindingName: 'catFactory',
|
|
38
|
+
tsType: SESSION_INTERFACE_NAME,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The session's own types, rendered for the tier this account holds.
|
|
43
|
+
*
|
|
44
|
+
* Tier-specific rather than a copy of the vendor's, because this is the one the contract points a
|
|
45
|
+
* caller at: `ResourceDescription.tsType` must name an export of THIS method's output, and a
|
|
46
|
+
* session carries exactly its granted operations.
|
|
47
|
+
*/
|
|
48
|
+
async getTypeScriptTypes() {
|
|
49
|
+
return renderTierSessionTypes(this.#tier());
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Open a governed session.
|
|
53
|
+
*
|
|
54
|
+
* The queue is not optional and not a decoration: every operation the returned object carries
|
|
55
|
+
* funnels through the one `invoke` closure, which submits actions to this queue and authorizes
|
|
56
|
+
* observations against it. The tier policy underneath is the FLOOR, so an operation the policy
|
|
57
|
+
* never granted is absent from the object rather than something the queue has to refuse.
|
|
58
|
+
*
|
|
59
|
+
* The queue passed here is OWNED by the session that comes back: it is released, along with
|
|
60
|
+
* every action that session left undecided, when the session is disposed. A caller reaching this
|
|
61
|
+
* over RPC therefore hands in a reference of its own rather than the parameter it received (see
|
|
62
|
+
* `resource.ts`), because the parameter's lifetime ends when this call returns and the session's
|
|
63
|
+
* does not.
|
|
64
|
+
*/
|
|
65
|
+
async startSession(approvalQueue) {
|
|
66
|
+
const gatekeeper = this.#gatekeeper();
|
|
67
|
+
const accountId = this.#props.accountId;
|
|
68
|
+
return gatekeeper.capabilityForAccount(accountId, queueGovernance({
|
|
69
|
+
queue: approvalQueue,
|
|
70
|
+
ledger: this.#ledger.openSession(),
|
|
71
|
+
subject: {
|
|
72
|
+
accountId,
|
|
73
|
+
tier: gatekeeper.tierForAccount(accountId).name,
|
|
74
|
+
deployment: gatekeeper.deployment,
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* How many submitted actions this object is still holding, across every live session.
|
|
80
|
+
*
|
|
81
|
+
* Exposed for the same reason the ledger counts them: the ONE unbounded thing about a long-lived
|
|
82
|
+
* resource object is this set, and a count that does not fall back to zero when the sessions are
|
|
83
|
+
* gone is the leak rather than a slow day.
|
|
84
|
+
*/
|
|
85
|
+
get pendingActionCount() {
|
|
86
|
+
return this.#ledger.pendingCount;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The action kinds this Gatekeeper may auto-apply, if the user opted into the kind.
|
|
90
|
+
*
|
|
91
|
+
* Derived from the same table and the same consequence reading `describeAction` stamps onto each
|
|
92
|
+
* submission, so a pre-approval UI listing kinds before any action exists and the `autoApprovable`
|
|
93
|
+
* flag on an action that has been submitted cannot disagree.
|
|
94
|
+
*
|
|
95
|
+
* TODAY THIS IS EMPTY, and that is the honest answer rather than a gap. The public surface
|
|
96
|
+
* annotates a consequence only where the stakes are real money or a merged pull request, so every
|
|
97
|
+
* other mutation is unannotated, and the table's documented reading of an unannotated mutation is
|
|
98
|
+
* that it is destructive. Offering those for unattended auto-approval would mean inverting that
|
|
99
|
+
* default here, which is precisely the misreading `resolveConsequence` exists to stop. If the
|
|
100
|
+
* surface ever states that a write is safe, it appears here with no further decision.
|
|
101
|
+
*/
|
|
102
|
+
async getAutoApprovableActions() {
|
|
103
|
+
return this.#tier()
|
|
104
|
+
.granted.filter((binding) => !binding.readOnly)
|
|
105
|
+
.filter((binding) => (binding.consequence?.destructive ?? true) === false)
|
|
106
|
+
.map(actionKindOf);
|
|
107
|
+
}
|
|
108
|
+
/** The workspace approved an action: perform it, and hand the result to the waiting call. */
|
|
109
|
+
async applyAction(action) {
|
|
110
|
+
await this.#ledger.apply(action);
|
|
111
|
+
}
|
|
112
|
+
/** The workspace rejected an action: the waiting call throws and nothing is performed. */
|
|
113
|
+
async rejectAction(action) {
|
|
114
|
+
this.#ledger.reject(action);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Reverting is not something this Gatekeeper can do, which every action it submits already states
|
|
118
|
+
* (`implementsRevert: false`).
|
|
119
|
+
*
|
|
120
|
+
* It answers rather than throwing because the caller is the workspace UI on behalf of a person who
|
|
121
|
+
* wants their change undone: a message naming what they have to do themselves is the useful
|
|
122
|
+
* answer, where an exception would surface as a failed revert they might retry.
|
|
123
|
+
*/
|
|
124
|
+
async revertAction(_action) {
|
|
125
|
+
return {
|
|
126
|
+
message: 'This Gatekeeper cannot revert a cat-factory operation. A started run is stopped from the ' +
|
|
127
|
+
'board or with `tasks_stop`; a merged pull request is reverted in the repository, not here.',
|
|
128
|
+
canRetry: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Sharing this resource's observations onward is REFUSED, which blocks the share.
|
|
133
|
+
*
|
|
134
|
+
* The contract asks the gatekeeper to verify that the new viewer could directly read everything
|
|
135
|
+
* historically observed through it. This Gatekeeper cannot answer that: it keeps no observation
|
|
136
|
+
* log, and the plausible rule (the observer's own tier reaches every operation that produced the
|
|
137
|
+
* observed data) needs a tier for a viewer this deployment's policy has never named. A share
|
|
138
|
+
* blocked loudly beats an observation leaked quietly, so the refusal stands until there is a rule
|
|
139
|
+
* worth writing down.
|
|
140
|
+
*/
|
|
141
|
+
async addObserver(id, _user) {
|
|
142
|
+
throw new GatekeeperError('sharing_refused', `This Gatekeeper cannot verify that '${id}' may see everything already read through this ` +
|
|
143
|
+
'resource, so it refuses the share. Give them their own connected account instead: their ' +
|
|
144
|
+
"tier is then resolved from this deployment's own policy.");
|
|
145
|
+
}
|
|
146
|
+
/** Idempotent by contract: nothing was ever added, so there is nothing to forget. */
|
|
147
|
+
async removeObserver(_id) { }
|
|
148
|
+
#gatekeeper() {
|
|
149
|
+
return Gatekeeper.create(this.#env, this.#policy);
|
|
150
|
+
}
|
|
151
|
+
#tier() {
|
|
152
|
+
return this.#gatekeeper().tierForAccount(this.#props.accountId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=resource-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-core.js","sourceRoot":"","sources":["../../src/os/resource-core.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,6FAA6F;AAC7F,oGAAoG;AACpG,mGAAmG;AACnG,8FAA8F;AAC9F,8FAA8F;AAC9F,mGAAmG;AACnG,mCAAmC;AACnC,EAAE;AACF,6FAA6F;AAC7F,kGAAkG;AAClG,yBAAyB;AAGzB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC1D,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAkBnF,+FAA+F;AAC/F,MAAM,OAAO,YAAY;IACd,IAAI,CAAe;IACnB,OAAO,CAAkB;IACzB,MAAM,CAAe;IACrB,OAAO,GAAG,IAAI,YAAY,EAAE,CAAA;IAErC,YAAY,GAAkB,EAAE,MAAwB,EAAE,KAAoB;QAC5E,IAAI,CAAC,IAAI,GAAG,GAAG,CAAA;QACf,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;IACrB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,CAAA;QACzB,OAAO;YACL,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU;YAClC,KAAK,EAAE,uBAAuB;YAC9B,OAAO,EACL,uEAAuE,IAAI,CAAC,IAAI,KAAK;gBACrF,GAAG,IAAI,CAAC,WAAW,EAAE;YACvB,oBAAoB,EAAE,YAAY;YAClC,MAAM,EAAE,sBAAsB;SAC/B,CAAA;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,kBAAkB;QACtB,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;IAC7C,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,YAAY,CAAC,aAA4B;QAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QACrC,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA;QACvC,OAAO,UAAU,CAAC,oBAAoB,CACpC,SAAS,EACT,eAAe,CAAC;YACd,KAAK,EAAE,aAAa;YACpB,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE;YAClC,OAAO,EAAE;gBACP,SAAS;gBACT,IAAI,EAAE,UAAU,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,IAAI;gBAC/C,UAAU,EAAE,UAAU,CAAC,UAAU;aAClC;SACF,CAAC,CACH,CAAA;IACH,CAAC;IAED;;;;;;OAMG;IACH,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,CAAA;IAClC,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,wBAAwB;QAC5B,OAAO,IAAI,CAAC,KAAK,EAAE;aAChB,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;aAC9C,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,WAAW,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC;aACzE,GAAG,CAAC,YAAY,CAAC,CAAA;IACtB,CAAC;IAED,6FAA6F;IAC7F,KAAK,CAAC,WAAW,CAAC,MAAc;QAC9B,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAClC,CAAC;IAED,0FAA0F;IAC1F,KAAK,CAAC,YAAY,CAAC,MAAc;QAC/B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC7B,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,YAAY,CAAC,OAAe;QAChC,OAAO;YACL,OAAO,EACL,2FAA2F;gBAC3F,4FAA4F;YAC9F,QAAQ,EAAE,KAAK;SAChB,CAAA;IACH,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,KAAc;QAC1C,MAAM,IAAI,eAAe,CACvB,iBAAiB,EACjB,uCAAuC,EAAE,iDAAiD;YACxF,0FAA0F;YAC1F,0DAA0D,CAC7D,CAAA;IACH,CAAC;IAED,qFAAqF;IACrF,KAAK,CAAC,cAAc,CAAC,GAAW,IAAkB,CAAC;IAEnD,WAAW;QACT,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;IACnD,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACjE,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GatekeeperEnv } from '../env.js';
|
|
2
|
+
import type { GatekeeperPolicy } from '../policy/compile.js';
|
|
3
|
+
import type { ResourceObject } from './protocol.js';
|
|
4
|
+
import { type ResourceProps } from './resource-core.js';
|
|
5
|
+
export type { ResourceProps } from './resource-core.js';
|
|
6
|
+
/**
|
|
7
|
+
* Build the resource Durable Object class over a deployment's policy.
|
|
8
|
+
*
|
|
9
|
+
* A factory rather than a class, for the reason `createGatekeeperWorker` is one: the policy is the
|
|
10
|
+
* deployment's and arrives as an argument, so a class that imported one would own the file the
|
|
11
|
+
* operator is supposed to write.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createGatekeeperResource(options: {
|
|
14
|
+
policy: GatekeeperPolicy;
|
|
15
|
+
}): new (ctx: DurableObjectState<ResourceProps>, env: GatekeeperEnv) => ResourceObject;
|
|
16
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/os/resource.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAkD,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnG,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAErE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,MAAM,EAAE,gBAAgB,CAAA;CACzB,GAAG,KAAK,GAAG,EAAE,kBAAkB,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,aAAa,KAAK,cAAc,CA+CrF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// The per-resource object the workspace binds: a Durable Object serving one paired cat-factory
|
|
2
|
+
// workspace to one account.
|
|
3
|
+
//
|
|
4
|
+
// WHAT A RESOURCE IS, decided here and recorded in the initiative tracker: the PAIRED cat-factory
|
|
5
|
+
// WORKSPACE, named by a URLPattern over the deployment origin. It follows from the credential
|
|
6
|
+
// rather than being a modelling preference. This Worker holds one provisioning key, a cat-factory
|
|
7
|
+
// key is scoped to exactly one workspace, and `STATE` is already keyed on the deployment origin for
|
|
8
|
+
// the same reason. So one Gatekeeper Worker serves one resource, and an OS deployment that wants
|
|
9
|
+
// two workspaces runs two Gatekeepers with two bindings, which is also the only arrangement in
|
|
10
|
+
// which the two workspaces' credentials sit in different secret stores.
|
|
11
|
+
//
|
|
12
|
+
// Everything below is a FACADE. The policy compilation, the key broker, the durable state and the
|
|
13
|
+
// approval answerers are the same single implementation the `/rpc` door reaches; what differs is
|
|
14
|
+
// the door, and the governance that door brings with it. This file is the SHELL: it supplies
|
|
15
|
+
// `ctx.props` and holds one `ResourceCore`, which is where the behaviour lives.
|
|
16
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
17
|
+
import { holdQueue } from './queue.js';
|
|
18
|
+
import { ResourceCore } from './resource-core.js';
|
|
19
|
+
/**
|
|
20
|
+
* Build the resource Durable Object class over a deployment's policy.
|
|
21
|
+
*
|
|
22
|
+
* A factory rather than a class, for the reason `createGatekeeperWorker` is one: the policy is the
|
|
23
|
+
* deployment's and arrives as an argument, so a class that imported one would own the file the
|
|
24
|
+
* operator is supposed to write.
|
|
25
|
+
*/
|
|
26
|
+
export function createGatekeeperResource(options) {
|
|
27
|
+
const { policy } = options;
|
|
28
|
+
return class CatFactoryResource extends DurableObject {
|
|
29
|
+
// ONE core for the object's lifetime, because the action ledger inside it is what a later
|
|
30
|
+
// `applyAction` settles: a core rebuilt per call would have forgotten the action the decision
|
|
31
|
+
// is about. Field initializers run after the base constructor, so `ctx` and `env` are set.
|
|
32
|
+
#core = new ResourceCore(this.env, policy, this.ctx.props);
|
|
33
|
+
async describe() {
|
|
34
|
+
return this.#core.describe();
|
|
35
|
+
}
|
|
36
|
+
async getTypeScriptTypes() {
|
|
37
|
+
return this.#core.getTypeScriptTypes();
|
|
38
|
+
}
|
|
39
|
+
// The one method where being the SHELL is not delegation: this is where the RPC boundary is,
|
|
40
|
+
// so this is where the queue's lifetime is taken over from the call that carried it in.
|
|
41
|
+
async startSession(approvalQueue) {
|
|
42
|
+
return this.#core.startSession(holdQueue(approvalQueue));
|
|
43
|
+
}
|
|
44
|
+
async getAutoApprovableActions() {
|
|
45
|
+
return this.#core.getAutoApprovableActions();
|
|
46
|
+
}
|
|
47
|
+
async applyAction(action) {
|
|
48
|
+
return this.#core.applyAction(action);
|
|
49
|
+
}
|
|
50
|
+
async rejectAction(action) {
|
|
51
|
+
return this.#core.rejectAction(action);
|
|
52
|
+
}
|
|
53
|
+
async revertAction(action) {
|
|
54
|
+
return this.#core.revertAction(action);
|
|
55
|
+
}
|
|
56
|
+
async addObserver(id, user) {
|
|
57
|
+
return this.#core.addObserver(id, user);
|
|
58
|
+
}
|
|
59
|
+
async removeObserver(id) {
|
|
60
|
+
return this.#core.removeObserver(id);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/os/resource.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,4BAA4B;AAC5B,EAAE;AACF,kGAAkG;AAClG,8FAA8F;AAC9F,kGAAkG;AAClG,oGAAoG;AACpG,iGAAiG;AACjG,+FAA+F;AAC/F,wEAAwE;AACxE,EAAE;AACF,kGAAkG;AAClG,iGAAiG;AACjG,6FAA6F;AAC7F,gFAAgF;AAEhF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAIlD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,YAAY,EAAsB,MAAM,oBAAoB,CAAA;AAIrE;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAExC;IACC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAE1B,OAAO,MAAM,kBAAmB,SAAQ,aAA2C;QACjF,0FAA0F;QAC1F,8FAA8F;QAC9F,2FAA2F;QAClF,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAEnE,KAAK,CAAC,QAAQ;YACZ,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;QAC9B,CAAC;QAED,KAAK,CAAC,kBAAkB;YACtB,OAAO,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAA;QACxC,CAAC;QAED,6FAA6F;QAC7F,wFAAwF;QACxF,KAAK,CAAC,YAAY,CAAC,aAA4B;YAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAA;QAC1D,CAAC;QAED,KAAK,CAAC,wBAAwB;YAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,wBAAwB,EAAE,CAAA;QAC9C,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,MAAc;YAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,MAAc;YAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;QACxC,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,MAAc;YAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;QACxC,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,IAAa;YACzC,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;QACzC,CAAC;QAED,KAAK,CAAC,cAAc,CAAC,EAAU;YAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;QACtC,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SupportedResource } from './protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* The single resource: the paired cat-factory workspace, named by a pattern over its origin.
|
|
4
|
+
*
|
|
5
|
+
* The pattern is anchored on the deployment's ORIGIN with a wildcard path, which is what makes a
|
|
6
|
+
* board URL a person pastes (`https://cat-factory.example.com/w/…`) bind to this Gatekeeper. It is
|
|
7
|
+
* not `grantable`: there is nothing to grant separately, because the account's access is the
|
|
8
|
+
* provisioning key this Worker already holds.
|
|
9
|
+
*/
|
|
10
|
+
export declare function supportedResourceFor(deployment: string): SupportedResource;
|
|
11
|
+
//# sourceMappingURL=resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/os/resources.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEtD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,CAW1E"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// What this Gatekeeper serves, as the workspace's own resource vocabulary.
|
|
2
|
+
//
|
|
3
|
+
// One function, in a file of its own, because three callers need the SAME answer and a second
|
|
4
|
+
// spelling of the URL pattern would be a resource an account advertises and a bind cannot match:
|
|
5
|
+
// the vendor lists it, the account lists it and matches a URL against it, and the resource object
|
|
6
|
+
// describes itself with it.
|
|
7
|
+
/**
|
|
8
|
+
* The single resource: the paired cat-factory workspace, named by a pattern over its origin.
|
|
9
|
+
*
|
|
10
|
+
* The pattern is anchored on the deployment's ORIGIN with a wildcard path, which is what makes a
|
|
11
|
+
* board URL a person pastes (`https://cat-factory.example.com/w/…`) bind to this Gatekeeper. It is
|
|
12
|
+
* not `grantable`: there is nothing to grant separately, because the account's access is the
|
|
13
|
+
* provisioning key this Worker already holds.
|
|
14
|
+
*/
|
|
15
|
+
export function supportedResourceFor(deployment) {
|
|
16
|
+
const origin = new URL(deployment).origin;
|
|
17
|
+
return {
|
|
18
|
+
urlPattern: `${origin}/*`,
|
|
19
|
+
title: 'cat-factory workspace',
|
|
20
|
+
description: 'The cat-factory workspace this Gatekeeper is paired with: file tasks, start runs, watch ' +
|
|
21
|
+
'them, and answer the decisions they park on. One Gatekeeper serves one workspace, because ' +
|
|
22
|
+
'the provisioning key it holds is scoped to one.',
|
|
23
|
+
grantable: false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/os/resources.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,8FAA8F;AAC9F,iGAAiG;AACjG,kGAAkG;AAClG,4BAA4B;AAI5B;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,CAAA;IACzC,OAAO;QACL,UAAU,EAAE,GAAG,MAAM,IAAI;QACzB,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,0FAA0F;YAC1F,4FAA4F;YAC5F,iDAAiD;QACnD,SAAS,EAAE,KAAK;KACjB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CompiledTier } from '../policy/compile.js';
|
|
2
|
+
/** The type name a resource declares as its `tsType`, and the interface the `.d.ts` exports. */
|
|
3
|
+
export declare const SESSION_INTERFACE_NAME = "CatFactoryWorkspace";
|
|
4
|
+
/** Render the session `.d.ts` for one compiled tier. */
|
|
5
|
+
export declare function renderTierSessionTypes(tier: CompiledTier): string;
|
|
6
|
+
//# sourceMappingURL=session-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-types.d.ts","sourceRoot":"","sources":["../../src/os/session-types.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAExD,gGAAgG;AAChG,eAAO,MAAM,sBAAsB,wBAAwB,CAAA;AA2D3D,wDAAwD;AACxD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAUjE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The `.d.ts` a session serves through `getTypeScriptTypes()`, composed for one tier.
|
|
2
|
+
//
|
|
3
|
+
// The per-operation signatures are GENERATED (`@cat-factory/gatekeeper-bindings`'s
|
|
4
|
+
// `SESSION_METHOD_SIGNATURES`, rendered by `pnpm gen:sdk` from the same spec the bindings table
|
|
5
|
+
// comes from), so the types an agent codes against cannot describe a surface the deployment does
|
|
6
|
+
// not serve. What is composed here is the tier's own subset plus the reserved methods, because a
|
|
7
|
+
// session carries exactly what its policy granted: a file naming the whole surface would promise
|
|
8
|
+
// methods the object does not have.
|
|
9
|
+
//
|
|
10
|
+
// The reserved methods are written out here rather than generated because they are this package's,
|
|
11
|
+
// not the deployment's: they exist at exactly the same version as this file.
|
|
12
|
+
import { renderSessionTypes } from '@cat-factory/gatekeeper-bindings';
|
|
13
|
+
/** The type name a resource declares as its `tsType`, and the interface the `.d.ts` exports. */
|
|
14
|
+
export const SESSION_INTERFACE_NAME = 'CatFactoryWorkspace';
|
|
15
|
+
/**
|
|
16
|
+
* The methods every session carries whatever its policy: who the caller is, what they hold, what
|
|
17
|
+
* they do not, and the approval inbox.
|
|
18
|
+
*
|
|
19
|
+
* `withheld()` is the one worth reading twice. An agent that cannot tell "your policy hides this"
|
|
20
|
+
* from "no policy could grant this" from "ask for it another way" reports the wrong one to whoever
|
|
21
|
+
* has to fix it, which is why it is on the object rather than in a document.
|
|
22
|
+
*/
|
|
23
|
+
const RESERVED_MEMBERS = [
|
|
24
|
+
` /** Who this session acts as, and the policy tier that account resolved to. */
|
|
25
|
+
tier(): Promise<{ actorId: string; tier: string; description: string; keyScope: string }>
|
|
26
|
+
`,
|
|
27
|
+
` /**
|
|
28
|
+
* Every operation this session carries, with the scope floor and consequence of each.
|
|
29
|
+
*/
|
|
30
|
+
bindings(): Promise<
|
|
31
|
+
{
|
|
32
|
+
name: string
|
|
33
|
+
summary: string
|
|
34
|
+
minScope: string
|
|
35
|
+
readOnly: boolean
|
|
36
|
+
destructive: boolean
|
|
37
|
+
idempotent: boolean
|
|
38
|
+
pathParams: readonly string[]
|
|
39
|
+
queryParams: readonly { name: string; required: boolean }[]
|
|
40
|
+
hasBody: boolean
|
|
41
|
+
}[]
|
|
42
|
+
>
|
|
43
|
+
`,
|
|
44
|
+
` /**
|
|
45
|
+
* Every operation this session does NOT carry, and why: \`not_in_policy\` and
|
|
46
|
+
* \`denied_by_policy\` are your operator's decision, \`above_key_scope\` needs a higher tier,
|
|
47
|
+
* and \`not_relayable\` cannot cross a session call at all (ask for it another way).
|
|
48
|
+
*/
|
|
49
|
+
withheld(): Promise<{ name: string; reason: string; detail: string }[]>
|
|
50
|
+
`,
|
|
51
|
+
` /** The approval cards the paired deployment has raised and this Gatekeeper still holds open. */
|
|
52
|
+
approvals_list(): Promise<unknown[]>
|
|
53
|
+
`,
|
|
54
|
+
` /**
|
|
55
|
+
* What a card's run is ACTUALLY parked on now, with the verbs each park takes and whether this
|
|
56
|
+
* session holds the operation behind it. The card is a pointer; the run is the truth.
|
|
57
|
+
*/
|
|
58
|
+
approvals_inspect(cardId: string): Promise<unknown>
|
|
59
|
+
`,
|
|
60
|
+
` /**
|
|
61
|
+
* Answer a park. Three outcomes are distinguishable and all three matter: \`answered\` (the run
|
|
62
|
+
* is unparked), \`recorded\` (your vote counted, the quorum is unmet, the run is still parked)
|
|
63
|
+
* and \`stale\` (the run moved on).
|
|
64
|
+
*/
|
|
65
|
+
approvals_answer(cardId: string, input: unknown): Promise<unknown>
|
|
66
|
+
`,
|
|
67
|
+
` /** Every run this Gatekeeper has been pushed lifecycle events for. */
|
|
68
|
+
runs_watched(): Promise<unknown[]>
|
|
69
|
+
`,
|
|
70
|
+
];
|
|
71
|
+
/** Render the session `.d.ts` for one compiled tier. */
|
|
72
|
+
export function renderTierSessionTypes(tier) {
|
|
73
|
+
return renderSessionTypes({
|
|
74
|
+
interfaceName: SESSION_INTERFACE_NAME,
|
|
75
|
+
bindings: tier.granted.map((binding) => binding.name),
|
|
76
|
+
extraMembers: RESERVED_MEMBERS,
|
|
77
|
+
preamble: `// The session for policy tier \`${tier.name}\`: ${tier.description}\n` +
|
|
78
|
+
`// Calls are made with a per-account API key at scope \`${tier.keyScope}\`, and every one of\n` +
|
|
79
|
+
"// them passes through this workspace's approval queue.",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=session-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-types.js","sourceRoot":"","sources":["../../src/os/session-types.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,EAAE;AACF,mFAAmF;AACnF,gGAAgG;AAChG,iGAAiG;AACjG,iGAAiG;AACjG,iGAAiG;AACjG,oCAAoC;AACpC,EAAE;AACF,mGAAmG;AACnG,6EAA6E;AAE7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAGrE,gGAAgG;AAChG,MAAM,CAAC,MAAM,sBAAsB,GAAG,qBAAqB,CAAA;AAE3D;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAsB;IAC1C;;CAED;IACC;;;;;;;;;;;;;;;;CAgBD;IACC;;;;;;CAMD;IACC;;CAED;IACC;;;;;CAKD;IACC;;;;;;CAMD;IACC;;CAED;CACA,CAAA;AAED,wDAAwD;AACxD,MAAM,UAAU,sBAAsB,CAAC,IAAkB;IACvD,OAAO,kBAAkB,CAAC;QACxB,aAAa,EAAE,sBAAsB;QACrC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QACrD,YAAY,EAAE,gBAAgB;QAC9B,QAAQ,EACN,oCAAoC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,WAAW,IAAI;YACxE,2DAA2D,IAAI,CAAC,QAAQ,wBAAwB;YAChG,yDAAyD;KAC5D,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { GatekeeperEnv } from '../env.js';
|
|
2
|
+
import type { GatekeeperPolicy } from '../policy/compile.js';
|
|
3
|
+
import type { VendorEntrypoint } from './protocol.js';
|
|
4
|
+
/**
|
|
5
|
+
* Build the vendor entrypoint class over a deployment's policy.
|
|
6
|
+
*
|
|
7
|
+
* The deployment exports the result under the name the OS pins:
|
|
8
|
+
*
|
|
9
|
+
* export const GatekeeperVendor = createGatekeeperVendor({ policy: POLICY })
|
|
10
|
+
*/
|
|
11
|
+
export declare function createGatekeeperVendor(options: {
|
|
12
|
+
policy: GatekeeperPolicy;
|
|
13
|
+
}): new (ctx: ExecutionContext, env: GatekeeperEnv) => VendorEntrypoint;
|
|
14
|
+
//# sourceMappingURL=vendor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendor.d.ts","sourceRoot":"","sources":["../../src/os/vendor.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAE9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG5D,OAAO,KAAK,EAAwC,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAe3F;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE;IAC9C,MAAM,EAAE,gBAAgB,CAAA;CACzB,GAAG,KAAK,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,KAAK,gBAAgB,CA4DtE"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// The entrypoint a Cloudflare OS deployment discovers this Worker through.
|
|
2
|
+
//
|
|
3
|
+
// The workspace scans its own environment for `GATEKEEPER_*` SERVICE BINDINGS and treats each as a
|
|
4
|
+
// vendor, reaching the bound Worker's `GatekeeperVendor` entrypoint over native Workers RPC. That
|
|
5
|
+
// is a different door from `/rpc`, and the difference that matters is the authorization:
|
|
6
|
+
//
|
|
7
|
+
// - On this path, HOLDING THE BINDING IS THE AUTHORIZATION. A service binding is configuration
|
|
8
|
+
// only the OS deployment's operator can write, and the call never traverses the internet.
|
|
9
|
+
// There is no shared token in the published model and none is asked for here; adding one would
|
|
10
|
+
// be a second secret to rotate that protects nothing the binding does not already.
|
|
11
|
+
// - `/rpc` stays bearer-gated by `OS_SHARED_TOKEN`, because a Worker with a route attached is
|
|
12
|
+
// reachable by anyone who finds it. Nothing about that door changes: it is what a non-OS
|
|
13
|
+
// consumer speaks, and the promise that this Worker is not Cloudflare-OS-only rests on it.
|
|
14
|
+
//
|
|
15
|
+
// Everything the vendor answers is a PROJECTION of the operation table or of the policy compiled
|
|
16
|
+
// against it. Nothing here is transcribed, so a surface change cannot leave the description of it
|
|
17
|
+
// behind.
|
|
18
|
+
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
19
|
+
import { Gatekeeper } from '../gatekeeper.js';
|
|
20
|
+
import { loopbackExport } from './exports.js';
|
|
21
|
+
import { supportedResourceFor } from './resources.js';
|
|
22
|
+
import { renderTierSessionTypes } from './session-types.js';
|
|
23
|
+
/**
|
|
24
|
+
* A fresh account id.
|
|
25
|
+
*
|
|
26
|
+
* Random rather than derived: the vendor is handed nothing to derive one FROM (`createAccount()`
|
|
27
|
+
* takes no arguments by design, so that a public method cannot be used to look an existing account
|
|
28
|
+
* up), and a guessable id would be one an agent could name in place of its own.
|
|
29
|
+
*/
|
|
30
|
+
function mintAccountId() {
|
|
31
|
+
return `acct_${crypto.randomUUID().replaceAll('-', '')}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build the vendor entrypoint class over a deployment's policy.
|
|
35
|
+
*
|
|
36
|
+
* The deployment exports the result under the name the OS pins:
|
|
37
|
+
*
|
|
38
|
+
* export const GatekeeperVendor = createGatekeeperVendor({ policy: POLICY })
|
|
39
|
+
*/
|
|
40
|
+
export function createGatekeeperVendor(options) {
|
|
41
|
+
const { policy } = options;
|
|
42
|
+
return class GatekeeperVendor extends WorkerEntrypoint {
|
|
43
|
+
#gatekeeper() {
|
|
44
|
+
return Gatekeeper.create(this.env, policy);
|
|
45
|
+
}
|
|
46
|
+
async describe() {
|
|
47
|
+
const deployment = this.#gatekeeper().deployment;
|
|
48
|
+
return {
|
|
49
|
+
displayName: 'cat-factory',
|
|
50
|
+
url: deployment,
|
|
51
|
+
tagline: 'Ship software with managed agents.',
|
|
52
|
+
description: 'File work onto a cat-factory board, start a pipeline run, watch it, and answer what it ' +
|
|
53
|
+
'parks on. Calls are made with a per-account API key this Gatekeeper mints and holds; ' +
|
|
54
|
+
'no agent ever sees a credential.',
|
|
55
|
+
// This vendor cannot authenticate anyone for sign-in: there is no per-user OAuth on the
|
|
56
|
+
// cat-factory side, so no provider-verified email exists to be one.
|
|
57
|
+
providesAuth: false,
|
|
58
|
+
// It CAN mint an account with no flow, which is the contract's own escape hatch for
|
|
59
|
+
// exactly this case. See `account.ts` for what that does and does not buy.
|
|
60
|
+
autoProvisionsAccount: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async getSupportedResources(_options) {
|
|
64
|
+
return [supportedResourceFor(this.#gatekeeper().deployment)];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The session types, rendered for the tier an auto-provisioned account will get.
|
|
68
|
+
*
|
|
69
|
+
* That is `autoProvisionedTier`, never `defaultTier`: the two are different knobs answering
|
|
70
|
+
* different questions (`policy/compile.ts` holds why), and this door only ever mints accounts.
|
|
71
|
+
* The tier is resolved WITHOUT an account, because there is none yet and inventing one to
|
|
72
|
+
* resolve through would make the refusal name an `acct_…` id no operator could find. The
|
|
73
|
+
* authoritative copy for a bound session is the resource's own `getTypeScriptTypes()`, which
|
|
74
|
+
* knows the tier the account actually resolved to and is the one `ResourceDescription.tsType`
|
|
75
|
+
* points at.
|
|
76
|
+
*/
|
|
77
|
+
async getTypeScriptTypes() {
|
|
78
|
+
return renderTierSessionTypes(this.#gatekeeper().tierForNewAccount());
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Mint a new connected account.
|
|
82
|
+
*
|
|
83
|
+
* Deliberately creating-only and identity-free, which is what makes it safe on a public
|
|
84
|
+
* interface: it cannot look an existing account up, so holding the binding buys the ability to
|
|
85
|
+
* make an account and never the ability to reach somebody else's.
|
|
86
|
+
*/
|
|
87
|
+
async createAccount() {
|
|
88
|
+
const props = { accountId: mintAccountId() };
|
|
89
|
+
// Resolved before the account is handed back, so a deployment missing the export is refused
|
|
90
|
+
// at the call that would have created the account rather than at the first use of it.
|
|
91
|
+
return loopbackExport(this.ctx.exports, 'account', props);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=vendor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendor.js","sourceRoot":"","sources":["../../src/os/vendor.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,mGAAmG;AACnG,kGAAkG;AAClG,yFAAyF;AACzF,EAAE;AACF,iGAAiG;AACjG,8FAA8F;AAC9F,mGAAmG;AACnG,uFAAuF;AACvF,gGAAgG;AAChG,6FAA6F;AAC7F,+FAA+F;AAC/F,EAAE;AACF,iGAAiG;AACjG,kGAAkG;AAClG,UAAU;AAEV,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAG7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAE3D;;;;;;GAMG;AACH,SAAS,aAAa;IACpB,OAAO,QAAQ,MAAM,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAA;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAEtC;IACC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAE1B,OAAO,MAAM,gBAAiB,SAAQ,gBAA+B;QACnE,WAAW;YACT,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC5C,CAAC;QAED,KAAK,CAAC,QAAQ;YACZ,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAA;YAChD,OAAO;gBACL,WAAW,EAAE,aAAa;gBAC1B,GAAG,EAAE,UAAU;gBACf,OAAO,EAAE,oCAAoC;gBAC7C,WAAW,EACT,yFAAyF;oBACzF,uFAAuF;oBACvF,kCAAkC;gBACpC,wFAAwF;gBACxF,oEAAoE;gBACpE,YAAY,EAAE,KAAK;gBACnB,oFAAoF;gBACpF,2EAA2E;gBAC3E,qBAAqB,EAAE,IAAI;aAC5B,CAAA;QACH,CAAC;QAED,KAAK,CAAC,qBAAqB,CAAC,QAA8B;YACxD,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;QAC9D,CAAC;QAED;;;;;;;;;;WAUG;QACH,KAAK,CAAC,kBAAkB;YACtB,OAAO,sBAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAA;QACvE,CAAC;QAED;;;;;;WAMG;QACH,KAAK,CAAC,aAAa;YACjB,MAAM,KAAK,GAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,CAAA;YAC1D,4FAA4F;YAC5F,sFAAsF;YACtF,OAAO,cAAc,CAAU,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;QACpE,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/policy/compile.d.ts
CHANGED
|
@@ -25,6 +25,25 @@ export interface TierPolicy {
|
|
|
25
25
|
export interface GatekeeperPolicy {
|
|
26
26
|
/** The tier an actor with no explicit grant is given. Name `null` to refuse unknown actors. */
|
|
27
27
|
defaultTier: string | null;
|
|
28
|
+
/**
|
|
29
|
+
* The tier an AUTO-PROVISIONED account gets: the Cloudflare OS entrypoint's own default.
|
|
30
|
+
*
|
|
31
|
+
* A second knob rather than a reuse of `defaultTier`, because the two doors establish identity
|
|
32
|
+
* differently and an operator's answer about one is not their answer about the other.
|
|
33
|
+
* `defaultTier` is about a person the OS names on a `connect({ actorId })` call, so a deployment
|
|
34
|
+
* can keep a roster and refuse strangers. An account minted through `createAccount()` has no name
|
|
35
|
+
* to put on a roster: the contract's auto-provisioning flow carries no identity by design, so
|
|
36
|
+
* `grants` can never match one and every such account falls to this.
|
|
37
|
+
*
|
|
38
|
+
* Absent or `null` REFUSES, and it does not inherit from `defaultTier`. Inheriting would widen
|
|
39
|
+
* silently in whichever direction the deployment did not mean: a roster deployment would hand a
|
|
40
|
+
* tier to every account the workspace mints, or turning on OS discovery would quietly give every
|
|
41
|
+
* unrostered `/rpc` caller one. Naming a tier here is what turns Cloudflare OS discovery on.
|
|
42
|
+
*
|
|
43
|
+
* An account still resolves through `grants` first, so an operator who wants ONE account raised
|
|
44
|
+
* reads its id off the account's own `describe()` (`uniqueName`) and grants that id directly.
|
|
45
|
+
*/
|
|
46
|
+
autoProvisionedTier?: string | null;
|
|
28
47
|
tiers: Record<string, TierPolicy>;
|
|
29
48
|
/** OS user identity (whatever the OS authenticates) to tier name. */
|
|
30
49
|
grants: Record<string, string>;
|
|
@@ -54,6 +73,7 @@ export interface CompiledTier {
|
|
|
54
73
|
/** The policy, resolved once and read per request. */
|
|
55
74
|
export interface CompiledPolicy {
|
|
56
75
|
defaultTier: string | null;
|
|
76
|
+
autoProvisionedTier: string | null;
|
|
57
77
|
tiers: ReadonlyMap<string, CompiledTier>;
|
|
58
78
|
grants: ReadonlyMap<string, string>;
|
|
59
79
|
}
|
|
@@ -70,6 +90,24 @@ export declare function compilePolicy(policy: GatekeeperPolicy): CompiledPolicy;
|
|
|
70
90
|
* ask for a better one.
|
|
71
91
|
*/
|
|
72
92
|
export declare function tierForActor(policy: CompiledPolicy, actorId: string): CompiledTier | null;
|
|
93
|
+
/**
|
|
94
|
+
* The tier EVERY auto-provisioned account falls to, resolved without an account to ask about.
|
|
95
|
+
*
|
|
96
|
+
* Asked on its own because the vendor is questioned before any account exists: the session types
|
|
97
|
+
* it publishes are the ones a not-yet-created account will get. Answering that by minting a
|
|
98
|
+
* throwaway id and letting it miss `grants` produces the same tier and a refusal naming an id that
|
|
99
|
+
* exists nowhere in the operator's config, which is the wrong sentence for the one reader who has
|
|
100
|
+
* to act on it.
|
|
101
|
+
*/
|
|
102
|
+
export declare function autoProvisionedTier(policy: CompiledPolicy): CompiledTier | null;
|
|
103
|
+
/**
|
|
104
|
+
* The tier one auto-provisioned Cloudflare OS account holds, or a refusal.
|
|
105
|
+
*
|
|
106
|
+
* `grants` is consulted FIRST and by the account's own minted id, which is the whole affordance an
|
|
107
|
+
* operator has for raising one account above the rest: nothing else about such an account is
|
|
108
|
+
* nameable, because nothing about it was named when it was created.
|
|
109
|
+
*/
|
|
110
|
+
export declare function tierForAccount(policy: CompiledPolicy, accountId: string): CompiledTier | null;
|
|
73
111
|
/**
|
|
74
112
|
* What the OS needs to run its own approval governance over a call: the consequence the platform
|
|
75
113
|
* annotates, with the cautious default already applied.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/policy/compile.ts"],"names":[],"mappings":"AAaA,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAA;AAGzC;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,QAAQ,EAAE,cAAc,CAAA;IACxB,4EAA4E;IAC5E,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAA;IAC9B,mFAAmF;IACnF,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACzB;AAED,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACjC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GACtB,eAAe,GACf,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,CAAA;AAEnB,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,yDAAyD;AACzD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,cAAc,CAAA;IACxB,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAA;IACrC;;;;OAIG;IACH,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAA;IACpC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACxB;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACxC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AA2HD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/policy/compile.ts"],"names":[],"mappings":"AAaA,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAA;AAGzC;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,QAAQ,EAAE,cAAc,CAAA;IACxB,4EAA4E;IAC5E,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAA;IAC9B,mFAAmF;IACnF,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACzB;AAED,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B;;;;;;;;;;;;;;;;;OAiBG;IACH,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACjC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GACtB,eAAe,GACf,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,CAAA;AAEnB,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,yDAAyD;AACzD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,cAAc,CAAA;IACxB,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAA;IACrC;;;;OAIG;IACH,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAA;IACpC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACxB;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACxC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AA2HD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,CAgCtE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAIzF;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,IAAI,CAG/E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAI7F;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,cAAc,CAAA;IACxB,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,OAAO,CAAA;IACpB,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,WAAW,EAAE,SAAS,oBAAoB,EAAE,CAAA;IAC5C,OAAO,EAAE,OAAO,CAAA;CACjB,CAaA"}
|