@exaudeus/workrail 0.12.0 → 0.13.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/application/services/enhanced-loop-validator.js +3 -3
- package/dist/application/services/step-output-decoder.d.ts +6 -0
- package/dist/application/services/step-output-decoder.js +49 -0
- package/dist/application/services/validation-engine.d.ts +9 -0
- package/dist/application/services/validation-engine.js +142 -18
- package/dist/application/services/workflow-interpreter.d.ts +1 -1
- package/dist/application/services/workflow-interpreter.js +147 -81
- package/dist/application/services/workflow-service.d.ts +2 -0
- package/dist/application/services/workflow-service.js +3 -3
- package/dist/application/use-cases/validate-step-output.d.ts +2 -0
- package/dist/di/container.js +19 -3
- package/dist/di/tokens.d.ts +3 -0
- package/dist/di/tokens.js +3 -0
- package/dist/domain/execution/state.d.ts +6 -6
- package/dist/domain/workflow-id-policy.d.ts +17 -0
- package/dist/domain/workflow-id-policy.js +57 -0
- package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +33 -6
- package/dist/infrastructure/storage/file-workflow-storage.js +3 -1
- package/dist/infrastructure/storage/schema-validating-workflow-storage.js +13 -8
- package/dist/manifest.json +261 -109
- package/dist/mcp/handlers/v2-execution.js +62 -79
- package/dist/mcp/handlers/v2-workflow.js +14 -9
- package/dist/mcp/server.js +53 -48
- package/dist/mcp/tool-descriptions.js +15 -15
- package/dist/mcp/tools.js +14 -14
- package/dist/mcp/types/tool-description-types.d.ts +1 -1
- package/dist/mcp/types/tool-description-types.js +5 -5
- package/dist/mcp/types.d.ts +2 -0
- package/dist/v2/durable-core/constants.d.ts +15 -0
- package/dist/v2/durable-core/constants.js +18 -0
- package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +32 -0
- package/dist/v2/durable-core/domain/ack-advance-append-plan.js +95 -0
- package/dist/v2/durable-core/domain/loop-runtime.d.ts +50 -0
- package/dist/v2/durable-core/domain/loop-runtime.js +95 -0
- package/dist/v2/durable-core/domain/notes-markdown.d.ts +4 -0
- package/dist/v2/durable-core/domain/notes-markdown.js +46 -0
- package/dist/v2/durable-core/domain/outputs.d.ts +12 -0
- package/dist/v2/durable-core/domain/outputs.js +18 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +113 -113
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +11 -10
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +7129 -0
- package/dist/v2/durable-core/schemas/export-bundle/index.js +82 -0
- package/dist/v2/durable-core/schemas/lib/decision-trace-ref.d.ts +80 -0
- package/dist/v2/durable-core/schemas/lib/decision-trace-ref.js +38 -0
- package/dist/v2/durable-core/schemas/lib/dedupe-key.d.ts +8 -0
- package/dist/v2/durable-core/schemas/lib/dedupe-key.js +28 -0
- package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.d.ts +6 -0
- package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.js +12 -0
- package/dist/v2/durable-core/schemas/session/events.d.ts +158 -12
- package/dist/v2/durable-core/schemas/session/events.js +47 -20
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +1 -1
- package/dist/v2/durable-core/schemas/session/manifest.js +6 -1
- package/dist/v2/durable-core/schemas/session/preferences.d.ts +5 -0
- package/dist/v2/durable-core/schemas/session/preferences.js +6 -0
- package/dist/v2/durable-core/schemas/session/session-health.d.ts +3 -0
- package/dist/v2/durable-core/tokens/index.d.ts +0 -1
- package/dist/v2/durable-core/tokens/index.js +1 -4
- package/dist/v2/durable-core/tokens/token-codec.d.ts +3 -2
- package/dist/v2/durable-core/tokens/token-codec.js +12 -6
- package/dist/v2/durable-core/tokens/token-signer.d.ts +3 -2
- package/dist/v2/durable-core/tokens/token-signer.js +8 -9
- package/dist/v2/infra/local/base64url/index.d.ts +5 -0
- package/dist/v2/infra/local/base64url/index.js +48 -0
- package/dist/v2/infra/local/fs/index.js +8 -4
- package/dist/v2/infra/local/keyring/index.d.ts +5 -1
- package/dist/v2/infra/local/keyring/index.js +41 -32
- package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +3 -1
- package/dist/v2/infra/local/pinned-workflow-store/index.js +50 -62
- package/dist/v2/infra/local/random-entropy/index.d.ts +4 -0
- package/dist/v2/infra/local/random-entropy/index.js +10 -0
- package/dist/v2/infra/local/session-lock/index.d.ts +3 -1
- package/dist/v2/infra/local/session-lock/index.js +4 -3
- package/dist/v2/infra/local/session-store/index.js +26 -4
- package/dist/v2/infra/local/snapshot-store/index.js +20 -25
- package/dist/v2/infra/local/time-clock/index.d.ts +5 -0
- package/dist/v2/infra/local/time-clock/index.js +12 -0
- package/dist/v2/infra/local/utf8/index.d.ts +5 -0
- package/dist/v2/infra/local/utf8/index.js +12 -0
- package/dist/v2/ports/base64url.port.d.ts +12 -0
- package/dist/v2/ports/base64url.port.js +2 -0
- package/dist/v2/ports/random-entropy.port.d.ts +3 -0
- package/dist/v2/ports/random-entropy.port.js +2 -0
- package/dist/v2/ports/time-clock.port.d.ts +4 -0
- package/dist/v2/ports/time-clock.port.js +2 -0
- package/dist/v2/ports/utf8.port.d.ts +3 -0
- package/dist/v2/ports/utf8.port.js +2 -0
- package/dist/v2/projections/node-outputs.js +28 -11
- package/dist/v2/projections/preferences.d.ts +1 -2
- package/dist/v2/projections/preferences.js +11 -4
- package/dist/v2/projections/run-dag.js +40 -28
- package/dist/v2/projections/run-status-signals.d.ts +1 -2
- package/dist/v2/usecases/execution-session-gate.js +33 -34
- package/package.json +3 -1
- package/spec/workflow.schema.json +2 -2
- package/workflows/coding-task-workflow-agentic.json +213 -50
- package/workflows/relocation-workflow-us.json +430 -0
- package/dist/v2/durable-core/tokens/base64url.d.ts +0 -7
- package/dist/v2/durable-core/tokens/base64url.js +0 -16
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RiskPolicyV2Schema = exports.AutonomyV2Schema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
exports.AutonomyV2Schema = zod_1.z.enum(['guided', 'full_auto_stop_on_user_deps', 'full_auto_never_stop']);
|
|
6
|
+
exports.RiskPolicyV2Schema = zod_1.z.enum(['conservative', 'balanced', 'aggressive']);
|
|
@@ -10,6 +10,9 @@ export type CorruptionReasonV2 = {
|
|
|
10
10
|
} | {
|
|
11
11
|
readonly code: 'unknown_schema_version';
|
|
12
12
|
readonly message: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly code: 'schema_validation_failed';
|
|
15
|
+
readonly message: string;
|
|
13
16
|
};
|
|
14
17
|
export type SessionHealthV2 = {
|
|
15
18
|
readonly kind: 'healthy';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { encodeBase64Url, decodeBase64Url } from './base64url.js';
|
|
2
1
|
export { TokenPayloadV1Schema, StateTokenPayloadV1Schema, AckTokenPayloadV1Schema, CheckpointTokenPayloadV1Schema, expectedPrefixForTokenKind, } from './payloads.js';
|
|
3
2
|
export type { TokenPayloadV1, StateTokenPayloadV1, AckTokenPayloadV1, CheckpointTokenPayloadV1 } from './payloads.js';
|
|
4
3
|
export { encodeTokenPayloadV1, encodeUnsignedTokenV1, parseTokenV1 } from './token-codec.js';
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.asOutputId = exports.asAttemptId = exports.assertTokenScopeMatchesState = exports.verifyTokenSignatureV1 = exports.signTokenV1 = exports.parseTokenV1 = exports.encodeUnsignedTokenV1 = exports.encodeTokenPayloadV1 = exports.expectedPrefixForTokenKind = exports.CheckpointTokenPayloadV1Schema = exports.AckTokenPayloadV1Schema = exports.StateTokenPayloadV1Schema = exports.TokenPayloadV1Schema =
|
|
4
|
-
var base64url_js_1 = require("./base64url.js");
|
|
5
|
-
Object.defineProperty(exports, "encodeBase64Url", { enumerable: true, get: function () { return base64url_js_1.encodeBase64Url; } });
|
|
6
|
-
Object.defineProperty(exports, "decodeBase64Url", { enumerable: true, get: function () { return base64url_js_1.decodeBase64Url; } });
|
|
3
|
+
exports.asOutputId = exports.asAttemptId = exports.assertTokenScopeMatchesState = exports.verifyTokenSignatureV1 = exports.signTokenV1 = exports.parseTokenV1 = exports.encodeUnsignedTokenV1 = exports.encodeTokenPayloadV1 = exports.expectedPrefixForTokenKind = exports.CheckpointTokenPayloadV1Schema = exports.AckTokenPayloadV1Schema = exports.StateTokenPayloadV1Schema = exports.TokenPayloadV1Schema = void 0;
|
|
7
4
|
var payloads_js_1 = require("./payloads.js");
|
|
8
5
|
Object.defineProperty(exports, "TokenPayloadV1Schema", { enumerable: true, get: function () { return payloads_js_1.TokenPayloadV1Schema; } });
|
|
9
6
|
Object.defineProperty(exports, "StateTokenPayloadV1Schema", { enumerable: true, get: function () { return payloads_js_1.StateTokenPayloadV1Schema; } });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Result } from 'neverthrow';
|
|
2
2
|
import type { CanonicalBytes } from '../ids/index.js';
|
|
3
3
|
import type { TokenStringV1 } from '../ids/index.js';
|
|
4
|
+
import type { Base64UrlPortV2 } from '../../ports/base64url.port.js';
|
|
4
5
|
import { type TokenPayloadV1, type TokenPrefixV1 } from './payloads.js';
|
|
5
6
|
export type TokenDecodeErrorV2 = {
|
|
6
7
|
readonly code: 'TOKEN_INVALID_FORMAT';
|
|
@@ -24,8 +25,8 @@ export interface ParsedTokenV1 {
|
|
|
24
25
|
readonly payload: TokenPayloadV1;
|
|
25
26
|
}
|
|
26
27
|
export declare function encodeTokenPayloadV1(payload: TokenPayloadV1): Result<CanonicalBytes, TokenDecodeErrorV2>;
|
|
27
|
-
export declare function encodeUnsignedTokenV1(payload: TokenPayloadV1): Result<{
|
|
28
|
+
export declare function encodeUnsignedTokenV1(payload: TokenPayloadV1, base64url: Base64UrlPortV2): Result<{
|
|
28
29
|
readonly token: TokenStringV1;
|
|
29
30
|
readonly payloadBytes: CanonicalBytes;
|
|
30
31
|
}, TokenDecodeErrorV2>;
|
|
31
|
-
export declare function parseTokenV1(token: string): Result<ParsedTokenV1, TokenDecodeErrorV2>;
|
|
32
|
+
export declare function parseTokenV1(token: string, base64url: Base64UrlPortV2): Result<ParsedTokenV1, TokenDecodeErrorV2>;
|
|
@@ -6,7 +6,6 @@ exports.parseTokenV1 = parseTokenV1;
|
|
|
6
6
|
const neverthrow_1 = require("neverthrow");
|
|
7
7
|
const jcs_js_1 = require("../canonical/jcs.js");
|
|
8
8
|
const index_js_1 = require("../ids/index.js");
|
|
9
|
-
const base64url_js_1 = require("./base64url.js");
|
|
10
9
|
const payloads_js_1 = require("./payloads.js");
|
|
11
10
|
function encodeTokenPayloadV1(payload) {
|
|
12
11
|
return (0, jcs_js_1.toCanonicalBytes)(payload).mapErr((e) => ({
|
|
@@ -14,15 +13,15 @@ function encodeTokenPayloadV1(payload) {
|
|
|
14
13
|
message: e.message,
|
|
15
14
|
}));
|
|
16
15
|
}
|
|
17
|
-
function encodeUnsignedTokenV1(payload) {
|
|
16
|
+
function encodeUnsignedTokenV1(payload, base64url) {
|
|
18
17
|
const bytes = encodeTokenPayloadV1(payload);
|
|
19
18
|
if (bytes.isErr())
|
|
20
19
|
return (0, neverthrow_1.err)(bytes.error);
|
|
21
20
|
const prefix = (0, payloads_js_1.expectedPrefixForTokenKind)(payload.tokenKind);
|
|
22
|
-
const token = `${prefix}.v1.${
|
|
21
|
+
const token = `${prefix}.v1.${base64url.encodeBase64Url(bytes.value)}.`;
|
|
23
22
|
return (0, neverthrow_1.ok)({ token: (0, index_js_1.asTokenStringV1)(token), payloadBytes: bytes.value });
|
|
24
23
|
}
|
|
25
|
-
function parseTokenV1(token) {
|
|
24
|
+
function parseTokenV1(token, base64url) {
|
|
26
25
|
const parts = token.split('.');
|
|
27
26
|
if (parts.length !== 4)
|
|
28
27
|
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Expected 4 dot-separated segments' });
|
|
@@ -35,13 +34,20 @@ function parseTokenV1(token) {
|
|
|
35
34
|
if (payloadB64.trim() === '' || sigB64.trim() === '') {
|
|
36
35
|
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Missing payload or signature segment' });
|
|
37
36
|
}
|
|
38
|
-
const decoded =
|
|
37
|
+
const decoded = base64url.decodeBase64Url(payloadB64);
|
|
39
38
|
if (decoded.isErr())
|
|
40
39
|
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: decoded.error.message });
|
|
41
40
|
const payloadBytes = (0, index_js_1.asCanonicalBytes)(decoded.value);
|
|
41
|
+
let payloadText;
|
|
42
|
+
try {
|
|
43
|
+
payloadText = new TextDecoder('utf-8', { fatal: true }).decode(payloadBytes);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Payload is not valid UTF-8' });
|
|
47
|
+
}
|
|
42
48
|
let payloadJson;
|
|
43
49
|
try {
|
|
44
|
-
payloadJson = JSON.parse(
|
|
50
|
+
payloadJson = JSON.parse(payloadText);
|
|
45
51
|
}
|
|
46
52
|
catch {
|
|
47
53
|
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Payload is not valid JSON' });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Result } from 'neverthrow';
|
|
2
2
|
import type { HmacSha256PortV2 } from '../../ports/hmac-sha256.port.js';
|
|
3
3
|
import type { KeyringV1 } from '../../ports/keyring.port.js';
|
|
4
|
+
import type { Base64UrlPortV2 } from '../../ports/base64url.port.js';
|
|
4
5
|
import type { CanonicalBytes, TokenStringV1 } from '../ids/index.js';
|
|
5
6
|
import type { ParsedTokenV1, TokenDecodeErrorV2 } from './token-codec.js';
|
|
6
7
|
export type TokenVerifyErrorV2 = {
|
|
@@ -10,6 +11,6 @@ export type TokenVerifyErrorV2 = {
|
|
|
10
11
|
readonly code: 'TOKEN_INVALID_FORMAT';
|
|
11
12
|
readonly message: string;
|
|
12
13
|
};
|
|
13
|
-
export declare function signTokenV1(unsignedTokenPrefix: 'st.v1.' | 'ack.v1.' | 'chk.v1.', payloadBytes: CanonicalBytes, keyring: KeyringV1, hmac: HmacSha256PortV2): Result<TokenStringV1, TokenVerifyErrorV2>;
|
|
14
|
-
export declare function verifyTokenSignatureV1(parsed: ParsedTokenV1, keyring: KeyringV1, hmac: HmacSha256PortV2): Result<void, TokenVerifyErrorV2>;
|
|
14
|
+
export declare function signTokenV1(unsignedTokenPrefix: 'st.v1.' | 'ack.v1.' | 'chk.v1.', payloadBytes: CanonicalBytes, keyring: KeyringV1, hmac: HmacSha256PortV2, base64url: Base64UrlPortV2): Result<TokenStringV1, TokenVerifyErrorV2>;
|
|
15
|
+
export declare function verifyTokenSignatureV1(parsed: ParsedTokenV1, keyring: KeyringV1, hmac: HmacSha256PortV2, base64url: Base64UrlPortV2): Result<void, TokenVerifyErrorV2>;
|
|
15
16
|
export declare function assertTokenScopeMatchesState(state: ParsedTokenV1, other: ParsedTokenV1): Result<void, TokenDecodeErrorV2>;
|
|
@@ -4,26 +4,25 @@ exports.signTokenV1 = signTokenV1;
|
|
|
4
4
|
exports.verifyTokenSignatureV1 = verifyTokenSignatureV1;
|
|
5
5
|
exports.assertTokenScopeMatchesState = assertTokenScopeMatchesState;
|
|
6
6
|
const neverthrow_1 = require("neverthrow");
|
|
7
|
-
const base64url_js_1 = require("./base64url.js");
|
|
8
7
|
const index_js_1 = require("../ids/index.js");
|
|
9
|
-
function decodeKeyBytes(keyBase64Url) {
|
|
10
|
-
const decoded =
|
|
8
|
+
function decodeKeyBytes(keyBase64Url, base64url) {
|
|
9
|
+
const decoded = base64url.decodeBase64Url(keyBase64Url);
|
|
11
10
|
if (decoded.isErr())
|
|
12
11
|
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid key encoding' });
|
|
13
12
|
if (decoded.value.length !== 32)
|
|
14
13
|
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid key length' });
|
|
15
14
|
return (0, neverthrow_1.ok)(decoded.value);
|
|
16
15
|
}
|
|
17
|
-
function signTokenV1(unsignedTokenPrefix, payloadBytes, keyring, hmac) {
|
|
18
|
-
const key = decodeKeyBytes(keyring.current.keyBase64Url);
|
|
16
|
+
function signTokenV1(unsignedTokenPrefix, payloadBytes, keyring, hmac, base64url) {
|
|
17
|
+
const key = decodeKeyBytes(keyring.current.keyBase64Url, base64url);
|
|
19
18
|
if (key.isErr())
|
|
20
19
|
return (0, neverthrow_1.err)(key.error);
|
|
21
20
|
const sig = hmac.hmacSha256(key.value, payloadBytes);
|
|
22
|
-
const token = `${unsignedTokenPrefix}${
|
|
21
|
+
const token = `${unsignedTokenPrefix}${base64url.encodeBase64Url(payloadBytes)}.${base64url.encodeBase64Url(sig)}`;
|
|
23
22
|
return (0, neverthrow_1.ok)((0, index_js_1.asTokenStringV1)(token));
|
|
24
23
|
}
|
|
25
|
-
function verifyTokenSignatureV1(parsed, keyring, hmac) {
|
|
26
|
-
const sigBytes =
|
|
24
|
+
function verifyTokenSignatureV1(parsed, keyring, hmac, base64url) {
|
|
25
|
+
const sigBytes = base64url.decodeBase64Url(parsed.sigBase64Url);
|
|
27
26
|
if (sigBytes.isErr())
|
|
28
27
|
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid signature encoding' });
|
|
29
28
|
if (sigBytes.value.length !== 32)
|
|
@@ -32,7 +31,7 @@ function verifyTokenSignatureV1(parsed, keyring, hmac) {
|
|
|
32
31
|
if (keyring.previous)
|
|
33
32
|
keys.push(keyring.previous.keyBase64Url);
|
|
34
33
|
for (const k of keys) {
|
|
35
|
-
const key = decodeKeyBytes(k);
|
|
34
|
+
const key = decodeKeyBytes(k, base64url);
|
|
36
35
|
if (key.isErr())
|
|
37
36
|
continue;
|
|
38
37
|
const expected = hmac.hmacSha256(key.value, parsed.payloadBytes);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Base64UrlPortV2 } from '../../../ports/base64url.port.js';
|
|
2
|
+
export declare class NodeBase64UrlV2 implements Base64UrlPortV2 {
|
|
3
|
+
encodeBase64Url(bytes: Uint8Array): string;
|
|
4
|
+
decodeBase64Url(input: string): ReturnType<Base64UrlPortV2['decodeBase64Url']>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeBase64UrlV2 = void 0;
|
|
4
|
+
const neverthrow_1 = require("neverthrow");
|
|
5
|
+
const node_buffer_1 = require("node:buffer");
|
|
6
|
+
class NodeBase64UrlV2 {
|
|
7
|
+
encodeBase64Url(bytes) {
|
|
8
|
+
return node_buffer_1.Buffer.from(bytes).toString('base64url');
|
|
9
|
+
}
|
|
10
|
+
decodeBase64Url(input) {
|
|
11
|
+
if (input.trim() === '') {
|
|
12
|
+
return (0, neverthrow_1.err)({
|
|
13
|
+
code: 'INVALID_BASE64URL_CHARACTERS',
|
|
14
|
+
message: 'Invalid base64url: empty input',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (input.includes('=')) {
|
|
18
|
+
return (0, neverthrow_1.err)({
|
|
19
|
+
code: 'INVALID_BASE64URL_PADDING',
|
|
20
|
+
message: 'Invalid base64url: padding is not allowed',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const base64UrlRe = /^[A-Za-z0-9_-]+$/;
|
|
24
|
+
if (!base64UrlRe.test(input)) {
|
|
25
|
+
return (0, neverthrow_1.err)({
|
|
26
|
+
code: 'INVALID_BASE64URL_CHARACTERS',
|
|
27
|
+
message: 'Invalid base64url: invalid characters',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const decoded = node_buffer_1.Buffer.from(input, 'base64url');
|
|
32
|
+
if (decoded.toString('base64url') !== input) {
|
|
33
|
+
return (0, neverthrow_1.err)({
|
|
34
|
+
code: 'INVALID_BASE64URL_CHARACTERS',
|
|
35
|
+
message: 'Invalid base64url: non-canonical encoding',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return (0, neverthrow_1.ok)(new Uint8Array(decoded));
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
return (0, neverthrow_1.err)({
|
|
42
|
+
code: 'INVALID_BASE64URL_CHARACTERS',
|
|
43
|
+
message: `Invalid base64url: ${e instanceof Error ? e.message : String(e)}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.NodeBase64UrlV2 = NodeBase64UrlV2;
|
|
@@ -38,9 +38,14 @@ const fs = __importStar(require("fs/promises"));
|
|
|
38
38
|
const fsCb = __importStar(require("fs"));
|
|
39
39
|
const fs_1 = require("fs");
|
|
40
40
|
const neverthrow_1 = require("neverthrow");
|
|
41
|
+
function nodeErrorCode(e) {
|
|
42
|
+
if (typeof e !== 'object' || e === null)
|
|
43
|
+
return undefined;
|
|
44
|
+
const code = e.code;
|
|
45
|
+
return typeof code === 'string' ? code : undefined;
|
|
46
|
+
}
|
|
41
47
|
function mapFsError(e, filePath) {
|
|
42
|
-
const
|
|
43
|
-
const code = any?.code;
|
|
48
|
+
const code = nodeErrorCode(e);
|
|
44
49
|
if (code === 'ENOENT')
|
|
45
50
|
return { code: 'FS_NOT_FOUND', message: `Not found: ${filePath}` };
|
|
46
51
|
if (code === 'EEXIST')
|
|
@@ -130,8 +135,7 @@ class NodeFileSystemV2 {
|
|
|
130
135
|
await dirHandle.close();
|
|
131
136
|
}
|
|
132
137
|
})(), (e) => {
|
|
133
|
-
const
|
|
134
|
-
const code = any?.code;
|
|
138
|
+
const code = nodeErrorCode(e);
|
|
135
139
|
if (code === 'EINVAL' || code === 'ENOTSUP') {
|
|
136
140
|
return { code: 'FS_UNSUPPORTED', message: `Directory fsync unsupported for: ${dirPath}` };
|
|
137
141
|
}
|
|
@@ -2,10 +2,14 @@ import type { ResultAsync } from 'neverthrow';
|
|
|
2
2
|
import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
|
|
3
3
|
import type { FileSystemPortV2 } from '../../../ports/fs.port.js';
|
|
4
4
|
import type { KeyringError, KeyringPortV2, KeyringV1 } from '../../../ports/keyring.port.js';
|
|
5
|
+
import type { Base64UrlPortV2 } from '../../../ports/base64url.port.js';
|
|
6
|
+
import type { RandomEntropyPortV2 } from '../../../ports/random-entropy.port.js';
|
|
5
7
|
export declare class LocalKeyringV2 implements KeyringPortV2 {
|
|
6
8
|
private readonly dataDir;
|
|
7
9
|
private readonly fs;
|
|
8
|
-
|
|
10
|
+
private readonly base64url;
|
|
11
|
+
private readonly entropy;
|
|
12
|
+
constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2, base64url: Base64UrlPortV2, entropy: RandomEntropyPortV2);
|
|
9
13
|
loadOrCreate(): ResultAsync<KeyringV1, KeyringError>;
|
|
10
14
|
rotate(): ResultAsync<KeyringV1, KeyringError>;
|
|
11
15
|
private createAndPersistFresh;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LocalKeyringV2 = void 0;
|
|
4
|
-
const crypto_1 = require("crypto");
|
|
5
4
|
const zod_1 = require("zod");
|
|
6
5
|
const neverthrow_1 = require("neverthrow");
|
|
7
6
|
const jcs_js_1 = require("../../../durable-core/canonical/jcs.js");
|
|
@@ -14,32 +13,32 @@ const KeyringFileV1Schema = zod_1.z.object({
|
|
|
14
13
|
current: KeyRecordSchema,
|
|
15
14
|
previous: KeyRecordSchema.nullable(),
|
|
16
15
|
});
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
function validateKeyMaterialWithPort(base64url, keyBase64Url) {
|
|
17
|
+
const decoded = base64url.decodeBase64Url(keyBase64Url);
|
|
18
|
+
if (decoded.isErr()) {
|
|
19
|
+
return (0, neverthrow_1.err)({
|
|
20
|
+
code: 'KEYRING_CORRUPTION_DETECTED',
|
|
21
|
+
message: `Invalid base64url in key material: ${decoded.error.code}`,
|
|
22
|
+
});
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
return
|
|
24
|
+
if (decoded.value.length !== 32) {
|
|
25
|
+
return (0, neverthrow_1.err)({
|
|
26
|
+
code: 'KEYRING_CORRUPTION_DETECTED',
|
|
27
|
+
message: `Key material must be exactly 32 bytes, got ${decoded.value.length}`,
|
|
28
|
+
});
|
|
26
29
|
}
|
|
30
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
27
31
|
}
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
throw new Error('invalid_base64url');
|
|
32
|
-
if (decoded.length !== 32)
|
|
33
|
-
throw new Error('invalid_key_length');
|
|
34
|
-
}
|
|
35
|
-
function createFreshKeyRecord() {
|
|
36
|
-
const bytes = (0, crypto_1.randomBytes)(32);
|
|
37
|
-
return { alg: 'hmac_sha256', keyBase64Url: encodeBase64Url(bytes) };
|
|
32
|
+
function createFreshKeyRecord(base64url, entropy) {
|
|
33
|
+
const bytes = entropy.generateBytes(32);
|
|
34
|
+
return { alg: 'hmac_sha256', keyBase64Url: base64url.encodeBase64Url(bytes) };
|
|
38
35
|
}
|
|
39
36
|
class LocalKeyringV2 {
|
|
40
|
-
constructor(dataDir, fs) {
|
|
37
|
+
constructor(dataDir, fs, base64url, entropy) {
|
|
41
38
|
this.dataDir = dataDir;
|
|
42
39
|
this.fs = fs;
|
|
40
|
+
this.base64url = base64url;
|
|
41
|
+
this.entropy = entropy;
|
|
43
42
|
}
|
|
44
43
|
loadOrCreate() {
|
|
45
44
|
const path = this.dataDir.keyringPath();
|
|
@@ -56,27 +55,37 @@ class LocalKeyringV2 {
|
|
|
56
55
|
return this.loadOrCreate().andThen((kr) => {
|
|
57
56
|
const next = {
|
|
58
57
|
v: 1,
|
|
59
|
-
current: createFreshKeyRecord(),
|
|
58
|
+
current: createFreshKeyRecord(this.base64url, this.entropy),
|
|
60
59
|
previous: kr.current,
|
|
61
60
|
};
|
|
62
61
|
return this.persist(next).map(() => next);
|
|
63
62
|
});
|
|
64
63
|
}
|
|
65
64
|
createAndPersistFresh() {
|
|
66
|
-
const fresh = { v: 1, current: createFreshKeyRecord(), previous: null };
|
|
65
|
+
const fresh = { v: 1, current: createFreshKeyRecord(this.base64url, this.entropy), previous: null };
|
|
67
66
|
return this.persist(fresh).map(() => fresh);
|
|
68
67
|
}
|
|
69
68
|
parseAndValidate(raw, filePath) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
let parsed;
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(raw);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return (0, neverthrow_1.errAsync)({ code: 'KEYRING_CORRUPTION_DETECTED', message: `Invalid keyring file: ${filePath}` });
|
|
75
|
+
}
|
|
76
|
+
const validated = KeyringFileV1Schema.safeParse(parsed);
|
|
77
|
+
if (!validated.success) {
|
|
78
|
+
return (0, neverthrow_1.errAsync)({ code: 'KEYRING_CORRUPTION_DETECTED', message: `Invalid keyring file: ${filePath}` });
|
|
79
|
+
}
|
|
80
|
+
const currentValidation = validateKeyMaterialWithPort(this.base64url, validated.data.current.keyBase64Url);
|
|
81
|
+
if (currentValidation.isErr())
|
|
82
|
+
return (0, neverthrow_1.errAsync)(currentValidation.error);
|
|
83
|
+
if (validated.data.previous) {
|
|
84
|
+
const prevValidation = validateKeyMaterialWithPort(this.base64url, validated.data.previous.keyBase64Url);
|
|
85
|
+
if (prevValidation.isErr())
|
|
86
|
+
return (0, neverthrow_1.errAsync)(prevValidation.error);
|
|
87
|
+
}
|
|
88
|
+
return (0, neverthrow_1.okAsync)(validated.data);
|
|
80
89
|
}
|
|
81
90
|
persist(keyring) {
|
|
82
91
|
const dir = this.dataDir.keysDir();
|
|
@@ -2,10 +2,12 @@ import type { ResultAsync } from 'neverthrow';
|
|
|
2
2
|
import type { PinnedWorkflowStorePortV2, PinnedWorkflowStoreError } from '../../../ports/pinned-workflow-store.port.js';
|
|
3
3
|
import type { WorkflowHash } from '../../../durable-core/ids/index.js';
|
|
4
4
|
import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
|
|
5
|
+
import type { FileSystemPortV2 } from '../../../ports/fs.port.js';
|
|
5
6
|
import { type CompiledWorkflowSnapshot } from '../../../durable-core/schemas/compiled-workflow/index.js';
|
|
6
7
|
export declare class LocalPinnedWorkflowStoreV2 implements PinnedWorkflowStorePortV2 {
|
|
7
8
|
private readonly dataDir;
|
|
8
|
-
|
|
9
|
+
private readonly fs;
|
|
10
|
+
constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2);
|
|
9
11
|
get(workflowHash: WorkflowHash): ResultAsync<CompiledWorkflowSnapshot | null, PinnedWorkflowStoreError>;
|
|
10
12
|
put(workflowHash: WorkflowHash, compiled: CompiledWorkflowSnapshot): ResultAsync<void, PinnedWorkflowStoreError>;
|
|
11
13
|
}
|
|
@@ -1,84 +1,72 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.LocalPinnedWorkflowStoreV2 = void 0;
|
|
37
|
-
const fs = __importStar(require("fs/promises"));
|
|
38
4
|
const neverthrow_1 = require("neverthrow");
|
|
39
5
|
const index_js_1 = require("../../../durable-core/schemas/compiled-workflow/index.js");
|
|
40
6
|
const jcs_js_1 = require("../../../durable-core/canonical/jcs.js");
|
|
7
|
+
function mapFsToStoreError(e) {
|
|
8
|
+
return { code: 'PINNED_WORKFLOW_IO_ERROR', message: e.message };
|
|
9
|
+
}
|
|
41
10
|
class LocalPinnedWorkflowStoreV2 {
|
|
42
|
-
constructor(dataDir) {
|
|
11
|
+
constructor(dataDir, fs) {
|
|
43
12
|
this.dataDir = dataDir;
|
|
13
|
+
this.fs = fs;
|
|
44
14
|
}
|
|
45
15
|
get(workflowHash) {
|
|
46
16
|
const filePath = this.dataDir.pinnedWorkflowPath(workflowHash);
|
|
47
|
-
return
|
|
17
|
+
return this.fs.readFileUtf8(filePath)
|
|
18
|
+
.orElse((e) => {
|
|
19
|
+
if (e.code === 'FS_NOT_FOUND')
|
|
20
|
+
return (0, neverthrow_1.okAsync)(null);
|
|
21
|
+
return (0, neverthrow_1.errAsync)(mapFsToStoreError(e));
|
|
22
|
+
})
|
|
23
|
+
.andThen((raw) => {
|
|
24
|
+
if (raw === null)
|
|
25
|
+
return (0, neverthrow_1.okAsync)(null);
|
|
26
|
+
let parsed;
|
|
48
27
|
try {
|
|
49
|
-
|
|
50
|
-
const parsed = JSON.parse(raw);
|
|
51
|
-
const validated = index_js_1.CompiledWorkflowSnapshotSchema.safeParse(parsed);
|
|
52
|
-
if (!validated.success) {
|
|
53
|
-
throw new Error(`Pinned workflow snapshot is invalid: ${filePath}`);
|
|
54
|
-
}
|
|
55
|
-
return validated.data;
|
|
28
|
+
parsed = JSON.parse(raw);
|
|
56
29
|
}
|
|
57
|
-
catch
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
30
|
+
catch {
|
|
31
|
+
return (0, neverthrow_1.errAsync)({
|
|
32
|
+
code: 'PINNED_WORKFLOW_IO_ERROR',
|
|
33
|
+
message: `Invalid JSON in pinned workflow snapshot: ${filePath}`,
|
|
34
|
+
});
|
|
61
35
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
36
|
+
const validated = index_js_1.CompiledWorkflowSnapshotSchema.safeParse(parsed);
|
|
37
|
+
if (!validated.success) {
|
|
38
|
+
return (0, neverthrow_1.errAsync)({
|
|
39
|
+
code: 'PINNED_WORKFLOW_IO_ERROR',
|
|
40
|
+
message: `Pinned workflow snapshot is invalid: ${filePath}`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return (0, neverthrow_1.okAsync)(validated.data);
|
|
44
|
+
});
|
|
66
45
|
}
|
|
67
46
|
put(workflowHash, compiled) {
|
|
68
47
|
const dir = this.dataDir.pinnedWorkflowsDir();
|
|
69
48
|
const filePath = this.dataDir.pinnedWorkflowPath(workflowHash);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const canonical = (0, jcs_js_1.toCanonicalBytes)(compiled);
|
|
73
|
-
if (canonical.isErr()) {
|
|
74
|
-
throw new Error(`Failed to canonicalize compiled snapshot for storage: ${canonical.error.message}`);
|
|
75
|
-
}
|
|
76
|
-
const tmp = `${filePath}.tmp`;
|
|
77
|
-
await fs.writeFile(tmp, Buffer.from(canonical.value));
|
|
78
|
-
await fs.rename(tmp, filePath);
|
|
79
|
-
})(), (e) => ({
|
|
49
|
+
const tmpPath = `${filePath}.tmp`;
|
|
50
|
+
const canonical = (0, jcs_js_1.toCanonicalBytes)(compiled).mapErr((e) => ({
|
|
80
51
|
code: 'PINNED_WORKFLOW_IO_ERROR',
|
|
81
|
-
message: `Failed to
|
|
52
|
+
message: `Failed to canonicalize compiled snapshot for storage: ${e.message}`,
|
|
53
|
+
}));
|
|
54
|
+
if (canonical.isErr())
|
|
55
|
+
return (0, neverthrow_1.errAsync)(canonical.error);
|
|
56
|
+
const bytes = canonical.value;
|
|
57
|
+
return this.fs.mkdirp(dir)
|
|
58
|
+
.mapErr(mapFsToStoreError)
|
|
59
|
+
.andThen(() => this.fs.openWriteTruncate(tmpPath).mapErr(mapFsToStoreError))
|
|
60
|
+
.andThen(({ fd }) => this.fs.writeAll(fd, bytes)
|
|
61
|
+
.mapErr(mapFsToStoreError)
|
|
62
|
+
.andThen(() => this.fs.fsyncFile(fd).mapErr(mapFsToStoreError))
|
|
63
|
+
.andThen(() => this.fs.closeFile(fd).mapErr(mapFsToStoreError))
|
|
64
|
+
.orElse((e) => this.fs.closeFile(fd)
|
|
65
|
+
.mapErr(() => e)
|
|
66
|
+
.andThen(() => (0, neverthrow_1.errAsync)(e))))
|
|
67
|
+
.andThen(() => this.fs.rename(tmpPath, filePath).mapErr(mapFsToStoreError))
|
|
68
|
+
.andThen(() => this.fs.fsyncDir(dir).mapErr((e) => {
|
|
69
|
+
return mapFsToStoreError(e);
|
|
82
70
|
}));
|
|
83
71
|
}
|
|
84
72
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeRandomEntropyV2 = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
class NodeRandomEntropyV2 {
|
|
6
|
+
generateBytes(count) {
|
|
7
|
+
return new Uint8Array((0, node_crypto_1.randomBytes)(count));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.NodeRandomEntropyV2 = NodeRandomEntropyV2;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { ResultAsync } from 'neverthrow';
|
|
2
2
|
import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
|
|
3
3
|
import type { FileSystemPortV2 } from '../../../ports/fs.port.js';
|
|
4
|
+
import type { TimeClockPortV2 } from '../../../ports/time-clock.port.js';
|
|
4
5
|
import type { SessionId } from '../../../durable-core/ids/index.js';
|
|
5
6
|
import type { SessionLockHandleV2, SessionLockError, SessionLockPortV2 } from '../../../ports/session-lock.port.js';
|
|
6
7
|
export declare class LocalSessionLockV2 implements SessionLockPortV2 {
|
|
7
8
|
private readonly dataDir;
|
|
8
9
|
private readonly fs;
|
|
9
|
-
|
|
10
|
+
private readonly clock;
|
|
11
|
+
constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2, clock: TimeClockPortV2);
|
|
10
12
|
acquire(sessionId: SessionId): ResultAsync<SessionLockHandleV2, SessionLockError>;
|
|
11
13
|
release(handle: SessionLockHandleV2): ResultAsync<void, SessionLockError>;
|
|
12
14
|
}
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LocalSessionLockV2 = void 0;
|
|
4
4
|
class LocalSessionLockV2 {
|
|
5
|
-
constructor(dataDir, fs) {
|
|
5
|
+
constructor(dataDir, fs, clock) {
|
|
6
6
|
this.dataDir = dataDir;
|
|
7
7
|
this.fs = fs;
|
|
8
|
+
this.clock = clock;
|
|
8
9
|
}
|
|
9
10
|
acquire(sessionId) {
|
|
10
11
|
const sessionDir = this.dataDir.sessionDir(sessionId);
|
|
@@ -25,8 +26,8 @@ class LocalSessionLockV2 {
|
|
|
25
26
|
.andThen(() => this.fs.openExclusive(lockPath, new TextEncoder().encode(JSON.stringify({
|
|
26
27
|
v: 1,
|
|
27
28
|
sessionId,
|
|
28
|
-
pid:
|
|
29
|
-
startedAtMs:
|
|
29
|
+
pid: this.clock.getPid(),
|
|
30
|
+
startedAtMs: this.clock.nowMs(),
|
|
30
31
|
}))))
|
|
31
32
|
.andThen(({ fd }) => this.fs.fsyncFile(fd).andThen(() => this.fs.closeFile(fd)))
|
|
32
33
|
.mapErr(mapFs)
|
|
@@ -233,7 +233,15 @@ class LocalSessionEventLogStoreV2 {
|
|
|
233
233
|
const validated = index_js_1.ManifestRecordV1Schema.safeParse(parsed);
|
|
234
234
|
if (!validated.success) {
|
|
235
235
|
isComplete = false;
|
|
236
|
-
|
|
236
|
+
const rawVersion = (typeof parsed === 'object' && parsed !== null && 'v' in parsed)
|
|
237
|
+
? parsed.v
|
|
238
|
+
: undefined;
|
|
239
|
+
if (rawVersion !== 1) {
|
|
240
|
+
tailReason ?? (tailReason = { code: 'unknown_schema_version', message: `Expected v=1, got v=${rawVersion}` });
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
tailReason ?? (tailReason = { code: 'schema_validation_failed', message: 'Manifest record schema validation failed (corrupt tail)' });
|
|
244
|
+
}
|
|
237
245
|
break;
|
|
238
246
|
}
|
|
239
247
|
if (validated.data.manifestIndex !== i) {
|
|
@@ -366,7 +374,10 @@ class LocalSessionEventLogStoreV2 {
|
|
|
366
374
|
const bytes = bytesRes.value;
|
|
367
375
|
return this.fs.writeAll(handle.fd, bytes).mapErr(mapFsToStoreError)
|
|
368
376
|
.andThen(() => this.fs.fsyncFile(handle.fd).mapErr(mapFsToStoreError))
|
|
369
|
-
.andThen(() => this.fs.closeFile(handle.fd).mapErr(mapFsToStoreError))
|
|
377
|
+
.andThen(() => this.fs.closeFile(handle.fd).mapErr(mapFsToStoreError))
|
|
378
|
+
.orElse((err) => this.fs.closeFile(handle.fd)
|
|
379
|
+
.mapErr(() => err)
|
|
380
|
+
.andThen(() => (0, neverthrow_1.errAsync)(err)));
|
|
370
381
|
});
|
|
371
382
|
}
|
|
372
383
|
}
|
|
@@ -515,11 +526,22 @@ function parseJsonlText(text, schema) {
|
|
|
515
526
|
}
|
|
516
527
|
const validated = schema.safeParse(parsed);
|
|
517
528
|
if (!validated.success) {
|
|
529
|
+
const rawVersion = (typeof parsed === 'object' && parsed !== null && 'v' in parsed)
|
|
530
|
+
? parsed.v
|
|
531
|
+
: undefined;
|
|
532
|
+
if (rawVersion !== 1) {
|
|
533
|
+
return (0, neverthrow_1.err)({
|
|
534
|
+
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
535
|
+
location: i === 0 ? 'head' : 'tail',
|
|
536
|
+
reason: { code: 'unknown_schema_version', message: `Expected v=1, got v=${rawVersion}` },
|
|
537
|
+
message: `Unknown schema version at line ${i}: expected v=1, got v=${rawVersion}`,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
518
540
|
return (0, neverthrow_1.err)({
|
|
519
541
|
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
520
542
|
location: i === 0 ? 'head' : 'tail',
|
|
521
|
-
reason: { code: '
|
|
522
|
-
message: `
|
|
543
|
+
reason: { code: 'schema_validation_failed', message: `Schema validation failed at line ${i}` },
|
|
544
|
+
message: `Schema validation failed at line ${i}`,
|
|
523
545
|
});
|
|
524
546
|
}
|
|
525
547
|
out.push(validated.data);
|