@cat-factory/gatekeeper-bindings 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/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/bindings.generated.d.ts +68 -0
- package/dist/bindings.generated.d.ts.map +1 -0
- package/dist/bindings.generated.js +1552 -0
- package/dist/bindings.generated.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Igor Savin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @cat-factory/gatekeeper-bindings
|
|
2
|
+
|
|
3
|
+
## What it is
|
|
4
|
+
|
|
5
|
+
A policy-annotated table of every cat-factory **public API** (`/api/v1`) operation, plus the
|
|
6
|
+
scope-ladder helpers a policy layer ranks keys and classifies calls with. Like
|
|
7
|
+
[`sdk/mcp`](https://github.com/kibertoad/cat-factory/tree/main/sdk/mcp), this is **not a fifth
|
|
8
|
+
client**: it is the same operations the four SDKs expose, projected as a table and generated from
|
|
9
|
+
the same OpenAPI spec (`pnpm gen:sdk`), so it cannot drift from the surface it meters. The thunks
|
|
10
|
+
call [`@cat-factory/sdk`](https://www.npmjs.com/package/@cat-factory/sdk) and re-implement none of
|
|
11
|
+
its behaviour.
|
|
12
|
+
|
|
13
|
+
It is the data half of the cat-factory **Gatekeeper** family, three pieces taken two different
|
|
14
|
+
ways:
|
|
15
|
+
|
|
16
|
+
| Piece | What it is | How you take it |
|
|
17
|
+
| ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|
18
|
+
| `@cat-factory/gatekeeper-bindings` (this package) | the generated operation table and the scope helpers | install, to build your own policy layer |
|
|
19
|
+
| [`@cat-factory/gatekeeper-worker`](https://www.npmjs.com/package/@cat-factory/gatekeeper-worker) | the full Gatekeeper Worker machinery: Cap'n Web capability surface, key broker, webhook receiver, approvals | install, and write only a policy |
|
|
20
|
+
| [`deploy/gatekeeper`](https://github.com/kibertoad/cat-factory/tree/main/deploy/gatekeeper) | the deployment template: a policy file, wrangler bindings, three lines of wiring | copy, and edit `src/policy.config.ts` |
|
|
21
|
+
|
|
22
|
+
## Purpose and goal
|
|
23
|
+
|
|
24
|
+
For building **credential-holding front-ends**: services that hold the cat-factory API key
|
|
25
|
+
themselves and meter what their own callers may do. The motivating consumer is a
|
|
26
|
+
[Cloudflare OS](https://github.com/cloudflare/cloudflare-os) Gatekeeper Worker (design record: the
|
|
27
|
+
[initiative tracker](https://github.com/kibertoad/cat-factory/blob/main/docs/initiatives/cloudflare-os-gatekeeper.md)),
|
|
28
|
+
but nothing here is Cloudflare-specific: any proxy, bot or governance layer that fronts a
|
|
29
|
+
cat-factory key can use it.
|
|
30
|
+
|
|
31
|
+
The goal is that such a layer enforces policy against the operations the deployment actually
|
|
32
|
+
serves, at the scope floors it actually enforces, never against a hand-curated list that drifts.
|
|
33
|
+
The floors come from the spec's `x-min-scope`, which the server generates from the same contract
|
|
34
|
+
field its controllers enforce, so the declared scope and the enforced scope are one value.
|
|
35
|
+
|
|
36
|
+
## What each binding carries
|
|
37
|
+
|
|
38
|
+
One `GatekeeperBinding` per `/api/v1` operation:
|
|
39
|
+
|
|
40
|
+
- `name` (`tasks_create`): the policy spelling, identical to the MCP facade's tool name.
|
|
41
|
+
- `minScope`: the key-scope floor the deployment enforces for the route (`read` / `write` /
|
|
42
|
+
`decide` / `admin`), read off the spec's `x-min-scope`. This is the STATIC floor: a run-starting
|
|
43
|
+
operation can still be refused `pipeline_requires_decide_scope` at request time when the named
|
|
44
|
+
pipeline can park on a human.
|
|
45
|
+
- `readOnly`, `consequence`: what a front-end needs to decide which calls get waved through,
|
|
46
|
+
which get logged, and which need a human. `consequence` is present only where the stakes are
|
|
47
|
+
real money or a merged pull request, exactly as in the MCP tool table, so read it through
|
|
48
|
+
`resolveConsequence(binding)` rather than directly: an unannotated mutation is cautiously
|
|
49
|
+
destructive, and `binding.consequence?.destructive` would answer `false` for it.
|
|
50
|
+
- `result`: how `invoke`'s answer comes back (`value`, SSE `stream`, or `binary` bytes), so a
|
|
51
|
+
JSON-relay front-end can route or withhold the non-value operations, stating which.
|
|
52
|
+
- `pathParams` / `queryParams` / `hasBody` and `invoke(client, args)`: enough to expose the
|
|
53
|
+
whole surface dynamically without hand-writing a wrapper per operation. The argument
|
|
54
|
+
convention is the MCP facade's (path params and query keys at the top level, body under
|
|
55
|
+
`body`; unknown keys are dropped, never forwarded).
|
|
56
|
+
|
|
57
|
+
## The helpers
|
|
58
|
+
|
|
59
|
+
Hand-written beside the table (`src/index.ts`), because every consumer needs them and deriving
|
|
60
|
+
them independently is how one gets them backwards:
|
|
61
|
+
|
|
62
|
+
- `scopeSatisfies(have, need)`: whether a key of scope `have` clears a floor of `need`. The
|
|
63
|
+
ladder is inclusive: every rung can do everything below it.
|
|
64
|
+
- `bindingsWithinScope(scope)`: every operation a key of that scope can call.
|
|
65
|
+
- `bindingByName(name)`: one operation by its policy spelling; `undefined` for a name the surface
|
|
66
|
+
does not have, so a misspelled or retired name is a condition the caller reports rather than a
|
|
67
|
+
thrown surprise.
|
|
68
|
+
- `resolveConsequence(binding)`: the consequence annotation with the cautious default applied (an
|
|
69
|
+
unannotated mutation counts as destructive and non-idempotent).
|
|
70
|
+
- `PUBLIC_API_SCOPE_LADDER`: the scope ranking itself, emitted from the spec's
|
|
71
|
+
`x-public-api-scopes`.
|
|
72
|
+
|
|
73
|
+
The ladder helpers **throw on a scope they do not carry** rather than ranking it below `read`:
|
|
74
|
+
a deployment one release ahead of this package must read as version skew, never as a key with no
|
|
75
|
+
permissions.
|
|
76
|
+
|
|
77
|
+
## How to use it
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { CatFactoryClient } from '@cat-factory/sdk'
|
|
81
|
+
import {
|
|
82
|
+
bindingByName,
|
|
83
|
+
bindingsWithinScope,
|
|
84
|
+
resolveConsequence,
|
|
85
|
+
scopeSatisfies,
|
|
86
|
+
} from '@cat-factory/gatekeeper-bindings'
|
|
87
|
+
|
|
88
|
+
const client = new CatFactoryClient({ baseUrl: process.env.BASE_URL!, apiKey: process.env.KEY! })
|
|
89
|
+
|
|
90
|
+
// Expose to this caller only what a `write` key can do, minus anything destructive. Read the
|
|
91
|
+
// consequence through the helper: most mutations carry no annotation, and the unannotated ones
|
|
92
|
+
// are the cautious case, not the safe one.
|
|
93
|
+
const exposed = bindingsWithinScope('write').filter((b) => !resolveConsequence(b).destructive)
|
|
94
|
+
|
|
95
|
+
// Forward a metered call.
|
|
96
|
+
const binding = bindingByName('tasks_create')!
|
|
97
|
+
if (!scopeSatisfies('write', binding.minScope)) throw new Error('caller tier too low')
|
|
98
|
+
const result = await binding.invoke(client, {
|
|
99
|
+
serviceId: 'svc_123',
|
|
100
|
+
body: { title: 'Fix the flaky login test', description: '...' },
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Two routes are deliberately absent from the table because they have no honest operation shape:
|
|
105
|
+
`GET /api/v1/openapi.json` (spec discovery) and `ALL /api/v1/mcp` (the hosted MCP endpoint). Both
|
|
106
|
+
gate at `read` on the server.
|
|
107
|
+
|
|
108
|
+
## Configuration and customization
|
|
109
|
+
|
|
110
|
+
Nothing in this package is configured or customized: it is generated data plus pure helpers, and
|
|
111
|
+
the policy built on top of it belongs to the consumer (see `@cat-factory/gatekeeper-worker` for a
|
|
112
|
+
ready-made one). `src/bindings.generated.ts` is GENERATED (`pnpm gen:sdk`); change the route
|
|
113
|
+
contracts, never the file.
|
|
114
|
+
|
|
115
|
+
## References
|
|
116
|
+
|
|
117
|
+
- [`backend/docs/public-api.md`](https://github.com/kibertoad/cat-factory/blob/main/backend/docs/public-api.md):
|
|
118
|
+
the full API reference (keys, scopes, endpoint semantics).
|
|
119
|
+
- [`sdk/README.md`](https://github.com/kibertoad/cat-factory/blob/main/sdk/README.md): the client
|
|
120
|
+
family and the generation chain this table rides.
|
|
121
|
+
- [`@cat-factory/gatekeeper-worker`](https://www.npmjs.com/package/@cat-factory/gatekeeper-worker):
|
|
122
|
+
the Worker machinery that compiles deployment policy against this table.
|
|
123
|
+
- [The initiative tracker](https://github.com/kibertoad/cat-factory/blob/main/docs/initiatives/cloudflare-os-gatekeeper.md):
|
|
124
|
+
design record, decisions and gotchas.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { CatFactoryClient } from '@cat-factory/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* A public-API key scope. The ladder is INCLUSIVE and ordered
|
|
4
|
+
* (read < write < decide < admin): a key satisfies every floor at or below its own rung. See
|
|
5
|
+
* `backend/docs/public-api.md`, "Pick the right scope".
|
|
6
|
+
*/
|
|
7
|
+
export type PublicApiScope = 'read' | 'write' | 'decide' | 'admin';
|
|
8
|
+
/**
|
|
9
|
+
* The scope ladder, least to greatest. The ARRAY ORDER is the ranking, and this is the
|
|
10
|
+
* deployment's own vocabulary: generated from the spec's `x-public-api-scopes`, which the
|
|
11
|
+
* server stamps from the same constant its admission check ranks with.
|
|
12
|
+
*/
|
|
13
|
+
export declare const PUBLIC_API_SCOPE_LADDER: readonly PublicApiScope[];
|
|
14
|
+
/** One `/api/v1` operation, as a policy-annotated capability. */
|
|
15
|
+
export interface GatekeeperBinding {
|
|
16
|
+
/** The name a policy file addresses, `<group>_<method>`: the MCP tool's own spelling. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** The SDK resource group, spelled as the TypeScript client mounts it (`client.taskTypes`). */
|
|
19
|
+
group: string;
|
|
20
|
+
/** The method on that resource client. */
|
|
21
|
+
method: string;
|
|
22
|
+
/** The spec's `operationId`, for correlating a forwarded call with a deployment's logs. */
|
|
23
|
+
operationId: string;
|
|
24
|
+
httpMethod: string;
|
|
25
|
+
/** The route, with `{param}` placeholders. */
|
|
26
|
+
path: string;
|
|
27
|
+
/** One-line label from the spec's `summary`. */
|
|
28
|
+
summary: string;
|
|
29
|
+
/**
|
|
30
|
+
* The least key scope the route admits: the STATIC floor the deployment enforces, and only
|
|
31
|
+
* that: a run-starting operation can still be refused `pipeline_requires_decide_scope` at
|
|
32
|
+
* request time when the named pipeline can park on a human.
|
|
33
|
+
*/
|
|
34
|
+
minScope: PublicApiScope;
|
|
35
|
+
/** Whether the call changes nothing (a GET). */
|
|
36
|
+
readOnly: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* How `invoke`'s result comes back: a decoded `value`, an SSE `stream` reader, or raw
|
|
39
|
+
* `binary` bytes. A front-end that only relays JSON should route `stream`/`binary`
|
|
40
|
+
* bindings specially or withhold them, stating which.
|
|
41
|
+
*/
|
|
42
|
+
result: 'value' | 'stream' | 'binary';
|
|
43
|
+
/**
|
|
44
|
+
* The annotations the HTTP method cannot supply, present only where the consequence is real
|
|
45
|
+
* money or a merged pull request (shared with the MCP table's hints). Absent means the cautious
|
|
46
|
+
* reading applies: treat an unannotated mutation as destructive and non-idempotent.
|
|
47
|
+
*/
|
|
48
|
+
consequence?: {
|
|
49
|
+
destructive: boolean;
|
|
50
|
+
idempotent: boolean;
|
|
51
|
+
};
|
|
52
|
+
/** Path parameter names `invoke` reads from `args`, in path order. */
|
|
53
|
+
pathParams: readonly string[];
|
|
54
|
+
/** Query parameter names `invoke` forwards from `args`; anything else is dropped. */
|
|
55
|
+
queryParams: readonly string[];
|
|
56
|
+
/** Whether `invoke` reads `args.body` as the request body. */
|
|
57
|
+
hasBody: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Forward a call to the SDK: path params and query keys at the top level, body under `body`.
|
|
60
|
+
*
|
|
61
|
+
* Every failure is a REJECTION, the SDK's own and a missing-argument `TypeError` alike, so one
|
|
62
|
+
* `.catch()` (or one `try` around an `await`) covers a forwarded call.
|
|
63
|
+
*/
|
|
64
|
+
invoke: (client: CatFactoryClient, args: Record<string, unknown>) => Promise<unknown>;
|
|
65
|
+
}
|
|
66
|
+
/** Every `/api/v1` operation as a policy-annotated binding, in resource-group order. */
|
|
67
|
+
export declare const GATEKEEPER_BINDINGS: readonly GatekeeperBinding[];
|
|
68
|
+
//# sourceMappingURL=bindings.generated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindings.generated.d.ts","sourceRoot":"","sources":["../src/bindings.generated.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,SAAS,cAAc,EAAyC,CAAA;AAEtG,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAA;IACZ,+FAA+F;IAC/F,KAAK,EAAE,MAAM,CAAA;IACb,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd,2FAA2F;IAC3F,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAA;IACZ,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,QAAQ,EAAE,cAAc,CAAA;IACxB,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAA;IAC3D,sEAAsE;IACtE,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,qFAAqF;IACrF,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;OAKG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACtF;AAkCD,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,EAAE,SAAS,iBAAiB,EA0+C3D,CAAA"}
|