@heddleagent/execution-host-client 6.3.0 → 6.5.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 +121 -20
- package/dist/coordinator/contracts.d.ts +130 -0
- package/dist/coordinator/contracts.d.ts.map +1 -0
- package/dist/coordinator/contracts.js +112 -0
- package/dist/coordinator/contracts.js.map +1 -0
- package/dist/coordinator/hosted-heartbeat-coordinator-client.d.ts +20 -0
- package/dist/coordinator/hosted-heartbeat-coordinator-client.d.ts.map +1 -0
- package/dist/coordinator/hosted-heartbeat-coordinator-client.js +77 -0
- package/dist/coordinator/hosted-heartbeat-coordinator-client.js.map +1 -0
- package/dist/coordinator/hosted-heartbeat-delegation-client.d.ts +21 -0
- package/dist/coordinator/hosted-heartbeat-delegation-client.d.ts.map +1 -0
- package/dist/coordinator/hosted-heartbeat-delegation-client.js +117 -0
- package/dist/coordinator/hosted-heartbeat-delegation-client.js.map +1 -0
- package/dist/coordinator/hosted-heartbeat-delegation-service.d.ts +15 -0
- package/dist/coordinator/hosted-heartbeat-delegation-service.d.ts.map +1 -0
- package/dist/coordinator/hosted-heartbeat-delegation-service.js +81 -0
- package/dist/coordinator/hosted-heartbeat-delegation-service.js.map +1 -0
- package/dist/coordinator/hosted-heartbeat-task-reconciler.d.ts +11 -0
- package/dist/coordinator/hosted-heartbeat-task-reconciler.d.ts.map +1 -0
- package/dist/coordinator/hosted-heartbeat-task-reconciler.js +38 -0
- package/dist/coordinator/hosted-heartbeat-task-reconciler.js.map +1 -0
- package/dist/coordinator/index.d.ts +9 -0
- package/dist/coordinator/index.d.ts.map +1 -0
- package/dist/coordinator/index.js +7 -0
- package/dist/coordinator/index.js.map +1 -0
- package/dist/coordinator/node/delegation-http-service.d.ts +11 -0
- package/dist/coordinator/node/delegation-http-service.d.ts.map +1 -0
- package/dist/coordinator/node/delegation-http-service.js +131 -0
- package/dist/coordinator/node/delegation-http-service.js.map +1 -0
- package/dist/coordinator/node/environment.d.ts +3 -0
- package/dist/coordinator/node/environment.d.ts.map +1 -0
- package/dist/coordinator/node/environment.js +13 -0
- package/dist/coordinator/node/environment.js.map +1 -0
- package/dist/coordinator/node/index.d.ts +4 -0
- package/dist/coordinator/node/index.d.ts.map +1 -0
- package/dist/coordinator/node/index.js +3 -0
- package/dist/coordinator/node/index.js.map +1 -0
- package/dist/coordinator/node/types.d.ts +20 -0
- package/dist/coordinator/node/types.d.ts.map +1 -0
- package/dist/coordinator/node/types.js +2 -0
- package/dist/coordinator/node/types.js.map +1 -0
- package/dist/coordinator/runtime-session.d.ts +13 -0
- package/dist/coordinator/runtime-session.d.ts.map +1 -0
- package/dist/coordinator/runtime-session.js +23 -0
- package/dist/coordinator/runtime-session.js.map +1 -0
- package/dist/coordinator/service-token.d.ts +3 -0
- package/dist/coordinator/service-token.d.ts.map +1 -0
- package/dist/coordinator/service-token.js +7 -0
- package/dist/coordinator/service-token.js.map +1 -0
- package/dist/coordinator/types.d.ts +63 -0
- package/dist/coordinator/types.d.ts.map +1 -0
- package/dist/coordinator/types.js +2 -0
- package/dist/coordinator/types.js.map +1 -0
- package/dist/host/errors.d.ts +23 -0
- package/dist/host/errors.d.ts.map +1 -0
- package/dist/host/errors.js +32 -0
- package/dist/host/errors.js.map +1 -0
- package/dist/host/index.d.ts +7 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +6 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/jwt-execution-host-mcp-capability-verifier.d.ts +8 -0
- package/dist/host/jwt-execution-host-mcp-capability-verifier.d.ts.map +1 -0
- package/dist/host/jwt-execution-host-mcp-capability-verifier.js +86 -0
- package/dist/host/jwt-execution-host-mcp-capability-verifier.js.map +1 -0
- package/dist/host/jwt-execution-identity-verifier.d.ts +8 -0
- package/dist/host/jwt-execution-identity-verifier.d.ts.map +1 -0
- package/dist/host/jwt-execution-identity-verifier.js +76 -0
- package/dist/host/jwt-execution-identity-verifier.js.map +1 -0
- package/dist/host/schemas.d.ts +37 -0
- package/dist/host/schemas.d.ts.map +1 -0
- package/dist/host/schemas.js +24 -0
- package/dist/host/schemas.js.map +1 -0
- package/dist/host/types.d.ts +62 -0
- package/dist/host/types.d.ts.map +1 -0
- package/dist/host/types.js +7 -0
- package/dist/host/types.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/jwt-verification.d.ts +5 -0
- package/dist/internal/jwt-verification.d.ts.map +1 -0
- package/dist/internal/jwt-verification.js +25 -0
- package/dist/internal/jwt-verification.js.map +1 -0
- package/dist/mcp/jwt-capability-verifier.d.ts.map +1 -1
- package/dist/mcp/jwt-capability-verifier.js +6 -25
- package/dist/mcp/jwt-capability-verifier.js.map +1 -1
- package/dist/node/http-service.d.ts.map +1 -1
- package/dist/node/http-service.js +5 -108
- package/dist/node/http-service.js.map +1 -1
- package/dist/node/http-utils.d.ts +15 -0
- package/dist/node/http-utils.d.ts.map +1 -0
- package/dist/node/http-utils.js +109 -0
- package/dist/node/http-utils.js.map +1 -0
- package/package.json +13 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { HEARTBEAT_TASK_WORKFLOW, isSafeWebUrl, } from '../contracts/index.js';
|
|
3
|
+
import { HostedHeartbeatAgentExecutionTransport, HostedHeartbeatTaskService, } from '../heartbeat/index.js';
|
|
4
|
+
import { HOSTED_HEARTBEAT_DELEGATIONS_PATH, HostedHeartbeatDelegationRequestSchema, HostedHeartbeatDelegationSchema, } from './contracts.js';
|
|
5
|
+
import { HostedHeartbeatServiceTokenSchema } from './service-token.js';
|
|
6
|
+
const SafeBaseUrlSchema = z.custom((value) => value instanceof URL && isSafeWebUrl(value), 'baseUrl must be a safe HTTPS or loopback HTTP URL');
|
|
7
|
+
const AbsolutePathSchema = z.string().min(1).max(256).regex(/^\/(?:[^?#\s]*)$/, 'must be an absolute path without query, fragment, or whitespace');
|
|
8
|
+
export class HostedHeartbeatDelegationRequestError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
name = 'HostedHeartbeatDelegationRequestError';
|
|
11
|
+
constructor(status) {
|
|
12
|
+
super(`Heartbeat delegation failed with status ${status}.`);
|
|
13
|
+
this.status = status;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Authenticated coordinator client for one product-issued authority bundle. */
|
|
17
|
+
export class HostedHeartbeatDelegationClient {
|
|
18
|
+
#endpoint;
|
|
19
|
+
#authorization;
|
|
20
|
+
#fetch;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
const baseUrl = SafeBaseUrlSchema.parse(config.baseUrl);
|
|
23
|
+
this.#endpoint = new URL(AbsolutePathSchema.parse(config.path ?? HOSTED_HEARTBEAT_DELEGATIONS_PATH), baseUrl);
|
|
24
|
+
this.#authorization = `Bearer ${HostedHeartbeatServiceTokenSchema.parse(config.apiToken)}`;
|
|
25
|
+
this.#fetch = config.fetch ?? globalThis.fetch;
|
|
26
|
+
}
|
|
27
|
+
async issue(rawInput, signal) {
|
|
28
|
+
const input = HostedHeartbeatDelegationRequestSchema.parse(rawInput);
|
|
29
|
+
const response = await this.#fetch(this.#endpoint, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
authorization: this.#authorization,
|
|
33
|
+
'content-type': 'application/json',
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(input),
|
|
36
|
+
redirect: 'error',
|
|
37
|
+
signal,
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new HostedHeartbeatDelegationRequestError(response.status);
|
|
41
|
+
}
|
|
42
|
+
const delegation = HostedHeartbeatDelegationSchema.parse(await response.json());
|
|
43
|
+
if (delegation.taskId !== input.taskId
|
|
44
|
+
|| delegation.executionId !== input.executionId) {
|
|
45
|
+
throw new Error('Product backend returned a mismatched heartbeat delegation.');
|
|
46
|
+
}
|
|
47
|
+
return delegation;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Executes a claimed task with the exact product-issued authority bundle. */
|
|
51
|
+
export class HostedHeartbeatDelegatedExecutionTransport {
|
|
52
|
+
#config;
|
|
53
|
+
constructor(config) {
|
|
54
|
+
this.#config = config;
|
|
55
|
+
}
|
|
56
|
+
async execute(input) {
|
|
57
|
+
const delegation = await this.#config.delegations.issue({
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
taskId: input.request.taskId,
|
|
60
|
+
executionId: input.request.executionId,
|
|
61
|
+
}, input.signal);
|
|
62
|
+
const service = new HostedHeartbeatTaskService({
|
|
63
|
+
authority: new DelegatedExecutionAuthorityIssuer(delegation),
|
|
64
|
+
executionHost: this.#config.executionHost,
|
|
65
|
+
modelCredentials: this.#config.modelCredentials,
|
|
66
|
+
mcp: {
|
|
67
|
+
allowedTools: delegation.authority.metadata.mcp.allowedTools,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
const transport = new HostedHeartbeatAgentExecutionTransport({
|
|
71
|
+
runner: service,
|
|
72
|
+
resolveInvocationContext: () => ({
|
|
73
|
+
scope: delegation.scope,
|
|
74
|
+
runtimeSessionId: delegation.runtimeSessionId,
|
|
75
|
+
deadlineAt: delegation.deadlineAt,
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
return await transport.execute(input);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Ensures downstream execution cannot widen a delegated authority bundle. */
|
|
82
|
+
class DelegatedExecutionAuthorityIssuer {
|
|
83
|
+
delegation;
|
|
84
|
+
constructor(delegation) {
|
|
85
|
+
this.delegation = delegation;
|
|
86
|
+
}
|
|
87
|
+
async issue(input) {
|
|
88
|
+
const metadata = this.delegation.authority.metadata;
|
|
89
|
+
if (input.workflow !== HEARTBEAT_TASK_WORKFLOW
|
|
90
|
+
|| input.workflow !== metadata.workflow
|
|
91
|
+
|| input.invocationId !== metadata.invocationId
|
|
92
|
+
|| input.runtimeSessionId !== metadata.runtimeSessionId
|
|
93
|
+
|| !sameScope(input.scope, this.delegation.scope)
|
|
94
|
+
|| !sameScope(input.scope, metadata.scope)
|
|
95
|
+
|| !sameTools(input.mcp?.allowedTools, metadata.mcp.allowedTools)) {
|
|
96
|
+
throw new Error('Heartbeat delegation does not match Heddle execution.');
|
|
97
|
+
}
|
|
98
|
+
const authority = this.delegation.authority;
|
|
99
|
+
return Object.freeze({
|
|
100
|
+
metadata: metadata,
|
|
101
|
+
executionAssertion: () => authority.executionAssertion,
|
|
102
|
+
mcpCapability: () => authority.mcpCapability,
|
|
103
|
+
toJSON: () => metadata,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function sameScope(left, right) {
|
|
108
|
+
return left.tenantId === right.tenantId
|
|
109
|
+
&& left.subjectId === right.subjectId
|
|
110
|
+
&& left.productSessionId === right.productSessionId;
|
|
111
|
+
}
|
|
112
|
+
function sameTools(left, right) {
|
|
113
|
+
return Boolean(left
|
|
114
|
+
&& left.length === right.length
|
|
115
|
+
&& left.every((tool, index) => tool === right[index]));
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=hosted-heartbeat-delegation-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted-heartbeat-delegation-client.js","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-delegation-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type {\n ExecutionAuthorityIssueInput,\n ExecutionAuthorityIssuer,\n IssuedExecutionAuthority,\n IssuedExecutionAuthorityMetadata,\n} from '../authority/index.js';\nimport {\n HEARTBEAT_TASK_WORKFLOW,\n isSafeWebUrl,\n} from '../contracts/index.js';\nimport {\n HostedHeartbeatAgentExecutionTransport,\n HostedHeartbeatTaskService,\n type HostedHeartbeatAgentExecutionTransportInput,\n} from '../heartbeat/index.js';\nimport {\n HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n HostedHeartbeatDelegationRequestSchema,\n HostedHeartbeatDelegationSchema,\n type HostedHeartbeatDelegation,\n type HostedHeartbeatDelegationRequest,\n} from './contracts.js';\nimport { HostedHeartbeatServiceTokenSchema } from './service-token.js';\nimport type {\n HostedHeartbeatDelegatedExecutionTransportConfig,\n HostedHeartbeatDelegatedExecutionTransportPort,\n HostedHeartbeatDelegationClientConfig,\n HostedHeartbeatDelegationIssuer,\n} from './types.js';\n\nconst SafeBaseUrlSchema = z.custom<URL>(\n (value) => value instanceof URL && isSafeWebUrl(value),\n 'baseUrl must be a safe HTTPS or loopback HTTP URL',\n);\nconst AbsolutePathSchema = z.string().min(1).max(256).regex(\n /^\\/(?:[^?#\\s]*)$/,\n 'must be an absolute path without query, fragment, or whitespace',\n);\n\nexport class HostedHeartbeatDelegationRequestError extends Error {\n readonly name = 'HostedHeartbeatDelegationRequestError';\n\n constructor(readonly status: number) {\n super(`Heartbeat delegation failed with status ${status}.`);\n }\n}\n\n/** Authenticated coordinator client for one product-issued authority bundle. */\nexport class HostedHeartbeatDelegationClient\nimplements HostedHeartbeatDelegationIssuer {\n readonly #endpoint: URL;\n readonly #authorization: string;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(config: HostedHeartbeatDelegationClientConfig) {\n const baseUrl = SafeBaseUrlSchema.parse(config.baseUrl);\n this.#endpoint = new URL(\n AbsolutePathSchema.parse(\n config.path ?? HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n ),\n baseUrl,\n );\n this.#authorization = `Bearer ${\n HostedHeartbeatServiceTokenSchema.parse(config.apiToken)\n }`;\n this.#fetch = config.fetch ?? globalThis.fetch;\n }\n\n async issue(\n rawInput: HostedHeartbeatDelegationRequest,\n signal?: AbortSignal,\n ): Promise<HostedHeartbeatDelegation> {\n const input = HostedHeartbeatDelegationRequestSchema.parse(rawInput);\n const response = await this.#fetch(this.#endpoint, {\n method: 'POST',\n headers: {\n authorization: this.#authorization,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(input),\n redirect: 'error',\n signal,\n });\n if (!response.ok) {\n throw new HostedHeartbeatDelegationRequestError(response.status);\n }\n const delegation = HostedHeartbeatDelegationSchema.parse(\n await response.json(),\n );\n if (\n delegation.taskId !== input.taskId\n || delegation.executionId !== input.executionId\n ) {\n throw new Error(\n 'Product backend returned a mismatched heartbeat delegation.',\n );\n }\n return delegation;\n }\n}\n\n/** Executes a claimed task with the exact product-issued authority bundle. */\nexport class HostedHeartbeatDelegatedExecutionTransport\nimplements HostedHeartbeatDelegatedExecutionTransportPort {\n readonly #config: HostedHeartbeatDelegatedExecutionTransportConfig;\n\n constructor(config: HostedHeartbeatDelegatedExecutionTransportConfig) {\n this.#config = config;\n }\n\n async execute(\n input: HostedHeartbeatAgentExecutionTransportInput,\n ): Promise<unknown> {\n const delegation = await this.#config.delegations.issue({\n schemaVersion: 1,\n taskId: input.request.taskId,\n executionId: input.request.executionId,\n }, input.signal);\n const service = new HostedHeartbeatTaskService({\n authority: new DelegatedExecutionAuthorityIssuer(delegation),\n executionHost: this.#config.executionHost,\n modelCredentials: this.#config.modelCredentials,\n mcp: {\n allowedTools: delegation.authority.metadata.mcp.allowedTools,\n },\n });\n const transport = new HostedHeartbeatAgentExecutionTransport({\n runner: service,\n resolveInvocationContext: () => ({\n scope: delegation.scope,\n runtimeSessionId: delegation.runtimeSessionId,\n deadlineAt: delegation.deadlineAt,\n }),\n });\n return await transport.execute(input);\n }\n}\n\n/** Ensures downstream execution cannot widen a delegated authority bundle. */\nclass DelegatedExecutionAuthorityIssuer implements ExecutionAuthorityIssuer {\n constructor(private readonly delegation: HostedHeartbeatDelegation) {}\n\n async issue(\n input: ExecutionAuthorityIssueInput,\n ): Promise<IssuedExecutionAuthority> {\n const metadata = this.delegation.authority.metadata;\n if (\n input.workflow !== HEARTBEAT_TASK_WORKFLOW\n || input.workflow !== metadata.workflow\n || input.invocationId !== metadata.invocationId\n || input.runtimeSessionId !== metadata.runtimeSessionId\n || !sameScope(input.scope, this.delegation.scope)\n || !sameScope(input.scope, metadata.scope)\n || !sameTools(input.mcp?.allowedTools, metadata.mcp.allowedTools)\n ) {\n throw new Error('Heartbeat delegation does not match Heddle execution.');\n }\n const authority = this.delegation.authority;\n return Object.freeze({\n metadata: metadata as IssuedExecutionAuthorityMetadata,\n executionAssertion: () => authority.executionAssertion,\n mcpCapability: () => authority.mcpCapability,\n toJSON: () => metadata as IssuedExecutionAuthorityMetadata,\n });\n }\n}\n\nfunction sameScope(\n left: ExecutionAuthorityIssueInput['scope'],\n right: HostedHeartbeatDelegation['scope']\n | HostedHeartbeatDelegation['authority']['metadata']['scope'],\n): boolean {\n return left.tenantId === right.tenantId\n && left.subjectId === right.subjectId\n && left.productSessionId === right.productSessionId;\n}\n\nfunction sameTools(\n left: readonly string[] | undefined,\n right: readonly string[],\n): boolean {\n return Boolean(\n left\n && left.length === right.length\n && left.every((tool, index) => tool === right[index]),\n );\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HostedHeartbeatDelegation, type HostedHeartbeatDelegationRequest } from './contracts.js';
|
|
2
|
+
import type { HostedHeartbeatDelegationIssuer, HostedHeartbeatDelegationServiceConfig } from './types.js';
|
|
3
|
+
export declare class HostedHeartbeatDelegationRejectedError extends Error {
|
|
4
|
+
readonly name = "HostedHeartbeatDelegationRejectedError";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Converts one product authorization decision into a fully bound Heddle
|
|
8
|
+
* authority bundle. Product code never constructs the delegation wire shape.
|
|
9
|
+
*/
|
|
10
|
+
export declare class HostedHeartbeatDelegationService implements HostedHeartbeatDelegationIssuer {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(config: HostedHeartbeatDelegationServiceConfig);
|
|
13
|
+
issue(rawInput: HostedHeartbeatDelegationRequest, rawSignal?: AbortSignal): Promise<HostedHeartbeatDelegation>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=hosted-heartbeat-delegation-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted-heartbeat-delegation-service.d.ts","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-delegation-service.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACtC,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EACV,+BAA+B,EAC/B,sCAAsC,EACvC,MAAM,YAAY,CAAC;AAOpB,qBAAa,sCAAuC,SAAQ,KAAK;IAC/D,QAAQ,CAAC,IAAI,4CAA4C;CAC1D;AAED;;;GAGG;AACH,qBAAa,gCACb,YAAW,+BAA+B;;gBAO5B,MAAM,EAAE,sCAAsC;IAYpD,KAAK,CACT,QAAQ,EAAE,gCAAgC,EAC1C,SAAS,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,yBAAyB,CAAC;CAqDtC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { HEARTBEAT_TASK_WORKFLOW, OpaqueIdSchema } from '../contracts/index.js';
|
|
4
|
+
import { HostedHeartbeatDelegationAuthorizationSchema, HostedHeartbeatDelegationRequestSchema, HostedHeartbeatDelegationSchema, } from './contracts.js';
|
|
5
|
+
import { createHostedRuntimeSessionId } from './runtime-session.js';
|
|
6
|
+
const DelegationServiceConfigSchema = z.object({
|
|
7
|
+
runtimeSessionNamespace: OpaqueIdSchema,
|
|
8
|
+
maxExecutionMs: z.number().int().min(1_000).max(Number.MAX_SAFE_INTEGER),
|
|
9
|
+
}).strict();
|
|
10
|
+
export class HostedHeartbeatDelegationRejectedError extends Error {
|
|
11
|
+
name = 'HostedHeartbeatDelegationRejectedError';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Converts one product authorization decision into a fully bound Heddle
|
|
15
|
+
* authority bundle. Product code never constructs the delegation wire shape.
|
|
16
|
+
*/
|
|
17
|
+
export class HostedHeartbeatDelegationService {
|
|
18
|
+
#authority;
|
|
19
|
+
#authorizer;
|
|
20
|
+
#runtimeSessionNamespace;
|
|
21
|
+
#maxExecutionMs;
|
|
22
|
+
#now;
|
|
23
|
+
constructor(config) {
|
|
24
|
+
const parsed = DelegationServiceConfigSchema.parse({
|
|
25
|
+
runtimeSessionNamespace: config.runtimeSessionNamespace,
|
|
26
|
+
maxExecutionMs: config.maxExecutionMs,
|
|
27
|
+
});
|
|
28
|
+
this.#authority = config.authority;
|
|
29
|
+
this.#authorizer = config.authorizer;
|
|
30
|
+
this.#runtimeSessionNamespace = parsed.runtimeSessionNamespace;
|
|
31
|
+
this.#maxExecutionMs = parsed.maxExecutionMs;
|
|
32
|
+
this.#now = config.now ?? (() => new Date());
|
|
33
|
+
}
|
|
34
|
+
async issue(rawInput, rawSignal) {
|
|
35
|
+
const input = HostedHeartbeatDelegationRequestSchema.parse(rawInput);
|
|
36
|
+
const signal = rawSignal ?? new AbortController().signal;
|
|
37
|
+
signal.throwIfAborted();
|
|
38
|
+
const rawAuthorization = await this.#authorizer.authorize({
|
|
39
|
+
taskId: input.taskId,
|
|
40
|
+
executionId: input.executionId,
|
|
41
|
+
signal,
|
|
42
|
+
});
|
|
43
|
+
if (!rawAuthorization) {
|
|
44
|
+
throw new HostedHeartbeatDelegationRejectedError('The product rejected this heartbeat execution.');
|
|
45
|
+
}
|
|
46
|
+
const authorization = HostedHeartbeatDelegationAuthorizationSchema.parse(rawAuthorization);
|
|
47
|
+
const runtimeSessionId = createHostedRuntimeSessionId({
|
|
48
|
+
namespace: this.#runtimeSessionNamespace,
|
|
49
|
+
scope: authorization.scope,
|
|
50
|
+
});
|
|
51
|
+
const deadlineAt = dayjs(this.#now())
|
|
52
|
+
.add(this.#maxExecutionMs, 'millisecond')
|
|
53
|
+
.toISOString();
|
|
54
|
+
const issued = await this.#authority.issue({
|
|
55
|
+
scope: authorization.scope,
|
|
56
|
+
runtimeSessionId,
|
|
57
|
+
invocationId: input.executionId,
|
|
58
|
+
workflow: HEARTBEAT_TASK_WORKFLOW,
|
|
59
|
+
mcp: { allowedTools: authorization.allowedTools },
|
|
60
|
+
});
|
|
61
|
+
signal.throwIfAborted();
|
|
62
|
+
const mcpCapability = issued.mcpCapability();
|
|
63
|
+
if (!mcpCapability || !issued.metadata.mcp) {
|
|
64
|
+
throw new Error('Hosted heartbeat delegation requires an MCP-capable execution authority.');
|
|
65
|
+
}
|
|
66
|
+
return HostedHeartbeatDelegationSchema.parse({
|
|
67
|
+
schemaVersion: 1,
|
|
68
|
+
taskId: input.taskId,
|
|
69
|
+
executionId: input.executionId,
|
|
70
|
+
scope: authorization.scope,
|
|
71
|
+
runtimeSessionId,
|
|
72
|
+
deadlineAt,
|
|
73
|
+
authority: {
|
|
74
|
+
metadata: issued.metadata,
|
|
75
|
+
executionAssertion: issued.executionAssertion(),
|
|
76
|
+
mcpCapability,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=hosted-heartbeat-delegation-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted-heartbeat-delegation-service.js","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-delegation-service.ts"],"names":[],"mappings":"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","sourcesContent":["import dayjs from 'dayjs';\nimport { z } from 'zod';\nimport { HEARTBEAT_TASK_WORKFLOW, OpaqueIdSchema } from '../contracts/index.js';\nimport {\n HostedHeartbeatDelegationAuthorizationSchema,\n HostedHeartbeatDelegationRequestSchema,\n HostedHeartbeatDelegationSchema,\n type HostedHeartbeatDelegation,\n type HostedHeartbeatDelegationRequest,\n} from './contracts.js';\nimport { createHostedRuntimeSessionId } from './runtime-session.js';\nimport type {\n HostedHeartbeatDelegationIssuer,\n HostedHeartbeatDelegationServiceConfig,\n} from './types.js';\n\nconst DelegationServiceConfigSchema = z.object({\n runtimeSessionNamespace: OpaqueIdSchema,\n maxExecutionMs: z.number().int().min(1_000).max(Number.MAX_SAFE_INTEGER),\n}).strict();\n\nexport class HostedHeartbeatDelegationRejectedError extends Error {\n readonly name = 'HostedHeartbeatDelegationRejectedError';\n}\n\n/**\n * Converts one product authorization decision into a fully bound Heddle\n * authority bundle. Product code never constructs the delegation wire shape.\n */\nexport class HostedHeartbeatDelegationService\nimplements HostedHeartbeatDelegationIssuer {\n readonly #authority: HostedHeartbeatDelegationServiceConfig['authority'];\n readonly #authorizer: HostedHeartbeatDelegationServiceConfig['authorizer'];\n readonly #runtimeSessionNamespace: string;\n readonly #maxExecutionMs: number;\n readonly #now: () => Date;\n\n constructor(config: HostedHeartbeatDelegationServiceConfig) {\n const parsed = DelegationServiceConfigSchema.parse({\n runtimeSessionNamespace: config.runtimeSessionNamespace,\n maxExecutionMs: config.maxExecutionMs,\n });\n this.#authority = config.authority;\n this.#authorizer = config.authorizer;\n this.#runtimeSessionNamespace = parsed.runtimeSessionNamespace;\n this.#maxExecutionMs = parsed.maxExecutionMs;\n this.#now = config.now ?? (() => new Date());\n }\n\n async issue(\n rawInput: HostedHeartbeatDelegationRequest,\n rawSignal?: AbortSignal,\n ): Promise<HostedHeartbeatDelegation> {\n const input = HostedHeartbeatDelegationRequestSchema.parse(rawInput);\n const signal = rawSignal ?? new AbortController().signal;\n signal.throwIfAborted();\n const rawAuthorization = await this.#authorizer.authorize({\n taskId: input.taskId,\n executionId: input.executionId,\n signal,\n });\n if (!rawAuthorization) {\n throw new HostedHeartbeatDelegationRejectedError(\n 'The product rejected this heartbeat execution.',\n );\n }\n const authorization = HostedHeartbeatDelegationAuthorizationSchema.parse(\n rawAuthorization,\n );\n const runtimeSessionId = createHostedRuntimeSessionId({\n namespace: this.#runtimeSessionNamespace,\n scope: authorization.scope,\n });\n const deadlineAt = dayjs(this.#now())\n .add(this.#maxExecutionMs, 'millisecond')\n .toISOString();\n const issued = await this.#authority.issue({\n scope: authorization.scope,\n runtimeSessionId,\n invocationId: input.executionId,\n workflow: HEARTBEAT_TASK_WORKFLOW,\n mcp: { allowedTools: authorization.allowedTools },\n });\n signal.throwIfAborted();\n const mcpCapability = issued.mcpCapability();\n if (!mcpCapability || !issued.metadata.mcp) {\n throw new Error(\n 'Hosted heartbeat delegation requires an MCP-capable execution authority.',\n );\n }\n\n return HostedHeartbeatDelegationSchema.parse({\n schemaVersion: 1,\n taskId: input.taskId,\n executionId: input.executionId,\n scope: authorization.scope,\n runtimeSessionId,\n deadlineAt,\n authority: {\n metadata: issued.metadata,\n executionAssertion: issued.executionAssertion(),\n mcpCapability,\n },\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HostedHeartbeatTaskReconcilerConfig, HostedHeartbeatTaskReconciliation, HostedHeartbeatTaskReconciliationInput } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Publishes one product's desired catalog while keeping admission paused until
|
|
4
|
+
* every deletion and upsert has succeeded.
|
|
5
|
+
*/
|
|
6
|
+
export declare class HostedHeartbeatTaskReconciler {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(config: HostedHeartbeatTaskReconcilerConfig);
|
|
9
|
+
reconcile(rawInput: HostedHeartbeatTaskReconciliationInput): Promise<HostedHeartbeatTaskReconciliation>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=hosted-heartbeat-task-reconciler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted-heartbeat-task-reconciler.d.ts","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-task-reconciler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,mCAAmC,EACnC,iCAAiC,EACjC,sCAAsC,EACvC,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,qBAAa,6BAA6B;;gBAG5B,MAAM,EAAE,mCAAmC;IAIjD,SAAS,CACb,QAAQ,EAAE,sCAAsC,GAC/C,OAAO,CAAC,iCAAiC,CAAC;CAkC9C"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { HostedHeartbeatDesiredTaskCatalogSchema, } from './contracts.js';
|
|
2
|
+
/**
|
|
3
|
+
* Publishes one product's desired catalog while keeping admission paused until
|
|
4
|
+
* every deletion and upsert has succeeded.
|
|
5
|
+
*/
|
|
6
|
+
export class HostedHeartbeatTaskReconciler {
|
|
7
|
+
#coordinator;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.#coordinator = config.coordinator;
|
|
10
|
+
}
|
|
11
|
+
async reconcile(rawInput) {
|
|
12
|
+
const input = HostedHeartbeatDesiredTaskCatalogSchema.parse({
|
|
13
|
+
tasks: rawInput.desiredTasks,
|
|
14
|
+
resume: rawInput.resume,
|
|
15
|
+
});
|
|
16
|
+
rawInput.signal?.throwIfAborted();
|
|
17
|
+
await this.#coordinator.pause(rawInput.signal);
|
|
18
|
+
const existingTasks = await this.#coordinator.listTasks(rawInput.signal);
|
|
19
|
+
const desiredByTaskId = new Map(input.tasks.map((task) => [task.taskId, task]));
|
|
20
|
+
const deletedTaskIds = existingTasks
|
|
21
|
+
.filter(({ id, workspaceId }) => {
|
|
22
|
+
const desired = desiredByTaskId.get(id);
|
|
23
|
+
return !desired || workspaceId !== desired.input.workspaceId;
|
|
24
|
+
})
|
|
25
|
+
.map(({ id }) => id);
|
|
26
|
+
await Promise.all(deletedTaskIds.map((taskId) => (this.#coordinator.deleteTask(taskId, rawInput.signal))));
|
|
27
|
+
await Promise.all(input.tasks.map(({ taskId, input: task }) => (this.#coordinator.upsertTask(taskId, task, rawInput.signal))));
|
|
28
|
+
if (input.resume) {
|
|
29
|
+
await this.#coordinator.resume(rawInput.signal);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
deleted: deletedTaskIds.length,
|
|
33
|
+
upserted: input.tasks.length,
|
|
34
|
+
resumed: input.resume,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=hosted-heartbeat-task-reconciler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted-heartbeat-task-reconciler.js","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-task-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uCAAuC,GACxC,MAAM,gBAAgB,CAAC;AAOxB;;;GAGG;AACH,MAAM,OAAO,6BAA6B;IAC/B,YAAY,CAAqD;IAE1E,YAAY,MAA2C;QACrD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,SAAS,CACb,QAAgD;QAEhD,MAAM,KAAK,GAAG,uCAAuC,CAAC,KAAK,CAAC;YAC1D,KAAK,EAAE,QAAQ,CAAC,YAAY;YAC5B,MAAM,EAAE,QAAQ,CAAC,MAAM;SACxB,CAAC,CAAC;QACH,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;QAClC,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzE,MAAM,eAAe,GAAG,IAAI,GAAG,CAC7B,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAC/C,CAAC;QACF,MAAM,cAAc,GAAG,aAAa;aACjC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;YAC9B,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACxC,OAAO,CAAC,OAAO,IAAI,WAAW,KAAK,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;QAC/D,CAAC,CAAC;aACD,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QAEvB,MAAM,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAC/C,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CACtD,CAAC,CAAC,CAAC;QACJ,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAC7D,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAC5D,CAAC,CAAC,CAAC;QACJ,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClD,CAAC;QAED,OAAO;YACL,OAAO,EAAE,cAAc,CAAC,MAAM;YAC9B,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;YAC5B,OAAO,EAAE,KAAK,CAAC,MAAM;SACtB,CAAC;IACJ,CAAC;CACF","sourcesContent":["import {\n HostedHeartbeatDesiredTaskCatalogSchema,\n} from './contracts.js';\nimport type {\n HostedHeartbeatTaskReconcilerConfig,\n HostedHeartbeatTaskReconciliation,\n HostedHeartbeatTaskReconciliationInput,\n} from './types.js';\n\n/**\n * Publishes one product's desired catalog while keeping admission paused until\n * every deletion and upsert has succeeded.\n */\nexport class HostedHeartbeatTaskReconciler {\n readonly #coordinator: HostedHeartbeatTaskReconcilerConfig['coordinator'];\n\n constructor(config: HostedHeartbeatTaskReconcilerConfig) {\n this.#coordinator = config.coordinator;\n }\n\n async reconcile(\n rawInput: HostedHeartbeatTaskReconciliationInput,\n ): Promise<HostedHeartbeatTaskReconciliation> {\n const input = HostedHeartbeatDesiredTaskCatalogSchema.parse({\n tasks: rawInput.desiredTasks,\n resume: rawInput.resume,\n });\n rawInput.signal?.throwIfAborted();\n await this.#coordinator.pause(rawInput.signal);\n const existingTasks = await this.#coordinator.listTasks(rawInput.signal);\n const desiredByTaskId = new Map(\n input.tasks.map((task) => [task.taskId, task]),\n );\n const deletedTaskIds = existingTasks\n .filter(({ id, workspaceId }) => {\n const desired = desiredByTaskId.get(id);\n return !desired || workspaceId !== desired.input.workspaceId;\n })\n .map(({ id }) => id);\n\n await Promise.all(deletedTaskIds.map((taskId) => (\n this.#coordinator.deleteTask(taskId, rawInput.signal)\n )));\n await Promise.all(input.tasks.map(({ taskId, input: task }) => (\n this.#coordinator.upsertTask(taskId, task, rawInput.signal)\n )));\n if (input.resume) {\n await this.#coordinator.resume(rawInput.signal);\n }\n\n return {\n deleted: deletedTaskIds.length,\n upserted: input.tasks.length,\n resumed: input.resume,\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { HOSTED_HEARTBEAT_COORDINATOR_PATHS, HOSTED_HEARTBEAT_DELEGATIONS_PATH, HostedHeartbeatCoordinatorTaskInputSchema, HostedHeartbeatCoordinatorTaskListSchema, HostedHeartbeatCoordinatorTaskSummarySchema, HostedHeartbeatDelegationAuthorizationSchema, HostedHeartbeatDelegationRequestSchema, HostedHeartbeatDelegationSchema, HostedHeartbeatDesiredTaskCatalogSchema, HostedHeartbeatDesiredTaskSchema, } from './contracts.js';
|
|
2
|
+
export type { HostedHeartbeatCoordinatorTaskInput, HostedHeartbeatCoordinatorTaskSummary, HostedHeartbeatDelegation, HostedHeartbeatDelegationAuthorization, HostedHeartbeatDelegationRequest, HostedHeartbeatDesiredTask, HostedHeartbeatDesiredTaskCatalog, } from './contracts.js';
|
|
3
|
+
export { HostedHeartbeatCoordinatorClient, HostedHeartbeatCoordinatorRequestError, } from './hosted-heartbeat-coordinator-client.js';
|
|
4
|
+
export { HostedHeartbeatDelegatedExecutionTransport, HostedHeartbeatDelegationClient, HostedHeartbeatDelegationRequestError, } from './hosted-heartbeat-delegation-client.js';
|
|
5
|
+
export { HostedHeartbeatDelegationRejectedError, HostedHeartbeatDelegationService, } from './hosted-heartbeat-delegation-service.js';
|
|
6
|
+
export { HostedHeartbeatTaskReconciler } from './hosted-heartbeat-task-reconciler.js';
|
|
7
|
+
export { createHostedRuntimeSessionId } from './runtime-session.js';
|
|
8
|
+
export type { HostedHeartbeatCoordinatorClientConfig, HostedHeartbeatCoordinatorTaskApi, HostedHeartbeatDelegatedExecutionTransportConfig, HostedHeartbeatDelegatedExecutionTransportPort, HostedHeartbeatDelegationAuthorizationInput, HostedHeartbeatDelegationAuthorizer, HostedHeartbeatDelegationClientConfig, HostedHeartbeatDelegationIssuer, HostedHeartbeatDelegationServiceConfig, HostedHeartbeatTaskReconcilerConfig, HostedHeartbeatTaskReconciliation, HostedHeartbeatTaskReconciliationInput, } from './types.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/coordinator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,yCAAyC,EACzC,wCAAwC,EACxC,2CAA2C,EAC3C,4CAA4C,EAC5C,sCAAsC,EACtC,+BAA+B,EAC/B,uCAAuC,EACvC,gCAAgC,GACjC,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mCAAmC,EACnC,qCAAqC,EACrC,yBAAyB,EACzB,sCAAsC,EACtC,gCAAgC,EAChC,0BAA0B,EAC1B,iCAAiC,GAClC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,gCAAgC,EAChC,sCAAsC,GACvC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACL,0CAA0C,EAC1C,+BAA+B,EAC/B,qCAAqC,GACtC,MAAM,yCAAyC,CAAC;AACjD,OAAO,EACL,sCAAsC,EACtC,gCAAgC,GACjC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AACtF,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AACpE,YAAY,EACV,sCAAsC,EACtC,iCAAiC,EACjC,gDAAgD,EAChD,8CAA8C,EAC9C,2CAA2C,EAC3C,mCAAmC,EACnC,qCAAqC,EACrC,+BAA+B,EAC/B,sCAAsC,EACtC,mCAAmC,EACnC,iCAAiC,EACjC,sCAAsC,GACvC,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { HOSTED_HEARTBEAT_COORDINATOR_PATHS, HOSTED_HEARTBEAT_DELEGATIONS_PATH, HostedHeartbeatCoordinatorTaskInputSchema, HostedHeartbeatCoordinatorTaskListSchema, HostedHeartbeatCoordinatorTaskSummarySchema, HostedHeartbeatDelegationAuthorizationSchema, HostedHeartbeatDelegationRequestSchema, HostedHeartbeatDelegationSchema, HostedHeartbeatDesiredTaskCatalogSchema, HostedHeartbeatDesiredTaskSchema, } from './contracts.js';
|
|
2
|
+
export { HostedHeartbeatCoordinatorClient, HostedHeartbeatCoordinatorRequestError, } from './hosted-heartbeat-coordinator-client.js';
|
|
3
|
+
export { HostedHeartbeatDelegatedExecutionTransport, HostedHeartbeatDelegationClient, HostedHeartbeatDelegationRequestError, } from './hosted-heartbeat-delegation-client.js';
|
|
4
|
+
export { HostedHeartbeatDelegationRejectedError, HostedHeartbeatDelegationService, } from './hosted-heartbeat-delegation-service.js';
|
|
5
|
+
export { HostedHeartbeatTaskReconciler } from './hosted-heartbeat-task-reconciler.js';
|
|
6
|
+
export { createHostedRuntimeSessionId } from './runtime-session.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/coordinator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,yCAAyC,EACzC,wCAAwC,EACxC,2CAA2C,EAC3C,4CAA4C,EAC5C,sCAAsC,EACtC,+BAA+B,EAC/B,uCAAuC,EACvC,gCAAgC,GACjC,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EACL,gCAAgC,EAChC,sCAAsC,GACvC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACL,0CAA0C,EAC1C,+BAA+B,EAC/B,qCAAqC,GACtC,MAAM,yCAAyC,CAAC;AACjD,OAAO,EACL,sCAAsC,EACtC,gCAAgC,GACjC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AACtF,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["export {\n HOSTED_HEARTBEAT_COORDINATOR_PATHS,\n HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n HostedHeartbeatCoordinatorTaskInputSchema,\n HostedHeartbeatCoordinatorTaskListSchema,\n HostedHeartbeatCoordinatorTaskSummarySchema,\n HostedHeartbeatDelegationAuthorizationSchema,\n HostedHeartbeatDelegationRequestSchema,\n HostedHeartbeatDelegationSchema,\n HostedHeartbeatDesiredTaskCatalogSchema,\n HostedHeartbeatDesiredTaskSchema,\n} from './contracts.js';\nexport type {\n HostedHeartbeatCoordinatorTaskInput,\n HostedHeartbeatCoordinatorTaskSummary,\n HostedHeartbeatDelegation,\n HostedHeartbeatDelegationAuthorization,\n HostedHeartbeatDelegationRequest,\n HostedHeartbeatDesiredTask,\n HostedHeartbeatDesiredTaskCatalog,\n} from './contracts.js';\nexport {\n HostedHeartbeatCoordinatorClient,\n HostedHeartbeatCoordinatorRequestError,\n} from './hosted-heartbeat-coordinator-client.js';\nexport {\n HostedHeartbeatDelegatedExecutionTransport,\n HostedHeartbeatDelegationClient,\n HostedHeartbeatDelegationRequestError,\n} from './hosted-heartbeat-delegation-client.js';\nexport {\n HostedHeartbeatDelegationRejectedError,\n HostedHeartbeatDelegationService,\n} from './hosted-heartbeat-delegation-service.js';\nexport { HostedHeartbeatTaskReconciler } from './hosted-heartbeat-task-reconciler.js';\nexport { createHostedRuntimeSessionId } from './runtime-session.js';\nexport type {\n HostedHeartbeatCoordinatorClientConfig,\n HostedHeartbeatCoordinatorTaskApi,\n HostedHeartbeatDelegatedExecutionTransportConfig,\n HostedHeartbeatDelegatedExecutionTransportPort,\n HostedHeartbeatDelegationAuthorizationInput,\n HostedHeartbeatDelegationAuthorizer,\n HostedHeartbeatDelegationClientConfig,\n HostedHeartbeatDelegationIssuer,\n HostedHeartbeatDelegationServiceConfig,\n HostedHeartbeatTaskReconcilerConfig,\n HostedHeartbeatTaskReconciliation,\n HostedHeartbeatTaskReconciliationInput,\n} from './types.js';\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { NodeHostedHeartbeatDelegationHttpHandler, NodeHostedHeartbeatDelegationHttpServiceConfig } from './types.js';
|
|
3
|
+
/** Standard authenticated Node HTTP edge for product-owned run authority. */
|
|
4
|
+
export declare class NodeHostedHeartbeatDelegationHttpService implements NodeHostedHeartbeatDelegationHttpHandler {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(config: NodeHostedHeartbeatDelegationHttpServiceConfig);
|
|
7
|
+
handle(request: IncomingMessage, response: ServerResponse): boolean;
|
|
8
|
+
handleDelegation(request: IncomingMessage, response: ServerResponse): void;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=delegation-http-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation-http-service.d.ts","sourceRoot":"","sources":["../../../src/coordinator/node/delegation-http-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAoBjE,OAAO,KAAK,EAEV,wCAAwC,EACxC,8CAA8C,EAC/C,MAAM,YAAY,CAAC;AAWpB,6EAA6E;AAC7E,qBAAa,wCACb,YAAW,wCAAwC;;gBAcrC,MAAM,EAAE,8CAA8C;IAclE,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO;IAQnE,gBAAgB,CACd,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,GACvB,IAAI;IA0DP,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAkEvB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { z, ZodError } from 'zod';
|
|
3
|
+
import { HOSTED_HEARTBEAT_DELEGATIONS_PATH, HostedHeartbeatDelegationRequestSchema, } from '../contracts.js';
|
|
4
|
+
import { HostedHeartbeatDelegationRejectedError, } from '../hosted-heartbeat-delegation-service.js';
|
|
5
|
+
import { HostedHeartbeatServiceTokenSchema } from '../service-token.js';
|
|
6
|
+
import { NodeHttpPathSchema, NodeHttpRequestError, errorType, readJsonBody, readPathname, takeAuthorization, writeJson, writeJsonError, } from '../../node/http-utils.js';
|
|
7
|
+
const DEFAULT_MAX_BODY_BYTES = 16 * 1_024;
|
|
8
|
+
/** Standard authenticated Node HTTP edge for product-owned run authority. */
|
|
9
|
+
export class NodeHostedHeartbeatDelegationHttpService {
|
|
10
|
+
#delegations;
|
|
11
|
+
#tokenDigest;
|
|
12
|
+
#path;
|
|
13
|
+
#maxBodyBytes;
|
|
14
|
+
#reportFailure;
|
|
15
|
+
#active = new Set();
|
|
16
|
+
#closed = false;
|
|
17
|
+
#closePromise;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.#delegations = config.delegations;
|
|
20
|
+
this.#tokenDigest = digest(HostedHeartbeatServiceTokenSchema.parse(config.apiToken));
|
|
21
|
+
this.#path = NodeHttpPathSchema.parse(config.path ?? HOSTED_HEARTBEAT_DELEGATIONS_PATH);
|
|
22
|
+
this.#maxBodyBytes = z.number().int().min(1).max(1_048_576).parse(config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
|
|
23
|
+
this.#reportFailure = config.reportFailure;
|
|
24
|
+
}
|
|
25
|
+
handle(request, response) {
|
|
26
|
+
if (readPathname(request.url) !== this.#path) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
this.handleDelegation(request, response);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
handleDelegation(request, response) {
|
|
33
|
+
if (this.#closed) {
|
|
34
|
+
request.resume();
|
|
35
|
+
writeJsonError(response, 503, 'Hosted execution is unavailable.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
let authorization;
|
|
39
|
+
try {
|
|
40
|
+
authorization = takeAuthorization(request);
|
|
41
|
+
if (request.method !== 'POST') {
|
|
42
|
+
request.resume();
|
|
43
|
+
writeJsonError(response, 405, 'Method not allowed.', {
|
|
44
|
+
Allow: 'POST',
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
request.resume();
|
|
51
|
+
writeJsonError(response, error instanceof NodeHttpRequestError ? error.statusCode : 400, 'Invalid heartbeat delegation request.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!this.#authenticates(authorization)) {
|
|
55
|
+
request.resume();
|
|
56
|
+
writeJsonError(response, 401, 'Authentication is required.', {
|
|
57
|
+
'WWW-Authenticate': 'Bearer',
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const abortController = new AbortController();
|
|
62
|
+
const abort = () => abortController.abort(new Error('The heartbeat delegation request closed.'));
|
|
63
|
+
request.once('aborted', abort);
|
|
64
|
+
response.once('close', abort);
|
|
65
|
+
const active = {
|
|
66
|
+
abortController,
|
|
67
|
+
request,
|
|
68
|
+
response,
|
|
69
|
+
pending: Promise.resolve(),
|
|
70
|
+
};
|
|
71
|
+
active.pending = this.#serve(request, response, abortController.signal).finally(() => {
|
|
72
|
+
request.removeListener('aborted', abort);
|
|
73
|
+
response.removeListener('close', abort);
|
|
74
|
+
this.#active.delete(active);
|
|
75
|
+
});
|
|
76
|
+
this.#active.add(active);
|
|
77
|
+
}
|
|
78
|
+
close() {
|
|
79
|
+
if (!this.#closePromise) {
|
|
80
|
+
this.#closed = true;
|
|
81
|
+
const active = [...this.#active];
|
|
82
|
+
active.forEach(({ abortController, request, response }) => {
|
|
83
|
+
abortController.abort(new Error('The heartbeat delegation service is stopping.'));
|
|
84
|
+
request.destroy();
|
|
85
|
+
response.destroy();
|
|
86
|
+
});
|
|
87
|
+
this.#closePromise = Promise.allSettled(active.map(({ pending }) => pending)).then(() => undefined);
|
|
88
|
+
}
|
|
89
|
+
return this.#closePromise;
|
|
90
|
+
}
|
|
91
|
+
async #serve(request, response, signal) {
|
|
92
|
+
try {
|
|
93
|
+
const input = HostedHeartbeatDelegationRequestSchema.parse(await readJsonBody(request, this.#maxBodyBytes, signal));
|
|
94
|
+
const delegation = await this.#delegations.issue(input, signal);
|
|
95
|
+
writeJson(response, 200, delegation);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (signal.aborted) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (error instanceof HostedHeartbeatDelegationRejectedError) {
|
|
102
|
+
writeJsonError(response, 403, error.message);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (error instanceof NodeHttpRequestError || error instanceof ZodError) {
|
|
106
|
+
writeJsonError(response, error instanceof NodeHttpRequestError ? error.statusCode : 400, 'Invalid heartbeat delegation request.');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.#report({ phase: 'delegation', errorType: errorType(error) });
|
|
110
|
+
writeJsonError(response, 500, 'Heartbeat delegation failed.');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
#authenticates(authorization) {
|
|
114
|
+
const token = /^Bearer ([^\s]+)$/i.exec(authorization?.trim() ?? '')?.[1];
|
|
115
|
+
return token
|
|
116
|
+
? timingSafeEqual(digest(token), this.#tokenDigest)
|
|
117
|
+
: false;
|
|
118
|
+
}
|
|
119
|
+
#report(failure) {
|
|
120
|
+
try {
|
|
121
|
+
this.#reportFailure?.(Object.freeze({ ...failure }));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Observability must not change request settlement.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function digest(value) {
|
|
129
|
+
return createHash('sha256').update(value, 'utf8').digest();
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=delegation-http-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation-http-service.js","sourceRoot":"","sources":["../../../src/coordinator/node/delegation-http-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { z, ZodError } from 'zod';\nimport {\n HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n HostedHeartbeatDelegationRequestSchema,\n} from '../contracts.js';\nimport {\n HostedHeartbeatDelegationRejectedError,\n} from '../hosted-heartbeat-delegation-service.js';\nimport { HostedHeartbeatServiceTokenSchema } from '../service-token.js';\nimport {\n NodeHttpPathSchema,\n NodeHttpRequestError,\n errorType,\n readJsonBody,\n readPathname,\n takeAuthorization,\n writeJson,\n writeJsonError,\n} from '../../node/http-utils.js';\nimport type {\n NodeHostedHeartbeatDelegationFailure,\n NodeHostedHeartbeatDelegationHttpHandler,\n NodeHostedHeartbeatDelegationHttpServiceConfig,\n} from './types.js';\n\nconst DEFAULT_MAX_BODY_BYTES = 16 * 1_024;\n\ntype ActiveDelegation = {\n abortController: AbortController;\n request: IncomingMessage;\n response: ServerResponse;\n pending: Promise<void>;\n};\n\n/** Standard authenticated Node HTTP edge for product-owned run authority. */\nexport class NodeHostedHeartbeatDelegationHttpService\nimplements NodeHostedHeartbeatDelegationHttpHandler {\n readonly #delegations: NodeHostedHeartbeatDelegationHttpServiceConfig[\n 'delegations'\n ];\n readonly #tokenDigest: Buffer;\n readonly #path: string;\n readonly #maxBodyBytes: number;\n readonly #reportFailure: NodeHostedHeartbeatDelegationHttpServiceConfig[\n 'reportFailure'\n ];\n readonly #active = new Set<ActiveDelegation>();\n #closed = false;\n #closePromise: Promise<void> | undefined;\n\n constructor(config: NodeHostedHeartbeatDelegationHttpServiceConfig) {\n this.#delegations = config.delegations;\n this.#tokenDigest = digest(\n HostedHeartbeatServiceTokenSchema.parse(config.apiToken),\n );\n this.#path = NodeHttpPathSchema.parse(\n config.path ?? HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n );\n this.#maxBodyBytes = z.number().int().min(1).max(1_048_576).parse(\n config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n );\n this.#reportFailure = config.reportFailure;\n }\n\n handle(request: IncomingMessage, response: ServerResponse): boolean {\n if (readPathname(request.url) !== this.#path) {\n return false;\n }\n this.handleDelegation(request, response);\n return true;\n }\n\n handleDelegation(\n request: IncomingMessage,\n response: ServerResponse,\n ): void {\n if (this.#closed) {\n request.resume();\n writeJsonError(response, 503, 'Hosted execution is unavailable.');\n return;\n }\n\n let authorization: string | undefined;\n try {\n authorization = takeAuthorization(request);\n if (request.method !== 'POST') {\n request.resume();\n writeJsonError(response, 405, 'Method not allowed.', {\n Allow: 'POST',\n });\n return;\n }\n } catch (error) {\n request.resume();\n writeJsonError(\n response,\n error instanceof NodeHttpRequestError ? error.statusCode : 400,\n 'Invalid heartbeat delegation request.',\n );\n return;\n }\n if (!this.#authenticates(authorization)) {\n request.resume();\n writeJsonError(response, 401, 'Authentication is required.', {\n 'WWW-Authenticate': 'Bearer',\n });\n return;\n }\n\n const abortController = new AbortController();\n const abort = () => abortController.abort(\n new Error('The heartbeat delegation request closed.'),\n );\n request.once('aborted', abort);\n response.once('close', abort);\n const active: ActiveDelegation = {\n abortController,\n request,\n response,\n pending: Promise.resolve(),\n };\n active.pending = this.#serve(\n request,\n response,\n abortController.signal,\n ).finally(() => {\n request.removeListener('aborted', abort);\n response.removeListener('close', abort);\n this.#active.delete(active);\n });\n this.#active.add(active);\n }\n\n close(): Promise<void> {\n if (!this.#closePromise) {\n this.#closed = true;\n const active = [...this.#active];\n active.forEach(({ abortController, request, response }) => {\n abortController.abort(\n new Error('The heartbeat delegation service is stopping.'),\n );\n request.destroy();\n response.destroy();\n });\n this.#closePromise = Promise.allSettled(\n active.map(({ pending }) => pending),\n ).then(() => undefined);\n }\n return this.#closePromise;\n }\n\n async #serve(\n request: IncomingMessage,\n response: ServerResponse,\n signal: AbortSignal,\n ): Promise<void> {\n try {\n const input = HostedHeartbeatDelegationRequestSchema.parse(\n await readJsonBody(request, this.#maxBodyBytes, signal),\n );\n const delegation = await this.#delegations.issue(input, signal);\n writeJson(response, 200, delegation);\n } catch (error) {\n if (signal.aborted) {\n return;\n }\n if (error instanceof HostedHeartbeatDelegationRejectedError) {\n writeJsonError(response, 403, error.message);\n return;\n }\n if (error instanceof NodeHttpRequestError || error instanceof ZodError) {\n writeJsonError(\n response,\n error instanceof NodeHttpRequestError ? error.statusCode : 400,\n 'Invalid heartbeat delegation request.',\n );\n return;\n }\n this.#report({ phase: 'delegation', errorType: errorType(error) });\n writeJsonError(response, 500, 'Heartbeat delegation failed.');\n }\n }\n\n #authenticates(authorization: string | undefined): boolean {\n const token = /^Bearer ([^\\s]+)$/i.exec(\n authorization?.trim() ?? '',\n )?.[1];\n return token\n ? timingSafeEqual(digest(token), this.#tokenDigest)\n : false;\n }\n\n #report(failure: NodeHostedHeartbeatDelegationFailure): void {\n try {\n this.#reportFailure?.(Object.freeze({ ...failure }));\n } catch {\n // Observability must not change request settlement.\n }\n }\n}\n\nfunction digest(value: string): Buffer {\n return createHash('sha256').update(value, 'utf8').digest();\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../../src/coordinator/node/environment.ts"],"names":[],"mappings":"AAQA,6EAA6E;AAC7E,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,MAAM,CAAC,UAAU,EAC9B,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,SAAS,CAOpB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { HostedHeartbeatServiceTokenSchema } from '../service-token.js';
|
|
3
|
+
const EnvironmentNameSchema = z.string().min(1).max(128).regex(/^[A-Za-z_][A-Za-z0-9_]*$/, 'must be an environment variable name');
|
|
4
|
+
/** Removes one optional coordinator secret from the supplied environment. */
|
|
5
|
+
export function takeHostedHeartbeatServiceToken(environment, rawName) {
|
|
6
|
+
const name = EnvironmentNameSchema.parse(rawName);
|
|
7
|
+
const token = environment[name];
|
|
8
|
+
delete environment[name];
|
|
9
|
+
return token?.trim()
|
|
10
|
+
? HostedHeartbeatServiceTokenSchema.parse(token)
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=environment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../../src/coordinator/node/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iCAAiC,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAC5D,0BAA0B,EAC1B,sCAAsC,CACvC,CAAC;AAEF,6EAA6E;AAC7E,MAAM,UAAU,+BAA+B,CAC7C,WAA8B,EAC9B,OAAe;IAEf,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAChC,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,KAAK,EAAE,IAAI,EAAE;QAClB,CAAC,CAAC,iCAAiC,CAAC,KAAK,CAAC,KAAK,CAAC;QAChD,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC","sourcesContent":["import { z } from 'zod';\nimport { HostedHeartbeatServiceTokenSchema } from '../service-token.js';\n\nconst EnvironmentNameSchema = z.string().min(1).max(128).regex(\n /^[A-Za-z_][A-Za-z0-9_]*$/,\n 'must be an environment variable name',\n);\n\n/** Removes one optional coordinator secret from the supplied environment. */\nexport function takeHostedHeartbeatServiceToken(\n environment: NodeJS.ProcessEnv,\n rawName: string,\n): string | undefined {\n const name = EnvironmentNameSchema.parse(rawName);\n const token = environment[name];\n delete environment[name];\n return token?.trim()\n ? HostedHeartbeatServiceTokenSchema.parse(token)\n : undefined;\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { NodeHostedHeartbeatDelegationHttpService } from './delegation-http-service.js';
|
|
2
|
+
export { takeHostedHeartbeatServiceToken } from './environment.js';
|
|
3
|
+
export type { NodeHostedHeartbeatDelegationFailure, NodeHostedHeartbeatDelegationHttpHandler, NodeHostedHeartbeatDelegationHttpServiceConfig, } from './types.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/coordinator/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wCAAwC,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAC;AACnE,YAAY,EACV,oCAAoC,EACpC,wCAAwC,EACxC,8CAA8C,GAC/C,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/coordinator/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wCAAwC,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export { NodeHostedHeartbeatDelegationHttpService } from './delegation-http-service.js';\nexport { takeHostedHeartbeatServiceToken } from './environment.js';\nexport type {\n NodeHostedHeartbeatDelegationFailure,\n NodeHostedHeartbeatDelegationHttpHandler,\n NodeHostedHeartbeatDelegationHttpServiceConfig,\n} from './types.js';\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { HostedHeartbeatDelegationIssuer } from '../types.js';
|
|
3
|
+
export type NodeHostedHeartbeatDelegationFailure = {
|
|
4
|
+
phase: 'delegation';
|
|
5
|
+
errorType: string;
|
|
6
|
+
};
|
|
7
|
+
export type NodeHostedHeartbeatDelegationHttpServiceConfig = {
|
|
8
|
+
delegations: HostedHeartbeatDelegationIssuer;
|
|
9
|
+
apiToken: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
maxBodyBytes?: number;
|
|
12
|
+
/** Receives credential-free operational metadata, never the raw error. */
|
|
13
|
+
reportFailure?: (failure: NodeHostedHeartbeatDelegationFailure) => void;
|
|
14
|
+
};
|
|
15
|
+
export interface NodeHostedHeartbeatDelegationHttpHandler {
|
|
16
|
+
handle(request: IncomingMessage, response: ServerResponse): boolean;
|
|
17
|
+
handleDelegation(request: IncomingMessage, response: ServerResponse): void;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/coordinator/node/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,MAAM,oCAAoC,GAAG;IACjD,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,8CAA8C,GAAG;IAC3D,WAAW,EAAE,+BAA+B,CAAC;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,oCAAoC,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,MAAM,WAAW,wCAAwC;IACvD,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC;IACpE,gBAAgB,CACd,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,GACvB,IAAI,CAAC;IACR,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/coordinator/node/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { HostedHeartbeatDelegationIssuer } from '../types.js';\n\nexport type NodeHostedHeartbeatDelegationFailure = {\n phase: 'delegation';\n errorType: string;\n};\n\nexport type NodeHostedHeartbeatDelegationHttpServiceConfig = {\n delegations: HostedHeartbeatDelegationIssuer;\n apiToken: string;\n path?: string;\n maxBodyBytes?: number;\n /** Receives credential-free operational metadata, never the raw error. */\n reportFailure?: (failure: NodeHostedHeartbeatDelegationFailure) => void;\n};\n\nexport interface NodeHostedHeartbeatDelegationHttpHandler {\n handle(request: IncomingMessage, response: ServerResponse): boolean;\n handleDelegation(\n request: IncomingMessage,\n response: ServerResponse,\n ): void;\n close(): Promise<void>;\n}\n"]}
|