@zauso-ai/capstan-core 0.2.0 → 1.0.0-beta.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/api.d.ts +8 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +10 -0
- package/dist/api.js.map +1 -1
- package/dist/approval-routes.d.ts +21 -0
- package/dist/approval-routes.d.ts.map +1 -0
- package/dist/approval-routes.js +112 -0
- package/dist/approval-routes.js.map +1 -0
- package/dist/csrf.d.ts +18 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/csrf.js +80 -0
- package/dist/csrf.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +14 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +81 -0
- package/dist/logger.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +7 -97
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -16,4 +16,12 @@ export declare function defineAPI<TInput = unknown, TOutput = unknown>(def: APID
|
|
|
16
16
|
* Primarily consumed by `createCapstanApp` when building route metadata.
|
|
17
17
|
*/
|
|
18
18
|
export declare function getAPIRegistry(): ReadonlyArray<APIDefinition>;
|
|
19
|
+
/**
|
|
20
|
+
* Clear all entries from the global API registry.
|
|
21
|
+
*
|
|
22
|
+
* This should be called at the start of `createCapstanApp` or between test
|
|
23
|
+
* cases to prevent stale definitions from leaking across app instances,
|
|
24
|
+
* hot-reload cycles, or test runs.
|
|
25
|
+
*/
|
|
26
|
+
export declare function clearAPIRegistry(): void;
|
|
19
27
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAGd,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EAC3D,GAAG,EAAE,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAiChC;AAQD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,aAAa,CAAC,aAAa,CAAC,CAE7D"}
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAGd,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EAC3D,GAAG,EAAE,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAiChC;AAQD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,aAAa,CAAC,aAAa,CAAC,CAE7D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
package/dist/api.js
CHANGED
|
@@ -46,4 +46,14 @@ const apiRegistry = [];
|
|
|
46
46
|
export function getAPIRegistry() {
|
|
47
47
|
return apiRegistry;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Clear all entries from the global API registry.
|
|
51
|
+
*
|
|
52
|
+
* This should be called at the start of `createCapstanApp` or between test
|
|
53
|
+
* cases to prevent stale definitions from leaking across app instances,
|
|
54
|
+
* hot-reload cycles, or test runs.
|
|
55
|
+
*/
|
|
56
|
+
export function clearAPIRegistry() {
|
|
57
|
+
apiRegistry.length = 0;
|
|
58
|
+
}
|
|
49
59
|
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CACvB,GAAmC;IAEnC,MAAM,cAAc,GAAG,KAAK,EAC1B,IAA6B,EACX,EAAE;QACpB,yEAAyE;QACzE,IAAI,cAAc,GAAW,IAAI,CAAC,KAAK,CAAC;QACxC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACd,cAAc,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAW,CAAC;QACzD,CAAC;QAED,yEAAyE;QACzE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC;YAC/B,KAAK,EAAE,cAAc;YACrB,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC,CAAC;QAEH,yEAAyE;QACzE,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAY,CAAC;QAC7C,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,OAAO,GAAmC;QAC9C,GAAG,GAAG;QACN,OAAO,EAAE,cAAc;KACxB,CAAC;IAEF,8BAA8B;IAC9B,WAAW,CAAC,IAAI,CAAC,OAAwB,CAAC,CAAC;IAE3C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAE9E,MAAM,WAAW,GAAoB,EAAE,CAAC;AAExC;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CACvB,GAAmC;IAEnC,MAAM,cAAc,GAAG,KAAK,EAC1B,IAA6B,EACX,EAAE;QACpB,yEAAyE;QACzE,IAAI,cAAc,GAAW,IAAI,CAAC,KAAK,CAAC;QACxC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACd,cAAc,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAW,CAAC;QACzD,CAAC;QAED,yEAAyE;QACzE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC;YAC/B,KAAK,EAAE,cAAc;YACrB,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC,CAAC;QAEH,yEAAyE;QACzE,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAY,CAAC;QAC7C,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,OAAO,GAAmC;QAC9C,GAAG,GAAG;QACN,OAAO,EAAE,cAAc;KACxB,CAAC;IAEF,8BAA8B;IAC9B,WAAW,CAAC,IAAI,CAAC,OAAwB,CAAC,CAAC;IAE3C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAE9E,MAAM,WAAW,GAAoB,EAAE,CAAC;AAExC;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB;IAC9B,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Hono } from "hono";
|
|
2
|
+
import type { CapstanContext } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handler registry type shared between the production and dev servers.
|
|
5
|
+
* Maps "METHOD /path" keys to functions that re-execute a route handler
|
|
6
|
+
* with the stored approval input.
|
|
7
|
+
*/
|
|
8
|
+
export type HandlerRegistry = Map<string, (input: unknown, ctx: CapstanContext) => Promise<unknown>>;
|
|
9
|
+
/**
|
|
10
|
+
* Mount the approval management endpoints on a Hono app.
|
|
11
|
+
*
|
|
12
|
+
* These routes allow agents and humans to list, approve, and deny pending
|
|
13
|
+
* approvals created by policy enforcement. The `handlerRegistry` is used
|
|
14
|
+
* by the "approve" endpoint to re-execute the original handler once an
|
|
15
|
+
* approval has been granted.
|
|
16
|
+
*
|
|
17
|
+
* Both `createCapstanApp` (production) and the dev server call this
|
|
18
|
+
* function so the logic lives in one place.
|
|
19
|
+
*/
|
|
20
|
+
export declare function mountApprovalRoutes(app: Hono<any>, handlerRegistry: HandlerRegistry): void;
|
|
21
|
+
//# sourceMappingURL=approval-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-routes.d.ts","sourceRoot":"","sources":["../src/approval-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAA0B,MAAM,MAAM,CAAC;AAOzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,GAAG,CAC/B,MAAM,EACN,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAC1D,CAAC;AAEF;;;;;;;;;;GAUG;AAEH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,eAAe,EAAE,eAAe,GAAG,IAAI,CAqH1F"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createContext } from "./context.js";
|
|
2
|
+
import { getApproval, listApprovals, resolveApproval, } from "./approval.js";
|
|
3
|
+
/**
|
|
4
|
+
* Mount the approval management endpoints on a Hono app.
|
|
5
|
+
*
|
|
6
|
+
* These routes allow agents and humans to list, approve, and deny pending
|
|
7
|
+
* approvals created by policy enforcement. The `handlerRegistry` is used
|
|
8
|
+
* by the "approve" endpoint to re-execute the original handler once an
|
|
9
|
+
* approval has been granted.
|
|
10
|
+
*
|
|
11
|
+
* Both `createCapstanApp` (production) and the dev server call this
|
|
12
|
+
* function so the logic lives in one place.
|
|
13
|
+
*/
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
export function mountApprovalRoutes(app, handlerRegistry) {
|
|
16
|
+
/** List all approvals, optionally filtered by ?status=pending|approved|denied */
|
|
17
|
+
app.get("/capstan/approvals", (c) => {
|
|
18
|
+
const statusParam = new URL(c.req.url).searchParams.get("status");
|
|
19
|
+
const items = listApprovals(statusParam ?? undefined);
|
|
20
|
+
return c.json({ approvals: items });
|
|
21
|
+
});
|
|
22
|
+
/** Get a single approval by ID */
|
|
23
|
+
app.get("/capstan/approvals/:id", (c) => {
|
|
24
|
+
const id = c.req.param("id");
|
|
25
|
+
const approval = getApproval(id);
|
|
26
|
+
if (!approval) {
|
|
27
|
+
return c.json({ error: "Approval not found" }, 404);
|
|
28
|
+
}
|
|
29
|
+
return c.json(approval);
|
|
30
|
+
});
|
|
31
|
+
/** Approve a pending approval -- re-executes the original handler */
|
|
32
|
+
app.post("/capstan/approvals/:id/approve", async (c) => {
|
|
33
|
+
const id = c.req.param("id");
|
|
34
|
+
const existing = getApproval(id);
|
|
35
|
+
if (!existing) {
|
|
36
|
+
return c.json({ error: "Approval not found" }, 404);
|
|
37
|
+
}
|
|
38
|
+
if (existing.status !== "pending") {
|
|
39
|
+
return c.json({ error: "Approval already resolved", status: existing.status }, 409);
|
|
40
|
+
}
|
|
41
|
+
// Parse optional body for resolvedBy
|
|
42
|
+
let resolvedBy;
|
|
43
|
+
try {
|
|
44
|
+
const body = await c.req.json();
|
|
45
|
+
if (typeof body.resolvedBy === "string") {
|
|
46
|
+
resolvedBy = body.resolvedBy;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// No body or invalid JSON -- that's fine.
|
|
51
|
+
}
|
|
52
|
+
const approval = resolveApproval(id, "approved", resolvedBy);
|
|
53
|
+
if (!approval) {
|
|
54
|
+
return c.json({ error: "Approval not found" }, 404);
|
|
55
|
+
}
|
|
56
|
+
// Re-execute the original handler with the stored input.
|
|
57
|
+
const routeKey = `${approval.method} ${approval.path}`;
|
|
58
|
+
const handler = handlerRegistry.get(routeKey);
|
|
59
|
+
if (!handler) {
|
|
60
|
+
return c.json({ error: "Handler not found for route", route: routeKey }, 500);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const ctx = createContext(c);
|
|
64
|
+
const result = await handler(approval.input, ctx);
|
|
65
|
+
approval.result = result;
|
|
66
|
+
return c.json({
|
|
67
|
+
status: "approved",
|
|
68
|
+
approvalId: approval.id,
|
|
69
|
+
result,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const message = err instanceof Error ? err.message : "Handler execution failed";
|
|
74
|
+
return c.json({ error: message, approvalId: approval.id }, 500);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
/** Deny a pending approval */
|
|
78
|
+
app.post("/capstan/approvals/:id/deny", async (c) => {
|
|
79
|
+
const id = c.req.param("id");
|
|
80
|
+
const existing = getApproval(id);
|
|
81
|
+
if (!existing) {
|
|
82
|
+
return c.json({ error: "Approval not found" }, 404);
|
|
83
|
+
}
|
|
84
|
+
if (existing.status !== "pending") {
|
|
85
|
+
return c.json({ error: "Approval already resolved", status: existing.status }, 409);
|
|
86
|
+
}
|
|
87
|
+
let resolvedBy;
|
|
88
|
+
let reason;
|
|
89
|
+
try {
|
|
90
|
+
const body = await c.req.json();
|
|
91
|
+
if (typeof body.resolvedBy === "string") {
|
|
92
|
+
resolvedBy = body.resolvedBy;
|
|
93
|
+
}
|
|
94
|
+
if (typeof body.reason === "string") {
|
|
95
|
+
reason = body.reason;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// No body or invalid JSON -- that's fine.
|
|
100
|
+
}
|
|
101
|
+
const approval = resolveApproval(id, "denied", resolvedBy);
|
|
102
|
+
if (!approval) {
|
|
103
|
+
return c.json({ error: "Approval not found" }, 404);
|
|
104
|
+
}
|
|
105
|
+
return c.json({
|
|
106
|
+
status: "denied",
|
|
107
|
+
approvalId: approval.id,
|
|
108
|
+
...(reason !== undefined ? { reason } : {}),
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=approval-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-routes.js","sourceRoot":"","sources":["../src/approval-routes.ts"],"names":[],"mappings":"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"}
|
package/dist/csrf.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Context, Next } from "hono";
|
|
2
|
+
/**
|
|
3
|
+
* Create a Hono middleware that enforces Double Submit Cookie CSRF protection.
|
|
4
|
+
*
|
|
5
|
+
* **GET / HEAD / OPTIONS** requests:
|
|
6
|
+
* - A fresh CSRF token is generated, set as the `__csrf` cookie
|
|
7
|
+
* (`HttpOnly=false` so client-side JS can read it), and echoed in the
|
|
8
|
+
* `X-CSRF-Token` response header.
|
|
9
|
+
*
|
|
10
|
+
* **POST / PUT / DELETE / PATCH** requests:
|
|
11
|
+
* - The middleware verifies that the `X-CSRF-Token` request header matches
|
|
12
|
+
* the `__csrf` cookie. On mismatch (or either value missing) the request
|
|
13
|
+
* is rejected with a `403` response.
|
|
14
|
+
* - Requests that carry a `Bearer` token in the `Authorization` header are
|
|
15
|
+
* **exempt** from CSRF checks because they are not cookie-based.
|
|
16
|
+
*/
|
|
17
|
+
export declare function csrfProtection(): (c: Context, next: Next) => Promise<Response | void>;
|
|
18
|
+
//# sourceMappingURL=csrf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAoC1C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,KACd,GAAG,OAAO,EAAE,MAAM,IAAI,KAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA4ChE"}
|
package/dist/csrf.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// CSRF Protection Middleware (Double Submit Cookie)
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
const CSRF_COOKIE_NAME = "__csrf";
|
|
6
|
+
const CSRF_HEADER_NAME = "X-CSRF-Token";
|
|
7
|
+
const STATE_CHANGING_METHODS = new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
8
|
+
/**
|
|
9
|
+
* Generate a cryptographically random CSRF token (32 hex characters).
|
|
10
|
+
*/
|
|
11
|
+
function generateCsrfToken() {
|
|
12
|
+
return randomBytes(16).toString("hex");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse cookies from a `Cookie` header string and return the value for the
|
|
16
|
+
* given cookie name, or `undefined` if not found.
|
|
17
|
+
*/
|
|
18
|
+
function getCookieValue(cookieHeader, name) {
|
|
19
|
+
if (!cookieHeader)
|
|
20
|
+
return undefined;
|
|
21
|
+
for (const pair of cookieHeader.split(";")) {
|
|
22
|
+
const eqIndex = pair.indexOf("=");
|
|
23
|
+
if (eqIndex === -1)
|
|
24
|
+
continue;
|
|
25
|
+
const key = pair.slice(0, eqIndex).trim();
|
|
26
|
+
const value = pair.slice(eqIndex + 1).trim();
|
|
27
|
+
if (key === name)
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a Hono middleware that enforces Double Submit Cookie CSRF protection.
|
|
34
|
+
*
|
|
35
|
+
* **GET / HEAD / OPTIONS** requests:
|
|
36
|
+
* - A fresh CSRF token is generated, set as the `__csrf` cookie
|
|
37
|
+
* (`HttpOnly=false` so client-side JS can read it), and echoed in the
|
|
38
|
+
* `X-CSRF-Token` response header.
|
|
39
|
+
*
|
|
40
|
+
* **POST / PUT / DELETE / PATCH** requests:
|
|
41
|
+
* - The middleware verifies that the `X-CSRF-Token` request header matches
|
|
42
|
+
* the `__csrf` cookie. On mismatch (or either value missing) the request
|
|
43
|
+
* is rejected with a `403` response.
|
|
44
|
+
* - Requests that carry a `Bearer` token in the `Authorization` header are
|
|
45
|
+
* **exempt** from CSRF checks because they are not cookie-based.
|
|
46
|
+
*/
|
|
47
|
+
export function csrfProtection() {
|
|
48
|
+
return async (c, next) => {
|
|
49
|
+
const method = c.req.method.toUpperCase();
|
|
50
|
+
// Requests authenticated via Bearer token skip CSRF — they are not
|
|
51
|
+
// vulnerable to cross-site request forgery because the token is not
|
|
52
|
+
// sent automatically by the browser.
|
|
53
|
+
const authHeader = c.req.header("authorization");
|
|
54
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
55
|
+
await next();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (STATE_CHANGING_METHODS.has(method)) {
|
|
59
|
+
// --- Validate CSRF token on state-changing requests ---
|
|
60
|
+
const cookieToken = getCookieValue(c.req.header("cookie"), CSRF_COOKIE_NAME);
|
|
61
|
+
const headerToken = c.req.header(CSRF_HEADER_NAME);
|
|
62
|
+
if (!cookieToken ||
|
|
63
|
+
!headerToken ||
|
|
64
|
+
cookieToken !== headerToken) {
|
|
65
|
+
return c.json({ error: "CSRF token mismatch" }, 403);
|
|
66
|
+
}
|
|
67
|
+
await next();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// --- Issue a fresh token on safe requests ---
|
|
71
|
+
const token = generateCsrfToken();
|
|
72
|
+
await next();
|
|
73
|
+
// Set the cookie and header after downstream handlers have run so that
|
|
74
|
+
// the response object already exists.
|
|
75
|
+
c.header(CSRF_HEADER_NAME, token);
|
|
76
|
+
c.header("Set-Cookie", `${CSRF_COOKIE_NAME}=${token}; Path=/; SameSite=Lax`);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=csrf.js.map
|
package/dist/csrf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,8EAA8E;AAC9E,oDAAoD;AACpD,8EAA8E;AAE9E,MAAM,gBAAgB,GAAG,QAAQ,CAAC;AAClC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AACxC,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAE3E;;GAEG;AACH,SAAS,iBAAiB;IACxB,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CACrB,YAAuC,EACvC,IAAY;IAEZ,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,SAAS;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IACjC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,KAAK,EAAE,CAAU,EAAE,IAAU,EAA4B,EAAE;QAChE,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAE1C,mEAAmE;QACnE,oEAAoE;QACpE,qCAAqC;QACrC,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QACjD,IAAI,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QAED,IAAI,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACvC,yDAAyD;YACzD,MAAM,WAAW,GAAG,cAAc,CAChC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EACtB,gBAAgB,CACjB,CAAC;YACF,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;YAEnD,IACE,CAAC,WAAW;gBACZ,CAAC,WAAW;gBACZ,WAAW,KAAK,WAAW,EAC3B,CAAC;gBACD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAC;YACvD,CAAC;YAED,MAAM,IAAI,EAAE,CAAC;QACf,CAAC;aAAM,CAAC;YACN,+CAA+C;YAC/C,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;YAElC,MAAM,IAAI,EAAE,CAAC;YAEb,uEAAuE;YACvE,sCAAsC;YACtC,CAAC,CAAC,MAAM,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;YAClC,CAAC,CAAC,MAAM,CACN,YAAY,EACZ,GAAG,gBAAgB,IAAI,KAAK,wBAAwB,CACrD,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
export { defineAPI, getAPIRegistry } from "./api.js";
|
|
1
|
+
export { defineAPI, getAPIRegistry, clearAPIRegistry } from "./api.js";
|
|
2
2
|
export { defineMiddleware } from "./middleware.js";
|
|
3
|
+
export { csrfProtection } from "./csrf.js";
|
|
3
4
|
export { definePolicy, enforcePolicies } from "./policy.js";
|
|
4
5
|
export { defineConfig, env } from "./config.js";
|
|
5
6
|
export { createCapstanApp } from "./server.js";
|
|
6
7
|
export { createContext } from "./context.js";
|
|
7
8
|
export { createApproval, getApproval, listApprovals, resolveApproval, clearApprovals, } from "./approval.js";
|
|
9
|
+
export { mountApprovalRoutes } from "./approval-routes.js";
|
|
10
|
+
export type { HandlerRegistry } from "./approval-routes.js";
|
|
8
11
|
export type { PendingApproval } from "./approval.js";
|
|
9
12
|
export type { CapstanApp } from "./server.js";
|
|
10
13
|
export type { APIDefinition, APIHandlerInput, CapstanAuthContext, CapstanConfig, CapstanContext, HttpMethod, MiddlewareDefinition, PolicyCheckResult, PolicyDefinition, PolicyEffect, RouteMetadata, } from "./types.js";
|
|
14
|
+
export { createRequestLogger } from "./logger.js";
|
|
11
15
|
export { verifyCapstanApp, renderRuntimeVerifyText } from "./verify.js";
|
|
12
16
|
export type { VerifyReport, VerifyDiagnostic, VerifyStep } from "./verify.js";
|
|
13
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,eAAe,EACf,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,UAAU,EACV,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// Public API ----------------------------------------------------------------
|
|
2
|
-
export { defineAPI, getAPIRegistry } from "./api.js";
|
|
2
|
+
export { defineAPI, getAPIRegistry, clearAPIRegistry } from "./api.js";
|
|
3
3
|
export { defineMiddleware } from "./middleware.js";
|
|
4
|
+
export { csrfProtection } from "./csrf.js";
|
|
4
5
|
export { definePolicy, enforcePolicies } from "./policy.js";
|
|
5
6
|
export { defineConfig, env } from "./config.js";
|
|
6
7
|
export { createCapstanApp } from "./server.js";
|
|
7
8
|
export { createContext } from "./context.js";
|
|
8
9
|
export { createApproval, getApproval, listApprovals, resolveApproval, clearApprovals, } from "./approval.js";
|
|
10
|
+
export { mountApprovalRoutes } from "./approval-routes.js";
|
|
11
|
+
export { createRequestLogger } from "./logger.js";
|
|
9
12
|
export { verifyCapstanApp, renderRuntimeVerifyText } from "./verify.js";
|
|
10
13
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,eAAe,EACf,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAqB3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
/**
|
|
3
|
+
* Create Hono middleware that logs structured JSON lines for every HTTP
|
|
4
|
+
* request. Each request receives a unique ID set as the `X-Request-Id`
|
|
5
|
+
* response header.
|
|
6
|
+
*
|
|
7
|
+
* The logger respects the `LOG_LEVEL` environment variable:
|
|
8
|
+
* - `debug` -- log request start + completion
|
|
9
|
+
* - `info` -- log request completion only (default)
|
|
10
|
+
* - `warn` -- only log 4xx/5xx responses
|
|
11
|
+
* - `error` -- only log 5xx responses
|
|
12
|
+
*/
|
|
13
|
+
export declare function createRequestLogger(): MiddlewareHandler;
|
|
14
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAuEvD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,iBAAiB,CAwCvD"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const LOG_LEVEL_RANK = {
|
|
2
|
+
debug: 0,
|
|
3
|
+
info: 1,
|
|
4
|
+
warn: 2,
|
|
5
|
+
error: 3,
|
|
6
|
+
};
|
|
7
|
+
function resolveLogLevel() {
|
|
8
|
+
const raw = (process.env["LOG_LEVEL"] ?? "info").toLowerCase();
|
|
9
|
+
if (raw in LOG_LEVEL_RANK) {
|
|
10
|
+
return raw;
|
|
11
|
+
}
|
|
12
|
+
return "info";
|
|
13
|
+
}
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Request ID
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
function generateRequestId() {
|
|
18
|
+
return crypto.randomUUID();
|
|
19
|
+
}
|
|
20
|
+
function shouldLogCompletion(level, status) {
|
|
21
|
+
switch (level) {
|
|
22
|
+
case "debug":
|
|
23
|
+
case "info":
|
|
24
|
+
return true;
|
|
25
|
+
case "warn":
|
|
26
|
+
return status >= 400;
|
|
27
|
+
case "error":
|
|
28
|
+
return status >= 500;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// createRequestLogger
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/**
|
|
35
|
+
* Create Hono middleware that logs structured JSON lines for every HTTP
|
|
36
|
+
* request. Each request receives a unique ID set as the `X-Request-Id`
|
|
37
|
+
* response header.
|
|
38
|
+
*
|
|
39
|
+
* The logger respects the `LOG_LEVEL` environment variable:
|
|
40
|
+
* - `debug` -- log request start + completion
|
|
41
|
+
* - `info` -- log request completion only (default)
|
|
42
|
+
* - `warn` -- only log 4xx/5xx responses
|
|
43
|
+
* - `error` -- only log 5xx responses
|
|
44
|
+
*/
|
|
45
|
+
export function createRequestLogger() {
|
|
46
|
+
return async (c, next) => {
|
|
47
|
+
const level = resolveLogLevel();
|
|
48
|
+
const reqId = generateRequestId();
|
|
49
|
+
const method = c.req.method;
|
|
50
|
+
const path = c.req.path;
|
|
51
|
+
const start = performance.now();
|
|
52
|
+
// Set the request ID header early so downstream middleware can read it.
|
|
53
|
+
c.header("X-Request-Id", reqId);
|
|
54
|
+
// debug: log request start
|
|
55
|
+
if (level === "debug") {
|
|
56
|
+
const entry = {
|
|
57
|
+
ts: new Date().toISOString(),
|
|
58
|
+
reqId,
|
|
59
|
+
method,
|
|
60
|
+
path,
|
|
61
|
+
event: "start",
|
|
62
|
+
};
|
|
63
|
+
console.log(JSON.stringify(entry));
|
|
64
|
+
}
|
|
65
|
+
await next();
|
|
66
|
+
const ms = Math.round(performance.now() - start);
|
|
67
|
+
const status = c.res.status;
|
|
68
|
+
if (shouldLogCompletion(level, status)) {
|
|
69
|
+
const entry = {
|
|
70
|
+
ts: new Date().toISOString(),
|
|
71
|
+
reqId,
|
|
72
|
+
method,
|
|
73
|
+
path,
|
|
74
|
+
status,
|
|
75
|
+
ms,
|
|
76
|
+
};
|
|
77
|
+
console.log(JSON.stringify(entry));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAQA,MAAM,cAAc,GAA6B;IAC/C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,SAAS,eAAe;IACtB,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/D,IAAI,GAAG,IAAI,cAAc,EAAE,CAAC;QAC1B,OAAO,GAAe,CAAC;IACzB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,SAAS,iBAAiB;IACxB,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;AAC7B,CAAC;AAuBD,SAAS,mBAAmB,CAC1B,KAAe,EACf,MAAc;IAEd,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,OAAO,CAAC;QACb,KAAK,MAAM;YACT,OAAO,IAAI,CAAC;QACd,KAAK,MAAM;YACT,OAAO,MAAM,IAAI,GAAG,CAAC;QACvB,KAAK,OAAO;YACV,OAAO,MAAM,IAAI,GAAG,CAAC;IACzB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,KAAK,EAAE,CAAU,EAAE,IAAyB,EAAE,EAAE;QACrD,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;QAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;QACxB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAEhC,wEAAwE;QACxE,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QAEhC,2BAA2B;QAC3B,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACtB,MAAM,KAAK,GAAyB;gBAClC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,KAAK;gBACL,MAAM;gBACN,IAAI;gBACJ,KAAK,EAAE,OAAO;aACf,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACrC,CAAC;QAED,MAAM,IAAI,EAAE,CAAC;QAEb,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;QAE5B,IAAI,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;YACvC,MAAM,KAAK,GAAoB;gBAC7B,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,KAAK;gBACL,MAAM;gBACN,IAAI;gBACJ,MAAM;gBACN,EAAE;aACH,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACrC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,2EAA2E;AAC3E,UAAU,UAAU;IAClB,SAAS,EAAE;QACT,UAAU,EAAE,cAAc,CAAC;KAC5B,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtB,2EAA2E;IAC3E,aAAa,EAAE,aAAa,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,EAAE,CACX,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,EACrB,QAAQ,CAAC,EAAE,gBAAgB,EAAE,KAC1B,IAAI,CAAC;CACX;AAuBD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,UAAU,CAmMlE"}
|
package/dist/server.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { cors } from "hono/cors";
|
|
3
3
|
import { toJSONSchema } from "zod";
|
|
4
|
+
import { clearAPIRegistry } from "./api.js";
|
|
4
5
|
import { createContext } from "./context.js";
|
|
5
6
|
import { enforcePolicies } from "./policy.js";
|
|
6
|
-
import { createApproval
|
|
7
|
+
import { createApproval } from "./approval.js";
|
|
8
|
+
import { mountApprovalRoutes } from "./approval-routes.js";
|
|
7
9
|
/**
|
|
8
10
|
* Build a RouteMetadata entry, omitting keys whose value would be undefined
|
|
9
11
|
* (required by exactOptionalPropertyTypes).
|
|
@@ -33,6 +35,9 @@ function buildRouteMetadata(method, path, apiDef, inputSchema, outputSchema) {
|
|
|
33
35
|
* - `registerAPI()` -- helper to register an API definition as an HTTP route
|
|
34
36
|
*/
|
|
35
37
|
export function createCapstanApp(config) {
|
|
38
|
+
// Reset the global API registry so that previous app instances (e.g. from
|
|
39
|
+
// hot reload or prior test cases) don't leak stale definitions.
|
|
40
|
+
clearAPIRegistry();
|
|
36
41
|
const app = new Hono();
|
|
37
42
|
const routeRegistry = [];
|
|
38
43
|
/**
|
|
@@ -163,102 +168,7 @@ export function createCapstanApp(config) {
|
|
|
163
168
|
// ------------------------------------------------------------------
|
|
164
169
|
// Approval management endpoints
|
|
165
170
|
// ------------------------------------------------------------------
|
|
166
|
-
|
|
167
|
-
app.get("/capstan/approvals", (c) => {
|
|
168
|
-
const statusParam = new URL(c.req.url).searchParams.get("status");
|
|
169
|
-
const items = listApprovals(statusParam ?? undefined);
|
|
170
|
-
return c.json({ approvals: items });
|
|
171
|
-
});
|
|
172
|
-
/** Get a single approval by ID */
|
|
173
|
-
app.get("/capstan/approvals/:id", (c) => {
|
|
174
|
-
const id = c.req.param("id");
|
|
175
|
-
const approval = getApproval(id);
|
|
176
|
-
if (!approval) {
|
|
177
|
-
return c.json({ error: "Approval not found" }, 404);
|
|
178
|
-
}
|
|
179
|
-
return c.json(approval);
|
|
180
|
-
});
|
|
181
|
-
/** Approve a pending approval — re-executes the original handler */
|
|
182
|
-
app.post("/capstan/approvals/:id/approve", async (c) => {
|
|
183
|
-
const id = c.req.param("id");
|
|
184
|
-
const existing = getApproval(id);
|
|
185
|
-
if (!existing) {
|
|
186
|
-
return c.json({ error: "Approval not found" }, 404);
|
|
187
|
-
}
|
|
188
|
-
if (existing.status !== "pending") {
|
|
189
|
-
return c.json({ error: "Approval already resolved", status: existing.status }, 409);
|
|
190
|
-
}
|
|
191
|
-
// Parse optional body for resolvedBy
|
|
192
|
-
let resolvedBy;
|
|
193
|
-
try {
|
|
194
|
-
const body = await c.req.json();
|
|
195
|
-
if (typeof body.resolvedBy === "string") {
|
|
196
|
-
resolvedBy = body.resolvedBy;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
// No body or invalid JSON — that's fine.
|
|
201
|
-
}
|
|
202
|
-
const approval = resolveApproval(id, "approved", resolvedBy);
|
|
203
|
-
if (!approval) {
|
|
204
|
-
return c.json({ error: "Approval not found" }, 404);
|
|
205
|
-
}
|
|
206
|
-
// Re-execute the original handler with the stored input.
|
|
207
|
-
const routeKey = `${approval.method} ${approval.path}`;
|
|
208
|
-
const handler = handlerRegistry.get(routeKey);
|
|
209
|
-
if (!handler) {
|
|
210
|
-
return c.json({ error: "Handler not found for route", route: routeKey }, 500);
|
|
211
|
-
}
|
|
212
|
-
try {
|
|
213
|
-
// Build a synthetic context for the approver.
|
|
214
|
-
const ctx = createContext(c);
|
|
215
|
-
const result = await handler(approval.input, ctx);
|
|
216
|
-
approval.result = result;
|
|
217
|
-
return c.json({
|
|
218
|
-
status: "approved",
|
|
219
|
-
approvalId: approval.id,
|
|
220
|
-
result,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
catch (err) {
|
|
224
|
-
const message = err instanceof Error ? err.message : "Handler execution failed";
|
|
225
|
-
return c.json({ error: message, approvalId: approval.id }, 500);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
/** Deny a pending approval */
|
|
229
|
-
app.post("/capstan/approvals/:id/deny", async (c) => {
|
|
230
|
-
const id = c.req.param("id");
|
|
231
|
-
const existing = getApproval(id);
|
|
232
|
-
if (!existing) {
|
|
233
|
-
return c.json({ error: "Approval not found" }, 404);
|
|
234
|
-
}
|
|
235
|
-
if (existing.status !== "pending") {
|
|
236
|
-
return c.json({ error: "Approval already resolved", status: existing.status }, 409);
|
|
237
|
-
}
|
|
238
|
-
let resolvedBy;
|
|
239
|
-
let reason;
|
|
240
|
-
try {
|
|
241
|
-
const body = await c.req.json();
|
|
242
|
-
if (typeof body.resolvedBy === "string") {
|
|
243
|
-
resolvedBy = body.resolvedBy;
|
|
244
|
-
}
|
|
245
|
-
if (typeof body.reason === "string") {
|
|
246
|
-
reason = body.reason;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
// No body or invalid JSON — that's fine.
|
|
251
|
-
}
|
|
252
|
-
const approval = resolveApproval(id, "denied", resolvedBy);
|
|
253
|
-
if (!approval) {
|
|
254
|
-
return c.json({ error: "Approval not found" }, 404);
|
|
255
|
-
}
|
|
256
|
-
return c.json({
|
|
257
|
-
status: "denied",
|
|
258
|
-
approvalId: approval.id,
|
|
259
|
-
...(reason !== undefined ? { reason } : {}),
|
|
260
|
-
});
|
|
261
|
-
});
|
|
171
|
+
mountApprovalRoutes(app, handlerRegistry);
|
|
262
172
|
// ------------------------------------------------------------------
|
|
263
173
|
// Agent manifest endpoint
|
|
264
174
|
// ------------------------------------------------------------------
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|