@serfab/cadre-host 1.10.0 → 1.11.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/README.md +423 -423
- package/dist/ui/assets/index-C6nELNfC.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/global-setup.ts +58 -58
- package/src/__tests__/orchestrator-handle-contract.ts +134 -134
- package/src/__tests__/orchestrator-node-identity.test.ts +203 -203
- package/src/__tests__/orchestrator-ports.test.ts +143 -143
- package/src/__tests__/orchestrator-teardown.ts +65 -65
- package/src/__tests__/orchestrator.test.ts +1063 -1063
- package/src/bin/host.ts +1357 -1357
- package/src/donation/__tests__/fake-orchestrator.test.ts +173 -173
- package/src/donation/donation-store.ts +176 -176
- package/src/donation/grant-service.ts +252 -252
- package/src/donation/index.ts +65 -65
- package/src/donation/types.ts +260 -260
- package/src/orchestrator/host-process-orchestrator.ts +1260 -1260
- package/src/orchestrator/port-allocator.ts +73 -73
- package/src/server/__tests__/grants-admin-route.test.ts +171 -171
- package/src/server/events/types.ts +24 -24
- package/src/server/index.ts +267 -267
- package/src/server/routes/grants-admin.ts +74 -74
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Donation grant **admin** HTTP routes — thin adapters around
|
|
3
|
-
* `GrantAdminHandlers` (`createGrantAdminHandlers(service, donations)`).
|
|
4
|
-
*
|
|
5
|
-
* Mount path: `/grants-admin` — the admin surface the `cadre-host grant` CLI
|
|
6
|
-
* targets. It is **loopback, no bearer**: same-machine admin, matching
|
|
7
|
-
* cadre-host's local-UI "no login" posture (see docs/cadre-host.md §
|
|
8
|
-
* Security posture). This is distinct from the grantee-facing `/grants`
|
|
9
|
-
* provisioning surface (`routes/grants.ts`), which carries the bearer gate.
|
|
10
|
-
*
|
|
11
|
-
* Clients: the `cadre-host grant` CLI and the local UI's Grants page. The list
|
|
12
|
-
* reports each grant's live-node count and the donations a revoke would end.
|
|
13
|
-
*
|
|
14
|
-
* Revoking a grant also terminates the nodes donated under it unless the
|
|
15
|
-
* caller passes `?keepNodes=true`; `DELETE /grants-admin/donations/:id` ends a
|
|
16
|
-
* single donated node. Those are the host's only teardown paths once a grant is
|
|
17
|
-
* revoked — the grantee's own `DELETE /grants/:id` is refused from then on.
|
|
18
|
-
*
|
|
19
|
-
* Each successful mutation publishes a `grants-changed` bus event, so an open
|
|
20
|
-
* Grants page refreshes when the CLI acts.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { FastifyInstance } from 'fastify';
|
|
24
|
-
|
|
25
|
-
import type { GrantAdminHandlers } from '../../donation/types.js';
|
|
26
|
-
import type { EventBus } from '../events/bus.js';
|
|
27
|
-
|
|
28
|
-
export interface GrantsAdminRoutesOptions {
|
|
29
|
-
handlers: GrantAdminHandlers;
|
|
30
|
-
events: EventBus;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function registerGrantsAdminRoutes(app: FastifyInstance, opts: GrantsAdminRoutesOptions): void {
|
|
34
|
-
const { handlers, events } = opts;
|
|
35
|
-
|
|
36
|
-
app.get('/grants-admin', async () => {
|
|
37
|
-
return handlers.listGrants();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
app.post('/grants-admin', async (request) => {
|
|
41
|
-
const body = (request.body ?? {}) as { label?: unknown; maxNodes?: unknown; ttlMs?: unknown };
|
|
42
|
-
const label = typeof body.label === 'string' ? body.label : '';
|
|
43
|
-
const args: { label: string; maxNodes?: number; ttlMs?: number } = { label };
|
|
44
|
-
if (typeof body.maxNodes === 'number') args.maxNodes = body.maxNodes;
|
|
45
|
-
if (typeof body.ttlMs === 'number') args.ttlMs = body.ttlMs;
|
|
46
|
-
const result = await handlers.postGrant(args);
|
|
47
|
-
events.publish({ type: 'grants-changed', kind: 'issued' });
|
|
48
|
-
return result;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
app.delete<{ Params: { token: string }; Querystring: { keepNodes?: string } }>(
|
|
52
|
-
'/grants-admin/:token',
|
|
53
|
-
async (request) => {
|
|
54
|
-
const keepNodes = isTrueFlag(request.query.keepNodes);
|
|
55
|
-
const { terminated } = await handlers.deleteGrant(request.params.token, { keepNodes });
|
|
56
|
-
events.publish({ type: 'grants-changed', kind: 'revoked' });
|
|
57
|
-
return { ok: true, terminated };
|
|
58
|
-
},
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// Two path segments past `/grants-admin`, so it never collides with `/:token`.
|
|
62
|
-
const { terminateDonation } = handlers;
|
|
63
|
-
if (terminateDonation) {
|
|
64
|
-
app.delete<{ Params: { id: string } }>('/grants-admin/donations/:id', async (request) => {
|
|
65
|
-
await terminateDonation(request.params.id);
|
|
66
|
-
events.publish({ type: 'grants-changed', kind: 'terminated' });
|
|
67
|
-
return { ok: true };
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function isTrueFlag(value: string | undefined): boolean {
|
|
73
|
-
return value === 'true' || value === '1';
|
|
74
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Donation grant **admin** HTTP routes — thin adapters around
|
|
3
|
+
* `GrantAdminHandlers` (`createGrantAdminHandlers(service, donations)`).
|
|
4
|
+
*
|
|
5
|
+
* Mount path: `/grants-admin` — the admin surface the `cadre-host grant` CLI
|
|
6
|
+
* targets. It is **loopback, no bearer**: same-machine admin, matching
|
|
7
|
+
* cadre-host's local-UI "no login" posture (see docs/cadre-host.md §
|
|
8
|
+
* Security posture). This is distinct from the grantee-facing `/grants`
|
|
9
|
+
* provisioning surface (`routes/grants.ts`), which carries the bearer gate.
|
|
10
|
+
*
|
|
11
|
+
* Clients: the `cadre-host grant` CLI and the local UI's Grants page. The list
|
|
12
|
+
* reports each grant's live-node count and the donations a revoke would end.
|
|
13
|
+
*
|
|
14
|
+
* Revoking a grant also terminates the nodes donated under it unless the
|
|
15
|
+
* caller passes `?keepNodes=true`; `DELETE /grants-admin/donations/:id` ends a
|
|
16
|
+
* single donated node. Those are the host's only teardown paths once a grant is
|
|
17
|
+
* revoked — the grantee's own `DELETE /grants/:id` is refused from then on.
|
|
18
|
+
*
|
|
19
|
+
* Each successful mutation publishes a `grants-changed` bus event, so an open
|
|
20
|
+
* Grants page refreshes when the CLI acts.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { FastifyInstance } from 'fastify';
|
|
24
|
+
|
|
25
|
+
import type { GrantAdminHandlers } from '../../donation/types.js';
|
|
26
|
+
import type { EventBus } from '../events/bus.js';
|
|
27
|
+
|
|
28
|
+
export interface GrantsAdminRoutesOptions {
|
|
29
|
+
handlers: GrantAdminHandlers;
|
|
30
|
+
events: EventBus;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function registerGrantsAdminRoutes(app: FastifyInstance, opts: GrantsAdminRoutesOptions): void {
|
|
34
|
+
const { handlers, events } = opts;
|
|
35
|
+
|
|
36
|
+
app.get('/grants-admin', async () => {
|
|
37
|
+
return handlers.listGrants();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
app.post('/grants-admin', async (request) => {
|
|
41
|
+
const body = (request.body ?? {}) as { label?: unknown; maxNodes?: unknown; ttlMs?: unknown };
|
|
42
|
+
const label = typeof body.label === 'string' ? body.label : '';
|
|
43
|
+
const args: { label: string; maxNodes?: number; ttlMs?: number } = { label };
|
|
44
|
+
if (typeof body.maxNodes === 'number') args.maxNodes = body.maxNodes;
|
|
45
|
+
if (typeof body.ttlMs === 'number') args.ttlMs = body.ttlMs;
|
|
46
|
+
const result = await handlers.postGrant(args);
|
|
47
|
+
events.publish({ type: 'grants-changed', kind: 'issued' });
|
|
48
|
+
return result;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
app.delete<{ Params: { token: string }; Querystring: { keepNodes?: string } }>(
|
|
52
|
+
'/grants-admin/:token',
|
|
53
|
+
async (request) => {
|
|
54
|
+
const keepNodes = isTrueFlag(request.query.keepNodes);
|
|
55
|
+
const { terminated } = await handlers.deleteGrant(request.params.token, { keepNodes });
|
|
56
|
+
events.publish({ type: 'grants-changed', kind: 'revoked' });
|
|
57
|
+
return { ok: true, terminated };
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Two path segments past `/grants-admin`, so it never collides with `/:token`.
|
|
62
|
+
const { terminateDonation } = handlers;
|
|
63
|
+
if (terminateDonation) {
|
|
64
|
+
app.delete<{ Params: { id: string } }>('/grants-admin/donations/:id', async (request) => {
|
|
65
|
+
await terminateDonation(request.params.id);
|
|
66
|
+
events.publish({ type: 'grants-changed', kind: 'terminated' });
|
|
67
|
+
return { ok: true };
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isTrueFlag(value: string | undefined): boolean {
|
|
73
|
+
return value === 'true' || value === '1';
|
|
74
|
+
}
|