@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,115 @@
|
|
|
1
|
+
import type { GatekeeperEnv } from '../env.js';
|
|
2
|
+
import type { GatekeeperPolicy } from '../policy/compile.js';
|
|
3
|
+
import type { ActionKind, ApprovalQueue, ResourceDescription, ResourceObject } from './protocol.js';
|
|
4
|
+
/**
|
|
5
|
+
* What the account imbues a resource object with when it hands the class to the workspace.
|
|
6
|
+
*
|
|
7
|
+
* The account and NOTHING else. The URL the workspace matched is deliberately not here: under this
|
|
8
|
+
* Gatekeeper's own model a resource IS the paired workspace, so every URL that matches the pattern
|
|
9
|
+
* binds the same one and the matched URL decides nothing. Carrying it would be state whose only
|
|
10
|
+
* effect is that the next reader has to work out it decides nothing, and it had already been
|
|
11
|
+
* assigned the pattern rather than the match without anything noticing. A consumer that genuinely
|
|
12
|
+
* needs the bound URL adds it back WITH the reader that wants it, which is also the change that
|
|
13
|
+
* would say which of the two it meant.
|
|
14
|
+
*/
|
|
15
|
+
export interface ResourceProps {
|
|
16
|
+
/** The account this resource is bound for. Every key minted through it is stamped with this. */
|
|
17
|
+
accountId: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* What a resource needs beyond its own environment: the Worker's exports.
|
|
21
|
+
*
|
|
22
|
+
* A hook's controller is one of this Worker's own named exports, resolved as
|
|
23
|
+
* `ctx.exports.CatFactoryHookController` against the DEPLOYMENT's entry module, and the shell is
|
|
24
|
+
* the only place that bag exists. It is a dependency rather than a lookup so this core stays
|
|
25
|
+
* drivable in process, where the suite supplies its own.
|
|
26
|
+
*/
|
|
27
|
+
export interface ResourceDependencies {
|
|
28
|
+
/** The Worker's own exports, as the object model reaches them. */
|
|
29
|
+
exports: unknown;
|
|
30
|
+
}
|
|
31
|
+
/** One bound resource: the session it opens, its types, and the action lifecycle behind it. */
|
|
32
|
+
export declare class ResourceCore implements ResourceObject {
|
|
33
|
+
#private;
|
|
34
|
+
constructor(env: GatekeeperEnv, policy: GatekeeperPolicy, props: ResourceProps, deps: ResourceDependencies);
|
|
35
|
+
describe(): Promise<ResourceDescription>;
|
|
36
|
+
/**
|
|
37
|
+
* The session's own types, rendered for the tier this account holds.
|
|
38
|
+
*
|
|
39
|
+
* Tier-specific rather than a copy of the vendor's, because this is the one the contract points a
|
|
40
|
+
* caller at: `ResourceDescription.tsType` must name an export of THIS method's output, and a
|
|
41
|
+
* session carries exactly its granted operations.
|
|
42
|
+
*/
|
|
43
|
+
getTypeScriptTypes(): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Open a governed session.
|
|
46
|
+
*
|
|
47
|
+
* The queue is not optional and not a decoration: every operation the returned object carries
|
|
48
|
+
* funnels through the one `invoke` closure, which submits actions to this queue and authorizes
|
|
49
|
+
* observations against it. The tier policy underneath is the FLOOR, so an operation the policy
|
|
50
|
+
* never granted is absent from the object rather than something the queue has to refuse.
|
|
51
|
+
*
|
|
52
|
+
* The queue passed here is OWNED by the session that comes back: it is released, along with
|
|
53
|
+
* every action that session left undecided, when the session is disposed. A caller reaching this
|
|
54
|
+
* over RPC therefore hands in a reference of its own rather than the parameter it received (see
|
|
55
|
+
* `resource.ts`), because the parameter's lifetime ends when this call returns and the session's
|
|
56
|
+
* does not.
|
|
57
|
+
*/
|
|
58
|
+
startSession(approvalQueue: ApprovalQueue): Promise<unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* How many submitted actions this object is still holding, across every live session.
|
|
61
|
+
*
|
|
62
|
+
* Exposed for the same reason the ledger counts them: the ONE unbounded thing about a long-lived
|
|
63
|
+
* resource object is this set, and a count that does not fall back to zero when the sessions are
|
|
64
|
+
* gone is the leak rather than a slow day.
|
|
65
|
+
*/
|
|
66
|
+
get pendingActionCount(): number;
|
|
67
|
+
/**
|
|
68
|
+
* The action kinds this Gatekeeper may auto-apply, if the user opted into the kind.
|
|
69
|
+
*
|
|
70
|
+
* Derived from the same table and the same consequence reading `describeAction` stamps onto each
|
|
71
|
+
* submission, so a pre-approval UI listing kinds before any action exists and the `autoApprovable`
|
|
72
|
+
* flag on an action that has been submitted cannot disagree.
|
|
73
|
+
*
|
|
74
|
+
* TODAY THIS IS EMPTY, and that is the honest answer rather than a gap. The public surface
|
|
75
|
+
* annotates a consequence only where the stakes are real money or a merged pull request, so every
|
|
76
|
+
* other mutation is unannotated, and the table's documented reading of an unannotated mutation is
|
|
77
|
+
* that it is destructive. Offering those for unattended auto-approval would mean inverting that
|
|
78
|
+
* default here, which is precisely the misreading `resolveConsequence` exists to stop. If the
|
|
79
|
+
* surface ever states that a write is safe, it appears here with no further decision.
|
|
80
|
+
*/
|
|
81
|
+
getAutoApprovableActions(): Promise<ActionKind[]>;
|
|
82
|
+
/** The workspace approved an action: perform it, and hand the result to the waiting call. */
|
|
83
|
+
applyAction(action: number): Promise<void>;
|
|
84
|
+
/** The workspace rejected an action: the waiting call throws and nothing is performed. */
|
|
85
|
+
rejectAction(action: number): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Reverting is not something this Gatekeeper can do, which every action it submits already states
|
|
88
|
+
* (`implementsRevert: false`).
|
|
89
|
+
*
|
|
90
|
+
* It answers rather than throwing because the caller is the workspace UI on behalf of a person who
|
|
91
|
+
* wants their change undone: a message naming what they have to do themselves is the useful
|
|
92
|
+
* answer, where an exception would surface as a failed revert they might retry.
|
|
93
|
+
*/
|
|
94
|
+
revertAction(_action: number): Promise<{
|
|
95
|
+
message: string;
|
|
96
|
+
canRetry: false;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* Share this resource's observations onward, if the observer could have read them all directly.
|
|
100
|
+
*
|
|
101
|
+
* The contract's requirement, answered rather than declined: the observer names an account this
|
|
102
|
+
* deployment minted, its policy resolves that account's tier, and the share is admitted only
|
|
103
|
+
* when that tier reaches everything the tier THIS resource is bound at reaches. `sharing.ts`
|
|
104
|
+
* holds the tests and why each is the one the contract asks for, including why the account has
|
|
105
|
+
* to be recognised before its tier means anything.
|
|
106
|
+
*
|
|
107
|
+
* Nothing is recorded about an admitted observer, and that is a property of the rule rather than
|
|
108
|
+
* an omission: every accepted observer can read everything this resource could ever have
|
|
109
|
+
* observed, so there is never an observation to exclude from them afterwards.
|
|
110
|
+
*/
|
|
111
|
+
addObserver(id: string, user: unknown): Promise<void>;
|
|
112
|
+
/** Idempotent by contract: nothing is recorded about an observer, so nothing is forgotten. */
|
|
113
|
+
removeObserver(_id: string): Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=resource-core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-core.d.ts","sourceRoot":"","sources":["../../src/os/resource-core.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAE9C,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG1E,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAKnG;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,gGAAgG;IAChG,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,+FAA+F;AAC/F,qBAAa,YAAa,YAAW,cAAc;;IAOjD,YACE,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,oBAAoB,EAM3B;IAEK,QAAQ,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAW7C;IAED;;;;;;OAMG;IACG,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE1C;IAED;;;;;;;;;;;;;OAaG;IACG,YAAY,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAoBjE;IAED;;;;;;OAMG;IACH,IAAI,kBAAkB,IAAI,MAAM,CAE/B;IAED;;;;;;;;;;;;;OAaG;IACG,wBAAwB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAKtD;IAED,6FAA6F;IACvF,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;IAED,0FAA0F;IACpF,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhD;IAED;;;;;;;OAOG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAA;KAAE,CAAC,CAOjF;IAED;;;;;;;;;;;;OAYG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAW1D;IAED,8FAA8F;IACxF,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAG;CASpD"}
|
|
@@ -0,0 +1,172 @@
|
|
|
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 { Gatekeeper } from '../gatekeeper.js';
|
|
15
|
+
import { actionKindOf } from './descriptions.js';
|
|
16
|
+
import { loopbackExport } from './exports.js';
|
|
17
|
+
import { ActionLedger, queueGovernance } from './queue.js';
|
|
18
|
+
import { assertObserverMaySee, identifyObserver } from './sharing.js';
|
|
19
|
+
import { renderTierSessionTypes, SESSION_INTERFACE_NAME } from './session-types.js';
|
|
20
|
+
/** One bound resource: the session it opens, its types, and the action lifecycle behind it. */
|
|
21
|
+
export class ResourceCore {
|
|
22
|
+
#env;
|
|
23
|
+
#policy;
|
|
24
|
+
#props;
|
|
25
|
+
#deps;
|
|
26
|
+
#ledger = new ActionLedger();
|
|
27
|
+
constructor(env, policy, props, deps) {
|
|
28
|
+
this.#env = env;
|
|
29
|
+
this.#policy = policy;
|
|
30
|
+
this.#props = props;
|
|
31
|
+
this.#deps = deps;
|
|
32
|
+
}
|
|
33
|
+
async describe() {
|
|
34
|
+
const tier = this.#tier();
|
|
35
|
+
return {
|
|
36
|
+
url: this.#gatekeeper().deployment,
|
|
37
|
+
title: 'cat-factory workspace',
|
|
38
|
+
snippet: `File work, start runs and answer what they park on, at policy tier '${tier.name}': ` +
|
|
39
|
+
`${tier.description}`,
|
|
40
|
+
suggestedBindingName: 'catFactory',
|
|
41
|
+
tsType: SESSION_INTERFACE_NAME,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The session's own types, rendered for the tier this account holds.
|
|
46
|
+
*
|
|
47
|
+
* Tier-specific rather than a copy of the vendor's, because this is the one the contract points a
|
|
48
|
+
* caller at: `ResourceDescription.tsType` must name an export of THIS method's output, and a
|
|
49
|
+
* session carries exactly its granted operations.
|
|
50
|
+
*/
|
|
51
|
+
async getTypeScriptTypes() {
|
|
52
|
+
return renderTierSessionTypes(this.#tier());
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Open a governed session.
|
|
56
|
+
*
|
|
57
|
+
* The queue is not optional and not a decoration: every operation the returned object carries
|
|
58
|
+
* funnels through the one `invoke` closure, which submits actions to this queue and authorizes
|
|
59
|
+
* observations against it. The tier policy underneath is the FLOOR, so an operation the policy
|
|
60
|
+
* never granted is absent from the object rather than something the queue has to refuse.
|
|
61
|
+
*
|
|
62
|
+
* The queue passed here is OWNED by the session that comes back: it is released, along with
|
|
63
|
+
* every action that session left undecided, when the session is disposed. A caller reaching this
|
|
64
|
+
* over RPC therefore hands in a reference of its own rather than the parameter it received (see
|
|
65
|
+
* `resource.ts`), because the parameter's lifetime ends when this call returns and the session's
|
|
66
|
+
* does not.
|
|
67
|
+
*/
|
|
68
|
+
async startSession(approvalQueue) {
|
|
69
|
+
const gatekeeper = this.#gatekeeper();
|
|
70
|
+
const accountId = this.#props.accountId;
|
|
71
|
+
return gatekeeper.capabilityForAccount(accountId, queueGovernance({
|
|
72
|
+
queue: approvalQueue,
|
|
73
|
+
ledger: this.#ledger.openSession(),
|
|
74
|
+
subject: {
|
|
75
|
+
accountId,
|
|
76
|
+
tier: gatekeeper.tierForAccount(accountId).name,
|
|
77
|
+
deployment: gatekeeper.deployment,
|
|
78
|
+
},
|
|
79
|
+
// Resolved per bind rather than once per session: the refusal for a deployment missing
|
|
80
|
+
// the export belongs to the call that needed it, and a session that binds no hook is one
|
|
81
|
+
// this Gatekeeper has no reason to refuse at all.
|
|
82
|
+
controllerFor: (props) => loopbackExport(this.#deps.exports, 'hookController', props),
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* How many submitted actions this object is still holding, across every live session.
|
|
87
|
+
*
|
|
88
|
+
* Exposed for the same reason the ledger counts them: the ONE unbounded thing about a long-lived
|
|
89
|
+
* resource object is this set, and a count that does not fall back to zero when the sessions are
|
|
90
|
+
* gone is the leak rather than a slow day.
|
|
91
|
+
*/
|
|
92
|
+
get pendingActionCount() {
|
|
93
|
+
return this.#ledger.pendingCount;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The action kinds this Gatekeeper may auto-apply, if the user opted into the kind.
|
|
97
|
+
*
|
|
98
|
+
* Derived from the same table and the same consequence reading `describeAction` stamps onto each
|
|
99
|
+
* submission, so a pre-approval UI listing kinds before any action exists and the `autoApprovable`
|
|
100
|
+
* flag on an action that has been submitted cannot disagree.
|
|
101
|
+
*
|
|
102
|
+
* TODAY THIS IS EMPTY, and that is the honest answer rather than a gap. The public surface
|
|
103
|
+
* annotates a consequence only where the stakes are real money or a merged pull request, so every
|
|
104
|
+
* other mutation is unannotated, and the table's documented reading of an unannotated mutation is
|
|
105
|
+
* that it is destructive. Offering those for unattended auto-approval would mean inverting that
|
|
106
|
+
* default here, which is precisely the misreading `resolveConsequence` exists to stop. If the
|
|
107
|
+
* surface ever states that a write is safe, it appears here with no further decision.
|
|
108
|
+
*/
|
|
109
|
+
async getAutoApprovableActions() {
|
|
110
|
+
return this.#tier()
|
|
111
|
+
.granted.filter((binding) => !binding.readOnly)
|
|
112
|
+
.filter((binding) => (binding.consequence?.destructive ?? true) === false)
|
|
113
|
+
.map(actionKindOf);
|
|
114
|
+
}
|
|
115
|
+
/** The workspace approved an action: perform it, and hand the result to the waiting call. */
|
|
116
|
+
async applyAction(action) {
|
|
117
|
+
await this.#ledger.apply(action);
|
|
118
|
+
}
|
|
119
|
+
/** The workspace rejected an action: the waiting call throws and nothing is performed. */
|
|
120
|
+
async rejectAction(action) {
|
|
121
|
+
this.#ledger.reject(action);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reverting is not something this Gatekeeper can do, which every action it submits already states
|
|
125
|
+
* (`implementsRevert: false`).
|
|
126
|
+
*
|
|
127
|
+
* It answers rather than throwing because the caller is the workspace UI on behalf of a person who
|
|
128
|
+
* wants their change undone: a message naming what they have to do themselves is the useful
|
|
129
|
+
* answer, where an exception would surface as a failed revert they might retry.
|
|
130
|
+
*/
|
|
131
|
+
async revertAction(_action) {
|
|
132
|
+
return {
|
|
133
|
+
message: 'This Gatekeeper cannot revert a cat-factory operation. A started run is stopped from the ' +
|
|
134
|
+
'board or with `tasks_stop`; a merged pull request is reverted in the repository, not here.',
|
|
135
|
+
canRetry: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Share this resource's observations onward, if the observer could have read them all directly.
|
|
140
|
+
*
|
|
141
|
+
* The contract's requirement, answered rather than declined: the observer names an account this
|
|
142
|
+
* deployment minted, its policy resolves that account's tier, and the share is admitted only
|
|
143
|
+
* when that tier reaches everything the tier THIS resource is bound at reaches. `sharing.ts`
|
|
144
|
+
* holds the tests and why each is the one the contract asks for, including why the account has
|
|
145
|
+
* to be recognised before its tier means anything.
|
|
146
|
+
*
|
|
147
|
+
* Nothing is recorded about an admitted observer, and that is a property of the rule rather than
|
|
148
|
+
* an omission: every accepted observer can read everything this resource could ever have
|
|
149
|
+
* observed, so there is never an observation to exclude from them afterwards.
|
|
150
|
+
*/
|
|
151
|
+
async addObserver(id, user) {
|
|
152
|
+
const gatekeeper = this.#gatekeeper();
|
|
153
|
+
const observerAccount = await identifyObserver(user, {
|
|
154
|
+
recognize: (accountId) => gatekeeper.recognizesAccount(accountId),
|
|
155
|
+
});
|
|
156
|
+
assertObserverMaySee({
|
|
157
|
+
observerId: id,
|
|
158
|
+
observerAccount,
|
|
159
|
+
owner: this.#tier(),
|
|
160
|
+
observer: gatekeeper.tierForAccount(observerAccount),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/** Idempotent by contract: nothing is recorded about an observer, so nothing is forgotten. */
|
|
164
|
+
async removeObserver(_id) { }
|
|
165
|
+
#gatekeeper() {
|
|
166
|
+
return Gatekeeper.create(this.#env, this.#policy);
|
|
167
|
+
}
|
|
168
|
+
#tier() {
|
|
169
|
+
return this.#gatekeeper().tierForAccount(this.#props.accountId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=resource-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-core.js","sourceRoot":"","sources":["../../src/os/resource-core.ts"],"names":[],"mappings":"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"}
|
|
@@ -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,CAqDrF"}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
//
|
|
33
|
+
// The exports bag is handed down because this shell is the only thing that has one: a hook's
|
|
34
|
+
// controller is one of the Worker's own named exports, resolved against the DEPLOYMENT's entry
|
|
35
|
+
// module, and the core is deliberately drivable without a Durable Object at all.
|
|
36
|
+
#core = new ResourceCore(this.env, policy, this.ctx.props, {
|
|
37
|
+
exports: this.ctx.exports,
|
|
38
|
+
});
|
|
39
|
+
async describe() {
|
|
40
|
+
return this.#core.describe();
|
|
41
|
+
}
|
|
42
|
+
async getTypeScriptTypes() {
|
|
43
|
+
return this.#core.getTypeScriptTypes();
|
|
44
|
+
}
|
|
45
|
+
// The one method where being the SHELL is not delegation: this is where the RPC boundary is,
|
|
46
|
+
// so this is where the queue's lifetime is taken over from the call that carried it in.
|
|
47
|
+
async startSession(approvalQueue) {
|
|
48
|
+
return this.#core.startSession(holdQueue(approvalQueue));
|
|
49
|
+
}
|
|
50
|
+
async getAutoApprovableActions() {
|
|
51
|
+
return this.#core.getAutoApprovableActions();
|
|
52
|
+
}
|
|
53
|
+
async applyAction(action) {
|
|
54
|
+
return this.#core.applyAction(action);
|
|
55
|
+
}
|
|
56
|
+
async rejectAction(action) {
|
|
57
|
+
return this.#core.rejectAction(action);
|
|
58
|
+
}
|
|
59
|
+
async revertAction(action) {
|
|
60
|
+
return this.#core.revertAction(action);
|
|
61
|
+
}
|
|
62
|
+
async addObserver(id, user) {
|
|
63
|
+
return this.#core.addObserver(id, user);
|
|
64
|
+
}
|
|
65
|
+
async removeObserver(id) {
|
|
66
|
+
return this.#core.removeObserver(id);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# 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;QAC3F,EAAE;QACF,6FAA6F;QAC7F,+FAA+F;QAC/F,iFAAiF;QACxE,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE;YAClE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO;SAC1B,CAAC,CAAA;QAEF,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;AAuF3D,wDAAwD;AACxD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAUjE"}
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
* Be pushed each approval card as it is raised or settled, instead of polling
|
|
72
|
+
* \`approvals_list()\`. The callback needs one method:
|
|
73
|
+
* \`onApprovalCard(card): Promise<void>\`.
|
|
74
|
+
*
|
|
75
|
+
* The workspace holds the registration and may ask a person before enabling it, so binding is
|
|
76
|
+
* not receiving: nothing arrives until it is enabled, and \`hooks_bound()\` is where that shows.
|
|
77
|
+
*/
|
|
78
|
+
approvals_subscribe(callback: unknown): Promise<void>
|
|
79
|
+
`,
|
|
80
|
+
` /**
|
|
81
|
+
* Be pushed each run lifecycle transition, instead of polling \`runs_watched()\`. The callback
|
|
82
|
+
* needs one method: \`onRunEvent(state): Promise<void>\`.
|
|
83
|
+
*/
|
|
84
|
+
runs_subscribe(callback: unknown): Promise<void>
|
|
85
|
+
`,
|
|
86
|
+
` /**
|
|
87
|
+
* Every hook this account has enabled, with what each has been pushed (\`deliveries\`), what it
|
|
88
|
+
* could not be pushed (\`missed\`), what the workspace's own side refused (\`failures\`) and
|
|
89
|
+
* whether it is still \`live\`.
|
|
90
|
+
*
|
|
91
|
+
* A hook goes quiet when this Gatekeeper's durable object is evicted between deliveries, since
|
|
92
|
+
* the workspace's callback source cannot be stored. That reads as \`live: false\` with a rising
|
|
93
|
+
* \`missed\`, and the remedy is to bind again; what was missed is still in
|
|
94
|
+
* \`approvals_list()\` and \`runs_watched()\`, which is why they remain the truth.
|
|
95
|
+
*/
|
|
96
|
+
hooks_bound(): Promise<unknown[]>
|
|
97
|
+
`,
|
|
98
|
+
];
|
|
99
|
+
/** Render the session `.d.ts` for one compiled tier. */
|
|
100
|
+
export function renderTierSessionTypes(tier) {
|
|
101
|
+
return renderSessionTypes({
|
|
102
|
+
interfaceName: SESSION_INTERFACE_NAME,
|
|
103
|
+
bindings: tier.granted.map((binding) => binding.name),
|
|
104
|
+
extraMembers: RESERVED_MEMBERS,
|
|
105
|
+
preamble: `// The session for policy tier \`${tier.name}\`: ${tier.description}\n` +
|
|
106
|
+
`// Calls are made with a per-account API key at scope \`${tier.keyScope}\`, and every one of\n` +
|
|
107
|
+
"// them passes through this workspace's approval queue.",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# 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;IACC;;;;;;;;;CASD;IACC;;;;;CAKD;IACC;;;;;;;;;;;CAWD;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,35 @@
|
|
|
1
|
+
import type { CompiledTier } from '../policy/compile.js';
|
|
2
|
+
/** What the caller supplies so a claimed account can be checked against the ones this Gatekeeper made. */
|
|
3
|
+
export interface ObserverRecognition {
|
|
4
|
+
/** Whether this deployment minted the named account. Never a guess: an unknown id is refused. */
|
|
5
|
+
recognize: (accountId: string) => Promise<boolean>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the account behind an observer's verifier, or refuse.
|
|
9
|
+
*
|
|
10
|
+
* The verifier is the contract's own identity token: another vendor's gatekeeper is handed one,
|
|
11
|
+
* and the only thing it carries is who its holder is. Ours answers `{ accountId }`, so a stub that
|
|
12
|
+
* answers anything else did not come from this vendor and cannot be resolved against this
|
|
13
|
+
* deployment's policy. Answering the right SHAPE is not the same as being one of ours, which is
|
|
14
|
+
* why the id is then checked against the accounts this deployment actually minted.
|
|
15
|
+
*
|
|
16
|
+
* What that does NOT establish is worth stating, because the rule above is only as strong as this:
|
|
17
|
+
* a verifier that names an account of ours is taken at its word. The stub is reached over RPC from
|
|
18
|
+
* whoever the workspace handed us, so proving the holder IS that account would take a challenge
|
|
19
|
+
* the published contract does not carry. What is closed here is the case that needs no
|
|
20
|
+
* impersonation at all, which was every honest viewer from every other vendor.
|
|
21
|
+
*/
|
|
22
|
+
export declare function identifyObserver(user: unknown, deps: ObserverRecognition): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Refuse the share unless the observer could have read all of it themselves.
|
|
25
|
+
*
|
|
26
|
+
* Takes the two COMPILED tiers rather than resolving them, so the rule is testable without a
|
|
27
|
+
* policy, a Worker or an account: what it decides is a comparison, and everything else is lookup.
|
|
28
|
+
*/
|
|
29
|
+
export declare function assertObserverMaySee(deps: {
|
|
30
|
+
observerId: string;
|
|
31
|
+
observerAccount: string;
|
|
32
|
+
owner: CompiledTier;
|
|
33
|
+
observer: CompiledTier;
|
|
34
|
+
}): void;
|
|
35
|
+
//# sourceMappingURL=sharing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sharing.d.ts","sourceRoot":"","sources":["../../src/os/sharing.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAUxD,0GAA0G;AAC1G,MAAM,WAAW,mBAAmB;IAClC,iGAAiG;IACjG,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACnD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsChG;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,EAAE,YAAY,CAAA;IACnB,QAAQ,EAAE,YAAY,CAAA;CACvB,GAAG,IAAI,CAqCP"}
|