delegation-routes.mjs
202 lines 7.3 KB
Raw
sha256:dc02e0ce2c9c3616e8ce241f8a700e1558caae53cb1af50daeffdb2685cf9c3d feat(delegation): 7C-L1 live gate smoke + hosted bridge/gat… Human minor ⚠ breaking 31 days ago
1 /**
2 * Hosted bridge REST routes for agent delegation (Phase 7C-L1 hosted parity).
3 *
4 * Mirrors self-hosted `hub/server.mjs` delegation handlers against bridge `DATA_DIR`
5 * with JWT auth and hosted vault context resolution.
6 *
7 * @see docs/AGENT-DELEGATION-V0-SPEC.md §4
8 */
9
10 import {
11 handleAgentIdentityRegisterProposeRequest,
12 handleAgentIdentityListRequest,
13 handleDelegationConsentProposeRequest,
14 handleDelegationConsentRevokeRequest,
15 handleDelegationGrantMintRequest,
16 handleDelegationGrantListRequest,
17 handleDelegationGrantRevokeRequest,
18 handleDelegationAuditAppendRequest,
19 hashPrincipalRef,
20 } from '../../lib/agent/delegation.mjs';
21 import { createProposal } from '../proposals-store.mjs';
22
23 /**
24 * @param {import('express').Express} app
25 * @param {{
26 * dataDir: string,
27 * requireBridgeAuth: import('express').RequestHandler,
28 * resolveHostedBridgeContext: (req: import('express').Request, actorUid: string) => Promise<{
29 * ok: boolean,
30 * status?: number,
31 * error?: string,
32 * code?: string,
33 * vaultId?: string,
34 * }>,
35 * }} deps
36 */
37 export function registerBridgeDelegationRoutes(app, deps) {
38 const { dataDir, requireBridgeAuth, resolveHostedBridgeContext } = deps;
39
40 /**
41 * @param {import('express').Request} req
42 */
43 async function vaultContext(req) {
44 const hctx = await resolveHostedBridgeContext(req, req.uid);
45 return hctx;
46 }
47
48 app.post('/api/v1/agents/identities', requireBridgeAuth, async (req, res) => {
49 const hctx = await vaultContext(req);
50 if (!hctx.ok) return res.status(hctx.status).json({ error: hctx.error, code: hctx.code });
51 const body = req.body && typeof req.body === 'object' ? req.body : {};
52 const result = handleAgentIdentityRegisterProposeRequest({
53 dataDir,
54 vaultId: hctx.vaultId,
55 userId: req.uid,
56 kind: body.kind,
57 agentId: body.agent_id,
58 label: body.label,
59 scopeCeiling: body.scope_ceiling,
60 createProposal,
61 });
62 if (!result.ok) {
63 return res.status(result.status).json({ error: result.error, code: result.code });
64 }
65 return res.status(201).json(result.payload);
66 });
67
68 app.get('/api/v1/agents/identities', requireBridgeAuth, async (req, res) => {
69 const hctx = await vaultContext(req);
70 if (!hctx.ok) return res.status(hctx.status).json({ error: hctx.error, code: hctx.code });
71 const result = handleAgentIdentityListRequest({
72 dataDir,
73 vaultId: hctx.vaultId,
74 kind: typeof req.query.kind === 'string' ? req.query.kind : undefined,
75 status: typeof req.query.status === 'string' ? req.query.status : undefined,
76 });
77 if (!result.ok) {
78 return res.status(result.status).json({ error: result.error, code: result.code });
79 }
80 return res.json(result.payload);
81 });
82
83 app.post('/api/v1/delegation/consents', requireBridgeAuth, async (req, res) => {
84 const hctx = await vaultContext(req);
85 if (!hctx.ok) return res.status(hctx.status).json({ error: hctx.error, code: hctx.code });
86 const body = req.body && typeof req.body === 'object' ? req.body : {};
87 const result = handleDelegationConsentProposeRequest({
88 dataDir,
89 vaultId: hctx.vaultId,
90 userId: req.uid,
91 delegateAgentId: body.delegate_agent_id,
92 scope: body.scope,
93 workspaceId: body.workspace_id,
94 allowedFlowIds: body.allowed_flow_ids,
95 allowedTaskKinds: body.allowed_task_kinds,
96 allowedTaskIds: body.allowed_task_ids,
97 expiresAt: body.expires_at,
98 createProposal,
99 });
100 if (!result.ok) {
101 return res.status(result.status).json({ error: result.error, code: result.code });
102 }
103 return res.status(201).json(result.payload);
104 });
105
106 app.delete('/api/v1/delegation/consents/:consent_id', requireBridgeAuth, async (req, res) => {
107 const hctx = await vaultContext(req);
108 if (!hctx.ok) return res.status(hctx.status).json({ error: hctx.error, code: hctx.code });
109 const consentId =
110 typeof req.params.consent_id === 'string' ? decodeURIComponent(req.params.consent_id).trim() : '';
111 const result = handleDelegationConsentRevokeRequest({
112 dataDir,
113 vaultId: hctx.vaultId,
114 consentId,
115 userId: req.uid,
116 });
117 if (!result.ok) {
118 return res.status(result.status).json({ error: result.error, code: result.code });
119 }
120 return res.json(result.payload);
121 });
122
123 app.post('/api/v1/delegation/grants', requireBridgeAuth, async (req, res) => {
124 const hctx = await vaultContext(req);
125 if (!hctx.ok) return res.status(hctx.status).json({ error: hctx.error, code: hctx.code });
126 const body = req.body && typeof req.body === 'object' ? req.body : {};
127 const result = handleDelegationGrantMintRequest({
128 dataDir,
129 vaultId: hctx.vaultId,
130 consentId: body.consent_id,
131 actorAgentId: body.actor_agent_id,
132 taskRef: body.task_ref,
133 runRef: body.run_ref,
134 flowId: body.flow_id,
135 flowVersion: body.flow_version,
136 ttlSeconds: body.ttl_seconds,
137 });
138 if (!result.ok) {
139 return res.status(result.status).json({ error: result.error, code: result.code });
140 }
141 return res.status(201).json(result.payload);
142 });
143
144 app.get('/api/v1/delegation/grants', requireBridgeAuth, async (req, res) => {
145 const hctx = await vaultContext(req);
146 if (!hctx.ok) return res.status(hctx.status).json({ error: hctx.error, code: hctx.code });
147 const result = handleDelegationGrantListRequest({
148 dataDir,
149 vaultId: hctx.vaultId,
150 actorAgentId: typeof req.query.actor_agent_id === 'string' ? req.query.actor_agent_id : undefined,
151 });
152 if (!result.ok) {
153 return res.status(result.status).json({ error: result.error, code: result.code });
154 }
155 return res.json(result.payload);
156 });
157
158 app.delete('/api/v1/delegation/grants/:grant_id', requireBridgeAuth, async (req, res) => {
159 const hctx = await vaultContext(req);
160 if (!hctx.ok) return res.status(hctx.status).json({ error: hctx.error, code: hctx.code });
161 const grantId =
162 typeof req.params.grant_id === 'string' ? decodeURIComponent(req.params.grant_id).trim() : '';
163 const result = handleDelegationGrantRevokeRequest({
164 dataDir,
165 vaultId: hctx.vaultId,
166 grantId,
167 });
168 if (!result.ok) {
169 return res.status(result.status).json({ error: result.error, code: result.code });
170 }
171 return res.json(result.payload);
172 });
173
174 app.post('/api/v1/delegation/audit', requireBridgeAuth, async (req, res) => {
175 const hctx = await vaultContext(req);
176 if (!hctx.ok) return res.status(hctx.status).json({ error: hctx.error, code: hctx.code });
177 const body = req.body && typeof req.body === 'object' ? req.body : {};
178 const principalRef =
179 typeof body.principal_ref === 'string' && body.principal_ref.trim()
180 ? body.principal_ref.trim()
181 : hashPrincipalRef(req.uid);
182 const result = handleDelegationAuditAppendRequest({
183 dataDir,
184 vaultId: hctx.vaultId,
185 grantId: body.grant_id,
186 actorAgentId: body.actor_agent_id,
187 principalRef,
188 action: body.action,
189 evidenceRefs: body.evidence_refs,
190 taskRef: body.task_ref,
191 runRef: body.run_ref,
192 flowId: body.flow_id,
193 flowVersion: body.flow_version,
194 stepId: body.step_id,
195 executionLocation: body.execution_location,
196 });
197 if (!result.ok) {
198 return res.status(result.status).json({ error: result.error, code: result.code });
199 }
200 return res.status(201).json(result.payload);
201 });
202 }
File History 1 commit
sha256:dc02e0ce2c9c3616e8ce241f8a700e1558caae53cb1af50daeffdb2685cf9c3d feat(delegation): 7C-L1 live gate smoke + hosted bridge/gat… Human minor 31 days ago