@kelpie/server 0.5.0 → 0.5.2

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/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAItD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAQ3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAEhE;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;IACpD,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAA;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAA;IAC5C,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,MAAM,CAAA;IACzC,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAA;CACvD;AAED,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE;QACT,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAqBD,wBAAgB,SAAS,CAAC,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,CAyI1E"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAItD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAS3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAEhE;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;IACpD,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAA;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAA;IAC5C,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,MAAM,CAAA;IACzC,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAA;CACvD;AAED,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE;QACT,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAqBD,wBAAgB,SAAS,CAAC,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,CAwJ1E"}
package/dist/app.js CHANGED
@@ -8,6 +8,7 @@ import { MCP_INSTRUCTIONS, MCP_ROUTE_PREFIX, MCP_SERVER_INFO } from './modules/m
8
8
  import { createMcpEndpoint } from './modules/mcp/router.js';
9
9
  import { createAuthAndApiRateLimitMiddleware, createFormSubmitRateLimitMiddleware, } from './modules/rate-limit/middleware.js';
10
10
  import { createIdempotencyMiddleware } from './modules/workspace/idempotencyMiddleware.js';
11
+ import { createWorkspaceAccessMiddleware } from './modules/workspace/workspaceAccessMiddleware.js';
11
12
  const REQUEST_ID_HEADER = 'X-Request-Id';
12
13
  /**
13
14
  * Any origin, because a public endpoint exists to be called from a customer's
@@ -58,6 +59,15 @@ export function createApp(dependencies) {
58
59
  // itself; `createFormSubmitRateLimitMiddleware` below covers it, mounted
59
60
  // where the public CORS middleware requires.
60
61
  app.use('/v1/*', createAuthAndApiRateLimitMiddleware(rateLimitDependencies));
62
+ // Ahead of idempotency too, for the same reason: a request this blocks
63
+ // must never reserve a key it will not be allowed to spend. Inert without
64
+ // a module granting or denying `workspace.access` (`capabilities.ts`).
65
+ const workspaceAccessMiddleware = createWorkspaceAccessMiddleware({
66
+ db: dependencies.credentials.db,
67
+ now: dependencies.credentials.now,
68
+ entitlements: dependencies.contributions.entitlements,
69
+ });
70
+ app.use('/v1/*', workspaceAccessMiddleware);
61
71
  // Every module's `POST` gets this the same way, decided once here rather than
62
72
  // per route (`api.md`). It skips `/v1/public/*` itself — a public request has
63
73
  // no `Actor` to scope a key to — so it is mounted ahead of the public CORS
@@ -97,6 +107,9 @@ export function createApp(dependencies) {
97
107
  // comes through. No CORS layer to mind the order of here, unlike the forms
98
108
  // budget — the transport checks its own `Origin` inside the handler.
99
109
  app.use(MCP_ROUTE_PREFIX, createAuthAndApiRateLimitMiddleware(rateLimitDependencies));
110
+ // MCP mirrors `/v1` one-to-one, so a workspace this gate blocks must not
111
+ // still be reachable through agent tools.
112
+ app.use(MCP_ROUTE_PREFIX, workspaceAccessMiddleware);
100
113
  app.route(MCP_ROUTE_PREFIX, mcp.transport);
101
114
  app.route('/v1', mcp.catalog);
102
115
  // Module declarations on the app itself, outside /v1 (`runtime/module.ts`).
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { LimitCapability } from '../../runtime/entitlements.ts';
1
+ import type { FlagCapability, LimitCapability } from '../../runtime/entitlements.ts';
2
2
  /**
3
3
  * Capabilities the workspace module declares.
4
4
  *
@@ -6,4 +6,13 @@ import type { LimitCapability } from '../../runtime/entitlements.ts';
6
6
  * these constants rather than the string, so a typo is a compile error.
7
7
  */
8
8
  export declare const SEATS_LIMIT: LimitCapability;
9
+ /**
10
+ * Whether a workspace's own members may use it at all. Checked once per
11
+ * request by a blanket `/v1` and `/mcp` middleware (`app.ts`), not by any
12
+ * service — this is deliberately coarser than every other capability here.
13
+ * Granted by default: no provider answers it in a self-hosted assembly, so
14
+ * `EntitlementRegistry`'s open-source default (`entitlements.ts`) applies
15
+ * and nothing changes for anyone without a module that supplies one.
16
+ */
17
+ export declare const WORKSPACE_ACCESS: FlagCapability;
9
18
  //# sourceMappingURL=capabilities.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAEpE;;;;;GAKG;AAEH,eAAO,MAAM,WAAW,EAAE,eAIzB,CAAA"}
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAEpF;;;;;GAKG;AAEH,eAAO,MAAM,WAAW,EAAE,eAIzB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAI9B,CAAA"}
@@ -9,4 +9,17 @@ export const SEATS_LIMIT = {
9
9
  kind: 'limit',
10
10
  description: 'How many people can belong to one workspace. Unlimited in open source.',
11
11
  };
12
+ /**
13
+ * Whether a workspace's own members may use it at all. Checked once per
14
+ * request by a blanket `/v1` and `/mcp` middleware (`app.ts`), not by any
15
+ * service — this is deliberately coarser than every other capability here.
16
+ * Granted by default: no provider answers it in a self-hosted assembly, so
17
+ * `EntitlementRegistry`'s open-source default (`entitlements.ts`) applies
18
+ * and nothing changes for anyone without a module that supplies one.
19
+ */
20
+ export const WORKSPACE_ACCESS = {
21
+ name: 'workspace.access',
22
+ kind: 'flag',
23
+ description: 'Whether this workspace may be used at all. A deployment-wide override, not a plan feature.',
24
+ };
12
25
  //# sourceMappingURL=capabilities.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../../src/modules/workspace/capabilities.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAoB;IAC1C,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,OAAO;IACb,WAAW,EAAE,wEAAwE;CACtF,CAAA"}
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../../src/modules/workspace/capabilities.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAoB;IAC1C,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,OAAO;IACb,WAAW,EAAE,wEAAwE;CACtF,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,4FAA4F;CAC1G,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAS3D;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,mBAAmB,EAAE,MAAM,GAAG,YAAY,CAmD/E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAS3D;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,mBAAmB,EAAE,MAAM,GAAG,YAAY,CAoD/E"}
@@ -1,6 +1,6 @@
1
1
  import { appUrlConfigSchema } from '../../lib/appUrl.js';
2
2
  import { parseModuleCapability } from '../../runtime/moduleConfig.js';
3
- import { SEATS_LIMIT } from './capabilities.js';
3
+ import { SEATS_LIMIT, WORKSPACE_ACCESS } from './capabilities.js';
4
4
  import * as repository from './repository.js';
5
5
  import { mountWorkspaceRoutes } from './routes.js';
6
6
  import * as schema from './schema.js';
@@ -20,6 +20,7 @@ export function createWorkspaceModule(migrationsDirectory) {
20
20
  register(context) {
21
21
  const config = context.config(appUrlConfigSchema);
22
22
  context.entitlements.declare(SEATS_LIMIT);
23
+ context.entitlements.declare(WORKSPACE_ACCESS);
23
24
  // Answers `module.<id>` for whatever a config override left undecided
24
25
  // (`runtime/registry.ts` registers that provider first, ahead of this
25
26
  // one, so a locked value never reaches this query).
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/workspace/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,mBAA2B;IAC/D,OAAO;QACL,EAAE,EAAE,WAAW;QACf,QAAQ,EAAE,CAAC,MAAM,CAAC;QAClB,UAAU,EAAE,IAAI;QAEhB,QAAQ,CAAC,OAAO;YACd,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;YAEjD,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;YAEzC,sEAAsE;YACtE,sEAAsE;YACtE,oDAAoD;YACpD,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE;gBAC7D,MAAM,QAAQ,GAAG,qBAAqB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;gBAEvD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,OAAO,SAAS,CAAA;gBAClB,CAAC;gBAED,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAA;gBAErF,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAA;YACvF,CAAC,CAAC,CAAA;YAEF,MAAM,OAAO,GAAG,sBAAsB,CAAC;gBACrC,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,UAAU,EAAE,MAAM,CAAC,YAAY;gBAC/B,mBAAmB,EAAE,OAAO,CAAC,aAAa;qBACvC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;qBACpC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,qBAAqB,EAAE,OAAO,CAAC,YAAY;aAC5C,CAAC,CAAA;YAEF,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;YAE3C,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;gBACxB,oBAAoB,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;YAC7E,CAAC,CAAC,CAAA;YAEF,sBAAsB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;YAE5C,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;QAC1B,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/workspace/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AACrE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACjE,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,mBAA2B;IAC/D,OAAO;QACL,EAAE,EAAE,WAAW;QACf,QAAQ,EAAE,CAAC,MAAM,CAAC;QAClB,UAAU,EAAE,IAAI;QAEhB,QAAQ,CAAC,OAAO;YACd,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;YAEjD,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;YACzC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;YAE9C,sEAAsE;YACtE,sEAAsE;YACtE,oDAAoD;YACpD,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE;gBAC7D,MAAM,QAAQ,GAAG,qBAAqB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;gBAEvD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,OAAO,SAAS,CAAA;gBAClB,CAAC;gBAED,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAA;gBAErF,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAA;YACvF,CAAC,CAAC,CAAA;YAEF,MAAM,OAAO,GAAG,sBAAsB,CAAC;gBACrC,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,UAAU,EAAE,MAAM,CAAC,YAAY;gBAC/B,mBAAmB,EAAE,OAAO,CAAC,aAAa;qBACvC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;qBACpC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,qBAAqB,EAAE,OAAO,CAAC,YAAY;aAC5C,CAAC,CAAA;YAEF,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;YAE3C,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;gBACxB,oBAAoB,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;YAC7E,CAAC,CAAC,CAAA;YAEF,sBAAsB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;YAE5C,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;QAC1B,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ import type { EntitlementRegistry } from '../../runtime/entitlements.ts';
3
+ import type { CredentialDependencies } from '../auth/credentials.ts';
4
+ export interface WorkspaceAccessMiddlewareDependencies extends CredentialDependencies {
5
+ readonly entitlements: EntitlementRegistry;
6
+ }
7
+ export declare function createWorkspaceAccessMiddleware(dependencies: WorkspaceAccessMiddlewareDependencies): MiddlewareHandler;
8
+ //# sourceMappingURL=workspaceAccessMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspaceAccessMiddleware.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/workspaceAccessMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAGtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAGxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAqCpE,MAAM,WAAW,qCAAsC,SAAQ,sBAAsB;IACnF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAA;CAC3C;AAED,wBAAgB,+BAA+B,CAC7C,YAAY,EAAE,qCAAqC,GAClD,iBAAiB,CAmCnB"}
@@ -0,0 +1,66 @@
1
+ import { AppError } from '../../lib/errors.js';
2
+ import { actorWorkspaceId } from '../auth/actor.js';
3
+ import { resolveActorFrom } from '../auth/credentials.js';
4
+ import { WORKSPACE_ACCESS } from './capabilities.js';
5
+ /**
6
+ * `POST /v1/*` and every `/mcp` call carry a fixed workspace for the whole
7
+ * request (`api.md`): a session or key resolves to one `Actor.workspaceId`,
8
+ * and every query it makes is scoped to that workspace server-side. This
9
+ * blocks the request outright when that workspace's `workspace.access`
10
+ * entitlement is not granted — inert in a self-hosted assembly, since
11
+ * nothing registers a `GrantProvider` that answers it there
12
+ * (`capabilities.ts`).
13
+ *
14
+ * Mounted the same way as `createIdempotencyMiddleware`: tolerates the
15
+ * no-credential case, since an unauthenticated `/v1/auth/*` call has
16
+ * nothing to check, and ahead of idempotency in `app.ts` so a blocked
17
+ * request never reserves a key it will not be allowed to spend.
18
+ *
19
+ * Exempts `/v1/auth/*` and `/v1/account*`: they manage the *account*, not
20
+ * the workspace, and a member of a workspace this gate has locked out
21
+ * still needs to sign in, sign out, and see their own account to have any
22
+ * chance of understanding why. There is no `/v1` endpoint to switch a
23
+ * session to a different workspace membership — a session is bound to one
24
+ * at login — so there is nothing further to exempt for that case.
25
+ *
26
+ * Also exempts `DELETE /v1/workspaces/:id` itself, and only that exact
27
+ * path: a suspension must never trap an owner into a workspace they can no
28
+ * longer read or write but also cannot leave by deleting. Every other verb
29
+ * and every sub-resource under `/v1/workspaces/:id` (members, invites,
30
+ * modules) stays gated.
31
+ */
32
+ const WORKSPACE_DELETE_PATH = /^\/v1\/workspaces\/[^/]+$/u;
33
+ function isExempt(method, path) {
34
+ return path.startsWith('/v1/auth/') || path.startsWith('/v1/account') ||
35
+ (method === 'DELETE' && WORKSPACE_DELETE_PATH.test(path));
36
+ }
37
+ export function createWorkspaceAccessMiddleware(dependencies) {
38
+ return async (context, next) => {
39
+ if (isExempt(context.req.method, context.req.path)) {
40
+ await next();
41
+ return;
42
+ }
43
+ const actor = await resolveActorFrom(dependencies, context).catch((error) => {
44
+ if (error instanceof AppError && error.code === 'unauthorized') {
45
+ // No credential to check a workspace against. The handler this
46
+ // reaches answers its own 401; there is nothing this gate can add.
47
+ return undefined;
48
+ }
49
+ throw error;
50
+ });
51
+ const workspaceId = actor === undefined ? null : actorWorkspaceId(actor);
52
+ // Between signup and a first workspace there is nothing to gate
53
+ // (`POST /v1/workspaces`, `POST /v1/invites/accept`): the same case
54
+ // `createIdempotencyMiddleware` lets through unscoped.
55
+ if (workspaceId === null) {
56
+ await next();
57
+ return;
58
+ }
59
+ const entitlement = await dependencies.entitlements.check(workspaceId, WORKSPACE_ACCESS.name);
60
+ if (entitlement.kind === 'flag' && !entitlement.granted) {
61
+ throw new AppError('entitlement_required', 'This workspace does not have access');
62
+ }
63
+ await next();
64
+ };
65
+ }
66
+ //# sourceMappingURL=workspaceAccessMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspaceAccessMiddleware.js","sourceRoot":"","sources":["../../../src/modules/workspace/workspaceAccessMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,qBAAqB,GAAG,4BAA4B,CAAA;AAE1D,SAAS,QAAQ,CAAC,MAAc,EAAE,IAAY;IAC5C,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QACnE,CAAC,MAAM,KAAK,QAAQ,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;AAC7D,CAAC;AAMD,MAAM,UAAU,+BAA+B,CAC7C,YAAmD;IAEnD,OAAO,KAAK,EAAE,OAAgB,EAAE,IAAI,EAAE,EAAE;QACtC,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,EAAE,CAAA;YACZ,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACnF,IAAI,KAAK,YAAY,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAC/D,+DAA+D;gBAC/D,mEAAmE;gBACnE,OAAO,SAAS,CAAA;YAClB,CAAC;YAED,MAAM,KAAK,CAAA;QACb,CAAC,CAAC,CAAA;QAEF,MAAM,WAAW,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAA;QAExE,gEAAgE;QAChE,oEAAoE;QACpE,uDAAuD;QACvD,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,EAAE,CAAA;YACZ,OAAM;QACR,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,KAAK,CAAC,WAAW,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAA;QAE7F,IAAI,WAAW,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACxD,MAAM,IAAI,QAAQ,CAAC,sBAAsB,EAAE,qCAAqC,CAAC,CAAA;QACnF,CAAC;QAED,MAAM,IAAI,EAAE,CAAA;IACd,CAAC,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kelpie/server",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "The Kelpie service as a library: module runtime, core CRM modules, REST API, MCP surface, and the shared migration pipeline.",
5
5
  "keywords": [
6
6
  "kelpie",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@hono/node-server": "^2.0.12",
55
- "@kelpie/schemas": "^0.5.0",
55
+ "@kelpie/schemas": "^0.5.2",
56
56
  "@node-rs/argon2": "^2.0.2",
57
57
  "drizzle-orm": "^0.45.2",
58
58
  "hono": "^4.12.32",
package/src/app.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  createFormSubmitRateLimitMiddleware,
19
19
  } from './modules/rate-limit/middleware.ts'
20
20
  import { createIdempotencyMiddleware } from './modules/workspace/idempotencyMiddleware.ts'
21
+ import { createWorkspaceAccessMiddleware } from './modules/workspace/workspaceAccessMiddleware.ts'
21
22
  import type { ModuleContributions } from './runtime/registry.ts'
22
23
 
23
24
  /**
@@ -120,6 +121,17 @@ export function createApp(dependencies: AppDependencies): Hono<AppBindings> {
120
121
  // where the public CORS middleware requires.
121
122
  app.use('/v1/*', createAuthAndApiRateLimitMiddleware(rateLimitDependencies))
122
123
 
124
+ // Ahead of idempotency too, for the same reason: a request this blocks
125
+ // must never reserve a key it will not be allowed to spend. Inert without
126
+ // a module granting or denying `workspace.access` (`capabilities.ts`).
127
+ const workspaceAccessMiddleware = createWorkspaceAccessMiddleware({
128
+ db: dependencies.credentials.db,
129
+ now: dependencies.credentials.now,
130
+ entitlements: dependencies.contributions.entitlements,
131
+ })
132
+
133
+ app.use('/v1/*', workspaceAccessMiddleware)
134
+
123
135
  // Every module's `POST` gets this the same way, decided once here rather than
124
136
  // per route (`api.md`). It skips `/v1/public/*` itself — a public request has
125
137
  // no `Actor` to scope a key to — so it is mounted ahead of the public CORS
@@ -169,6 +181,10 @@ export function createApp(dependencies: AppDependencies): Hono<AppBindings> {
169
181
  // budget — the transport checks its own `Origin` inside the handler.
170
182
  app.use(MCP_ROUTE_PREFIX, createAuthAndApiRateLimitMiddleware(rateLimitDependencies))
171
183
 
184
+ // MCP mirrors `/v1` one-to-one, so a workspace this gate blocks must not
185
+ // still be reachable through agent tools.
186
+ app.use(MCP_ROUTE_PREFIX, workspaceAccessMiddleware)
187
+
172
188
  app.route(MCP_ROUTE_PREFIX, mcp.transport)
173
189
  app.route('/v1', mcp.catalog)
174
190
 
@@ -1,4 +1,4 @@
1
- import type { LimitCapability } from '../../runtime/entitlements.ts'
1
+ import type { FlagCapability, LimitCapability } from '../../runtime/entitlements.ts'
2
2
 
3
3
  /**
4
4
  * Capabilities the workspace module declares.
@@ -12,3 +12,17 @@ export const SEATS_LIMIT: LimitCapability = {
12
12
  kind: 'limit',
13
13
  description: 'How many people can belong to one workspace. Unlimited in open source.',
14
14
  }
15
+
16
+ /**
17
+ * Whether a workspace's own members may use it at all. Checked once per
18
+ * request by a blanket `/v1` and `/mcp` middleware (`app.ts`), not by any
19
+ * service — this is deliberately coarser than every other capability here.
20
+ * Granted by default: no provider answers it in a self-hosted assembly, so
21
+ * `EntitlementRegistry`'s open-source default (`entitlements.ts`) applies
22
+ * and nothing changes for anyone without a module that supplies one.
23
+ */
24
+ export const WORKSPACE_ACCESS: FlagCapability = {
25
+ name: 'workspace.access',
26
+ kind: 'flag',
27
+ description: 'Whether this workspace may be used at all. A deployment-wide override, not a plan feature.',
28
+ }
@@ -1,7 +1,7 @@
1
1
  import { appUrlConfigSchema } from '../../lib/appUrl.ts'
2
2
  import type { KelpieModule } from '../../runtime/module.ts'
3
3
  import { parseModuleCapability } from '../../runtime/moduleConfig.ts'
4
- import { SEATS_LIMIT } from './capabilities.ts'
4
+ import { SEATS_LIMIT, WORKSPACE_ACCESS } from './capabilities.ts'
5
5
  import * as repository from './repository.ts'
6
6
  import { mountWorkspaceRoutes } from './routes.ts'
7
7
  import * as schema from './schema.ts'
@@ -24,6 +24,7 @@ export function createWorkspaceModule(migrationsDirectory: string): KelpieModule
24
24
  const config = context.config(appUrlConfigSchema)
25
25
 
26
26
  context.entitlements.declare(SEATS_LIMIT)
27
+ context.entitlements.declare(WORKSPACE_ACCESS)
27
28
 
28
29
  // Answers `module.<id>` for whatever a config override left undecided
29
30
  // (`runtime/registry.ts` registers that provider first, ahead of this
@@ -0,0 +1,85 @@
1
+ import type { Context, MiddlewareHandler } from 'hono'
2
+
3
+ import { AppError } from '../../lib/errors.ts'
4
+ import type { EntitlementRegistry } from '../../runtime/entitlements.ts'
5
+ import { actorWorkspaceId } from '../auth/actor.ts'
6
+ import { resolveActorFrom } from '../auth/credentials.ts'
7
+ import type { CredentialDependencies } from '../auth/credentials.ts'
8
+ import { WORKSPACE_ACCESS } from './capabilities.ts'
9
+
10
+ /**
11
+ * `POST /v1/*` and every `/mcp` call carry a fixed workspace for the whole
12
+ * request (`api.md`): a session or key resolves to one `Actor.workspaceId`,
13
+ * and every query it makes is scoped to that workspace server-side. This
14
+ * blocks the request outright when that workspace's `workspace.access`
15
+ * entitlement is not granted — inert in a self-hosted assembly, since
16
+ * nothing registers a `GrantProvider` that answers it there
17
+ * (`capabilities.ts`).
18
+ *
19
+ * Mounted the same way as `createIdempotencyMiddleware`: tolerates the
20
+ * no-credential case, since an unauthenticated `/v1/auth/*` call has
21
+ * nothing to check, and ahead of idempotency in `app.ts` so a blocked
22
+ * request never reserves a key it will not be allowed to spend.
23
+ *
24
+ * Exempts `/v1/auth/*` and `/v1/account*`: they manage the *account*, not
25
+ * the workspace, and a member of a workspace this gate has locked out
26
+ * still needs to sign in, sign out, and see their own account to have any
27
+ * chance of understanding why. There is no `/v1` endpoint to switch a
28
+ * session to a different workspace membership — a session is bound to one
29
+ * at login — so there is nothing further to exempt for that case.
30
+ *
31
+ * Also exempts `DELETE /v1/workspaces/:id` itself, and only that exact
32
+ * path: a suspension must never trap an owner into a workspace they can no
33
+ * longer read or write but also cannot leave by deleting. Every other verb
34
+ * and every sub-resource under `/v1/workspaces/:id` (members, invites,
35
+ * modules) stays gated.
36
+ */
37
+ const WORKSPACE_DELETE_PATH = /^\/v1\/workspaces\/[^/]+$/u
38
+
39
+ function isExempt(method: string, path: string): boolean {
40
+ return path.startsWith('/v1/auth/') || path.startsWith('/v1/account') ||
41
+ (method === 'DELETE' && WORKSPACE_DELETE_PATH.test(path))
42
+ }
43
+
44
+ export interface WorkspaceAccessMiddlewareDependencies extends CredentialDependencies {
45
+ readonly entitlements: EntitlementRegistry
46
+ }
47
+
48
+ export function createWorkspaceAccessMiddleware(
49
+ dependencies: WorkspaceAccessMiddlewareDependencies,
50
+ ): MiddlewareHandler {
51
+ return async (context: Context, next) => {
52
+ if (isExempt(context.req.method, context.req.path)) {
53
+ await next()
54
+ return
55
+ }
56
+
57
+ const actor = await resolveActorFrom(dependencies, context).catch((error: unknown) => {
58
+ if (error instanceof AppError && error.code === 'unauthorized') {
59
+ // No credential to check a workspace against. The handler this
60
+ // reaches answers its own 401; there is nothing this gate can add.
61
+ return undefined
62
+ }
63
+
64
+ throw error
65
+ })
66
+
67
+ const workspaceId = actor === undefined ? null : actorWorkspaceId(actor)
68
+
69
+ // Between signup and a first workspace there is nothing to gate
70
+ // (`POST /v1/workspaces`, `POST /v1/invites/accept`): the same case
71
+ // `createIdempotencyMiddleware` lets through unscoped.
72
+ if (workspaceId === null) {
73
+ await next()
74
+ return
75
+ }
76
+
77
+ const entitlement = await dependencies.entitlements.check(workspaceId, WORKSPACE_ACCESS.name)
78
+
79
+ if (entitlement.kind === 'flag' && !entitlement.granted) {
80
+ throw new AppError('entitlement_required', 'This workspace does not have access')
81
+ }
82
+
83
+ await next()
84
+ }
85
+ }