@exaudeus/workrail 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manifest.json +176 -32
- package/dist/runtime/brand.d.ts +1 -3
- package/dist/v2/durable-core/canonical/hashing.d.ts +3 -1
- package/dist/v2/durable-core/canonical/hashing.js +9 -0
- package/dist/v2/durable-core/ids/index.d.ts +4 -0
- package/dist/v2/durable-core/ids/index.js +8 -0
- package/dist/v2/durable-core/ids/with-healthy-session-lock.d.ts +7 -0
- package/dist/v2/durable-core/ids/with-healthy-session-lock.js +2 -0
- package/dist/v2/durable-core/projections/snapshot-state.d.ts +3 -0
- package/dist/v2/durable-core/projections/snapshot-state.js +14 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +1042 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +119 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/index.d.ts +4 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/index.js +17 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/step-instance-key.d.ts +21 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/step-instance-key.js +67 -0
- package/dist/v2/durable-core/schemas/session/events.d.ts +3 -3
- package/dist/v2/durable-core/schemas/session/events.js +9 -5
- package/dist/v2/durable-core/schemas/session/index.d.ts +1 -0
- package/dist/v2/durable-core/schemas/session/session-health.d.ts +25 -0
- package/dist/v2/durable-core/schemas/session/session-health.js +2 -0
- package/dist/v2/durable-core/tokens/base64url.d.ts +7 -0
- package/dist/v2/durable-core/tokens/base64url.js +16 -0
- package/dist/v2/durable-core/tokens/index.d.ts +7 -0
- package/dist/v2/durable-core/tokens/index.js +20 -0
- package/dist/v2/durable-core/tokens/payloads.d.ts +210 -0
- package/dist/v2/durable-core/tokens/payloads.js +53 -0
- package/dist/v2/durable-core/tokens/token-codec.d.ts +31 -0
- package/dist/v2/durable-core/tokens/token-codec.js +64 -0
- package/dist/v2/durable-core/tokens/token-signer.d.ts +15 -0
- package/dist/v2/durable-core/tokens/token-signer.js +55 -0
- package/dist/v2/infra/local/data-dir/index.d.ts +4 -0
- package/dist/v2/infra/local/data-dir/index.js +12 -0
- package/dist/v2/infra/local/hmac-sha256/index.d.ts +5 -0
- package/dist/v2/infra/local/hmac-sha256/index.js +16 -0
- package/dist/v2/infra/local/keyring/index.d.ts +14 -0
- package/dist/v2/infra/local/keyring/index.js +103 -0
- package/dist/v2/infra/local/session-store/index.d.ts +7 -6
- package/dist/v2/infra/local/session-store/index.js +244 -129
- package/dist/v2/infra/local/snapshot-store/index.d.ts +15 -0
- package/dist/v2/infra/local/snapshot-store/index.js +76 -0
- package/dist/v2/ports/data-dir.port.d.ts +4 -0
- package/dist/v2/ports/hmac-sha256.port.d.ts +4 -0
- package/dist/v2/ports/hmac-sha256.port.js +2 -0
- package/dist/v2/ports/keyring.port.d.ts +26 -0
- package/dist/v2/ports/keyring.port.js +2 -0
- package/dist/v2/ports/session-event-log-store.port.d.ts +14 -2
- package/dist/v2/ports/snapshot-store.port.d.ts +17 -0
- package/dist/v2/ports/snapshot-store.port.js +2 -0
- package/dist/v2/projections/session-health.d.ts +1 -15
- package/dist/v2/projections/session-health.js +1 -4
- package/dist/v2/usecases/execution-session-gate.d.ts +53 -0
- package/dist/v2/usecases/execution-session-gate.js +167 -0
- package/package.json +2 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { CanonicalBytes } from '../ids/index.js';
|
|
3
|
+
import type { TokenStringV1 } from '../ids/index.js';
|
|
4
|
+
import { type TokenPayloadV1, type TokenPrefixV1 } from './payloads.js';
|
|
5
|
+
export type TokenDecodeErrorV2 = {
|
|
6
|
+
readonly code: 'TOKEN_INVALID_FORMAT';
|
|
7
|
+
readonly message: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly code: 'TOKEN_UNSUPPORTED_VERSION';
|
|
10
|
+
readonly message: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly code: 'TOKEN_SCOPE_MISMATCH';
|
|
13
|
+
readonly message: string;
|
|
14
|
+
} | {
|
|
15
|
+
readonly code: 'TOKEN_PAYLOAD_INVALID';
|
|
16
|
+
readonly message: string;
|
|
17
|
+
};
|
|
18
|
+
export interface ParsedTokenV1 {
|
|
19
|
+
readonly prefix: TokenPrefixV1;
|
|
20
|
+
readonly version: 1;
|
|
21
|
+
readonly payloadBase64Url: string;
|
|
22
|
+
readonly sigBase64Url: string;
|
|
23
|
+
readonly payloadBytes: CanonicalBytes;
|
|
24
|
+
readonly payload: TokenPayloadV1;
|
|
25
|
+
}
|
|
26
|
+
export declare function encodeTokenPayloadV1(payload: TokenPayloadV1): Result<CanonicalBytes, TokenDecodeErrorV2>;
|
|
27
|
+
export declare function encodeUnsignedTokenV1(payload: TokenPayloadV1): Result<{
|
|
28
|
+
readonly token: TokenStringV1;
|
|
29
|
+
readonly payloadBytes: CanonicalBytes;
|
|
30
|
+
}, TokenDecodeErrorV2>;
|
|
31
|
+
export declare function parseTokenV1(token: string): Result<ParsedTokenV1, TokenDecodeErrorV2>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.encodeTokenPayloadV1 = encodeTokenPayloadV1;
|
|
4
|
+
exports.encodeUnsignedTokenV1 = encodeUnsignedTokenV1;
|
|
5
|
+
exports.parseTokenV1 = parseTokenV1;
|
|
6
|
+
const neverthrow_1 = require("neverthrow");
|
|
7
|
+
const jcs_js_1 = require("../canonical/jcs.js");
|
|
8
|
+
const index_js_1 = require("../ids/index.js");
|
|
9
|
+
const base64url_js_1 = require("./base64url.js");
|
|
10
|
+
const payloads_js_1 = require("./payloads.js");
|
|
11
|
+
function encodeTokenPayloadV1(payload) {
|
|
12
|
+
return (0, jcs_js_1.toCanonicalBytes)(payload).mapErr((e) => ({
|
|
13
|
+
code: 'TOKEN_PAYLOAD_INVALID',
|
|
14
|
+
message: e.message,
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
function encodeUnsignedTokenV1(payload) {
|
|
18
|
+
const bytes = encodeTokenPayloadV1(payload);
|
|
19
|
+
if (bytes.isErr())
|
|
20
|
+
return (0, neverthrow_1.err)(bytes.error);
|
|
21
|
+
const prefix = (0, payloads_js_1.expectedPrefixForTokenKind)(payload.tokenKind);
|
|
22
|
+
const token = `${prefix}.v1.${(0, base64url_js_1.encodeBase64Url)(bytes.value)}.`;
|
|
23
|
+
return (0, neverthrow_1.ok)({ token: (0, index_js_1.asTokenStringV1)(token), payloadBytes: bytes.value });
|
|
24
|
+
}
|
|
25
|
+
function parseTokenV1(token) {
|
|
26
|
+
const parts = token.split('.');
|
|
27
|
+
if (parts.length !== 4)
|
|
28
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Expected 4 dot-separated segments' });
|
|
29
|
+
const [prefix, versionPart, payloadB64, sigB64] = parts;
|
|
30
|
+
if (versionPart !== 'v1')
|
|
31
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_UNSUPPORTED_VERSION', message: `Unsupported token version: ${versionPart}` });
|
|
32
|
+
if (prefix !== 'st' && prefix !== 'ack' && prefix !== 'chk') {
|
|
33
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: `Unknown token prefix: ${prefix}` });
|
|
34
|
+
}
|
|
35
|
+
if (payloadB64.trim() === '' || sigB64.trim() === '') {
|
|
36
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Missing payload or signature segment' });
|
|
37
|
+
}
|
|
38
|
+
const decoded = (0, base64url_js_1.decodeBase64Url)(payloadB64);
|
|
39
|
+
if (decoded.isErr())
|
|
40
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: decoded.error.message });
|
|
41
|
+
const payloadBytes = (0, index_js_1.asCanonicalBytes)(decoded.value);
|
|
42
|
+
let payloadJson;
|
|
43
|
+
try {
|
|
44
|
+
payloadJson = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Payload is not valid JSON' });
|
|
48
|
+
}
|
|
49
|
+
const validated = payloads_js_1.TokenPayloadV1Schema.safeParse(payloadJson);
|
|
50
|
+
if (!validated.success)
|
|
51
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Token payload failed schema validation' });
|
|
52
|
+
const expectedPrefix = (0, payloads_js_1.expectedPrefixForTokenKind)(validated.data.tokenKind);
|
|
53
|
+
if (expectedPrefix !== prefix) {
|
|
54
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'Token prefix does not match payload tokenKind' });
|
|
55
|
+
}
|
|
56
|
+
return (0, neverthrow_1.ok)({
|
|
57
|
+
prefix,
|
|
58
|
+
version: 1,
|
|
59
|
+
payloadBase64Url: payloadB64,
|
|
60
|
+
sigBase64Url: sigB64,
|
|
61
|
+
payloadBytes,
|
|
62
|
+
payload: validated.data,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { HmacSha256PortV2 } from '../../ports/hmac-sha256.port.js';
|
|
3
|
+
import type { KeyringV1 } from '../../ports/keyring.port.js';
|
|
4
|
+
import type { CanonicalBytes, TokenStringV1 } from '../ids/index.js';
|
|
5
|
+
import type { ParsedTokenV1, TokenDecodeErrorV2 } from './token-codec.js';
|
|
6
|
+
export type TokenVerifyErrorV2 = {
|
|
7
|
+
readonly code: 'TOKEN_BAD_SIGNATURE';
|
|
8
|
+
readonly message: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly code: 'TOKEN_INVALID_FORMAT';
|
|
11
|
+
readonly message: string;
|
|
12
|
+
};
|
|
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>;
|
|
15
|
+
export declare function assertTokenScopeMatchesState(state: ParsedTokenV1, other: ParsedTokenV1): Result<void, TokenDecodeErrorV2>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signTokenV1 = signTokenV1;
|
|
4
|
+
exports.verifyTokenSignatureV1 = verifyTokenSignatureV1;
|
|
5
|
+
exports.assertTokenScopeMatchesState = assertTokenScopeMatchesState;
|
|
6
|
+
const neverthrow_1 = require("neverthrow");
|
|
7
|
+
const base64url_js_1 = require("./base64url.js");
|
|
8
|
+
const index_js_1 = require("../ids/index.js");
|
|
9
|
+
function decodeKeyBytes(keyBase64Url) {
|
|
10
|
+
const decoded = (0, base64url_js_1.decodeBase64Url)(keyBase64Url);
|
|
11
|
+
if (decoded.isErr())
|
|
12
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid key encoding' });
|
|
13
|
+
if (decoded.value.length !== 32)
|
|
14
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid key length' });
|
|
15
|
+
return (0, neverthrow_1.ok)(decoded.value);
|
|
16
|
+
}
|
|
17
|
+
function signTokenV1(unsignedTokenPrefix, payloadBytes, keyring, hmac) {
|
|
18
|
+
const key = decodeKeyBytes(keyring.current.keyBase64Url);
|
|
19
|
+
if (key.isErr())
|
|
20
|
+
return (0, neverthrow_1.err)(key.error);
|
|
21
|
+
const sig = hmac.hmacSha256(key.value, payloadBytes);
|
|
22
|
+
const token = `${unsignedTokenPrefix}${(0, base64url_js_1.encodeBase64Url)(payloadBytes)}.${(0, base64url_js_1.encodeBase64Url)(sig)}`;
|
|
23
|
+
return (0, neverthrow_1.ok)((0, index_js_1.asTokenStringV1)(token));
|
|
24
|
+
}
|
|
25
|
+
function verifyTokenSignatureV1(parsed, keyring, hmac) {
|
|
26
|
+
const sigBytes = (0, base64url_js_1.decodeBase64Url)(parsed.sigBase64Url);
|
|
27
|
+
if (sigBytes.isErr())
|
|
28
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid signature encoding' });
|
|
29
|
+
if (sigBytes.value.length !== 32)
|
|
30
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid signature length' });
|
|
31
|
+
const keys = [keyring.current.keyBase64Url];
|
|
32
|
+
if (keyring.previous)
|
|
33
|
+
keys.push(keyring.previous.keyBase64Url);
|
|
34
|
+
for (const k of keys) {
|
|
35
|
+
const key = decodeKeyBytes(k);
|
|
36
|
+
if (key.isErr())
|
|
37
|
+
continue;
|
|
38
|
+
const expected = hmac.hmacSha256(key.value, parsed.payloadBytes);
|
|
39
|
+
if (hmac.timingSafeEqual(expected, sigBytes.value))
|
|
40
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
41
|
+
}
|
|
42
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_BAD_SIGNATURE', message: 'Signature verification failed' });
|
|
43
|
+
}
|
|
44
|
+
function assertTokenScopeMatchesState(state, other) {
|
|
45
|
+
if (state.payload.tokenKind !== 'state') {
|
|
46
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'Expected a state token for scope comparison' });
|
|
47
|
+
}
|
|
48
|
+
if (state.payload.sessionId !== other.payload.sessionId)
|
|
49
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'sessionId mismatch' });
|
|
50
|
+
if (state.payload.runId !== other.payload.runId)
|
|
51
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'runId mismatch' });
|
|
52
|
+
if (state.payload.nodeId !== other.payload.nodeId)
|
|
53
|
+
return (0, neverthrow_1.err)({ code: 'TOKEN_SCOPE_MISMATCH', message: 'nodeId mismatch' });
|
|
54
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
55
|
+
}
|
|
@@ -3,6 +3,10 @@ export declare class LocalDataDirV2 implements DataDirPortV2 {
|
|
|
3
3
|
private readonly env;
|
|
4
4
|
constructor(env: Record<string, string | undefined>);
|
|
5
5
|
private root;
|
|
6
|
+
snapshotsDir(): string;
|
|
7
|
+
snapshotPath(snapshotRef: string): string;
|
|
8
|
+
keysDir(): string;
|
|
9
|
+
keyringPath(): string;
|
|
6
10
|
pinnedWorkflowsDir(): string;
|
|
7
11
|
pinnedWorkflowPath(workflowHash: string): string;
|
|
8
12
|
sessionsDir(): string;
|
|
@@ -44,6 +44,18 @@ class LocalDataDirV2 {
|
|
|
44
44
|
const configured = this.env['WORKRAIL_DATA_DIR'];
|
|
45
45
|
return configured ? configured : path.join(os.homedir(), '.workrail', 'data');
|
|
46
46
|
}
|
|
47
|
+
snapshotsDir() {
|
|
48
|
+
return path.join(this.root(), 'snapshots');
|
|
49
|
+
}
|
|
50
|
+
snapshotPath(snapshotRef) {
|
|
51
|
+
return path.join(this.snapshotsDir(), `${snapshotRef}.json`);
|
|
52
|
+
}
|
|
53
|
+
keysDir() {
|
|
54
|
+
return path.join(this.root(), 'keys');
|
|
55
|
+
}
|
|
56
|
+
keyringPath() {
|
|
57
|
+
return path.join(this.keysDir(), 'keyring.json');
|
|
58
|
+
}
|
|
47
59
|
pinnedWorkflowsDir() {
|
|
48
60
|
return path.join(this.root(), 'workflows', 'pinned');
|
|
49
61
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeHmacSha256V2 = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
class NodeHmacSha256V2 {
|
|
6
|
+
hmacSha256(key, message) {
|
|
7
|
+
const out = (0, crypto_1.createHmac)('sha256', Buffer.from(key)).update(Buffer.from(message)).digest();
|
|
8
|
+
return new Uint8Array(out);
|
|
9
|
+
}
|
|
10
|
+
timingSafeEqual(a, b) {
|
|
11
|
+
if (a.length !== b.length)
|
|
12
|
+
return false;
|
|
13
|
+
return (0, crypto_1.timingSafeEqual)(Buffer.from(a), Buffer.from(b));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.NodeHmacSha256V2 = NodeHmacSha256V2;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ResultAsync } from 'neverthrow';
|
|
2
|
+
import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
|
|
3
|
+
import type { FileSystemPortV2 } from '../../../ports/fs.port.js';
|
|
4
|
+
import type { KeyringError, KeyringPortV2, KeyringV1 } from '../../../ports/keyring.port.js';
|
|
5
|
+
export declare class LocalKeyringV2 implements KeyringPortV2 {
|
|
6
|
+
private readonly dataDir;
|
|
7
|
+
private readonly fs;
|
|
8
|
+
constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2);
|
|
9
|
+
loadOrCreate(): ResultAsync<KeyringV1, KeyringError>;
|
|
10
|
+
rotate(): ResultAsync<KeyringV1, KeyringError>;
|
|
11
|
+
private createAndPersistFresh;
|
|
12
|
+
private parseAndValidate;
|
|
13
|
+
private persist;
|
|
14
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalKeyringV2 = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const neverthrow_1 = require("neverthrow");
|
|
7
|
+
const jcs_js_1 = require("../../../durable-core/canonical/jcs.js");
|
|
8
|
+
const KeyRecordSchema = zod_1.z.object({
|
|
9
|
+
alg: zod_1.z.literal('hmac_sha256'),
|
|
10
|
+
keyBase64Url: zod_1.z.string().min(1),
|
|
11
|
+
});
|
|
12
|
+
const KeyringFileV1Schema = zod_1.z.object({
|
|
13
|
+
v: zod_1.z.literal(1),
|
|
14
|
+
current: KeyRecordSchema,
|
|
15
|
+
previous: KeyRecordSchema.nullable(),
|
|
16
|
+
});
|
|
17
|
+
function encodeBase64Url(bytes) {
|
|
18
|
+
return Buffer.from(bytes).toString('base64url');
|
|
19
|
+
}
|
|
20
|
+
function decodeBase64Url(s) {
|
|
21
|
+
try {
|
|
22
|
+
return new Uint8Array(Buffer.from(s, 'base64url'));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function validateKeyMaterialOrThrow(keyBase64Url) {
|
|
29
|
+
const decoded = decodeBase64Url(keyBase64Url);
|
|
30
|
+
if (!decoded)
|
|
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) };
|
|
38
|
+
}
|
|
39
|
+
class LocalKeyringV2 {
|
|
40
|
+
constructor(dataDir, fs) {
|
|
41
|
+
this.dataDir = dataDir;
|
|
42
|
+
this.fs = fs;
|
|
43
|
+
}
|
|
44
|
+
loadOrCreate() {
|
|
45
|
+
const path = this.dataDir.keyringPath();
|
|
46
|
+
return this.fs
|
|
47
|
+
.readFileUtf8(path)
|
|
48
|
+
.andThen((raw) => this.parseAndValidate(raw, path))
|
|
49
|
+
.orElse((e) => {
|
|
50
|
+
if (e.code === 'FS_NOT_FOUND')
|
|
51
|
+
return this.createAndPersistFresh();
|
|
52
|
+
return (0, neverthrow_1.errAsync)({ code: 'KEYRING_IO_ERROR', message: e.message });
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
rotate() {
|
|
56
|
+
return this.loadOrCreate().andThen((kr) => {
|
|
57
|
+
const next = {
|
|
58
|
+
v: 1,
|
|
59
|
+
current: createFreshKeyRecord(),
|
|
60
|
+
previous: kr.current,
|
|
61
|
+
};
|
|
62
|
+
return this.persist(next).map(() => next);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
createAndPersistFresh() {
|
|
66
|
+
const fresh = { v: 1, current: createFreshKeyRecord(), previous: null };
|
|
67
|
+
return this.persist(fresh).map(() => fresh);
|
|
68
|
+
}
|
|
69
|
+
parseAndValidate(raw, filePath) {
|
|
70
|
+
return neverthrow_1.ResultAsync.fromPromise((async () => {
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
const validated = KeyringFileV1Schema.safeParse(parsed);
|
|
73
|
+
if (!validated.success)
|
|
74
|
+
throw new Error('invalid_shape');
|
|
75
|
+
validateKeyMaterialOrThrow(validated.data.current.keyBase64Url);
|
|
76
|
+
if (validated.data.previous)
|
|
77
|
+
validateKeyMaterialOrThrow(validated.data.previous.keyBase64Url);
|
|
78
|
+
return validated.data;
|
|
79
|
+
})(), () => ({ code: 'KEYRING_CORRUPTION_DETECTED', message: `Invalid keyring file: ${filePath}` }));
|
|
80
|
+
}
|
|
81
|
+
persist(keyring) {
|
|
82
|
+
const dir = this.dataDir.keysDir();
|
|
83
|
+
const filePath = this.dataDir.keyringPath();
|
|
84
|
+
const tmpPath = `${filePath}.tmp`;
|
|
85
|
+
const canonical = (0, jcs_js_1.toCanonicalBytes)(keyring).mapErr((e) => ({
|
|
86
|
+
code: 'KEYRING_INVARIANT_VIOLATION',
|
|
87
|
+
message: e.message,
|
|
88
|
+
}));
|
|
89
|
+
if (canonical.isErr())
|
|
90
|
+
return (0, neverthrow_1.errAsync)(canonical.error);
|
|
91
|
+
return this.fs
|
|
92
|
+
.mkdirp(dir)
|
|
93
|
+
.andThen(() => this.fs.openWriteTruncate(tmpPath))
|
|
94
|
+
.andThen((h) => this.fs
|
|
95
|
+
.writeAll(h.fd, canonical.value)
|
|
96
|
+
.andThen(() => this.fs.fsyncFile(h.fd))
|
|
97
|
+
.andThen(() => this.fs.closeFile(h.fd)))
|
|
98
|
+
.andThen(() => this.fs.rename(tmpPath, filePath))
|
|
99
|
+
.andThen(() => this.fs.fsyncDir(dir))
|
|
100
|
+
.mapErr((e) => ({ code: 'KEYRING_IO_ERROR', message: e.message }));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.LocalKeyringV2 = LocalKeyringV2;
|
|
@@ -2,20 +2,21 @@ 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 { Sha256PortV2 } from '../../../ports/sha256.port.js';
|
|
5
|
-
import type {
|
|
6
|
-
import type { AppendPlanV2, SessionEventLogStoreError, LoadedSessionTruthV2, SessionEventLogStorePortV2 } from '../../../ports/session-event-log-store.port.js';
|
|
5
|
+
import type { AppendPlanV2, LoadedValidatedPrefixV2, SessionEventLogStoreError, LoadedSessionTruthV2, SessionEventLogAppendStorePortV2, SessionEventLogReadonlyStorePortV2 } from '../../../ports/session-event-log-store.port.js';
|
|
7
6
|
import type { SessionId } from '../../../durable-core/ids/index.js';
|
|
8
|
-
|
|
7
|
+
import type { WithHealthySessionLock } from '../../../durable-core/ids/with-healthy-session-lock.js';
|
|
8
|
+
export declare class LocalSessionEventLogStoreV2 implements SessionEventLogReadonlyStorePortV2, SessionEventLogAppendStorePortV2 {
|
|
9
9
|
private readonly dataDir;
|
|
10
10
|
private readonly fs;
|
|
11
11
|
private readonly sha256;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
append(sessionId: SessionId, plan: AppendPlanV2): ResultAsync<void, SessionEventLogStoreError>;
|
|
12
|
+
constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2, sha256: Sha256PortV2);
|
|
13
|
+
append(lock: WithHealthySessionLock, plan: AppendPlanV2): ResultAsync<void, SessionEventLogStoreError>;
|
|
15
14
|
load(sessionId: SessionId): ResultAsync<LoadedSessionTruthV2, SessionEventLogStoreError>;
|
|
15
|
+
loadValidatedPrefix(sessionId: SessionId): ResultAsync<LoadedValidatedPrefixV2, SessionEventLogStoreError>;
|
|
16
16
|
private appendImpl;
|
|
17
17
|
private loadImpl;
|
|
18
18
|
private readManifestOrEmpty;
|
|
19
|
+
private loadValidatedPrefixImpl;
|
|
19
20
|
private loadTruthOrEmpty;
|
|
20
21
|
private appendManifestRecords;
|
|
21
22
|
private unwrap;
|