@exaudeus/workrail 0.17.0 → 1.0.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/dist/di/container.js +8 -0
- package/dist/di/tokens.d.ts +2 -0
- package/dist/di/tokens.js +2 -0
- package/dist/infrastructure/session/HttpServer.d.ts +2 -1
- package/dist/infrastructure/session/HttpServer.js +34 -10
- package/dist/infrastructure/session/SessionManager.js +19 -1
- package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +26 -2
- package/dist/infrastructure/storage/file-workflow-storage.js +4 -4
- package/dist/infrastructure/storage/git-workflow-storage.d.ts +0 -1
- package/dist/infrastructure/storage/git-workflow-storage.js +28 -29
- package/dist/infrastructure/storage/plugin-workflow-storage.js +11 -5
- package/dist/manifest.json +134 -70
- package/dist/mcp/handlers/v2-execution-helpers.d.ts +4 -4
- package/dist/mcp/handlers/v2-execution-helpers.js +29 -0
- package/dist/mcp/handlers/v2-execution.js +131 -101
- package/dist/mcp/output-schemas.d.ts +110 -21
- package/dist/mcp/output-schemas.js +8 -11
- package/dist/mcp/server.js +11 -3
- package/dist/mcp/types.d.ts +2 -6
- package/dist/utils/storage-security.js +15 -1
- package/dist/v2/durable-core/encoding/base32-lower.d.ts +1 -0
- package/dist/v2/durable-core/encoding/base32-lower.js +28 -0
- package/dist/v2/durable-core/ids/index.d.ts +4 -0
- package/dist/v2/durable-core/ids/index.js +7 -0
- package/dist/v2/durable-core/ids/workflow-hash-ref.d.ts +7 -0
- package/dist/v2/durable-core/ids/workflow-hash-ref.js +23 -0
- package/dist/v2/durable-core/tokens/binary-payload.d.ts +35 -0
- package/dist/v2/durable-core/tokens/binary-payload.js +279 -0
- package/dist/v2/durable-core/tokens/index.d.ts +9 -4
- package/dist/v2/durable-core/tokens/index.js +17 -7
- package/dist/v2/durable-core/tokens/payloads.d.ts +12 -8
- package/dist/v2/durable-core/tokens/payloads.js +5 -3
- package/dist/v2/durable-core/tokens/token-codec-capabilities.d.ts +4 -0
- package/dist/v2/durable-core/tokens/token-codec-capabilities.js +2 -0
- package/dist/v2/durable-core/tokens/token-codec-ports.d.ts +42 -0
- package/dist/v2/durable-core/tokens/token-codec-ports.js +27 -0
- package/dist/v2/durable-core/tokens/token-codec.d.ts +18 -0
- package/dist/v2/durable-core/tokens/token-codec.js +108 -0
- package/dist/v2/durable-core/tokens/token-signer.d.ts +13 -1
- package/dist/v2/durable-core/tokens/token-signer.js +65 -0
- package/dist/v2/infra/local/base32/index.d.ts +6 -0
- package/dist/v2/infra/local/base32/index.js +44 -0
- package/dist/v2/infra/local/bech32m/index.d.ts +8 -0
- package/dist/v2/infra/local/bech32m/index.js +56 -0
- package/dist/v2/infra/local/data-dir/index.d.ts +1 -0
- package/dist/v2/infra/local/data-dir/index.js +5 -2
- package/dist/v2/infra/local/fs/index.js +3 -0
- package/dist/v2/infra/local/session-store/index.js +38 -4
- package/dist/v2/ports/base32.port.d.ts +16 -0
- package/dist/v2/ports/base32.port.js +2 -0
- package/dist/v2/ports/bech32m.port.d.ts +11 -0
- package/dist/v2/ports/bech32m.port.js +2 -0
- package/package.json +20 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type { AttemptId, NodeId, RunId, SessionId, TokenStringV1,
|
|
2
|
+
import type { AttemptId, NodeId, RunId, SessionId, TokenStringV1, WorkflowHashRef } from '../ids/index.js';
|
|
3
3
|
export type TokenVersionV1 = 1;
|
|
4
4
|
export type TokenKindV1 = 'state' | 'ack' | 'checkpoint';
|
|
5
5
|
export declare const AttemptIdSchema: z.ZodEffects<z.ZodString, AttemptId, string>;
|
|
@@ -12,7 +12,7 @@ export declare const StateTokenPayloadV1Schema: z.ZodObject<{
|
|
|
12
12
|
sessionId: z.ZodEffects<z.ZodString, SessionId, string>;
|
|
13
13
|
runId: z.ZodEffects<z.ZodString, RunId, string>;
|
|
14
14
|
nodeId: z.ZodEffects<z.ZodString, NodeId, string>;
|
|
15
|
-
|
|
15
|
+
workflowHashRef: z.ZodEffects<z.ZodString, WorkflowHashRef, string>;
|
|
16
16
|
}, "strip", z.ZodTypeAny, {
|
|
17
17
|
sessionId: string & {
|
|
18
18
|
readonly __brand: "v2.SessionId";
|
|
@@ -23,16 +23,18 @@ export declare const StateTokenPayloadV1Schema: z.ZodObject<{
|
|
|
23
23
|
nodeId: string & {
|
|
24
24
|
readonly __brand: "v2.NodeId";
|
|
25
25
|
};
|
|
26
|
-
workflowHash: never;
|
|
27
26
|
tokenVersion: 1;
|
|
28
27
|
tokenKind: "state";
|
|
28
|
+
workflowHashRef: string & {
|
|
29
|
+
readonly __brand: "v2.WorkflowHashRef";
|
|
30
|
+
};
|
|
29
31
|
}, {
|
|
30
32
|
sessionId: string;
|
|
31
33
|
runId: string;
|
|
32
34
|
nodeId: string;
|
|
33
|
-
workflowHash: string;
|
|
34
35
|
tokenVersion: 1;
|
|
35
36
|
tokenKind: "state";
|
|
37
|
+
workflowHashRef: string;
|
|
36
38
|
}>;
|
|
37
39
|
export type StateTokenPayloadV1 = z.infer<typeof StateTokenPayloadV1Schema> & {
|
|
38
40
|
readonly tokenVersion: TokenVersionV1;
|
|
@@ -40,7 +42,7 @@ export type StateTokenPayloadV1 = z.infer<typeof StateTokenPayloadV1Schema> & {
|
|
|
40
42
|
readonly sessionId: SessionId;
|
|
41
43
|
readonly runId: RunId;
|
|
42
44
|
readonly nodeId: NodeId;
|
|
43
|
-
readonly
|
|
45
|
+
readonly workflowHashRef: WorkflowHashRef;
|
|
44
46
|
};
|
|
45
47
|
export declare const AckTokenPayloadV1Schema: z.ZodObject<{
|
|
46
48
|
tokenVersion: z.ZodLiteral<1>;
|
|
@@ -124,7 +126,7 @@ export declare const TokenPayloadV1Schema: z.ZodDiscriminatedUnion<"tokenKind",
|
|
|
124
126
|
sessionId: z.ZodEffects<z.ZodString, SessionId, string>;
|
|
125
127
|
runId: z.ZodEffects<z.ZodString, RunId, string>;
|
|
126
128
|
nodeId: z.ZodEffects<z.ZodString, NodeId, string>;
|
|
127
|
-
|
|
129
|
+
workflowHashRef: z.ZodEffects<z.ZodString, WorkflowHashRef, string>;
|
|
128
130
|
}, "strip", z.ZodTypeAny, {
|
|
129
131
|
sessionId: string & {
|
|
130
132
|
readonly __brand: "v2.SessionId";
|
|
@@ -135,16 +137,18 @@ export declare const TokenPayloadV1Schema: z.ZodDiscriminatedUnion<"tokenKind",
|
|
|
135
137
|
nodeId: string & {
|
|
136
138
|
readonly __brand: "v2.NodeId";
|
|
137
139
|
};
|
|
138
|
-
workflowHash: never;
|
|
139
140
|
tokenVersion: 1;
|
|
140
141
|
tokenKind: "state";
|
|
142
|
+
workflowHashRef: string & {
|
|
143
|
+
readonly __brand: "v2.WorkflowHashRef";
|
|
144
|
+
};
|
|
141
145
|
}, {
|
|
142
146
|
sessionId: string;
|
|
143
147
|
runId: string;
|
|
144
148
|
nodeId: string;
|
|
145
|
-
workflowHash: string;
|
|
146
149
|
tokenVersion: 1;
|
|
147
150
|
tokenKind: "state";
|
|
151
|
+
workflowHashRef: string;
|
|
148
152
|
}>, z.ZodObject<{
|
|
149
153
|
tokenVersion: z.ZodLiteral<1>;
|
|
150
154
|
tokenKind: z.ZodLiteral<"ack">;
|
|
@@ -5,8 +5,10 @@ exports.expectedPrefixForTokenKind = expectedPrefixForTokenKind;
|
|
|
5
5
|
exports.asTokenString = asTokenString;
|
|
6
6
|
const zod_1 = require("zod");
|
|
7
7
|
const index_js_1 = require("../ids/index.js");
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const workflowHashRefSchema = zod_1.z
|
|
9
|
+
.string()
|
|
10
|
+
.regex(/^wf_[a-z2-7]{26}$/, 'Expected wf_<26 base32 chars [a-z2-7]>')
|
|
11
|
+
.transform((v) => (0, index_js_1.asWorkflowHashRef)(v));
|
|
10
12
|
const nonEmpty = zod_1.z.string().min(1);
|
|
11
13
|
const delimiterSafeId = nonEmpty.regex(/^[^:\s]+$/, 'Expected a delimiter-safe ID (no ":" or whitespace)');
|
|
12
14
|
exports.AttemptIdSchema = delimiterSafeId.transform(index_js_1.asAttemptId);
|
|
@@ -19,7 +21,7 @@ exports.StateTokenPayloadV1Schema = zod_1.z.object({
|
|
|
19
21
|
sessionId: exports.SessionIdSchema,
|
|
20
22
|
runId: exports.RunIdSchema,
|
|
21
23
|
nodeId: exports.NodeIdSchema,
|
|
22
|
-
|
|
24
|
+
workflowHashRef: workflowHashRefSchema,
|
|
23
25
|
});
|
|
24
26
|
exports.AckTokenPayloadV1Schema = zod_1.z.object({
|
|
25
27
|
tokenVersion: zod_1.z.literal(1),
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TokenCodecPorts } from './token-codec-ports.js';
|
|
2
|
+
export type TokenParsePorts = Pick<TokenCodecPorts, 'bech32m' | 'base32'>;
|
|
3
|
+
export type TokenVerifyPorts = Pick<TokenCodecPorts, 'keyring' | 'hmac' | 'base64url'>;
|
|
4
|
+
export type TokenSignPorts = TokenCodecPorts;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { KeyringV1 } from '../../ports/keyring.port.js';
|
|
3
|
+
import type { HmacSha256PortV2 } from '../../ports/hmac-sha256.port.js';
|
|
4
|
+
import type { Base64UrlPortV2 } from '../../ports/base64url.port.js';
|
|
5
|
+
import type { Base32PortV2 } from '../../ports/base32.port.js';
|
|
6
|
+
import type { Bech32mPortV2 } from '../../ports/bech32m.port.js';
|
|
7
|
+
declare const tokenCodecPortsBrand: unique symbol;
|
|
8
|
+
export type TokenCodecPorts = Readonly<{
|
|
9
|
+
readonly keyring: KeyringV1;
|
|
10
|
+
readonly hmac: HmacSha256PortV2;
|
|
11
|
+
readonly base64url: Base64UrlPortV2;
|
|
12
|
+
readonly base32: Base32PortV2;
|
|
13
|
+
readonly bech32m: Bech32mPortV2;
|
|
14
|
+
}> & {
|
|
15
|
+
readonly [tokenCodecPortsBrand]: 'TokenCodecPorts';
|
|
16
|
+
};
|
|
17
|
+
export type TokenCodecPortsError = {
|
|
18
|
+
readonly code: 'TOKEN_CODEC_PORTS_MISSING_KEYRING';
|
|
19
|
+
} | {
|
|
20
|
+
readonly code: 'TOKEN_CODEC_PORTS_MISSING_HMAC';
|
|
21
|
+
} | {
|
|
22
|
+
readonly code: 'TOKEN_CODEC_PORTS_MISSING_BASE64URL';
|
|
23
|
+
} | {
|
|
24
|
+
readonly code: 'TOKEN_CODEC_PORTS_MISSING_BASE32';
|
|
25
|
+
} | {
|
|
26
|
+
readonly code: 'TOKEN_CODEC_PORTS_MISSING_BECH32M';
|
|
27
|
+
};
|
|
28
|
+
export declare function createTokenCodecPorts(deps: {
|
|
29
|
+
readonly keyring?: KeyringV1 | null;
|
|
30
|
+
readonly hmac?: HmacSha256PortV2 | null;
|
|
31
|
+
readonly base64url?: Base64UrlPortV2 | null;
|
|
32
|
+
readonly base32?: Base32PortV2 | null;
|
|
33
|
+
readonly bech32m?: Bech32mPortV2 | null;
|
|
34
|
+
}): Result<TokenCodecPorts, TokenCodecPortsError>;
|
|
35
|
+
export declare function unsafeTokenCodecPorts(deps: {
|
|
36
|
+
readonly keyring: KeyringV1;
|
|
37
|
+
readonly hmac: HmacSha256PortV2;
|
|
38
|
+
readonly base64url: Base64UrlPortV2;
|
|
39
|
+
readonly base32: Base32PortV2;
|
|
40
|
+
readonly bech32m: Bech32mPortV2;
|
|
41
|
+
}): TokenCodecPorts;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTokenCodecPorts = createTokenCodecPorts;
|
|
4
|
+
exports.unsafeTokenCodecPorts = unsafeTokenCodecPorts;
|
|
5
|
+
const neverthrow_1 = require("neverthrow");
|
|
6
|
+
function createTokenCodecPorts(deps) {
|
|
7
|
+
if (!deps.keyring)
|
|
8
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_CODEC_PORTS_MISSING_KEYRING' });
|
|
9
|
+
if (!deps.hmac)
|
|
10
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_CODEC_PORTS_MISSING_HMAC' });
|
|
11
|
+
if (!deps.base64url)
|
|
12
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_CODEC_PORTS_MISSING_BASE64URL' });
|
|
13
|
+
if (!deps.base32)
|
|
14
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_CODEC_PORTS_MISSING_BASE32' });
|
|
15
|
+
if (!deps.bech32m)
|
|
16
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_CODEC_PORTS_MISSING_BECH32M' });
|
|
17
|
+
return (0, neverthrow_1.ok)(Object.freeze({
|
|
18
|
+
keyring: deps.keyring,
|
|
19
|
+
hmac: deps.hmac,
|
|
20
|
+
base64url: deps.base64url,
|
|
21
|
+
base32: deps.base32,
|
|
22
|
+
bech32m: deps.bech32m,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
function unsafeTokenCodecPorts(deps) {
|
|
26
|
+
return Object.freeze({ ...deps });
|
|
27
|
+
}
|
|
@@ -2,10 +2,16 @@ import type { Result } from 'neverthrow';
|
|
|
2
2
|
import type { CanonicalBytes } from '../ids/index.js';
|
|
3
3
|
import type { TokenStringV1 } from '../ids/index.js';
|
|
4
4
|
import type { Base64UrlPortV2 } from '../../ports/base64url.port.js';
|
|
5
|
+
import type { Bech32mDecodeError, TokenHrp } from '../../ports/bech32m.port.js';
|
|
6
|
+
import type { Base32PortV2 } from '../../ports/base32.port.js';
|
|
5
7
|
import { type TokenPayloadV1, type TokenPrefixV1 } from './payloads.js';
|
|
8
|
+
import type { TokenParsePorts } from './token-codec-capabilities.js';
|
|
6
9
|
export type TokenDecodeErrorV2 = {
|
|
7
10
|
readonly code: 'TOKEN_INVALID_FORMAT';
|
|
8
11
|
readonly message: string;
|
|
12
|
+
readonly details?: {
|
|
13
|
+
bech32mError?: Bech32mDecodeError;
|
|
14
|
+
};
|
|
9
15
|
} | {
|
|
10
16
|
readonly code: 'TOKEN_UNSUPPORTED_VERSION';
|
|
11
17
|
readonly message: string;
|
|
@@ -30,3 +36,15 @@ export declare function encodeUnsignedTokenV1(payload: TokenPayloadV1, base64url
|
|
|
30
36
|
readonly payloadBytes: CanonicalBytes;
|
|
31
37
|
}, TokenDecodeErrorV2>;
|
|
32
38
|
export declare function parseTokenV1(token: string, base64url: Base64UrlPortV2): Result<ParsedTokenV1, TokenDecodeErrorV2>;
|
|
39
|
+
export interface ParsedTokenV1Binary {
|
|
40
|
+
readonly hrp: TokenHrp;
|
|
41
|
+
readonly version: '1';
|
|
42
|
+
readonly payloadBytes: Uint8Array;
|
|
43
|
+
readonly signatureBytes: Uint8Array;
|
|
44
|
+
readonly payload: TokenPayloadV1;
|
|
45
|
+
}
|
|
46
|
+
export declare function encodeTokenPayloadV1Binary(payload: TokenPayloadV1, base32: Base32PortV2): Result<{
|
|
47
|
+
payloadBytes: Uint8Array;
|
|
48
|
+
hrp: TokenHrp;
|
|
49
|
+
}, TokenDecodeErrorV2>;
|
|
50
|
+
export declare function parseTokenV1Binary(tokenString: string, ports: TokenParsePorts): Result<ParsedTokenV1Binary, TokenDecodeErrorV2>;
|
|
@@ -3,10 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.encodeTokenPayloadV1 = encodeTokenPayloadV1;
|
|
4
4
|
exports.encodeUnsignedTokenV1 = encodeUnsignedTokenV1;
|
|
5
5
|
exports.parseTokenV1 = parseTokenV1;
|
|
6
|
+
exports.encodeTokenPayloadV1Binary = encodeTokenPayloadV1Binary;
|
|
7
|
+
exports.parseTokenV1Binary = parseTokenV1Binary;
|
|
6
8
|
const neverthrow_1 = require("neverthrow");
|
|
7
9
|
const jcs_js_1 = require("../canonical/jcs.js");
|
|
8
10
|
const index_js_1 = require("../ids/index.js");
|
|
9
11
|
const payloads_js_1 = require("./payloads.js");
|
|
12
|
+
const binary_payload_js_1 = require("./binary-payload.js");
|
|
10
13
|
function encodeTokenPayloadV1(payload) {
|
|
11
14
|
return (0, jcs_js_1.toCanonicalBytes)(payload).mapErr((e) => ({
|
|
12
15
|
code: 'TOKEN_PAYLOAD_INVALID',
|
|
@@ -68,3 +71,108 @@ function parseTokenV1(token, base64url) {
|
|
|
68
71
|
payload: validated.data,
|
|
69
72
|
});
|
|
70
73
|
}
|
|
74
|
+
function encodeTokenPayloadV1Binary(payload, base32) {
|
|
75
|
+
let packResult;
|
|
76
|
+
let hrp;
|
|
77
|
+
switch (payload.tokenKind) {
|
|
78
|
+
case 'state':
|
|
79
|
+
packResult = (0, binary_payload_js_1.packStateTokenPayload)(payload, base32);
|
|
80
|
+
hrp = 'st';
|
|
81
|
+
break;
|
|
82
|
+
case 'ack':
|
|
83
|
+
packResult = (0, binary_payload_js_1.packAckTokenPayload)(payload, base32);
|
|
84
|
+
hrp = 'ack';
|
|
85
|
+
break;
|
|
86
|
+
case 'checkpoint':
|
|
87
|
+
packResult = (0, binary_payload_js_1.packCheckpointTokenPayload)(payload, base32);
|
|
88
|
+
hrp = 'chk';
|
|
89
|
+
break;
|
|
90
|
+
default: {
|
|
91
|
+
const _exhaustive = payload;
|
|
92
|
+
return (0, neverthrow_1.err)({
|
|
93
|
+
code: 'TOKEN_PAYLOAD_INVALID',
|
|
94
|
+
message: `Unknown token kind in payload: expected 'state' | 'ack' | 'checkpoint'`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (packResult.isErr()) {
|
|
99
|
+
return (0, neverthrow_1.err)({
|
|
100
|
+
code: 'TOKEN_PAYLOAD_INVALID',
|
|
101
|
+
message: `Binary pack failed: ${packResult.error.code}`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return (0, neverthrow_1.ok)({ payloadBytes: packResult.value, hrp });
|
|
105
|
+
}
|
|
106
|
+
function parseTokenV1Binary(tokenString, ports) {
|
|
107
|
+
const { bech32m, base32 } = ports;
|
|
108
|
+
let hrp;
|
|
109
|
+
let expectedKind;
|
|
110
|
+
if (tokenString.startsWith('st1')) {
|
|
111
|
+
hrp = 'st';
|
|
112
|
+
expectedKind = 0;
|
|
113
|
+
}
|
|
114
|
+
else if (tokenString.startsWith('ack1')) {
|
|
115
|
+
hrp = 'ack';
|
|
116
|
+
expectedKind = 1;
|
|
117
|
+
}
|
|
118
|
+
else if (tokenString.startsWith('chk1')) {
|
|
119
|
+
hrp = 'chk';
|
|
120
|
+
expectedKind = 2;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
return (0, neverthrow_1.err)({
|
|
124
|
+
code: 'TOKEN_INVALID_FORMAT',
|
|
125
|
+
message: `Invalid token format: expected st1/ack1/chk1 prefix, got '${tokenString.slice(0, 4)}'`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const decodedResult = bech32m.decode(tokenString, hrp);
|
|
129
|
+
if (decodedResult.isErr()) {
|
|
130
|
+
const bech32mErr = decodedResult.error;
|
|
131
|
+
if (bech32mErr.code === 'BECH32M_CHECKSUM_FAILED') {
|
|
132
|
+
return (0, neverthrow_1.err)({
|
|
133
|
+
code: 'TOKEN_INVALID_FORMAT',
|
|
134
|
+
message: 'Token corrupted (bech32m checksum failed). Likely copy/paste error.',
|
|
135
|
+
details: { bech32mError: bech32mErr },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return (0, neverthrow_1.err)({
|
|
139
|
+
code: 'TOKEN_INVALID_FORMAT',
|
|
140
|
+
message: `Bech32m decode failed: ${bech32mErr.message}`,
|
|
141
|
+
details: { bech32mError: bech32mErr },
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const allBytes = decodedResult.value;
|
|
145
|
+
if (allBytes.length !== 98) {
|
|
146
|
+
return (0, neverthrow_1.err)({
|
|
147
|
+
code: 'TOKEN_INVALID_FORMAT',
|
|
148
|
+
message: `Expected 98 bytes (66 payload + 32 sig), got ${allBytes.length}`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const payloadBytes = allBytes.slice(0, 66);
|
|
152
|
+
const signatureBytes = allBytes.slice(66, 98);
|
|
153
|
+
const unpackedResult = (0, binary_payload_js_1.unpackTokenPayload)(payloadBytes, base32);
|
|
154
|
+
if (unpackedResult.isErr()) {
|
|
155
|
+
const unpackErr = unpackedResult.error;
|
|
156
|
+
return (0, neverthrow_1.err)({
|
|
157
|
+
code: unpackErr.code === 'BINARY_UNSUPPORTED_VERSION' ? 'TOKEN_UNSUPPORTED_VERSION' : 'TOKEN_INVALID_FORMAT',
|
|
158
|
+
message: `Payload unpack failed: ${unpackErr.code}`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const actualKind = unpackedResult.value.tokenKind;
|
|
162
|
+
const expectedKindStr = expectedKind === 0 ? 'state' : expectedKind === 1 ? 'ack' : 'checkpoint';
|
|
163
|
+
if ((expectedKind === 0 && actualKind !== 'state') ||
|
|
164
|
+
(expectedKind === 1 && actualKind !== 'ack') ||
|
|
165
|
+
(expectedKind === 2 && actualKind !== 'checkpoint')) {
|
|
166
|
+
return (0, neverthrow_1.err)({
|
|
167
|
+
code: 'TOKEN_INVALID_FORMAT',
|
|
168
|
+
message: `Token kind mismatch: HRP implies ${expectedKindStr}, payload contains ${actualKind}`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return (0, neverthrow_1.ok)({
|
|
172
|
+
hrp,
|
|
173
|
+
version: '1',
|
|
174
|
+
payloadBytes: payloadBytes.slice(),
|
|
175
|
+
signatureBytes: signatureBytes.slice(),
|
|
176
|
+
payload: unpackedResult.value,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -3,7 +3,9 @@ import type { HmacSha256PortV2 } from '../../ports/hmac-sha256.port.js';
|
|
|
3
3
|
import type { KeyringV1 } from '../../ports/keyring.port.js';
|
|
4
4
|
import type { Base64UrlPortV2 } from '../../ports/base64url.port.js';
|
|
5
5
|
import type { CanonicalBytes, TokenStringV1 } from '../ids/index.js';
|
|
6
|
-
import type { ParsedTokenV1, TokenDecodeErrorV2 } from './token-codec.js';
|
|
6
|
+
import type { ParsedTokenV1, ParsedTokenV1Binary, TokenDecodeErrorV2 } from './token-codec.js';
|
|
7
|
+
import type { TokenPayloadV1 } from './payloads.js';
|
|
8
|
+
import type { TokenSignPorts, TokenVerifyPorts } from './token-codec-capabilities.js';
|
|
7
9
|
export type TokenVerifyErrorV2 = {
|
|
8
10
|
readonly code: 'TOKEN_BAD_SIGNATURE';
|
|
9
11
|
readonly message: string;
|
|
@@ -14,3 +16,13 @@ export type TokenVerifyErrorV2 = {
|
|
|
14
16
|
export declare function signTokenV1(unsignedTokenPrefix: 'st.v1.' | 'ack.v1.' | 'chk.v1.', payloadBytes: CanonicalBytes, keyring: KeyringV1, hmac: HmacSha256PortV2, base64url: Base64UrlPortV2): Result<TokenStringV1, TokenVerifyErrorV2>;
|
|
15
17
|
export declare function verifyTokenSignatureV1(parsed: ParsedTokenV1, keyring: KeyringV1, hmac: HmacSha256PortV2, base64url: Base64UrlPortV2): Result<void, TokenVerifyErrorV2>;
|
|
16
18
|
export declare function assertTokenScopeMatchesState(state: ParsedTokenV1, other: ParsedTokenV1): Result<void, TokenDecodeErrorV2>;
|
|
19
|
+
export type TokenSignErrorV2 = {
|
|
20
|
+
readonly code: 'TOKEN_ENCODE_FAILED';
|
|
21
|
+
readonly message: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly code: 'KEYRING_INVALID';
|
|
24
|
+
readonly message: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function signTokenV1Binary(payload: TokenPayloadV1, ports: TokenSignPorts): Result<string, TokenSignErrorV2>;
|
|
27
|
+
export declare function verifyTokenSignatureV1Binary(parsed: ParsedTokenV1Binary, ports: TokenVerifyPorts): Result<void, TokenVerifyErrorV2>;
|
|
28
|
+
export declare function assertTokenScopeMatchesStateBinary(state: ParsedTokenV1Binary, other: ParsedTokenV1Binary): Result<void, TokenDecodeErrorV2>;
|
|
@@ -3,8 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.signTokenV1 = signTokenV1;
|
|
4
4
|
exports.verifyTokenSignatureV1 = verifyTokenSignatureV1;
|
|
5
5
|
exports.assertTokenScopeMatchesState = assertTokenScopeMatchesState;
|
|
6
|
+
exports.signTokenV1Binary = signTokenV1Binary;
|
|
7
|
+
exports.verifyTokenSignatureV1Binary = verifyTokenSignatureV1Binary;
|
|
8
|
+
exports.assertTokenScopeMatchesStateBinary = assertTokenScopeMatchesStateBinary;
|
|
6
9
|
const neverthrow_1 = require("neverthrow");
|
|
7
10
|
const index_js_1 = require("../ids/index.js");
|
|
11
|
+
const token_codec_js_1 = require("./token-codec.js");
|
|
8
12
|
function decodeKeyBytes(keyBase64Url, base64url) {
|
|
9
13
|
const decoded = base64url.decodeBase64Url(keyBase64Url);
|
|
10
14
|
if (decoded.isErr())
|
|
@@ -52,3 +56,64 @@ function assertTokenScopeMatchesState(state, other) {
|
|
|
52
56
|
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'nodeId mismatch' });
|
|
53
57
|
return (0, neverthrow_1.ok)(undefined);
|
|
54
58
|
}
|
|
59
|
+
function signTokenV1Binary(payload, ports) {
|
|
60
|
+
const { keyring, hmac, base64url, base32, bech32m } = ports;
|
|
61
|
+
const key = decodeKeyBytes(keyring.current.keyBase64Url, base64url);
|
|
62
|
+
if (key.isErr()) {
|
|
63
|
+
return (0, neverthrow_1.err)({ code: 'KEYRING_INVALID', message: 'Invalid current key' });
|
|
64
|
+
}
|
|
65
|
+
const encodeResult = (0, token_codec_js_1.encodeTokenPayloadV1Binary)(payload, base32);
|
|
66
|
+
if (encodeResult.isErr()) {
|
|
67
|
+
return (0, neverthrow_1.err)({
|
|
68
|
+
code: 'TOKEN_ENCODE_FAILED',
|
|
69
|
+
message: `Binary pack failed: ${encodeResult.error.message}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const { payloadBytes, hrp } = encodeResult.value;
|
|
73
|
+
const signature = hmac.hmacSha256(key.value, payloadBytes);
|
|
74
|
+
const combined = new Uint8Array(98);
|
|
75
|
+
combined.set(payloadBytes, 0);
|
|
76
|
+
combined.set(signature, 66);
|
|
77
|
+
const token = bech32m.encode(hrp, combined);
|
|
78
|
+
return (0, neverthrow_1.ok)(token);
|
|
79
|
+
}
|
|
80
|
+
function verifyTokenSignatureV1Binary(parsed, ports) {
|
|
81
|
+
const { keyring, hmac, base64url } = ports;
|
|
82
|
+
if (parsed.signatureBytes.length !== 32) {
|
|
83
|
+
return (0, neverthrow_1.err)({
|
|
84
|
+
code: 'TOKEN_INVALID_FORMAT',
|
|
85
|
+
message: `Expected 32-byte signature, got ${parsed.signatureBytes.length}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const keys = [keyring.current.keyBase64Url];
|
|
89
|
+
if (keyring.previous)
|
|
90
|
+
keys.push(keyring.previous.keyBase64Url);
|
|
91
|
+
for (const k of keys) {
|
|
92
|
+
const key = decodeKeyBytes(k, base64url);
|
|
93
|
+
if (key.isErr())
|
|
94
|
+
continue;
|
|
95
|
+
const expected = hmac.hmacSha256(key.value, parsed.payloadBytes);
|
|
96
|
+
if (hmac.timingSafeEqual(expected, parsed.signatureBytes)) {
|
|
97
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return (0, neverthrow_1.err)({
|
|
101
|
+
code: 'TOKEN_BAD_SIGNATURE',
|
|
102
|
+
message: 'Signature verification failed',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function assertTokenScopeMatchesStateBinary(state, other) {
|
|
106
|
+
if (state.payload.tokenKind !== 'state') {
|
|
107
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'Expected a state token for scope comparison' });
|
|
108
|
+
}
|
|
109
|
+
if (state.payload.sessionId !== other.payload.sessionId) {
|
|
110
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'sessionId mismatch' });
|
|
111
|
+
}
|
|
112
|
+
if (state.payload.runId !== other.payload.runId) {
|
|
113
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'runId mismatch' });
|
|
114
|
+
}
|
|
115
|
+
if (state.payload.nodeId !== other.payload.nodeId) {
|
|
116
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'nodeId mismatch' });
|
|
117
|
+
}
|
|
118
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
119
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { Base32PortV2, Base32DecodeError } from '../../../ports/base32.port.js';
|
|
3
|
+
export declare class Base32AdapterV2 implements Base32PortV2 {
|
|
4
|
+
encode(bytes: Uint8Array): string;
|
|
5
|
+
decode(encoded: string): Result<Uint8Array, Base32DecodeError>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Base32AdapterV2 = void 0;
|
|
4
|
+
const neverthrow_1 = require("neverthrow");
|
|
5
|
+
const base32_lower_js_1 = require("../../../durable-core/encoding/base32-lower.js");
|
|
6
|
+
class Base32AdapterV2 {
|
|
7
|
+
encode(bytes) {
|
|
8
|
+
return (0, base32_lower_js_1.encodeBase32LowerNoPad)(bytes);
|
|
9
|
+
}
|
|
10
|
+
decode(encoded) {
|
|
11
|
+
if (!/^[a-z2-7]+$/.test(encoded)) {
|
|
12
|
+
const invalidMatch = encoded.match(/[^a-z2-7]/);
|
|
13
|
+
return (0, neverthrow_1.err)({
|
|
14
|
+
code: 'BASE32_INVALID_CHARACTERS',
|
|
15
|
+
message: `Invalid base32 character: ${invalidMatch?.[0] || 'unknown'}`,
|
|
16
|
+
position: invalidMatch?.index,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const bytes = (0, base32_lower_js_1.decodeBase32LowerNoPad)(encoded);
|
|
21
|
+
return (0, neverthrow_1.ok)(bytes);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
25
|
+
if (/padding|non-canonical/i.test(message)) {
|
|
26
|
+
return (0, neverthrow_1.err)({
|
|
27
|
+
code: 'BASE32_NON_CANONICAL',
|
|
28
|
+
message: `Non-canonical base32 encoding: ${message}`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (/length/i.test(message)) {
|
|
32
|
+
return (0, neverthrow_1.err)({
|
|
33
|
+
code: 'BASE32_INVALID_LENGTH',
|
|
34
|
+
message: `Invalid base32 length: ${message}`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return (0, neverthrow_1.err)({
|
|
38
|
+
code: 'BASE32_INVALID_CHARACTERS',
|
|
39
|
+
message: `Base32 decode failed: ${message}`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.Base32AdapterV2 = Base32AdapterV2;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { Bech32mPortV2, Bech32mDecodeError, TokenHrp } from '../../../ports/bech32m.port.js';
|
|
3
|
+
export declare class Bech32mAdapterV2 implements Bech32mPortV2 {
|
|
4
|
+
private static readonly MAX_LENGTH;
|
|
5
|
+
encode(hrp: TokenHrp, data: Uint8Array): string;
|
|
6
|
+
decode(encoded: string, expectedHrp: TokenHrp): Result<Uint8Array, Bech32mDecodeError>;
|
|
7
|
+
private tryExtractPosition;
|
|
8
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Bech32mAdapterV2 = void 0;
|
|
4
|
+
const base_1 = require("@scure/base");
|
|
5
|
+
const neverthrow_1 = require("neverthrow");
|
|
6
|
+
class Bech32mAdapterV2 {
|
|
7
|
+
encode(hrp, data) {
|
|
8
|
+
const words = base_1.bech32m.toWords(data);
|
|
9
|
+
return base_1.bech32m.encode(hrp, words, Bech32mAdapterV2.MAX_LENGTH);
|
|
10
|
+
}
|
|
11
|
+
decode(encoded, expectedHrp) {
|
|
12
|
+
try {
|
|
13
|
+
const decoded = base_1.bech32m.decode(encoded, Bech32mAdapterV2.MAX_LENGTH);
|
|
14
|
+
if (decoded.prefix !== expectedHrp) {
|
|
15
|
+
return (0, neverthrow_1.err)({
|
|
16
|
+
code: 'BECH32M_HRP_MISMATCH',
|
|
17
|
+
message: `HRP mismatch: expected '${expectedHrp}', got '${decoded.prefix}'`,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const bytes = base_1.bech32m.fromWords(decoded.words);
|
|
21
|
+
return (0, neverthrow_1.ok)(new Uint8Array(bytes));
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
25
|
+
if (/checksum/i.test(msg) || /invalid/i.test(msg)) {
|
|
26
|
+
return (0, neverthrow_1.err)({
|
|
27
|
+
code: 'BECH32M_CHECKSUM_FAILED',
|
|
28
|
+
message: `Bech32m checksum validation failed (likely corruption): ${msg}`,
|
|
29
|
+
position: this.tryExtractPosition(msg),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return (0, neverthrow_1.err)({
|
|
33
|
+
code: 'BECH32M_INVALID_FORMAT',
|
|
34
|
+
message: `Invalid bech32m: ${msg}`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
tryExtractPosition(msg) {
|
|
39
|
+
const patterns = [
|
|
40
|
+
/position\s*:?\s*(\d+)/i,
|
|
41
|
+
/at\s+position\s+(\d+)/i,
|
|
42
|
+
/pos\s+(\d+)/i,
|
|
43
|
+
/index\s+(\d+)/i,
|
|
44
|
+
];
|
|
45
|
+
for (const pattern of patterns) {
|
|
46
|
+
const match = msg.match(pattern);
|
|
47
|
+
if (match) {
|
|
48
|
+
const pos = parseInt(match[1], 10);
|
|
49
|
+
return Number.isNaN(pos) ? undefined : pos;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.Bech32mAdapterV2 = Bech32mAdapterV2;
|
|
56
|
+
Bech32mAdapterV2.MAX_LENGTH = 1023;
|
|
@@ -2,6 +2,7 @@ import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
|
|
|
2
2
|
export declare class LocalDataDirV2 implements DataDirPortV2 {
|
|
3
3
|
private readonly env;
|
|
4
4
|
constructor(env: Record<string, string | undefined>);
|
|
5
|
+
private safeFileSegment;
|
|
5
6
|
private root;
|
|
6
7
|
snapshotsDir(): string;
|
|
7
8
|
snapshotPath(snapshotRef: string): string;
|
|
@@ -40,6 +40,9 @@ class LocalDataDirV2 {
|
|
|
40
40
|
constructor(env) {
|
|
41
41
|
this.env = env;
|
|
42
42
|
}
|
|
43
|
+
safeFileSegment(raw) {
|
|
44
|
+
return raw.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
45
|
+
}
|
|
43
46
|
root() {
|
|
44
47
|
const configured = this.env['WORKRAIL_DATA_DIR'];
|
|
45
48
|
return configured ? configured : path.join(os.homedir(), '.workrail', 'data');
|
|
@@ -48,7 +51,7 @@ class LocalDataDirV2 {
|
|
|
48
51
|
return path.join(this.root(), 'snapshots');
|
|
49
52
|
}
|
|
50
53
|
snapshotPath(snapshotRef) {
|
|
51
|
-
return path.join(this.snapshotsDir(), `${snapshotRef}.json`);
|
|
54
|
+
return path.join(this.snapshotsDir(), `${this.safeFileSegment(snapshotRef)}.json`);
|
|
52
55
|
}
|
|
53
56
|
keysDir() {
|
|
54
57
|
return path.join(this.root(), 'keys');
|
|
@@ -60,7 +63,7 @@ class LocalDataDirV2 {
|
|
|
60
63
|
return path.join(this.root(), 'workflows', 'pinned');
|
|
61
64
|
}
|
|
62
65
|
pinnedWorkflowPath(workflowHash) {
|
|
63
|
-
return path.join(this.pinnedWorkflowsDir(), `${workflowHash}.json`);
|
|
66
|
+
return path.join(this.pinnedWorkflowsDir(), `${this.safeFileSegment(workflowHash)}.json`);
|
|
64
67
|
}
|
|
65
68
|
sessionsDir() {
|
|
66
69
|
return path.join(this.root(), 'sessions');
|
|
@@ -124,6 +124,9 @@ class NodeFileSystemV2 {
|
|
|
124
124
|
}), (e) => mapFsError(e, `fd:${fd}`));
|
|
125
125
|
}
|
|
126
126
|
fsyncDir(dirPath) {
|
|
127
|
+
if (process.platform === 'win32') {
|
|
128
|
+
return neverthrow_1.ResultAsync.fromPromise(Promise.resolve(undefined), (e) => mapFsError(e, dirPath));
|
|
129
|
+
}
|
|
127
130
|
return neverthrow_1.ResultAsync.fromPromise((async () => {
|
|
128
131
|
const dirHandle = await fs.open(dirPath, 'r');
|
|
129
132
|
try {
|