@cat-factory/gatekeeper-worker 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +276 -0
- package/dist/approvals.d.ts +90 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +193 -0
- package/dist/approvals.js.map +1 -0
- package/dist/capability.d.ts +61 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +125 -0
- package/dist/capability.js.map +1 -0
- package/dist/env.d.ts +45 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +90 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +30 -0
- package/dist/errors.js.map +1 -0
- package/dist/gatekeeper.d.ts +78 -0
- package/dist/gatekeeper.d.ts.map +1 -0
- package/dist/gatekeeper.js +162 -0
- package/dist/gatekeeper.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +62 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +152 -0
- package/dist/keys.js.map +1 -0
- package/dist/masking.d.ts +9 -0
- package/dist/masking.d.ts.map +1 -0
- package/dist/masking.js +43 -0
- package/dist/masking.js.map +1 -0
- package/dist/policy/compile.d.ts +88 -0
- package/dist/policy/compile.d.ts.map +1 -0
- package/dist/policy/compile.js +170 -0
- package/dist/policy/compile.js.map +1 -0
- package/dist/policy/decisions.d.ts +93 -0
- package/dist/policy/decisions.d.ts.map +1 -0
- package/dist/policy/decisions.js +659 -0
- package/dist/policy/decisions.js.map +1 -0
- package/dist/policy/index.d.ts +6 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +17 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/state.d.ts +150 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +229 -0
- package/dist/state.js.map +1 -0
- package/dist/webhook/delivery.d.ts +102 -0
- package/dist/webhook/delivery.d.ts.map +1 -0
- package/dist/webhook/delivery.js +162 -0
- package/dist/webhook/delivery.js.map +1 -0
- package/dist/webhook/signature.d.ts +17 -0
- package/dist/webhook/signature.d.ts.map +1 -0
- package/dist/webhook/signature.js +73 -0
- package/dist/webhook/signature.js.map +1 -0
- package/dist/worker.d.ts +21 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +155 -0
- package/dist/worker.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// The composition root: everything a request needs, assembled from the environment once.
|
|
2
|
+
//
|
|
3
|
+
// It also holds the flows that are neither pure policy nor pure transport: enrolling this
|
|
4
|
+
// Gatekeeper as an outbound webhook endpoint, taking delivery of one, and retiring an actor.
|
|
5
|
+
//
|
|
6
|
+
// The POLICY arrives as an argument, never as an import. This package is the base a deployment
|
|
7
|
+
// installs; the policy is the one thing that deployment writes, so a `policy.config.ts` this
|
|
8
|
+
// module reached for would be a file the base owns and the operator cannot replace without
|
|
9
|
+
// forking it.
|
|
10
|
+
import { CatFactoryClient } from '@cat-factory/sdk';
|
|
11
|
+
import { buildCapability } from './capability.js';
|
|
12
|
+
import { requireVar } from './env.js';
|
|
13
|
+
import { GatekeeperError } from './errors.js';
|
|
14
|
+
import { KeyBroker } from './keys.js';
|
|
15
|
+
import { compilePolicy, tierForActor, } from './policy/compile.js';
|
|
16
|
+
import { cardEffectOf, readDelivery, SUBSCRIBED_CARD_TYPES } from './webhook/delivery.js';
|
|
17
|
+
import { verifyDelivery } from './webhook/signature.js';
|
|
18
|
+
/** Identifies this integration in the deployment's logs, beside the SDK's own version. */
|
|
19
|
+
const USER_AGENT = 'cat-factory-gatekeeper';
|
|
20
|
+
export class Gatekeeper {
|
|
21
|
+
#env;
|
|
22
|
+
#policy;
|
|
23
|
+
#state;
|
|
24
|
+
#keys;
|
|
25
|
+
constructor(env, policy) {
|
|
26
|
+
this.#env = env;
|
|
27
|
+
this.#policy = policy;
|
|
28
|
+
const baseUrl = requireVar(env, 'CAT_FACTORY_BASE_URL');
|
|
29
|
+
const clientFor = (apiKey) => new CatFactoryClient({ baseUrl, apiKey, userAgent: USER_AGENT });
|
|
30
|
+
// One durable object per PAIRED DEPLOYMENT, named for the origin it is paired with. The name
|
|
31
|
+
// is the pairing's own identity, so pointing this Worker at a different cat-factory gets a
|
|
32
|
+
// different object rather than inheriting the previous one's cards and minted keys.
|
|
33
|
+
this.#state = env.STATE.get(env.STATE.idFromName(baseUrl));
|
|
34
|
+
this.#keys = new KeyBroker({
|
|
35
|
+
state: this.#state,
|
|
36
|
+
provisioning: clientFor(requireVar(env, 'PROVISIONING_KEY')),
|
|
37
|
+
clientFor,
|
|
38
|
+
now: () => Date.now(),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Assemble against the deployment's own policy, compiling it.
|
|
43
|
+
*
|
|
44
|
+
* Throws a `PolicyError` on a policy an operator has to fix, and a `ConfigError` on a binding
|
|
45
|
+
* they have not set. Both are refusals the request path answers as a 503 naming the cause, which
|
|
46
|
+
* is why the compile happens per assembly rather than once at module load: a Worker whose policy
|
|
47
|
+
* threw while its module evaluated serves nothing at all, not even the refusal that says why.
|
|
48
|
+
*/
|
|
49
|
+
static create(env, policy) {
|
|
50
|
+
return new Gatekeeper(env, compilePolicy(policy));
|
|
51
|
+
}
|
|
52
|
+
/** Whether the presented bearer token is this Gatekeeper's own. */
|
|
53
|
+
authorize(header) {
|
|
54
|
+
const expected = requireVar(this.#env, 'OS_SHARED_TOKEN');
|
|
55
|
+
const presented = header?.startsWith('Bearer ') === true ? header.slice('Bearer '.length) : '';
|
|
56
|
+
return timingSafeEqualStrings(presented, expected);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The capability for one actor the OS has authenticated.
|
|
60
|
+
*
|
|
61
|
+
* The tier is resolved from the Gatekeeper's OWN policy, never from anything the caller sent:
|
|
62
|
+
* an agent that could name its tier would be its own authorization.
|
|
63
|
+
*/
|
|
64
|
+
capabilityFor(actor) {
|
|
65
|
+
const tier = tierForActor(this.#policy, actor.id);
|
|
66
|
+
if (tier === null) {
|
|
67
|
+
throw new GatekeeperError('unknown_actor', `No tier is granted to '${actor.id}'. Add them to this Gatekeeper's policy under ` +
|
|
68
|
+
'grants, or set a defaultTier if this deployment means every OS user to have one.');
|
|
69
|
+
}
|
|
70
|
+
return buildCapability({ actor, tier, keys: this.#keys, state: this.#state });
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Retire an actor: revoke every key this Gatekeeper minted for them, upstream and here.
|
|
74
|
+
*
|
|
75
|
+
* Exposed on the ADMIN surface rather than on a capability, because it is the OS deployment's
|
|
76
|
+
* offboarding action and not something an agent acting AS someone should be able to do to them.
|
|
77
|
+
*/
|
|
78
|
+
async retire(actorId) {
|
|
79
|
+
return await this.#keys.revoke({ id: actorId });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Register this Worker as a named outbound webhook endpoint, idempotently.
|
|
83
|
+
*
|
|
84
|
+
* Safe to call on every cron tick and on demand: the route is keyed on the caller-chosen id, so
|
|
85
|
+
* re-asserting the registration heals a deployment whose endpoint was edited or removed without
|
|
86
|
+
* ever displacing a sibling integration's slot.
|
|
87
|
+
*/
|
|
88
|
+
async enroll() {
|
|
89
|
+
const webhookId = requireVar(this.#env, 'WEBHOOK_ID');
|
|
90
|
+
const url = new URL('/webhook', requireVar(this.#env, 'PUBLIC_URL')).toString();
|
|
91
|
+
const provisioning = new CatFactoryClient({
|
|
92
|
+
baseUrl: requireVar(this.#env, 'CAT_FACTORY_BASE_URL'),
|
|
93
|
+
apiKey: requireVar(this.#env, 'PROVISIONING_KEY'),
|
|
94
|
+
userAgent: USER_AGENT,
|
|
95
|
+
});
|
|
96
|
+
await provisioning.webhook.setNamed(webhookId, {
|
|
97
|
+
url,
|
|
98
|
+
name: 'Cloudflare OS gatekeeper',
|
|
99
|
+
secret: requireVar(this.#env, 'WEBHOOK_SECRET'),
|
|
100
|
+
enabled: true,
|
|
101
|
+
// Subscribe to exactly the card types the inbox raises something for, rather than leaving
|
|
102
|
+
// `types` unset for the platform's default tail: the defaults and this Gatekeeper's list are
|
|
103
|
+
// close but not equal, and the gap would arrive as cards that never appear (a type acted on
|
|
104
|
+
// but not subscribed) or cards nobody can do anything with (the reverse). What each type
|
|
105
|
+
// OFFERS is the card's `disposition`, so the list can carry a `notice` without the inbox
|
|
106
|
+
// presenting it as answerable.
|
|
107
|
+
types: [...SUBSCRIBED_CARD_TYPES],
|
|
108
|
+
// The lifecycle family is what lets a status Gadget close a run out without polling: it
|
|
109
|
+
// lands as a `runs_watched()` record, and a terminal event settles the run's open cards. It
|
|
110
|
+
// is opt-in per event on purpose, so name all three: a Gatekeeper that hears only
|
|
111
|
+
// `run.started` shows every run as forever in flight.
|
|
112
|
+
runEvents: ['run.started', 'run.completed', 'run.failed'],
|
|
113
|
+
});
|
|
114
|
+
return { webhookId, url };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Take delivery of one webhook POST.
|
|
118
|
+
*
|
|
119
|
+
* Order matters and is the whole security story of this method: verify the MAC over the RAW
|
|
120
|
+
* bytes FIRST, parse SECOND, and hand the dedupe and the effect to durable state as ONE call.
|
|
121
|
+
* Parsing before verifying would run this deployment's JSON decoder over unauthenticated input.
|
|
122
|
+
* Deduping in a call of its own would be worse than useless: a marker committed before the card
|
|
123
|
+
* write turns a failed write into a `duplicate` on the platform's retry, so the approval never
|
|
124
|
+
* reaches the inbox and nothing anywhere reports that it did not.
|
|
125
|
+
*/
|
|
126
|
+
async takeDelivery(request, now) {
|
|
127
|
+
const rawBody = await request.text();
|
|
128
|
+
const verdict = await verifyDelivery(request.headers, rawBody, requireVar(this.#env, 'WEBHOOK_SECRET'), now);
|
|
129
|
+
if (!verdict.ok)
|
|
130
|
+
return { handled: 'rejected', reason: verdict.reason };
|
|
131
|
+
let parsed;
|
|
132
|
+
try {
|
|
133
|
+
parsed = JSON.parse(rawBody);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { handled: 'unparseable' };
|
|
137
|
+
}
|
|
138
|
+
const delivery = readDelivery(parsed);
|
|
139
|
+
if (delivery === null)
|
|
140
|
+
return { handled: 'unparseable' };
|
|
141
|
+
const applied = await this.#state.applyDelivery(delivery.deliveryId, cardEffectOf(delivery), now);
|
|
142
|
+
if (!applied.applied)
|
|
143
|
+
return { handled: 'duplicate', deliveryId: delivery.deliveryId };
|
|
144
|
+
return { handled: 'accepted', deliveryId: delivery.deliveryId, effect: applied.effect };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Compare two secrets without an early exit on the first differing character.
|
|
149
|
+
*
|
|
150
|
+
* Lengths are compared first and that difference does leak, which is acceptable for a token the
|
|
151
|
+
* caller supplies: the alternative (hashing both to a fixed width) buys nothing here, since the
|
|
152
|
+
* expected token's length is a deployment constant an attacker cannot vary their way into.
|
|
153
|
+
*/
|
|
154
|
+
function timingSafeEqualStrings(a, b) {
|
|
155
|
+
if (a.length !== b.length)
|
|
156
|
+
return false;
|
|
157
|
+
let diff = 0;
|
|
158
|
+
for (let i = 0; i < a.length; i++)
|
|
159
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
160
|
+
return diff === 0;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=gatekeeper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gatekeeper.js","sourceRoot":"","sources":["../src/gatekeeper.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createGatekeeperWorker, type GatekeeperWorkerOptions } from './worker.js';
|
|
2
|
+
export { Gatekeeper, type DeliveryOutcome } from './gatekeeper.js';
|
|
3
|
+
export { GatekeeperState, type ApprovalCard, type DeliveryApplication, type DeliveryEffect, type MintClaim, type MintTicket, type RunState, type StoredKey, } from './state.js';
|
|
4
|
+
export { ConfigError, type GatekeeperEnv } from './env.js';
|
|
5
|
+
export { buildCapability, type CardInspection, type SessionDependencies, type TierSummary, } from './capability.js';
|
|
6
|
+
export { KeyBroker, type Actor, type KeyBrokerDependencies, type LeasedClient } from './keys.js';
|
|
7
|
+
export { answerCard, assertAnswerable, describeStale, pendingParks, type AnswerInput, type AnswerOutcome, type BindingInvoker, type DecisionListShape, type PendingPark, } from './approvals.js';
|
|
8
|
+
export { cardEffectOf, dispositionOf, PARKED_DECISION_CARD_TYPES, readDelivery, SUBSCRIBED_CARD_TYPES, type CardDisposition, type Delivery, } from './webhook/delivery.js';
|
|
9
|
+
export { DEFAULT_MAX_SKEW_MS, verifyDelivery, type VerificationResult, } from './webhook/signature.js';
|
|
10
|
+
export * from './policy/index.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAClF,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAIlE,OAAO,EACL,eAAe,EACf,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,SAAS,GACf,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAA;AAC1D,OAAO,EACL,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,SAAS,EAAE,KAAK,KAAK,EAAE,KAAK,qBAAqB,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA;AAEhG,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,WAAW,GACjB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,0BAA0B,EAC1B,YAAY,EACZ,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,QAAQ,GACd,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAA;AAI/B,cAAc,mBAAmB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// `@cat-factory/gatekeeper-worker`: the Gatekeeper machinery a deployment INSTALLS, as opposed to
|
|
2
|
+
// the policy and the wiring it WRITES (`deploy/gatekeeper` is the template for those).
|
|
3
|
+
//
|
|
4
|
+
// The split is what makes an upgrade a dependency bump. Everything here is deployment-neutral: the
|
|
5
|
+
// Cap'n Web capability surface, the per-actor key broker, the delivery receiver and its verifier,
|
|
6
|
+
// the approval inbox and the per-park answerers, the Durable Object all four keep their state in.
|
|
7
|
+
// What is NOT here is the one thing that differs per deployment, the policy, which arrives as an
|
|
8
|
+
// argument to `createGatekeeperWorker`.
|
|
9
|
+
export { createGatekeeperWorker } from './worker.js';
|
|
10
|
+
export { Gatekeeper } from './gatekeeper.js';
|
|
11
|
+
// The Durable Object class. A deployment re-exports it from its own entry module, because
|
|
12
|
+
// wrangler's `class_name` binding resolves against the Worker's exports, not this package's.
|
|
13
|
+
export { GatekeeperState, } from './state.js';
|
|
14
|
+
export { ConfigError } from './env.js';
|
|
15
|
+
export { buildCapability, } from './capability.js';
|
|
16
|
+
export { KeyBroker } from './keys.js';
|
|
17
|
+
export { answerCard, assertAnswerable, describeStale, pendingParks, } from './approvals.js';
|
|
18
|
+
export { cardEffectOf, dispositionOf, PARKED_DECISION_CARD_TYPES, readDelivery, SUBSCRIBED_CARD_TYPES, } from './webhook/delivery.js';
|
|
19
|
+
export { DEFAULT_MAX_SKEW_MS, verifyDelivery, } from './webhook/signature.js';
|
|
20
|
+
// The policy vocabulary, also reachable on its own at `@cat-factory/gatekeeper-worker/policy` for
|
|
21
|
+
// a policy file that should not have to load a Worker runtime to be read or tested.
|
|
22
|
+
export * from './policy/index.js';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,uFAAuF;AACvF,EAAE;AACF,mGAAmG;AACnG,kGAAkG;AAClG,kGAAkG;AAClG,iGAAiG;AACjG,wCAAwC;AAExC,OAAO,EAAE,sBAAsB,EAAgC,MAAM,aAAa,CAAA;AAClF,OAAO,EAAE,UAAU,EAAwB,MAAM,iBAAiB,CAAA;AAElE,0FAA0F;AAC1F,6FAA6F;AAC7F,OAAO,EACL,eAAe,GAQhB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,WAAW,EAAsB,MAAM,UAAU,CAAA;AAC1D,OAAO,EACL,eAAe,GAIhB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,SAAS,EAA6D,MAAM,WAAW,CAAA;AAEhG,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,YAAY,GAMb,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,0BAA0B,EAC1B,YAAY,EACZ,qBAAqB,GAGtB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mBAAmB,EACnB,cAAc,GAEf,MAAM,wBAAwB,CAAA;AAE/B,kGAAkG;AAClG,oFAAoF;AACpF,cAAc,mBAAmB,CAAA"}
|
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { CatFactoryClient } from '@cat-factory/sdk';
|
|
2
|
+
import type { PublicApiScope } from '@cat-factory/gatekeeper-bindings';
|
|
3
|
+
import type { GatekeeperState } from './state.js';
|
|
4
|
+
/** Who the OS says is calling. The Gatekeeper trusts this and NOTHING else the caller sends. */
|
|
5
|
+
export interface Actor {
|
|
6
|
+
/** The OS's own identity for the person. Rides `externalIdentity` onto every minted key. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Optional display name, used only to label the key for a human reading the key list. */
|
|
9
|
+
label?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface KeyBrokerDependencies {
|
|
12
|
+
/** The paired workspace's durable state, holding minted keys. */
|
|
13
|
+
state: DurableObjectStub<GatekeeperState>;
|
|
14
|
+
/** A client on the Gatekeeper's own `admin` provisioning key. */
|
|
15
|
+
provisioning: CatFactoryClient;
|
|
16
|
+
/** Build a client on a minted key. */
|
|
17
|
+
clientFor: (apiKey: string) => CatFactoryClient;
|
|
18
|
+
now: () => number;
|
|
19
|
+
/** Wait, injected so the claim-contention path is testable without real time. */
|
|
20
|
+
sleep?: (ms: number) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A leased client, paired with the key it is authenticated as.
|
|
24
|
+
*
|
|
25
|
+
* The key id travels with it because the caller needs it on failure, not on success: dropping a
|
|
26
|
+
* rejected credential has to name WHICH one it is dropping, or a concurrent call that has already
|
|
27
|
+
* minted the replacement loses it.
|
|
28
|
+
*/
|
|
29
|
+
export interface LeasedClient {
|
|
30
|
+
client: CatFactoryClient;
|
|
31
|
+
keyId: string;
|
|
32
|
+
}
|
|
33
|
+
export declare class KeyBroker {
|
|
34
|
+
#private;
|
|
35
|
+
constructor(deps: KeyBrokerDependencies);
|
|
36
|
+
/**
|
|
37
|
+
* Run one call as `actor`, at `scope`, re-minting once if the credential is rejected.
|
|
38
|
+
*
|
|
39
|
+
* The retry is deliberately narrow: ONE re-mint, on a 401 only, after dropping the row that
|
|
40
|
+
* produced it. Anything wider would turn a genuinely unusable deployment into a mint loop, and
|
|
41
|
+
* anything narrower would make the documented "rotate the provisioning key" kill switch a
|
|
42
|
+
* permanent outage for every actor whose key was cached.
|
|
43
|
+
*
|
|
44
|
+
* The scope is the TIER's, never the caller's request: a caller asking for a better key is
|
|
45
|
+
* asking the wrong side of the boundary.
|
|
46
|
+
*/
|
|
47
|
+
run<T>(actor: Actor, scope: PublicApiScope, call: (client: CatFactoryClient) => Promise<T>): Promise<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Revoke every key minted for an actor, for when the OS says they are gone.
|
|
50
|
+
*
|
|
51
|
+
* Revoked UPSTREAM FIRST, then forgotten, and only what was actually revoked is forgotten. The
|
|
52
|
+
* other order can leave a live cat-factory key that this Gatekeeper no longer knows exists,
|
|
53
|
+
* which is a credential nobody can find to revoke; this order can at worst re-revoke an id,
|
|
54
|
+
* which the platform documents as idempotent. A revoke that fails part-way leaves the rest of
|
|
55
|
+
* the rows in place, so a second call finishes the job rather than losing track of them.
|
|
56
|
+
*/
|
|
57
|
+
revoke(actor: Actor): Promise<{
|
|
58
|
+
revoked: string[];
|
|
59
|
+
remaining: string[];
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,gBAAgB,EAA+B,MAAM,kBAAkB,CAAA;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAa,MAAM,YAAY,CAAA;AAE5D,gGAAgG;AAChG,MAAM,WAAW,KAAK;IACpB,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAA;IACV,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAWD,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,KAAK,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAA;IACzC,iEAAiE;IACjE,YAAY,EAAE,gBAAgB,CAAA;IAC9B,sCAAsC;IACtC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,gBAAgB,CAAA;IAC/C,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,iFAAiF;IACjF,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,qBAAa,SAAS;;IAIpB,YAAY,IAAI,EAAE,qBAAqB,EAGtC;IAED;;;;;;;;;;OAUG;IACG,GAAG,CAAC,CAAC,EACT,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,CAAC,CAwBZ;IAED;;;;;;;;OAQG;IACG,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAgB9E;CAiDF"}
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Per-actor credentials: the reason an OS user's run is attributable at all.
|
|
2
|
+
//
|
|
3
|
+
// A Gatekeeper could forward every call on one shared workspace key, and every run would then be
|
|
4
|
+
// started by "the integration". That loses the two things the platform builds on the caller's
|
|
5
|
+
// identity: `externalIdentity` mapping a run back to a person without cat-factory holding your
|
|
6
|
+
// directory (slice 3), and role-scoped merge policy, which refuses a landing a shared credential
|
|
7
|
+
// should not make (ADR 0037/0039). So each actor gets their OWN key, minted at the tier's scope
|
|
8
|
+
// and stamped with their identity.
|
|
9
|
+
//
|
|
10
|
+
// Minting is CACHED, not per call. `POST /api/v1/keys` returns the raw secret exactly once, so a
|
|
11
|
+
// Gatekeeper that re-mints per call both spends a key row per operation and leaves a growing set
|
|
12
|
+
// of live credentials it has already forgotten how to revoke.
|
|
13
|
+
//
|
|
14
|
+
// Two things follow from "the mint is a real, non-idempotent side effect that returns its secret
|
|
15
|
+
// once", and both are the difference between a cache and a leak:
|
|
16
|
+
//
|
|
17
|
+
// - IT IS CLAIMED BEFORE IT RUNS. A read-then-mint-then-write has two pipelined calls for one
|
|
18
|
+
// actor both seeing "no key yet"; both mint, the second write wins, and the loser's key stays
|
|
19
|
+
// live upstream with nothing here recording that it exists. The claim is taken atomically in
|
|
20
|
+
// the Durable Object and expires, so a Worker that dies mid-mint does not wedge the actor out.
|
|
21
|
+
// - A REJECTED KEY IS DROPPED, NOT RETRIED FOREVER. The documented kill switch is revoking the
|
|
22
|
+
// provisioning key, which revokes every key it minted; a cache with no invalidation answers
|
|
23
|
+
// every call after that with the same dead secret and a 401 nobody can clear short of wiping
|
|
24
|
+
// the object. A 401 drops the row and re-mints ONCE, so a rotation heals itself and a genuinely
|
|
25
|
+
// unusable credential still fails rather than looping.
|
|
26
|
+
import { CatFactoryClient, CatFactoryUnauthorizedError } from '@cat-factory/sdk';
|
|
27
|
+
import { GatekeeperError } from './errors.js';
|
|
28
|
+
/** The label cap the key contract enforces. */
|
|
29
|
+
const MAX_LABEL_LENGTH = 120;
|
|
30
|
+
/** How many times a caller re-reads a claim another call is holding before giving up. */
|
|
31
|
+
const MINT_CLAIM_POLLS = 20;
|
|
32
|
+
/** How long between those reads. Short: the claim is held across one `POST /api/v1/keys`. */
|
|
33
|
+
const MINT_CLAIM_POLL_MS = 100;
|
|
34
|
+
export class KeyBroker {
|
|
35
|
+
#deps;
|
|
36
|
+
#sleep;
|
|
37
|
+
constructor(deps) {
|
|
38
|
+
this.#deps = deps;
|
|
39
|
+
this.#sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run one call as `actor`, at `scope`, re-minting once if the credential is rejected.
|
|
43
|
+
*
|
|
44
|
+
* The retry is deliberately narrow: ONE re-mint, on a 401 only, after dropping the row that
|
|
45
|
+
* produced it. Anything wider would turn a genuinely unusable deployment into a mint loop, and
|
|
46
|
+
* anything narrower would make the documented "rotate the provisioning key" kill switch a
|
|
47
|
+
* permanent outage for every actor whose key was cached.
|
|
48
|
+
*
|
|
49
|
+
* The scope is the TIER's, never the caller's request: a caller asking for a better key is
|
|
50
|
+
* asking the wrong side of the boundary.
|
|
51
|
+
*/
|
|
52
|
+
async run(actor, scope, call) {
|
|
53
|
+
const leased = await this.#lease(actor, scope);
|
|
54
|
+
try {
|
|
55
|
+
return await call(leased.client);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (!(error instanceof CatFactoryUnauthorizedError))
|
|
59
|
+
throw error;
|
|
60
|
+
const dropped = await this.#deps.state.forgetKeyIfCurrent(actor.id, scope, leased.keyId);
|
|
61
|
+
if (!dropped) {
|
|
62
|
+
// Another call already replaced it, so the credential in hand is simply stale. Retrying on
|
|
63
|
+
// the replacement is the same one-shot, not a second one.
|
|
64
|
+
return await call((await this.#lease(actor, scope)).client);
|
|
65
|
+
}
|
|
66
|
+
const fresh = await this.#lease(actor, scope);
|
|
67
|
+
try {
|
|
68
|
+
return await call(fresh.client);
|
|
69
|
+
}
|
|
70
|
+
catch (retryError) {
|
|
71
|
+
if (!(retryError instanceof CatFactoryUnauthorizedError))
|
|
72
|
+
throw retryError;
|
|
73
|
+
throw new GatekeeperError('credential_rejected', `A freshly minted '${scope}' key for '${actor.id}' was refused by the deployment. The ` +
|
|
74
|
+
'provisioning key is the likely cause: check it is still live and still `admin`-scoped.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Revoke every key minted for an actor, for when the OS says they are gone.
|
|
80
|
+
*
|
|
81
|
+
* Revoked UPSTREAM FIRST, then forgotten, and only what was actually revoked is forgotten. The
|
|
82
|
+
* other order can leave a live cat-factory key that this Gatekeeper no longer knows exists,
|
|
83
|
+
* which is a credential nobody can find to revoke; this order can at worst re-revoke an id,
|
|
84
|
+
* which the platform documents as idempotent. A revoke that fails part-way leaves the rest of
|
|
85
|
+
* the rows in place, so a second call finishes the job rather than losing track of them.
|
|
86
|
+
*/
|
|
87
|
+
async revoke(actor) {
|
|
88
|
+
const stored = await this.#deps.state.listKeys(actor.id);
|
|
89
|
+
const revoked = [];
|
|
90
|
+
const revokedScopes = [];
|
|
91
|
+
for (const { scope, key } of stored) {
|
|
92
|
+
await this.#deps.provisioning.keys.revoke(key.keyId);
|
|
93
|
+
revoked.push(key.keyId);
|
|
94
|
+
revokedScopes.push(scope);
|
|
95
|
+
}
|
|
96
|
+
await this.#deps.state.forgetKeys(actor.id, revokedScopes);
|
|
97
|
+
return {
|
|
98
|
+
revoked,
|
|
99
|
+
remaining: stored
|
|
100
|
+
.filter(({ scope }) => !revokedScopes.includes(scope))
|
|
101
|
+
.map(({ key }) => key.keyId),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async #lease(actor, scope) {
|
|
105
|
+
for (let attempt = 0; attempt <= MINT_CLAIM_POLLS; attempt++) {
|
|
106
|
+
const ticket = await this.#deps.state.claimKeyMint(actor.id, scope, this.#deps.now());
|
|
107
|
+
if (ticket.outcome === 'existing') {
|
|
108
|
+
return { client: this.#deps.clientFor(ticket.key.secret), keyId: ticket.key.keyId };
|
|
109
|
+
}
|
|
110
|
+
if (ticket.outcome === 'claimed')
|
|
111
|
+
return await this.#mint(actor, scope);
|
|
112
|
+
await this.#sleep(Math.min(MINT_CLAIM_POLL_MS, ticket.retryAfterMs));
|
|
113
|
+
}
|
|
114
|
+
throw new GatekeeperError('credential_rejected', `Minting a '${scope}' key for '${actor.id}' is still in flight on another request after ` +
|
|
115
|
+
`${MINT_CLAIM_POLLS * MINT_CLAIM_POLL_MS}ms. Retry; if it persists, the deployment's ` +
|
|
116
|
+
'`POST /api/v1/keys` is failing in a way that leaves the claim to expire.');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Mint under a held claim.
|
|
120
|
+
*
|
|
121
|
+
* The claim is RELEASED on failure rather than left to expire: a deployment that is briefly
|
|
122
|
+
* unreachable should not lock the actor out for the claim's whole TTL, and the release is safe
|
|
123
|
+
* precisely because nothing was minted.
|
|
124
|
+
*/
|
|
125
|
+
async #mint(actor, scope) {
|
|
126
|
+
let created;
|
|
127
|
+
try {
|
|
128
|
+
created = await this.#deps.provisioning.keys.create({
|
|
129
|
+
label: keyLabel(actor, scope),
|
|
130
|
+
scope: scope,
|
|
131
|
+
externalIdentity: actor.id,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
await this.#deps.state.releaseKeyMint(actor.id, scope);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
const key = {
|
|
139
|
+
keyId: created.key.id,
|
|
140
|
+
secret: created.secret,
|
|
141
|
+
mintedAt: this.#deps.now(),
|
|
142
|
+
};
|
|
143
|
+
// Stored BEFORE the client is handed out: the secret came back exactly once, so losing it
|
|
144
|
+
// between here and the caller would leave a live credential nobody can revoke.
|
|
145
|
+
await this.#deps.state.commitKeyMint(actor.id, scope, key);
|
|
146
|
+
return { client: this.#deps.clientFor(key.secret), keyId: key.keyId };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function keyLabel(actor, scope) {
|
|
150
|
+
return `gatekeeper ${scope}: ${actor.label ?? actor.id}`.slice(0, MAX_LABEL_LENGTH);
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** What a masked leaf becomes. Stated, never removed. */
|
|
2
|
+
export declare const MASKED = "[masked by gatekeeper policy]";
|
|
3
|
+
/**
|
|
4
|
+
* Apply every masked path to one result, returning a copy. The input is never mutated: the same
|
|
5
|
+
* decoded body may be handed to more than one consumer (a card derivation and the caller's
|
|
6
|
+
* result), and a mask applied in place would silently redact the other one too.
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyMask(value: unknown, paths: readonly string[]): unknown;
|
|
9
|
+
//# sourceMappingURL=masking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masking.d.ts","sourceRoot":"","sources":["../src/masking.ts"],"names":[],"mappings":"AAYA,yDAAyD;AACzD,eAAO,MAAM,MAAM,kCAAkC,CAAA;AAiBrD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQ3E"}
|
package/dist/masking.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Field masking: dropping a value from a result before it reaches the caller.
|
|
2
|
+
//
|
|
3
|
+
// The one rule worth stating is that masking REPLACES rather than deletes. A removed key and a
|
|
4
|
+
// key the deployment had no value for read identically to the agent consuming the result, and
|
|
5
|
+
// they are different facts: "your policy hides this" is a question for an operator, while "the
|
|
6
|
+
// platform said null" is an answer about the run. So a masked leaf becomes the sentinel below,
|
|
7
|
+
// which says which of the two happened, in the shape every other value already has.
|
|
8
|
+
//
|
|
9
|
+
// Paths are dotted and traverse arrays element-wise (`steps.status` masks the status of every
|
|
10
|
+
// step). A path that matches nothing is not an error: a result shape legitimately varies by
|
|
11
|
+
// operation, and a policy naming a field some responses do not carry is not a misconfiguration.
|
|
12
|
+
/** What a masked leaf becomes. Stated, never removed. */
|
|
13
|
+
export const MASKED = '[masked by gatekeeper policy]';
|
|
14
|
+
function maskIn(value, segments) {
|
|
15
|
+
const [head, ...rest] = segments;
|
|
16
|
+
if (head === undefined)
|
|
17
|
+
return MASKED;
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value.map((element) => maskIn(element, segments));
|
|
20
|
+
}
|
|
21
|
+
if (value === null || typeof value !== 'object')
|
|
22
|
+
return value;
|
|
23
|
+
const source = value;
|
|
24
|
+
if (!(head in source))
|
|
25
|
+
return value;
|
|
26
|
+
return { ...source, [head]: maskIn(source[head], rest) };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Apply every masked path to one result, returning a copy. The input is never mutated: the same
|
|
30
|
+
* decoded body may be handed to more than one consumer (a card derivation and the caller's
|
|
31
|
+
* result), and a mask applied in place would silently redact the other one too.
|
|
32
|
+
*/
|
|
33
|
+
export function applyMask(value, paths) {
|
|
34
|
+
let masked = value;
|
|
35
|
+
for (const path of paths) {
|
|
36
|
+
const segments = path.split('.').filter((segment) => segment.length > 0);
|
|
37
|
+
if (segments.length === 0)
|
|
38
|
+
continue;
|
|
39
|
+
masked = maskIn(masked, segments);
|
|
40
|
+
}
|
|
41
|
+
return masked;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=masking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masking.js","sourceRoot":"","sources":["../src/masking.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,EAAE;AACF,+FAA+F;AAC/F,8FAA8F;AAC9F,+FAA+F;AAC/F,+FAA+F;AAC/F,oFAAoF;AACpF,EAAE;AACF,8FAA8F;AAC9F,4FAA4F;AAC5F,gGAAgG;AAEhG,yDAAyD;AACzD,MAAM,CAAC,MAAM,MAAM,GAAG,+BAA+B,CAAA;AAErD,SAAS,MAAM,CAAC,KAAc,EAAE,QAA2B;IACzD,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAA;IAChC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IAErC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;IAC1D,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE7D,MAAM,MAAM,GAAG,KAAgC,CAAA;IAC/C,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IAEnC,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,CAAA;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc,EAAE,KAAwB;IAChE,IAAI,MAAM,GAAG,KAAK,CAAA;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACxE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QACnC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IACnC,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type GatekeeperBinding, type PublicApiScope } from '@cat-factory/gatekeeper-bindings';
|
|
2
|
+
/**
|
|
3
|
+
* What one tier of caller may do.
|
|
4
|
+
*
|
|
5
|
+
* `keyScope` is the scope of the cat-factory key the Gatekeeper mints for callers at this tier,
|
|
6
|
+
* and it is also the ceiling on what the tier can be granted. The two being ONE value is what
|
|
7
|
+
* keeps the credential and the capability from disagreeing.
|
|
8
|
+
*/
|
|
9
|
+
export interface TierPolicy {
|
|
10
|
+
/** Prose an OS deployment shows beside the tier. */
|
|
11
|
+
description: string;
|
|
12
|
+
/** The scope of the per-actor key this tier's calls are made with. */
|
|
13
|
+
keyScope: PublicApiScope;
|
|
14
|
+
/** Binding names to grant, or `'*'` for every binding within `keyScope`. */
|
|
15
|
+
allow: readonly string[] | '*';
|
|
16
|
+
/** Binding names to subtract from `allow`. Applied last, so a deny always wins. */
|
|
17
|
+
deny?: readonly string[];
|
|
18
|
+
/**
|
|
19
|
+
* Dotted paths masked out of every result before it reaches the caller, e.g.
|
|
20
|
+
* `run.pullRequestUrl`. Masking is REDACTION, not omission: see `masking.ts`.
|
|
21
|
+
*/
|
|
22
|
+
mask?: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
/** The whole policy: the tiers, and which OS actor gets which. */
|
|
25
|
+
export interface GatekeeperPolicy {
|
|
26
|
+
/** The tier an actor with no explicit grant is given. Name `null` to refuse unknown actors. */
|
|
27
|
+
defaultTier: string | null;
|
|
28
|
+
tiers: Record<string, TierPolicy>;
|
|
29
|
+
/** OS user identity (whatever the OS authenticates) to tier name. */
|
|
30
|
+
grants: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
/** Why a binding the deployment serves is not on a capability. */
|
|
33
|
+
export type WithheldReason = 'not_in_policy' | 'denied_by_policy' | 'above_key_scope' | 'not_relayable';
|
|
34
|
+
/** One binding the tier does not carry, and why. */
|
|
35
|
+
export interface WithheldBinding {
|
|
36
|
+
name: string;
|
|
37
|
+
reason: WithheldReason;
|
|
38
|
+
detail: string;
|
|
39
|
+
}
|
|
40
|
+
/** A tier, resolved against the live operation table. */
|
|
41
|
+
export interface CompiledTier {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
keyScope: PublicApiScope;
|
|
45
|
+
granted: readonly GatekeeperBinding[];
|
|
46
|
+
/**
|
|
47
|
+
* Every binding this tier does NOT carry, with the reason. Published on the capability
|
|
48
|
+
* (`withheld()`) because an agent that cannot tell "policy withheld this" from "the deployment
|
|
49
|
+
* does not have it" will report the wrong one to whoever has to fix it.
|
|
50
|
+
*/
|
|
51
|
+
withheld: readonly WithheldBinding[];
|
|
52
|
+
mask: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
/** The policy, resolved once and read per request. */
|
|
55
|
+
export interface CompiledPolicy {
|
|
56
|
+
defaultTier: string | null;
|
|
57
|
+
tiers: ReadonlyMap<string, CompiledTier>;
|
|
58
|
+
grants: ReadonlyMap<string, string>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Compile a policy against the live operation table, or throw a {@link PolicyError} naming the
|
|
62
|
+
* first thing an operator has to fix.
|
|
63
|
+
*/
|
|
64
|
+
export declare function compilePolicy(policy: GatekeeperPolicy): CompiledPolicy;
|
|
65
|
+
/**
|
|
66
|
+
* The tier an actor holds, or a refusal.
|
|
67
|
+
*
|
|
68
|
+
* Note what is NOT here: nothing the CALLER sends picks a tier. The OS authenticates the person
|
|
69
|
+
* and the Gatekeeper resolves what that person may do, so a compromised or confused agent cannot
|
|
70
|
+
* ask for a better one.
|
|
71
|
+
*/
|
|
72
|
+
export declare function tierForActor(policy: CompiledPolicy, actorId: string): CompiledTier | null;
|
|
73
|
+
/**
|
|
74
|
+
* What the OS needs to run its own approval governance over a call: the consequence the platform
|
|
75
|
+
* annotates, with the cautious default already applied.
|
|
76
|
+
*/
|
|
77
|
+
export declare function describeBinding(binding: GatekeeperBinding): {
|
|
78
|
+
name: string;
|
|
79
|
+
summary: string;
|
|
80
|
+
minScope: PublicApiScope;
|
|
81
|
+
readOnly: boolean;
|
|
82
|
+
destructive: boolean;
|
|
83
|
+
idempotent: boolean;
|
|
84
|
+
pathParams: readonly string[];
|
|
85
|
+
queryParams: readonly string[];
|
|
86
|
+
hasBody: boolean;
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/policy/compile.ts"],"names":[],"mappings":"AAaA,OAAO,EAIL,KAAK,iBAAiB,EACtB,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,CAqBtE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAIzF;AAED;;;GAGG;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,MAAM,EAAE,CAAA;IAC9B,OAAO,EAAE,OAAO,CAAA;CACjB,CAaA"}
|