open-multi-agent-kit 0.96.0 → 0.96.2
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/CHANGELOG.md +81 -0
- package/README.md +34 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +15 -20
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/resource-doctor-cli.d.ts +22 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
- package/dist/commands/resource-doctor-cli.js +98 -0
- package/dist/commands/resource-doctor-cli.js.map +1 -0
- package/dist/commands/sdk-session-cli.d.ts +24 -0
- package/dist/commands/sdk-session-cli.d.ts.map +1 -0
- package/dist/commands/sdk-session-cli.js +225 -0
- package/dist/commands/sdk-session-cli.js.map +1 -0
- package/dist/core/advisory-judge-model.d.ts +18 -0
- package/dist/core/advisory-judge-model.d.ts.map +1 -0
- package/dist/core/advisory-judge-model.js +168 -0
- package/dist/core/advisory-judge-model.js.map +1 -0
- package/dist/core/advisory-judge-response.d.ts +8 -0
- package/dist/core/advisory-judge-response.d.ts.map +1 -0
- package/dist/core/advisory-judge-response.js +61 -0
- package/dist/core/advisory-judge-response.js.map +1 -0
- package/dist/core/advisory-judge-types.d.ts +53 -0
- package/dist/core/advisory-judge-types.d.ts.map +1 -0
- package/dist/core/advisory-judge-types.js +2 -0
- package/dist/core/advisory-judge-types.js.map +1 -0
- package/dist/core/advisory-judge.d.ts +8 -0
- package/dist/core/advisory-judge.d.ts.map +1 -0
- package/dist/core/advisory-judge.js +178 -0
- package/dist/core/advisory-judge.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +41 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +396 -179
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +28 -4
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction-gate.d.ts +28 -0
- package/dist/core/compaction-gate.d.ts.map +1 -0
- package/dist/core/compaction-gate.js +34 -0
- package/dist/core/compaction-gate.js.map +1 -0
- package/dist/core/completion-sound.d.ts +109 -0
- package/dist/core/completion-sound.d.ts.map +1 -0
- package/dist/core/completion-sound.js +165 -0
- package/dist/core/completion-sound.js.map +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +5 -3
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/domain-loadouts.d.ts.map +1 -1
- package/dist/core/domain-loadouts.js +2 -2
- package/dist/core/domain-loadouts.js.map +1 -1
- package/dist/core/durable-goal-checkpoint.d.ts +6 -0
- package/dist/core/durable-goal-checkpoint.d.ts.map +1 -0
- package/dist/core/durable-goal-checkpoint.js +195 -0
- package/dist/core/durable-goal-checkpoint.js.map +1 -0
- package/dist/core/durable-goal-parse.d.ts +3 -0
- package/dist/core/durable-goal-parse.d.ts.map +1 -0
- package/dist/core/durable-goal-parse.js +86 -0
- package/dist/core/durable-goal-parse.js.map +1 -0
- package/dist/core/durable-goal-reducer.d.ts +3 -0
- package/dist/core/durable-goal-reducer.d.ts.map +1 -0
- package/dist/core/durable-goal-reducer.js +175 -0
- package/dist/core/durable-goal-reducer.js.map +1 -0
- package/dist/core/durable-goal-store.d.ts +19 -0
- package/dist/core/durable-goal-store.d.ts.map +1 -0
- package/dist/core/durable-goal-store.js +126 -0
- package/dist/core/durable-goal-store.js.map +1 -0
- package/dist/core/durable-goal.d.ts +92 -0
- package/dist/core/durable-goal.d.ts.map +1 -0
- package/dist/core/durable-goal.js +62 -0
- package/dist/core/durable-goal.js.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts +3 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.js +125 -0
- package/dist/core/extensions/builtin/goal-controller.js.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts +3 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.js +31 -0
- package/dist/core/extensions/builtin/identical-loop.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.js +13 -0
- package/dist/core/extensions/builtin/prompt-preset.js.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js +12 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js.map +1 -0
- package/dist/core/extensions/types.d.ts +6 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-continuation.d.ts +13 -0
- package/dist/core/goal-continuation.d.ts.map +1 -0
- package/dist/core/goal-continuation.js +10 -0
- package/dist/core/goal-continuation.js.map +1 -0
- package/dist/core/grok-harness.d.ts +1 -1
- package/dist/core/grok-harness.d.ts.map +1 -1
- package/dist/core/grok-harness.js +1 -1
- package/dist/core/grok-harness.js.map +1 -1
- package/dist/core/grok-playbook.d.ts +1 -1
- package/dist/core/grok-playbook.d.ts.map +1 -1
- package/dist/core/grok-playbook.js +1 -1
- package/dist/core/grok-playbook.js.map +1 -1
- package/dist/core/host-resource-probe.d.ts +63 -0
- package/dist/core/host-resource-probe.d.ts.map +1 -0
- package/dist/core/host-resource-probe.js +126 -0
- package/dist/core/host-resource-probe.js.map +1 -0
- package/dist/core/host-resource-snapshot.d.ts +54 -0
- package/dist/core/host-resource-snapshot.d.ts.map +1 -0
- package/dist/core/host-resource-snapshot.js +139 -0
- package/dist/core/host-resource-snapshot.js.map +1 -0
- package/dist/core/identical-loop.d.ts +15 -0
- package/dist/core/identical-loop.d.ts.map +1 -0
- package/dist/core/identical-loop.js +34 -0
- package/dist/core/identical-loop.js.map +1 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-prompt-policy.d.ts +11 -0
- package/dist/core/model-prompt-policy.d.ts.map +1 -0
- package/dist/core/model-prompt-policy.js +17 -0
- package/dist/core/model-prompt-policy.js.map +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -4
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-budget.d.ts +21 -0
- package/dist/core/prompt-budget.d.ts.map +1 -0
- package/dist/core/prompt-budget.js +51 -0
- package/dist/core/prompt-budget.js.map +1 -0
- package/dist/core/prompt-cache.d.ts +14 -0
- package/dist/core/prompt-cache.d.ts.map +1 -0
- package/dist/core/prompt-cache.js +16 -0
- package/dist/core/prompt-cache.js.map +1 -0
- package/dist/core/prompt-preset.d.ts +8 -0
- package/dist/core/prompt-preset.d.ts.map +1 -0
- package/dist/core/prompt-preset.js +56 -0
- package/dist/core/prompt-preset.js.map +1 -0
- package/dist/core/prompt-settlement.d.ts +81 -0
- package/dist/core/prompt-settlement.d.ts.map +1 -0
- package/dist/core/prompt-settlement.js +84 -0
- package/dist/core/prompt-settlement.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +16 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +24 -2
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-retry.d.ts +36 -0
- package/dist/core/provider-retry.d.ts.map +1 -0
- package/dist/core/provider-retry.js +73 -0
- package/dist/core/provider-retry.js.map +1 -0
- package/dist/core/provider-usage.d.ts +16 -2
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +150 -8
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
- package/dist/core/reasoning-router-resolver.js +1 -1
- package/dist/core/reasoning-router-resolver.js.map +1 -1
- package/dist/core/resource-admission-config.d.ts +55 -0
- package/dist/core/resource-admission-config.d.ts.map +1 -0
- package/dist/core/resource-admission-config.js +97 -0
- package/dist/core/resource-admission-config.js.map +1 -0
- package/dist/core/resource-admission.d.ts +81 -0
- package/dist/core/resource-admission.d.ts.map +1 -0
- package/dist/core/resource-admission.js +128 -0
- package/dist/core/resource-admission.js.map +1 -0
- package/dist/core/resource-governor-format.d.ts +50 -0
- package/dist/core/resource-governor-format.d.ts.map +1 -0
- package/dist/core/resource-governor-format.js +113 -0
- package/dist/core/resource-governor-format.js.map +1 -0
- package/dist/core/resource-governor-settings.d.ts +56 -0
- package/dist/core/resource-governor-settings.d.ts.map +1 -0
- package/dist/core/resource-governor-settings.js +89 -0
- package/dist/core/resource-governor-settings.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +32 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/resource-observation-journal.d.ts +74 -0
- package/dist/core/resource-observation-journal.d.ts.map +1 -0
- package/dist/core/resource-observation-journal.js +198 -0
- package/dist/core/resource-observation-journal.js.map +1 -0
- package/dist/core/resource-safety-gate.d.ts +54 -0
- package/dist/core/resource-safety-gate.d.ts.map +1 -0
- package/dist/core/resource-safety-gate.js +51 -0
- package/dist/core/resource-safety-gate.js.map +1 -0
- package/dist/core/run-journal.d.ts.map +1 -1
- package/dist/core/run-journal.js +13 -1
- package/dist/core/run-journal.js.map +1 -1
- package/dist/core/run-resource-lease.d.ts +73 -0
- package/dist/core/run-resource-lease.d.ts.map +1 -0
- package/dist/core/run-resource-lease.js +78 -0
- package/dist/core/run-resource-lease.js.map +1 -0
- package/dist/core/sandbox/backend.d.ts +12 -0
- package/dist/core/sandbox/backend.d.ts.map +1 -1
- package/dist/core/sandbox/backend.js +52 -42
- package/dist/core/sandbox/backend.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +2 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -1
- package/dist/core/sandbox/policy.js +2 -1
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-bash-service.d.ts +12 -0
- package/dist/core/session-bash-service.d.ts.map +1 -1
- package/dist/core/session-bash-service.js +14 -0
- package/dist/core/session-bash-service.js.map +1 -1
- package/dist/core/session-system-prompt.d.ts +30 -0
- package/dist/core/session-system-prompt.d.ts.map +1 -0
- package/dist/core/session-system-prompt.js +39 -0
- package/dist/core/session-system-prompt.js.map +1 -0
- package/dist/core/session-termination.d.ts +7 -2
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +29 -1
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +12 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +36 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/strict-record.d.ts +2 -0
- package/dist/core/strict-record.d.ts.map +1 -0
- package/dist/core/strict-record.js +9 -0
- package/dist/core/strict-record.js.map +1 -0
- package/dist/core/subagent-lane-launcher.d.ts +64 -0
- package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
- package/dist/core/subagent-lane-launcher.js +107 -0
- package/dist/core/subagent-lane-launcher.js.map +1 -0
- package/dist/core/system-cpu-sampler.d.ts +45 -0
- package/dist/core/system-cpu-sampler.d.ts.map +1 -0
- package/dist/core/system-cpu-sampler.js +95 -0
- package/dist/core/system-cpu-sampler.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -8
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tool-pair-repair.d.ts +11 -0
- package/dist/core/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/tool-pair-repair.js +38 -0
- package/dist/core/tool-pair-repair.js.map +1 -0
- package/dist/core/tools/artifact-spill.d.ts +13 -0
- package/dist/core/tools/artifact-spill.d.ts.map +1 -0
- package/dist/core/tools/artifact-spill.js +26 -0
- package/dist/core/tools/artifact-spill.js.map +1 -0
- package/dist/core/tools/bash.d.ts +7 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +8 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +10 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +2 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +32 -0
- package/dist/core/workload-classifier.d.ts.map +1 -0
- package/dist/core/workload-classifier.js +66 -0
- package/dist/core/workload-classifier.js.map +1 -0
- package/dist/core/workload-family-matcher.d.ts +3 -0
- package/dist/core/workload-family-matcher.d.ts.map +1 -0
- package/dist/core/workload-family-matcher.js +126 -0
- package/dist/core/workload-family-matcher.js.map +1 -0
- package/dist/core/workload-permit-pool.d.ts +78 -0
- package/dist/core/workload-permit-pool.d.ts.map +1 -0
- package/dist/core/workload-permit-pool.js +138 -0
- package/dist/core/workload-permit-pool.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +68 -0
- package/dist/core/workload-shard-executor.d.ts.map +1 -0
- package/dist/core/workload-shard-executor.js +156 -0
- package/dist/core/workload-shard-executor.js.map +1 -0
- package/dist/core/workload-shard-plan.d.ts +81 -0
- package/dist/core/workload-shard-plan.d.ts.map +1 -0
- package/dist/core/workload-shard-plan.js +183 -0
- package/dist/core/workload-shard-plan.js.map +1 -0
- package/dist/core/workload-shard-runner.d.ts +11 -0
- package/dist/core/workload-shard-runner.d.ts.map +1 -0
- package/dist/core/workload-shard-runner.js +98 -0
- package/dist/core/workload-shard-runner.js.map +1 -0
- package/dist/core/workload-shard-store.d.ts +44 -0
- package/dist/core/workload-shard-store.d.ts.map +1 -0
- package/dist/core/workload-shard-store.js +189 -0
- package/dist/core/workload-shard-store.js.map +1 -0
- package/dist/core/workload-sharders.d.ts +47 -0
- package/dist/core/workload-sharders.d.ts.map +1 -0
- package/dist/core/workload-sharders.js +162 -0
- package/dist/core/workload-sharders.js.map +1 -0
- package/dist/core/workload-shell-scan.d.ts +12 -0
- package/dist/core/workload-shell-scan.d.ts.map +1 -0
- package/dist/core/workload-shell-scan.js +79 -0
- package/dist/core/workload-shell-scan.js.map +1 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +14 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +81 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +14 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +1 -1
- package/docs/adaptorch-preview.md +2 -2
- package/docs/book-to-skill.md +1 -1
- package/docs/containerization.md +9 -0
- package/docs/docs.json +11 -3
- package/docs/environment-variables.md +11 -4
- package/docs/extensions.md +14 -1
- package/docs/grok-harness.md +35 -12
- package/docs/index.md +7 -6
- package/docs/loadout-domains/grok-harness.md +2 -2
- package/docs/models.md +10 -9
- package/docs/provider-resilience.md +9 -3
- package/docs/providers.md +9 -4
- package/docs/run-protocol.md +32 -0
- package/docs/sdk.md +100 -3
- package/docs/security.md +22 -32
- package/docs/sessions.md +14 -0
- package/docs/settings.md +64 -2
- package/docs/skills.md +16 -0
- package/docs/usage.md +37 -15
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
- package/examples/extensions/subagent/domain-profiles.ts +0 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +7 -9
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { computeDurableGoalGeneration, DURABLE_GOAL_CHECKPOINT_SCHEMA_VERSION, DurableGoalError, freshDurableGoalEvidence, } from "./durable-goal.js";
|
|
3
|
+
import { redactSensitiveTextForced } from "./redaction.js";
|
|
4
|
+
import { isExactRecord } from "./strict-record.js";
|
|
5
|
+
const MAX_LIST_ITEMS = 12;
|
|
6
|
+
const MAX_ITEM_CHARS = 512;
|
|
7
|
+
const MAX_NEXT_CHARS = 1_024;
|
|
8
|
+
const DIGEST_PATTERN = /^[0-9a-f]{64}$/u;
|
|
9
|
+
export function createDurableGoalCheckpoint(goal, input, now) {
|
|
10
|
+
const capturedAt = timestamp(input.capturedAt, "checkpoint capturedAt");
|
|
11
|
+
if (Date.parse(capturedAt) < Date.parse(goal.generationStartedAt) || Date.parse(capturedAt) > Date.parse(now)) {
|
|
12
|
+
throw new DurableGoalError("invalid-input", "checkpoint timestamp is outside current goal generation");
|
|
13
|
+
}
|
|
14
|
+
const verifiedEvidenceIds = evidenceIds(input.verifiedEvidenceIds, "invalid-input");
|
|
15
|
+
const freshEvidence = freshDurableGoalEvidence(goal);
|
|
16
|
+
if (verifiedEvidenceIds.some((id) => {
|
|
17
|
+
const evidence = freshEvidence.find((candidate) => candidate.id === id);
|
|
18
|
+
return !evidence || Date.parse(evidence.capturedAt) > Date.parse(capturedAt);
|
|
19
|
+
})) {
|
|
20
|
+
throw new DurableGoalError("invalid-input", "checkpoint evidence must reference fresh goal evidence");
|
|
21
|
+
}
|
|
22
|
+
return seal({
|
|
23
|
+
schemaVersion: DURABLE_GOAL_CHECKPOINT_SCHEMA_VERSION,
|
|
24
|
+
goalGeneration: computeDurableGoalGeneration(goal),
|
|
25
|
+
core: textList(input.core, "checkpoint core", "invalid-input"),
|
|
26
|
+
verifiedEvidenceIds,
|
|
27
|
+
open: textList(input.open, "checkpoint open", "invalid-input"),
|
|
28
|
+
next: text(input.next, MAX_NEXT_CHARS, "checkpoint next", "invalid-input"),
|
|
29
|
+
capturedAt,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function parseDurableGoalCheckpoint(value, goal) {
|
|
33
|
+
if (!isExactRecord(value, [
|
|
34
|
+
"schemaVersion",
|
|
35
|
+
"goalGeneration",
|
|
36
|
+
"core",
|
|
37
|
+
"verifiedEvidenceIds",
|
|
38
|
+
"open",
|
|
39
|
+
"next",
|
|
40
|
+
"capturedAt",
|
|
41
|
+
"digest",
|
|
42
|
+
])) {
|
|
43
|
+
throw invalidStore();
|
|
44
|
+
}
|
|
45
|
+
if (value.schemaVersion !== DURABLE_GOAL_CHECKPOINT_SCHEMA_VERSION ||
|
|
46
|
+
!DIGEST_PATTERN.test(stringValue(value.goalGeneration))) {
|
|
47
|
+
throw invalidStore();
|
|
48
|
+
}
|
|
49
|
+
const capturedAt = persistedTimestamp(value.capturedAt);
|
|
50
|
+
if (Date.parse(capturedAt) < Date.parse(goal.createdAt) || Date.parse(capturedAt) > Date.parse(goal.updatedAt)) {
|
|
51
|
+
throw invalidStore();
|
|
52
|
+
}
|
|
53
|
+
const material = {
|
|
54
|
+
schemaVersion: DURABLE_GOAL_CHECKPOINT_SCHEMA_VERSION,
|
|
55
|
+
goalGeneration: stringValue(value.goalGeneration),
|
|
56
|
+
core: persistedTextList(value.core),
|
|
57
|
+
verifiedEvidenceIds: persistedEvidenceIds(value.verifiedEvidenceIds),
|
|
58
|
+
open: persistedTextList(value.open),
|
|
59
|
+
next: persistedText(value.next, MAX_NEXT_CHARS),
|
|
60
|
+
capturedAt,
|
|
61
|
+
};
|
|
62
|
+
const checkpoint = seal(material);
|
|
63
|
+
if (checkpoint.digest !== value.digest)
|
|
64
|
+
throw invalidStore();
|
|
65
|
+
for (const id of checkpoint.verifiedEvidenceIds) {
|
|
66
|
+
const evidence = goal.evidence.find((candidate) => candidate.id === id && candidate.goalGeneration === checkpoint.goalGeneration);
|
|
67
|
+
if (!evidence || Date.parse(evidence.capturedAt) > Date.parse(checkpoint.capturedAt))
|
|
68
|
+
throw invalidStore();
|
|
69
|
+
}
|
|
70
|
+
return checkpoint;
|
|
71
|
+
}
|
|
72
|
+
export function formatDurableGoalCheckpoint(goal) {
|
|
73
|
+
const checkpoint = goal.checkpoint;
|
|
74
|
+
if (!checkpoint)
|
|
75
|
+
return `Goal: ${goal.objective}\nCheckpoint: none`;
|
|
76
|
+
return [
|
|
77
|
+
`Goal: ${goal.objective}`,
|
|
78
|
+
section("Core", checkpoint.core),
|
|
79
|
+
section("Verified", checkpoint.verifiedEvidenceIds),
|
|
80
|
+
section("Open", checkpoint.open),
|
|
81
|
+
`Next: ${checkpoint.next}`,
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
export function parseDurableGoalCheckpointCommand(payload, capturedAt) {
|
|
85
|
+
if (typeof payload !== "string" || payload.length === 0 || payload.length > 16_384) {
|
|
86
|
+
throw new DurableGoalError("invalid-input", "checkpoint JSON is invalid");
|
|
87
|
+
}
|
|
88
|
+
let value;
|
|
89
|
+
try {
|
|
90
|
+
value = JSON.parse(payload);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new DurableGoalError("invalid-input", "checkpoint JSON is invalid");
|
|
94
|
+
}
|
|
95
|
+
if (!isExactRecord(value, ["core", "verified", "open", "next"]) ||
|
|
96
|
+
!isStringArray(value.core) ||
|
|
97
|
+
!isStringArray(value.verified) ||
|
|
98
|
+
!isStringArray(value.open) ||
|
|
99
|
+
typeof value.next !== "string" ||
|
|
100
|
+
!isCanonicalTimestamp(capturedAt)) {
|
|
101
|
+
throw new DurableGoalError("invalid-input", "checkpoint JSON is invalid");
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
core: value.core,
|
|
105
|
+
verifiedEvidenceIds: value.verified,
|
|
106
|
+
open: value.open,
|
|
107
|
+
next: value.next,
|
|
108
|
+
capturedAt,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function seal(material) {
|
|
112
|
+
return { ...material, digest: createHash("sha256").update(JSON.stringify(material)).digest("hex") };
|
|
113
|
+
}
|
|
114
|
+
function textList(value, label, code) {
|
|
115
|
+
if (!Array.isArray(value) || value.length > MAX_LIST_ITEMS)
|
|
116
|
+
throw goalError(code, `${label} is invalid`);
|
|
117
|
+
const items = value.map((item) => text(item, MAX_ITEM_CHARS, label, code));
|
|
118
|
+
if (new Set(items).size !== items.length)
|
|
119
|
+
throw goalError(code, `${label} contains duplicates`);
|
|
120
|
+
return items;
|
|
121
|
+
}
|
|
122
|
+
function evidenceIds(value, code) {
|
|
123
|
+
const ids = textList(value, "checkpoint verified evidence", code);
|
|
124
|
+
if (ids.some((id) => redactSensitiveTextForced(id) !== id)) {
|
|
125
|
+
throw goalError(code, "checkpoint evidence id contains credential-shaped text");
|
|
126
|
+
}
|
|
127
|
+
return ids;
|
|
128
|
+
}
|
|
129
|
+
function text(value, maxChars, label, code) {
|
|
130
|
+
if (typeof value !== "string")
|
|
131
|
+
throw goalError(code, `${label} must be text`);
|
|
132
|
+
const redacted = redactSensitiveTextForced(value).trim();
|
|
133
|
+
if (redacted.length === 0 || redacted.length > maxChars)
|
|
134
|
+
throw goalError(code, `${label} is invalid`);
|
|
135
|
+
return redacted;
|
|
136
|
+
}
|
|
137
|
+
function timestamp(value, label) {
|
|
138
|
+
if (!isCanonicalTimestamp(value)) {
|
|
139
|
+
throw new DurableGoalError("invalid-input", `${label} must be an ISO timestamp`);
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
function persistedTextList(value) {
|
|
144
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
|
|
145
|
+
throw invalidStore();
|
|
146
|
+
const items = textList(value, "checkpoint text", "invalid-store");
|
|
147
|
+
if (items.some((item, index) => item !== value[index]))
|
|
148
|
+
throw invalidStore();
|
|
149
|
+
return items;
|
|
150
|
+
}
|
|
151
|
+
function persistedEvidenceIds(value) {
|
|
152
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
|
|
153
|
+
throw invalidStore();
|
|
154
|
+
const ids = evidenceIds(value, "invalid-store");
|
|
155
|
+
if (ids.some((id, index) => id !== value[index]))
|
|
156
|
+
throw invalidStore();
|
|
157
|
+
return ids;
|
|
158
|
+
}
|
|
159
|
+
function persistedText(value, maxChars) {
|
|
160
|
+
if (typeof value !== "string")
|
|
161
|
+
throw invalidStore();
|
|
162
|
+
const parsed = text(value, maxChars, "checkpoint text", "invalid-store");
|
|
163
|
+
if (parsed !== value)
|
|
164
|
+
throw invalidStore();
|
|
165
|
+
return parsed;
|
|
166
|
+
}
|
|
167
|
+
function persistedTimestamp(value) {
|
|
168
|
+
if (!isCanonicalTimestamp(value))
|
|
169
|
+
throw invalidStore();
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
function isCanonicalTimestamp(value) {
|
|
173
|
+
if (typeof value !== "string" || value.length === 0)
|
|
174
|
+
return false;
|
|
175
|
+
const timestamp = Date.parse(value);
|
|
176
|
+
return !Number.isNaN(timestamp) && new Date(timestamp).toISOString() === value;
|
|
177
|
+
}
|
|
178
|
+
function section(label, values) {
|
|
179
|
+
return values.length === 0 ? `${label}: (none)` : `${label}: ${values.join(" | ")}`;
|
|
180
|
+
}
|
|
181
|
+
function stringValue(value) {
|
|
182
|
+
if (typeof value !== "string")
|
|
183
|
+
throw invalidStore();
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
function isStringArray(value) {
|
|
187
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
188
|
+
}
|
|
189
|
+
function goalError(code, message) {
|
|
190
|
+
return code === "invalid-store" ? invalidStore() : new DurableGoalError(code, message);
|
|
191
|
+
}
|
|
192
|
+
function invalidStore() {
|
|
193
|
+
return new DurableGoalError("invalid-store", "durable goal journal is invalid");
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=durable-goal-checkpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-goal-checkpoint.js","sourceRoot":"","sources":["../../src/core/durable-goal-checkpoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n\tcomputeDurableGoalGeneration,\n\tDURABLE_GOAL_CHECKPOINT_SCHEMA_VERSION,\n\ttype DurableGoalCheckpoint,\n\ttype DurableGoalCheckpointInput,\n\tDurableGoalError,\n\ttype DurableGoalSnapshot,\n\tfreshDurableGoalEvidence,\n} from \"./durable-goal.ts\";\nimport { redactSensitiveTextForced } from \"./redaction.ts\";\nimport { isExactRecord } from \"./strict-record.ts\";\n\nconst MAX_LIST_ITEMS = 12;\nconst MAX_ITEM_CHARS = 512;\nconst MAX_NEXT_CHARS = 1_024;\nconst DIGEST_PATTERN = /^[0-9a-f]{64}$/u;\n\nexport function createDurableGoalCheckpoint(\n\tgoal: DurableGoalSnapshot,\n\tinput: DurableGoalCheckpointInput,\n\tnow: string,\n): DurableGoalCheckpoint {\n\tconst capturedAt = timestamp(input.capturedAt, \"checkpoint capturedAt\");\n\tif (Date.parse(capturedAt) < Date.parse(goal.generationStartedAt) || Date.parse(capturedAt) > Date.parse(now)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"checkpoint timestamp is outside current goal generation\");\n\t}\n\tconst verifiedEvidenceIds = evidenceIds(input.verifiedEvidenceIds, \"invalid-input\");\n\tconst freshEvidence = freshDurableGoalEvidence(goal);\n\tif (\n\t\tverifiedEvidenceIds.some((id) => {\n\t\t\tconst evidence = freshEvidence.find((candidate) => candidate.id === id);\n\t\t\treturn !evidence || Date.parse(evidence.capturedAt) > Date.parse(capturedAt);\n\t\t})\n\t) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"checkpoint evidence must reference fresh goal evidence\");\n\t}\n\treturn seal({\n\t\tschemaVersion: DURABLE_GOAL_CHECKPOINT_SCHEMA_VERSION,\n\t\tgoalGeneration: computeDurableGoalGeneration(goal),\n\t\tcore: textList(input.core, \"checkpoint core\", \"invalid-input\"),\n\t\tverifiedEvidenceIds,\n\t\topen: textList(input.open, \"checkpoint open\", \"invalid-input\"),\n\t\tnext: text(input.next, MAX_NEXT_CHARS, \"checkpoint next\", \"invalid-input\"),\n\t\tcapturedAt,\n\t});\n}\n\nexport function parseDurableGoalCheckpoint(value: unknown, goal: DurableGoalSnapshot): DurableGoalCheckpoint {\n\tif (\n\t\t!isExactRecord(value, [\n\t\t\t\"schemaVersion\",\n\t\t\t\"goalGeneration\",\n\t\t\t\"core\",\n\t\t\t\"verifiedEvidenceIds\",\n\t\t\t\"open\",\n\t\t\t\"next\",\n\t\t\t\"capturedAt\",\n\t\t\t\"digest\",\n\t\t])\n\t) {\n\t\tthrow invalidStore();\n\t}\n\tif (\n\t\tvalue.schemaVersion !== DURABLE_GOAL_CHECKPOINT_SCHEMA_VERSION ||\n\t\t!DIGEST_PATTERN.test(stringValue(value.goalGeneration))\n\t) {\n\t\tthrow invalidStore();\n\t}\n\tconst capturedAt = persistedTimestamp(value.capturedAt);\n\tif (Date.parse(capturedAt) < Date.parse(goal.createdAt) || Date.parse(capturedAt) > Date.parse(goal.updatedAt)) {\n\t\tthrow invalidStore();\n\t}\n\tconst material = {\n\t\tschemaVersion: DURABLE_GOAL_CHECKPOINT_SCHEMA_VERSION,\n\t\tgoalGeneration: stringValue(value.goalGeneration),\n\t\tcore: persistedTextList(value.core),\n\t\tverifiedEvidenceIds: persistedEvidenceIds(value.verifiedEvidenceIds),\n\t\topen: persistedTextList(value.open),\n\t\tnext: persistedText(value.next, MAX_NEXT_CHARS),\n\t\tcapturedAt,\n\t};\n\tconst checkpoint = seal(material);\n\tif (checkpoint.digest !== value.digest) throw invalidStore();\n\tfor (const id of checkpoint.verifiedEvidenceIds) {\n\t\tconst evidence = goal.evidence.find(\n\t\t\t(candidate) => candidate.id === id && candidate.goalGeneration === checkpoint.goalGeneration,\n\t\t);\n\t\tif (!evidence || Date.parse(evidence.capturedAt) > Date.parse(checkpoint.capturedAt)) throw invalidStore();\n\t}\n\treturn checkpoint;\n}\n\nexport function formatDurableGoalCheckpoint(goal: DurableGoalSnapshot): string {\n\tconst checkpoint = goal.checkpoint;\n\tif (!checkpoint) return `Goal: ${goal.objective}\\nCheckpoint: none`;\n\treturn [\n\t\t`Goal: ${goal.objective}`,\n\t\tsection(\"Core\", checkpoint.core),\n\t\tsection(\"Verified\", checkpoint.verifiedEvidenceIds),\n\t\tsection(\"Open\", checkpoint.open),\n\t\t`Next: ${checkpoint.next}`,\n\t].join(\"\\n\");\n}\n\nexport function parseDurableGoalCheckpointCommand(payload: string, capturedAt: string): DurableGoalCheckpointInput {\n\tif (typeof payload !== \"string\" || payload.length === 0 || payload.length > 16_384) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"checkpoint JSON is invalid\");\n\t}\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(payload);\n\t} catch {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"checkpoint JSON is invalid\");\n\t}\n\tif (\n\t\t!isExactRecord(value, [\"core\", \"verified\", \"open\", \"next\"]) ||\n\t\t!isStringArray(value.core) ||\n\t\t!isStringArray(value.verified) ||\n\t\t!isStringArray(value.open) ||\n\t\ttypeof value.next !== \"string\" ||\n\t\t!isCanonicalTimestamp(capturedAt)\n\t) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"checkpoint JSON is invalid\");\n\t}\n\treturn {\n\t\tcore: value.core,\n\t\tverifiedEvidenceIds: value.verified,\n\t\topen: value.open,\n\t\tnext: value.next,\n\t\tcapturedAt,\n\t};\n}\n\nfunction seal(material: Omit<DurableGoalCheckpoint, \"digest\">): DurableGoalCheckpoint {\n\treturn { ...material, digest: createHash(\"sha256\").update(JSON.stringify(material)).digest(\"hex\") };\n}\n\nfunction textList(value: readonly string[], label: string, code: \"invalid-input\" | \"invalid-store\"): readonly string[] {\n\tif (!Array.isArray(value) || value.length > MAX_LIST_ITEMS) throw goalError(code, `${label} is invalid`);\n\tconst items = value.map((item) => text(item, MAX_ITEM_CHARS, label, code));\n\tif (new Set(items).size !== items.length) throw goalError(code, `${label} contains duplicates`);\n\treturn items;\n}\n\nfunction evidenceIds(value: readonly string[], code: \"invalid-input\" | \"invalid-store\"): readonly string[] {\n\tconst ids = textList(value, \"checkpoint verified evidence\", code);\n\tif (ids.some((id) => redactSensitiveTextForced(id) !== id)) {\n\t\tthrow goalError(code, \"checkpoint evidence id contains credential-shaped text\");\n\t}\n\treturn ids;\n}\n\nfunction text(value: string, maxChars: number, label: string, code: \"invalid-input\" | \"invalid-store\"): string {\n\tif (typeof value !== \"string\") throw goalError(code, `${label} must be text`);\n\tconst redacted = redactSensitiveTextForced(value).trim();\n\tif (redacted.length === 0 || redacted.length > maxChars) throw goalError(code, `${label} is invalid`);\n\treturn redacted;\n}\n\nfunction timestamp(value: string, label: string): string {\n\tif (!isCanonicalTimestamp(value)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", `${label} must be an ISO timestamp`);\n\t}\n\treturn value;\n}\n\nfunction persistedTextList(value: unknown): readonly string[] {\n\tif (!Array.isArray(value) || value.some((item) => typeof item !== \"string\")) throw invalidStore();\n\tconst items = textList(value, \"checkpoint text\", \"invalid-store\");\n\tif (items.some((item, index) => item !== value[index])) throw invalidStore();\n\treturn items;\n}\n\nfunction persistedEvidenceIds(value: unknown): readonly string[] {\n\tif (!Array.isArray(value) || value.some((item) => typeof item !== \"string\")) throw invalidStore();\n\tconst ids = evidenceIds(value, \"invalid-store\");\n\tif (ids.some((id, index) => id !== value[index])) throw invalidStore();\n\treturn ids;\n}\n\nfunction persistedText(value: unknown, maxChars: number): string {\n\tif (typeof value !== \"string\") throw invalidStore();\n\tconst parsed = text(value, maxChars, \"checkpoint text\", \"invalid-store\");\n\tif (parsed !== value) throw invalidStore();\n\treturn parsed;\n}\n\nfunction persistedTimestamp(value: unknown): string {\n\tif (!isCanonicalTimestamp(value)) throw invalidStore();\n\treturn value;\n}\n\nfunction isCanonicalTimestamp(value: unknown): value is string {\n\tif (typeof value !== \"string\" || value.length === 0) return false;\n\tconst timestamp = Date.parse(value);\n\treturn !Number.isNaN(timestamp) && new Date(timestamp).toISOString() === value;\n}\n\nfunction section(label: string, values: readonly string[]): string {\n\treturn values.length === 0 ? `${label}: (none)` : `${label}: ${values.join(\" | \")}`;\n}\n\nfunction stringValue(value: unknown): string {\n\tif (typeof value !== \"string\") throw invalidStore();\n\treturn value;\n}\n\nfunction isStringArray(value: unknown): value is readonly string[] {\n\treturn Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nfunction goalError(code: \"invalid-input\" | \"invalid-store\", message: string): DurableGoalError {\n\treturn code === \"invalid-store\" ? invalidStore() : new DurableGoalError(code, message);\n}\n\nfunction invalidStore(): DurableGoalError {\n\treturn new DurableGoalError(\"invalid-store\", \"durable goal journal is invalid\");\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-goal-parse.d.ts","sourceRoot":"","sources":["../../src/core/durable-goal-parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,KAAK,mBAAmB,EAGxB,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAiC5E","sourcesContent":["import {\n\tDURABLE_GOAL_SCHEMA_VERSION,\n\tDURABLE_GOAL_STATUS_VALUES,\n\tDurableGoalError,\n\ttype DurableGoalEvidence,\n\ttype DurableGoalSnapshot,\n\ttype DurableGoalStatus,\n\tfreshDurableGoalEvidence,\n} from \"./durable-goal.ts\";\nimport { parseDurableGoalCheckpoint } from \"./durable-goal-checkpoint.ts\";\n\nexport function parseDurableGoalSnapshot(value: unknown): DurableGoalSnapshot {\n\tif (!isRecord(value) || value.schemaVersion !== DURABLE_GOAL_SCHEMA_VERSION || !isRecord(value.ref)) {\n\t\tthrow invalidStore();\n\t}\n\tconst status = value.status;\n\tif (!isGoalStatus(status) || !Array.isArray(value.evidence)) throw invalidStore();\n\tconst base: DurableGoalSnapshot = {\n\t\tschemaVersion: DURABLE_GOAL_SCHEMA_VERSION,\n\t\tref: { id: requireText(value.ref.id), revision: requirePositiveInteger(value.ref.revision) },\n\t\tobjective: requireText(value.objective),\n\t\tstatus,\n\t\tmaxRounds: requirePositiveInteger(value.maxRounds),\n\t\tcompletedRounds: requireNonNegativeInteger(value.completedRounds),\n\t\tevidence: value.evidence.map(parseEvidence),\n\t\tcreatedAt: requireTimestamp(value.createdAt),\n\t\tgenerationStartedAt: requireTimestamp(value.generationStartedAt),\n\t\tupdatedAt: requireTimestamp(value.updatedAt),\n\t\t...(value.blockedReason === undefined ? {} : { blockedReason: requireText(value.blockedReason) }),\n\t\t...(value.terminalAt === undefined ? {} : { terminalAt: requireTimestamp(value.terminalAt) }),\n\t};\n\tconst snapshot: DurableGoalSnapshot =\n\t\tvalue.checkpoint === undefined\n\t\t\t? base\n\t\t\t: { ...base, checkpoint: parseDurableGoalCheckpoint(value.checkpoint, base) };\n\tif (snapshot.completedRounds > snapshot.maxRounds) throw invalidStore();\n\tif (Date.parse(snapshot.generationStartedAt) < Date.parse(snapshot.createdAt)) throw invalidStore();\n\tif (Date.parse(snapshot.generationStartedAt) > Date.parse(snapshot.updatedAt)) throw invalidStore();\n\tif ((snapshot.status === \"blocked\") !== (snapshot.blockedReason !== undefined)) throw invalidStore();\n\tif ((snapshot.status === \"completed\" || snapshot.status === \"cleared\") !== (snapshot.terminalAt !== undefined)) {\n\t\tthrow invalidStore();\n\t}\n\tif (snapshot.status === \"completed\" && freshDurableGoalEvidence(snapshot).length === 0) throw invalidStore();\n\treturn snapshot;\n}\n\nfunction parseEvidence(value: unknown): DurableGoalEvidence {\n\tif (!isRecord(value)) throw invalidStore();\n\treturn {\n\t\tid: requireText(value.id),\n\t\tdigest: requireDigest(value.digest),\n\t\tcapturedAt: requireTimestamp(value.capturedAt),\n\t\tgoalGeneration: requireDigest(value.goalGeneration),\n\t};\n}\n\nfunction requireText(value: unknown): string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw invalidStore();\n\treturn value;\n}\n\nfunction requirePositiveInteger(value: unknown): number {\n\tif (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 1) throw invalidStore();\n\treturn value;\n}\n\nfunction requireNonNegativeInteger(value: unknown): number {\n\tif (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 0) throw invalidStore();\n\treturn value;\n}\n\nfunction requireTimestamp(value: unknown): string {\n\tif (typeof value !== \"string\" || value.length === 0 || Number.isNaN(Date.parse(value))) throw invalidStore();\n\treturn value;\n}\n\nfunction requireDigest(value: unknown): string {\n\tif (typeof value !== \"string\" || !/^[0-9a-f]{64}$/u.test(value)) throw invalidStore();\n\treturn value;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isGoalStatus(value: unknown): value is DurableGoalStatus {\n\treturn typeof value === \"string\" && DURABLE_GOAL_STATUS_VALUES.some((status) => status === value);\n}\n\nfunction invalidStore(): DurableGoalError {\n\treturn new DurableGoalError(\"invalid-store\", \"durable goal journal is invalid\");\n}\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { DURABLE_GOAL_SCHEMA_VERSION, DURABLE_GOAL_STATUS_VALUES, DurableGoalError, freshDurableGoalEvidence, } from "./durable-goal.js";
|
|
2
|
+
import { parseDurableGoalCheckpoint } from "./durable-goal-checkpoint.js";
|
|
3
|
+
export function parseDurableGoalSnapshot(value) {
|
|
4
|
+
if (!isRecord(value) || value.schemaVersion !== DURABLE_GOAL_SCHEMA_VERSION || !isRecord(value.ref)) {
|
|
5
|
+
throw invalidStore();
|
|
6
|
+
}
|
|
7
|
+
const status = value.status;
|
|
8
|
+
if (!isGoalStatus(status) || !Array.isArray(value.evidence))
|
|
9
|
+
throw invalidStore();
|
|
10
|
+
const base = {
|
|
11
|
+
schemaVersion: DURABLE_GOAL_SCHEMA_VERSION,
|
|
12
|
+
ref: { id: requireText(value.ref.id), revision: requirePositiveInteger(value.ref.revision) },
|
|
13
|
+
objective: requireText(value.objective),
|
|
14
|
+
status,
|
|
15
|
+
maxRounds: requirePositiveInteger(value.maxRounds),
|
|
16
|
+
completedRounds: requireNonNegativeInteger(value.completedRounds),
|
|
17
|
+
evidence: value.evidence.map(parseEvidence),
|
|
18
|
+
createdAt: requireTimestamp(value.createdAt),
|
|
19
|
+
generationStartedAt: requireTimestamp(value.generationStartedAt),
|
|
20
|
+
updatedAt: requireTimestamp(value.updatedAt),
|
|
21
|
+
...(value.blockedReason === undefined ? {} : { blockedReason: requireText(value.blockedReason) }),
|
|
22
|
+
...(value.terminalAt === undefined ? {} : { terminalAt: requireTimestamp(value.terminalAt) }),
|
|
23
|
+
};
|
|
24
|
+
const snapshot = value.checkpoint === undefined
|
|
25
|
+
? base
|
|
26
|
+
: { ...base, checkpoint: parseDurableGoalCheckpoint(value.checkpoint, base) };
|
|
27
|
+
if (snapshot.completedRounds > snapshot.maxRounds)
|
|
28
|
+
throw invalidStore();
|
|
29
|
+
if (Date.parse(snapshot.generationStartedAt) < Date.parse(snapshot.createdAt))
|
|
30
|
+
throw invalidStore();
|
|
31
|
+
if (Date.parse(snapshot.generationStartedAt) > Date.parse(snapshot.updatedAt))
|
|
32
|
+
throw invalidStore();
|
|
33
|
+
if ((snapshot.status === "blocked") !== (snapshot.blockedReason !== undefined))
|
|
34
|
+
throw invalidStore();
|
|
35
|
+
if ((snapshot.status === "completed" || snapshot.status === "cleared") !== (snapshot.terminalAt !== undefined)) {
|
|
36
|
+
throw invalidStore();
|
|
37
|
+
}
|
|
38
|
+
if (snapshot.status === "completed" && freshDurableGoalEvidence(snapshot).length === 0)
|
|
39
|
+
throw invalidStore();
|
|
40
|
+
return snapshot;
|
|
41
|
+
}
|
|
42
|
+
function parseEvidence(value) {
|
|
43
|
+
if (!isRecord(value))
|
|
44
|
+
throw invalidStore();
|
|
45
|
+
return {
|
|
46
|
+
id: requireText(value.id),
|
|
47
|
+
digest: requireDigest(value.digest),
|
|
48
|
+
capturedAt: requireTimestamp(value.capturedAt),
|
|
49
|
+
goalGeneration: requireDigest(value.goalGeneration),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function requireText(value) {
|
|
53
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
54
|
+
throw invalidStore();
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function requirePositiveInteger(value) {
|
|
58
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
59
|
+
throw invalidStore();
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function requireNonNegativeInteger(value) {
|
|
63
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
64
|
+
throw invalidStore();
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
function requireTimestamp(value) {
|
|
68
|
+
if (typeof value !== "string" || value.length === 0 || Number.isNaN(Date.parse(value)))
|
|
69
|
+
throw invalidStore();
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
function requireDigest(value) {
|
|
73
|
+
if (typeof value !== "string" || !/^[0-9a-f]{64}$/u.test(value))
|
|
74
|
+
throw invalidStore();
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
function isRecord(value) {
|
|
78
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
+
}
|
|
80
|
+
function isGoalStatus(value) {
|
|
81
|
+
return typeof value === "string" && DURABLE_GOAL_STATUS_VALUES.some((status) => status === value);
|
|
82
|
+
}
|
|
83
|
+
function invalidStore() {
|
|
84
|
+
return new DurableGoalError("invalid-store", "durable goal journal is invalid");
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=durable-goal-parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-goal-parse.js","sourceRoot":"","sources":["../../src/core/durable-goal-parse.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tDURABLE_GOAL_SCHEMA_VERSION,\n\tDURABLE_GOAL_STATUS_VALUES,\n\tDurableGoalError,\n\ttype DurableGoalEvidence,\n\ttype DurableGoalSnapshot,\n\ttype DurableGoalStatus,\n\tfreshDurableGoalEvidence,\n} from \"./durable-goal.ts\";\nimport { parseDurableGoalCheckpoint } from \"./durable-goal-checkpoint.ts\";\n\nexport function parseDurableGoalSnapshot(value: unknown): DurableGoalSnapshot {\n\tif (!isRecord(value) || value.schemaVersion !== DURABLE_GOAL_SCHEMA_VERSION || !isRecord(value.ref)) {\n\t\tthrow invalidStore();\n\t}\n\tconst status = value.status;\n\tif (!isGoalStatus(status) || !Array.isArray(value.evidence)) throw invalidStore();\n\tconst base: DurableGoalSnapshot = {\n\t\tschemaVersion: DURABLE_GOAL_SCHEMA_VERSION,\n\t\tref: { id: requireText(value.ref.id), revision: requirePositiveInteger(value.ref.revision) },\n\t\tobjective: requireText(value.objective),\n\t\tstatus,\n\t\tmaxRounds: requirePositiveInteger(value.maxRounds),\n\t\tcompletedRounds: requireNonNegativeInteger(value.completedRounds),\n\t\tevidence: value.evidence.map(parseEvidence),\n\t\tcreatedAt: requireTimestamp(value.createdAt),\n\t\tgenerationStartedAt: requireTimestamp(value.generationStartedAt),\n\t\tupdatedAt: requireTimestamp(value.updatedAt),\n\t\t...(value.blockedReason === undefined ? {} : { blockedReason: requireText(value.blockedReason) }),\n\t\t...(value.terminalAt === undefined ? {} : { terminalAt: requireTimestamp(value.terminalAt) }),\n\t};\n\tconst snapshot: DurableGoalSnapshot =\n\t\tvalue.checkpoint === undefined\n\t\t\t? base\n\t\t\t: { ...base, checkpoint: parseDurableGoalCheckpoint(value.checkpoint, base) };\n\tif (snapshot.completedRounds > snapshot.maxRounds) throw invalidStore();\n\tif (Date.parse(snapshot.generationStartedAt) < Date.parse(snapshot.createdAt)) throw invalidStore();\n\tif (Date.parse(snapshot.generationStartedAt) > Date.parse(snapshot.updatedAt)) throw invalidStore();\n\tif ((snapshot.status === \"blocked\") !== (snapshot.blockedReason !== undefined)) throw invalidStore();\n\tif ((snapshot.status === \"completed\" || snapshot.status === \"cleared\") !== (snapshot.terminalAt !== undefined)) {\n\t\tthrow invalidStore();\n\t}\n\tif (snapshot.status === \"completed\" && freshDurableGoalEvidence(snapshot).length === 0) throw invalidStore();\n\treturn snapshot;\n}\n\nfunction parseEvidence(value: unknown): DurableGoalEvidence {\n\tif (!isRecord(value)) throw invalidStore();\n\treturn {\n\t\tid: requireText(value.id),\n\t\tdigest: requireDigest(value.digest),\n\t\tcapturedAt: requireTimestamp(value.capturedAt),\n\t\tgoalGeneration: requireDigest(value.goalGeneration),\n\t};\n}\n\nfunction requireText(value: unknown): string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw invalidStore();\n\treturn value;\n}\n\nfunction requirePositiveInteger(value: unknown): number {\n\tif (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 1) throw invalidStore();\n\treturn value;\n}\n\nfunction requireNonNegativeInteger(value: unknown): number {\n\tif (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 0) throw invalidStore();\n\treturn value;\n}\n\nfunction requireTimestamp(value: unknown): string {\n\tif (typeof value !== \"string\" || value.length === 0 || Number.isNaN(Date.parse(value))) throw invalidStore();\n\treturn value;\n}\n\nfunction requireDigest(value: unknown): string {\n\tif (typeof value !== \"string\" || !/^[0-9a-f]{64}$/u.test(value)) throw invalidStore();\n\treturn value;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isGoalStatus(value: unknown): value is DurableGoalStatus {\n\treturn typeof value === \"string\" && DURABLE_GOAL_STATUS_VALUES.some((status) => status === value);\n}\n\nfunction invalidStore(): DurableGoalError {\n\treturn new DurableGoalError(\"invalid-store\", \"durable goal journal is invalid\");\n}\n"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type DurableGoalCommand, type DurableGoalSnapshot } from "./durable-goal.ts";
|
|
2
|
+
export declare function applyDurableGoalCommand(current: DurableGoalSnapshot, command: DurableGoalCommand, now: string): DurableGoalSnapshot;
|
|
3
|
+
//# sourceMappingURL=durable-goal-reducer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-goal-reducer.d.ts","sourceRoot":"","sources":["../../src/core/durable-goal-reducer.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,kBAAkB,EAIvB,KAAK,mBAAmB,EAGxB,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,kBAAkB,EAC3B,GAAG,EAAE,MAAM,GACT,mBAAmB,CAkCrB","sourcesContent":["import {\n\tcomputeDurableGoalGeneration,\n\ttype DurableGoalCheckpoint,\n\ttype DurableGoalCommand,\n\tDurableGoalError,\n\ttype DurableGoalEvidence,\n\ttype DurableGoalRef,\n\ttype DurableGoalSnapshot,\n\ttype DurableGoalStatus,\n\tfreshDurableGoalEvidence,\n} from \"./durable-goal.ts\";\nimport { createDurableGoalCheckpoint } from \"./durable-goal-checkpoint.ts\";\n\nexport function applyDurableGoalCommand(\n\tcurrent: DurableGoalSnapshot,\n\tcommand: DurableGoalCommand,\n\tnow: string,\n): DurableGoalSnapshot {\n\trequireCurrentRef(current, command.ref);\n\trequireMonotonicTimestamp(current, now);\n\tif (current.status === \"cleared\") throw illegal(current.status, command.kind);\n\n\tswitch (command.kind) {\n\t\tcase \"edit\":\n\t\t\treturn editGoal(current, command, now);\n\t\tcase \"pause\":\n\t\t\tif (current.status !== \"active\") throw illegal(current.status, command.kind);\n\t\t\treturn revise(current, now, { status: \"paused\" });\n\t\tcase \"resume\":\n\t\t\tif (current.status !== \"paused\" && current.status !== \"blocked\") throw illegal(current.status, command.kind);\n\t\t\treturn revise(current, now, { status: \"active\", clearBlocked: true });\n\t\tcase \"block\":\n\t\t\tif (current.status !== \"active\") throw illegal(current.status, command.kind);\n\t\t\treturn revise(current, now, {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tblockedReason: requireText(command.reason, \"blocked reason\"),\n\t\t\t});\n\t\tcase \"advance-round\":\n\t\t\treturn advanceRound(current, now);\n\t\tcase \"record-checkpoint\":\n\t\t\tif (current.status === \"completed\") throw illegal(current.status, command.kind);\n\t\t\treturn revise(current, now, { checkpoint: createDurableGoalCheckpoint(current, command.checkpoint, now) });\n\t\tcase \"attach-evidence\":\n\t\t\treturn attachEvidence(current, command, now);\n\t\tcase \"complete\":\n\t\t\treturn completeGoal(current, command.kind, now);\n\t\tcase \"clear\":\n\t\t\treturn revise(current, now, { status: \"cleared\", clearBlocked: true, terminalAt: now });\n\t\tdefault:\n\t\t\treturn assertNever(command);\n\t}\n}\n\nfunction editGoal(\n\tcurrent: DurableGoalSnapshot,\n\tcommand: Extract<DurableGoalCommand, { readonly kind: \"edit\" }>,\n\tnow: string,\n): DurableGoalSnapshot {\n\tif (current.status === \"completed\") throw illegal(current.status, command.kind);\n\tif (command.objective === undefined && command.maxRounds === undefined) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"edit requires objective or maxRounds\");\n\t}\n\tconst objective =\n\t\tcommand.objective === undefined ? current.objective : requireText(command.objective, \"goal objective\");\n\tconst maxRounds =\n\t\tcommand.maxRounds === undefined ? current.maxRounds : requirePositiveInteger(command.maxRounds, \"maxRounds\");\n\tif (maxRounds < current.completedRounds) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"maxRounds cannot be below completedRounds\");\n\t}\n\tconst generationChanged = objective !== current.objective || maxRounds !== current.maxRounds;\n\tif (generationChanged) requireGenerationAdvance(current, now);\n\treturn revise(current, now, {\n\t\tobjective,\n\t\tmaxRounds,\n\t\t...(generationChanged ? { generationStartedAt: now, clearCheckpoint: true } : {}),\n\t});\n}\n\nfunction advanceRound(current: DurableGoalSnapshot, now: string): DurableGoalSnapshot {\n\tif (current.status !== \"active\") throw illegal(current.status, \"advance-round\");\n\tif (current.completedRounds >= current.maxRounds) {\n\t\tthrow new DurableGoalError(\"round-limit\", \"goal round limit reached\");\n\t}\n\trequireGenerationAdvance(current, now);\n\treturn revise(current, now, { completedRounds: current.completedRounds + 1, generationStartedAt: now });\n}\n\nfunction attachEvidence(\n\tcurrent: DurableGoalSnapshot,\n\tcommand: Extract<DurableGoalCommand, { readonly kind: \"attach-evidence\" }>,\n\tnow: string,\n): DurableGoalSnapshot {\n\tif (current.status === \"completed\") throw illegal(current.status, command.kind);\n\tconst capturedAt = requireTimestamp(command.evidence.capturedAt, \"capturedAt\");\n\tif (Date.parse(capturedAt) < Date.parse(current.generationStartedAt) || Date.parse(capturedAt) > Date.parse(now)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"evidence timestamp is outside current goal generation\");\n\t}\n\tconst evidence: DurableGoalEvidence = {\n\t\tid: requireText(command.evidence.id, \"evidence id\"),\n\t\tdigest: requireDigest(command.evidence.digest),\n\t\tcapturedAt,\n\t\tgoalGeneration: computeDurableGoalGeneration(current),\n\t};\n\treturn revise(current, now, { evidence: [...current.evidence, evidence] });\n}\n\nfunction completeGoal(\n\tcurrent: DurableGoalSnapshot,\n\tcommand: Extract<DurableGoalCommand, { readonly kind: \"complete\" }>[\"kind\"],\n\tnow: string,\n): DurableGoalSnapshot {\n\tif (current.status !== \"active\" && current.status !== \"blocked\") throw illegal(current.status, command);\n\tif (freshDurableGoalEvidence(current).length === 0) {\n\t\tthrow new DurableGoalError(\"evidence-required\", \"fresh goal evidence is required\");\n\t}\n\treturn revise(current, now, { status: \"completed\", clearBlocked: true, terminalAt: now });\n}\n\ninterface RevisionPatch {\n\treadonly objective?: string;\n\treadonly status?: DurableGoalStatus;\n\treadonly maxRounds?: number;\n\treadonly completedRounds?: number;\n\treadonly evidence?: readonly DurableGoalEvidence[];\n\treadonly checkpoint?: DurableGoalCheckpoint;\n\treadonly clearCheckpoint?: boolean;\n\treadonly generationStartedAt?: string;\n\treadonly blockedReason?: string;\n\treadonly clearBlocked?: boolean;\n\treadonly terminalAt?: string;\n}\n\nfunction revise(current: DurableGoalSnapshot, now: string, patch: RevisionPatch): DurableGoalSnapshot {\n\treturn {\n\t\tschemaVersion: current.schemaVersion,\n\t\tref: { id: current.ref.id, revision: current.ref.revision + 1 },\n\t\tobjective: patch.objective ?? current.objective,\n\t\tstatus: patch.status ?? current.status,\n\t\tmaxRounds: patch.maxRounds ?? current.maxRounds,\n\t\tcompletedRounds: patch.completedRounds ?? current.completedRounds,\n\t\tevidence: patch.evidence ?? current.evidence,\n\t\t...checkpointFields(current, patch),\n\t\tcreatedAt: current.createdAt,\n\t\tgenerationStartedAt: patch.generationStartedAt ?? current.generationStartedAt,\n\t\tupdatedAt: now,\n\t\t...blockedFields(current, patch),\n\t\t...terminalFields(current, patch),\n\t};\n}\n\nfunction checkpointFields(\n\tcurrent: DurableGoalSnapshot,\n\tpatch: RevisionPatch,\n): { readonly checkpoint?: DurableGoalCheckpoint } {\n\tif (patch.clearCheckpoint) return {};\n\tif (patch.checkpoint) return { checkpoint: patch.checkpoint };\n\treturn current.checkpoint ? { checkpoint: current.checkpoint } : {};\n}\n\nfunction blockedFields(current: DurableGoalSnapshot, patch: RevisionPatch): { readonly blockedReason?: string } {\n\tif (patch.clearBlocked) return {};\n\tif (patch.blockedReason) return { blockedReason: patch.blockedReason };\n\treturn current.blockedReason ? { blockedReason: current.blockedReason } : {};\n}\n\nfunction terminalFields(current: DurableGoalSnapshot, patch: RevisionPatch): { readonly terminalAt?: string } {\n\tif (patch.terminalAt) return { terminalAt: patch.terminalAt };\n\treturn current.terminalAt ? { terminalAt: current.terminalAt } : {};\n}\n\nfunction requireCurrentRef(current: DurableGoalSnapshot, ref: DurableGoalRef): void {\n\tif (current.ref.id !== ref.id || current.ref.revision !== ref.revision) {\n\t\tthrow new DurableGoalError(\"stale-ref\", \"goal reference is stale\");\n\t}\n}\n\nfunction requireMonotonicTimestamp(current: DurableGoalSnapshot, now: string): void {\n\trequireTimestamp(now, \"now\");\n\tif (Date.parse(now) < Date.parse(current.updatedAt)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"goal timestamps must be monotonic\");\n\t}\n}\n\nfunction requireGenerationAdvance(current: DurableGoalSnapshot, now: string): void {\n\tif (Date.parse(now) <= Date.parse(current.generationStartedAt)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"goal generation timestamp must advance\");\n\t}\n}\n\nfunction requireText(value: string, label: string): string {\n\tconst normalized = value.trim();\n\tif (normalized.length === 0) throw new DurableGoalError(\"invalid-input\", `${label} must not be empty`);\n\treturn normalized;\n}\n\nfunction requirePositiveInteger(value: number, label: string): number {\n\tif (!Number.isSafeInteger(value) || value < 1)\n\t\tthrow new DurableGoalError(\"invalid-input\", `${label} must be a positive integer`);\n\treturn value;\n}\n\nfunction requireTimestamp(value: string, label: string): string {\n\tif (value.length === 0 || Number.isNaN(Date.parse(value))) {\n\t\tthrow new DurableGoalError(\"invalid-input\", `${label} must be an ISO timestamp`);\n\t}\n\treturn value;\n}\n\nfunction requireDigest(value: string): string {\n\tif (!/^[0-9a-f]{64}$/u.test(value)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"evidence digest must be lowercase SHA-256\");\n\t}\n\treturn value;\n}\n\nfunction illegal(status: DurableGoalStatus, command: DurableGoalCommand[\"kind\"]): DurableGoalError {\n\treturn new DurableGoalError(\"illegal-transition\", `cannot ${command} a ${status} goal`);\n}\n\nfunction assertNever(value: never): never {\n\tthrow new DurableGoalError(\"invalid-input\", `unsupported goal command: ${String(value)}`);\n}\n"]}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { computeDurableGoalGeneration, DurableGoalError, freshDurableGoalEvidence, } from "./durable-goal.js";
|
|
2
|
+
import { createDurableGoalCheckpoint } from "./durable-goal-checkpoint.js";
|
|
3
|
+
export function applyDurableGoalCommand(current, command, now) {
|
|
4
|
+
requireCurrentRef(current, command.ref);
|
|
5
|
+
requireMonotonicTimestamp(current, now);
|
|
6
|
+
if (current.status === "cleared")
|
|
7
|
+
throw illegal(current.status, command.kind);
|
|
8
|
+
switch (command.kind) {
|
|
9
|
+
case "edit":
|
|
10
|
+
return editGoal(current, command, now);
|
|
11
|
+
case "pause":
|
|
12
|
+
if (current.status !== "active")
|
|
13
|
+
throw illegal(current.status, command.kind);
|
|
14
|
+
return revise(current, now, { status: "paused" });
|
|
15
|
+
case "resume":
|
|
16
|
+
if (current.status !== "paused" && current.status !== "blocked")
|
|
17
|
+
throw illegal(current.status, command.kind);
|
|
18
|
+
return revise(current, now, { status: "active", clearBlocked: true });
|
|
19
|
+
case "block":
|
|
20
|
+
if (current.status !== "active")
|
|
21
|
+
throw illegal(current.status, command.kind);
|
|
22
|
+
return revise(current, now, {
|
|
23
|
+
status: "blocked",
|
|
24
|
+
blockedReason: requireText(command.reason, "blocked reason"),
|
|
25
|
+
});
|
|
26
|
+
case "advance-round":
|
|
27
|
+
return advanceRound(current, now);
|
|
28
|
+
case "record-checkpoint":
|
|
29
|
+
if (current.status === "completed")
|
|
30
|
+
throw illegal(current.status, command.kind);
|
|
31
|
+
return revise(current, now, { checkpoint: createDurableGoalCheckpoint(current, command.checkpoint, now) });
|
|
32
|
+
case "attach-evidence":
|
|
33
|
+
return attachEvidence(current, command, now);
|
|
34
|
+
case "complete":
|
|
35
|
+
return completeGoal(current, command.kind, now);
|
|
36
|
+
case "clear":
|
|
37
|
+
return revise(current, now, { status: "cleared", clearBlocked: true, terminalAt: now });
|
|
38
|
+
default:
|
|
39
|
+
return assertNever(command);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function editGoal(current, command, now) {
|
|
43
|
+
if (current.status === "completed")
|
|
44
|
+
throw illegal(current.status, command.kind);
|
|
45
|
+
if (command.objective === undefined && command.maxRounds === undefined) {
|
|
46
|
+
throw new DurableGoalError("invalid-input", "edit requires objective or maxRounds");
|
|
47
|
+
}
|
|
48
|
+
const objective = command.objective === undefined ? current.objective : requireText(command.objective, "goal objective");
|
|
49
|
+
const maxRounds = command.maxRounds === undefined ? current.maxRounds : requirePositiveInteger(command.maxRounds, "maxRounds");
|
|
50
|
+
if (maxRounds < current.completedRounds) {
|
|
51
|
+
throw new DurableGoalError("invalid-input", "maxRounds cannot be below completedRounds");
|
|
52
|
+
}
|
|
53
|
+
const generationChanged = objective !== current.objective || maxRounds !== current.maxRounds;
|
|
54
|
+
if (generationChanged)
|
|
55
|
+
requireGenerationAdvance(current, now);
|
|
56
|
+
return revise(current, now, {
|
|
57
|
+
objective,
|
|
58
|
+
maxRounds,
|
|
59
|
+
...(generationChanged ? { generationStartedAt: now, clearCheckpoint: true } : {}),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function advanceRound(current, now) {
|
|
63
|
+
if (current.status !== "active")
|
|
64
|
+
throw illegal(current.status, "advance-round");
|
|
65
|
+
if (current.completedRounds >= current.maxRounds) {
|
|
66
|
+
throw new DurableGoalError("round-limit", "goal round limit reached");
|
|
67
|
+
}
|
|
68
|
+
requireGenerationAdvance(current, now);
|
|
69
|
+
return revise(current, now, { completedRounds: current.completedRounds + 1, generationStartedAt: now });
|
|
70
|
+
}
|
|
71
|
+
function attachEvidence(current, command, now) {
|
|
72
|
+
if (current.status === "completed")
|
|
73
|
+
throw illegal(current.status, command.kind);
|
|
74
|
+
const capturedAt = requireTimestamp(command.evidence.capturedAt, "capturedAt");
|
|
75
|
+
if (Date.parse(capturedAt) < Date.parse(current.generationStartedAt) || Date.parse(capturedAt) > Date.parse(now)) {
|
|
76
|
+
throw new DurableGoalError("invalid-input", "evidence timestamp is outside current goal generation");
|
|
77
|
+
}
|
|
78
|
+
const evidence = {
|
|
79
|
+
id: requireText(command.evidence.id, "evidence id"),
|
|
80
|
+
digest: requireDigest(command.evidence.digest),
|
|
81
|
+
capturedAt,
|
|
82
|
+
goalGeneration: computeDurableGoalGeneration(current),
|
|
83
|
+
};
|
|
84
|
+
return revise(current, now, { evidence: [...current.evidence, evidence] });
|
|
85
|
+
}
|
|
86
|
+
function completeGoal(current, command, now) {
|
|
87
|
+
if (current.status !== "active" && current.status !== "blocked")
|
|
88
|
+
throw illegal(current.status, command);
|
|
89
|
+
if (freshDurableGoalEvidence(current).length === 0) {
|
|
90
|
+
throw new DurableGoalError("evidence-required", "fresh goal evidence is required");
|
|
91
|
+
}
|
|
92
|
+
return revise(current, now, { status: "completed", clearBlocked: true, terminalAt: now });
|
|
93
|
+
}
|
|
94
|
+
function revise(current, now, patch) {
|
|
95
|
+
return {
|
|
96
|
+
schemaVersion: current.schemaVersion,
|
|
97
|
+
ref: { id: current.ref.id, revision: current.ref.revision + 1 },
|
|
98
|
+
objective: patch.objective ?? current.objective,
|
|
99
|
+
status: patch.status ?? current.status,
|
|
100
|
+
maxRounds: patch.maxRounds ?? current.maxRounds,
|
|
101
|
+
completedRounds: patch.completedRounds ?? current.completedRounds,
|
|
102
|
+
evidence: patch.evidence ?? current.evidence,
|
|
103
|
+
...checkpointFields(current, patch),
|
|
104
|
+
createdAt: current.createdAt,
|
|
105
|
+
generationStartedAt: patch.generationStartedAt ?? current.generationStartedAt,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
...blockedFields(current, patch),
|
|
108
|
+
...terminalFields(current, patch),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function checkpointFields(current, patch) {
|
|
112
|
+
if (patch.clearCheckpoint)
|
|
113
|
+
return {};
|
|
114
|
+
if (patch.checkpoint)
|
|
115
|
+
return { checkpoint: patch.checkpoint };
|
|
116
|
+
return current.checkpoint ? { checkpoint: current.checkpoint } : {};
|
|
117
|
+
}
|
|
118
|
+
function blockedFields(current, patch) {
|
|
119
|
+
if (patch.clearBlocked)
|
|
120
|
+
return {};
|
|
121
|
+
if (patch.blockedReason)
|
|
122
|
+
return { blockedReason: patch.blockedReason };
|
|
123
|
+
return current.blockedReason ? { blockedReason: current.blockedReason } : {};
|
|
124
|
+
}
|
|
125
|
+
function terminalFields(current, patch) {
|
|
126
|
+
if (patch.terminalAt)
|
|
127
|
+
return { terminalAt: patch.terminalAt };
|
|
128
|
+
return current.terminalAt ? { terminalAt: current.terminalAt } : {};
|
|
129
|
+
}
|
|
130
|
+
function requireCurrentRef(current, ref) {
|
|
131
|
+
if (current.ref.id !== ref.id || current.ref.revision !== ref.revision) {
|
|
132
|
+
throw new DurableGoalError("stale-ref", "goal reference is stale");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function requireMonotonicTimestamp(current, now) {
|
|
136
|
+
requireTimestamp(now, "now");
|
|
137
|
+
if (Date.parse(now) < Date.parse(current.updatedAt)) {
|
|
138
|
+
throw new DurableGoalError("invalid-input", "goal timestamps must be monotonic");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function requireGenerationAdvance(current, now) {
|
|
142
|
+
if (Date.parse(now) <= Date.parse(current.generationStartedAt)) {
|
|
143
|
+
throw new DurableGoalError("invalid-input", "goal generation timestamp must advance");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function requireText(value, label) {
|
|
147
|
+
const normalized = value.trim();
|
|
148
|
+
if (normalized.length === 0)
|
|
149
|
+
throw new DurableGoalError("invalid-input", `${label} must not be empty`);
|
|
150
|
+
return normalized;
|
|
151
|
+
}
|
|
152
|
+
function requirePositiveInteger(value, label) {
|
|
153
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
154
|
+
throw new DurableGoalError("invalid-input", `${label} must be a positive integer`);
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
function requireTimestamp(value, label) {
|
|
158
|
+
if (value.length === 0 || Number.isNaN(Date.parse(value))) {
|
|
159
|
+
throw new DurableGoalError("invalid-input", `${label} must be an ISO timestamp`);
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
function requireDigest(value) {
|
|
164
|
+
if (!/^[0-9a-f]{64}$/u.test(value)) {
|
|
165
|
+
throw new DurableGoalError("invalid-input", "evidence digest must be lowercase SHA-256");
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
function illegal(status, command) {
|
|
170
|
+
return new DurableGoalError("illegal-transition", `cannot ${command} a ${status} goal`);
|
|
171
|
+
}
|
|
172
|
+
function assertNever(value) {
|
|
173
|
+
throw new DurableGoalError("invalid-input", `unsupported goal command: ${String(value)}`);
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=durable-goal-reducer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-goal-reducer.js","sourceRoot":"","sources":["../../src/core/durable-goal-reducer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tcomputeDurableGoalGeneration,\n\ttype DurableGoalCheckpoint,\n\ttype DurableGoalCommand,\n\tDurableGoalError,\n\ttype DurableGoalEvidence,\n\ttype DurableGoalRef,\n\ttype DurableGoalSnapshot,\n\ttype DurableGoalStatus,\n\tfreshDurableGoalEvidence,\n} from \"./durable-goal.ts\";\nimport { createDurableGoalCheckpoint } from \"./durable-goal-checkpoint.ts\";\n\nexport function applyDurableGoalCommand(\n\tcurrent: DurableGoalSnapshot,\n\tcommand: DurableGoalCommand,\n\tnow: string,\n): DurableGoalSnapshot {\n\trequireCurrentRef(current, command.ref);\n\trequireMonotonicTimestamp(current, now);\n\tif (current.status === \"cleared\") throw illegal(current.status, command.kind);\n\n\tswitch (command.kind) {\n\t\tcase \"edit\":\n\t\t\treturn editGoal(current, command, now);\n\t\tcase \"pause\":\n\t\t\tif (current.status !== \"active\") throw illegal(current.status, command.kind);\n\t\t\treturn revise(current, now, { status: \"paused\" });\n\t\tcase \"resume\":\n\t\t\tif (current.status !== \"paused\" && current.status !== \"blocked\") throw illegal(current.status, command.kind);\n\t\t\treturn revise(current, now, { status: \"active\", clearBlocked: true });\n\t\tcase \"block\":\n\t\t\tif (current.status !== \"active\") throw illegal(current.status, command.kind);\n\t\t\treturn revise(current, now, {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tblockedReason: requireText(command.reason, \"blocked reason\"),\n\t\t\t});\n\t\tcase \"advance-round\":\n\t\t\treturn advanceRound(current, now);\n\t\tcase \"record-checkpoint\":\n\t\t\tif (current.status === \"completed\") throw illegal(current.status, command.kind);\n\t\t\treturn revise(current, now, { checkpoint: createDurableGoalCheckpoint(current, command.checkpoint, now) });\n\t\tcase \"attach-evidence\":\n\t\t\treturn attachEvidence(current, command, now);\n\t\tcase \"complete\":\n\t\t\treturn completeGoal(current, command.kind, now);\n\t\tcase \"clear\":\n\t\t\treturn revise(current, now, { status: \"cleared\", clearBlocked: true, terminalAt: now });\n\t\tdefault:\n\t\t\treturn assertNever(command);\n\t}\n}\n\nfunction editGoal(\n\tcurrent: DurableGoalSnapshot,\n\tcommand: Extract<DurableGoalCommand, { readonly kind: \"edit\" }>,\n\tnow: string,\n): DurableGoalSnapshot {\n\tif (current.status === \"completed\") throw illegal(current.status, command.kind);\n\tif (command.objective === undefined && command.maxRounds === undefined) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"edit requires objective or maxRounds\");\n\t}\n\tconst objective =\n\t\tcommand.objective === undefined ? current.objective : requireText(command.objective, \"goal objective\");\n\tconst maxRounds =\n\t\tcommand.maxRounds === undefined ? current.maxRounds : requirePositiveInteger(command.maxRounds, \"maxRounds\");\n\tif (maxRounds < current.completedRounds) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"maxRounds cannot be below completedRounds\");\n\t}\n\tconst generationChanged = objective !== current.objective || maxRounds !== current.maxRounds;\n\tif (generationChanged) requireGenerationAdvance(current, now);\n\treturn revise(current, now, {\n\t\tobjective,\n\t\tmaxRounds,\n\t\t...(generationChanged ? { generationStartedAt: now, clearCheckpoint: true } : {}),\n\t});\n}\n\nfunction advanceRound(current: DurableGoalSnapshot, now: string): DurableGoalSnapshot {\n\tif (current.status !== \"active\") throw illegal(current.status, \"advance-round\");\n\tif (current.completedRounds >= current.maxRounds) {\n\t\tthrow new DurableGoalError(\"round-limit\", \"goal round limit reached\");\n\t}\n\trequireGenerationAdvance(current, now);\n\treturn revise(current, now, { completedRounds: current.completedRounds + 1, generationStartedAt: now });\n}\n\nfunction attachEvidence(\n\tcurrent: DurableGoalSnapshot,\n\tcommand: Extract<DurableGoalCommand, { readonly kind: \"attach-evidence\" }>,\n\tnow: string,\n): DurableGoalSnapshot {\n\tif (current.status === \"completed\") throw illegal(current.status, command.kind);\n\tconst capturedAt = requireTimestamp(command.evidence.capturedAt, \"capturedAt\");\n\tif (Date.parse(capturedAt) < Date.parse(current.generationStartedAt) || Date.parse(capturedAt) > Date.parse(now)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"evidence timestamp is outside current goal generation\");\n\t}\n\tconst evidence: DurableGoalEvidence = {\n\t\tid: requireText(command.evidence.id, \"evidence id\"),\n\t\tdigest: requireDigest(command.evidence.digest),\n\t\tcapturedAt,\n\t\tgoalGeneration: computeDurableGoalGeneration(current),\n\t};\n\treturn revise(current, now, { evidence: [...current.evidence, evidence] });\n}\n\nfunction completeGoal(\n\tcurrent: DurableGoalSnapshot,\n\tcommand: Extract<DurableGoalCommand, { readonly kind: \"complete\" }>[\"kind\"],\n\tnow: string,\n): DurableGoalSnapshot {\n\tif (current.status !== \"active\" && current.status !== \"blocked\") throw illegal(current.status, command);\n\tif (freshDurableGoalEvidence(current).length === 0) {\n\t\tthrow new DurableGoalError(\"evidence-required\", \"fresh goal evidence is required\");\n\t}\n\treturn revise(current, now, { status: \"completed\", clearBlocked: true, terminalAt: now });\n}\n\ninterface RevisionPatch {\n\treadonly objective?: string;\n\treadonly status?: DurableGoalStatus;\n\treadonly maxRounds?: number;\n\treadonly completedRounds?: number;\n\treadonly evidence?: readonly DurableGoalEvidence[];\n\treadonly checkpoint?: DurableGoalCheckpoint;\n\treadonly clearCheckpoint?: boolean;\n\treadonly generationStartedAt?: string;\n\treadonly blockedReason?: string;\n\treadonly clearBlocked?: boolean;\n\treadonly terminalAt?: string;\n}\n\nfunction revise(current: DurableGoalSnapshot, now: string, patch: RevisionPatch): DurableGoalSnapshot {\n\treturn {\n\t\tschemaVersion: current.schemaVersion,\n\t\tref: { id: current.ref.id, revision: current.ref.revision + 1 },\n\t\tobjective: patch.objective ?? current.objective,\n\t\tstatus: patch.status ?? current.status,\n\t\tmaxRounds: patch.maxRounds ?? current.maxRounds,\n\t\tcompletedRounds: patch.completedRounds ?? current.completedRounds,\n\t\tevidence: patch.evidence ?? current.evidence,\n\t\t...checkpointFields(current, patch),\n\t\tcreatedAt: current.createdAt,\n\t\tgenerationStartedAt: patch.generationStartedAt ?? current.generationStartedAt,\n\t\tupdatedAt: now,\n\t\t...blockedFields(current, patch),\n\t\t...terminalFields(current, patch),\n\t};\n}\n\nfunction checkpointFields(\n\tcurrent: DurableGoalSnapshot,\n\tpatch: RevisionPatch,\n): { readonly checkpoint?: DurableGoalCheckpoint } {\n\tif (patch.clearCheckpoint) return {};\n\tif (patch.checkpoint) return { checkpoint: patch.checkpoint };\n\treturn current.checkpoint ? { checkpoint: current.checkpoint } : {};\n}\n\nfunction blockedFields(current: DurableGoalSnapshot, patch: RevisionPatch): { readonly blockedReason?: string } {\n\tif (patch.clearBlocked) return {};\n\tif (patch.blockedReason) return { blockedReason: patch.blockedReason };\n\treturn current.blockedReason ? { blockedReason: current.blockedReason } : {};\n}\n\nfunction terminalFields(current: DurableGoalSnapshot, patch: RevisionPatch): { readonly terminalAt?: string } {\n\tif (patch.terminalAt) return { terminalAt: patch.terminalAt };\n\treturn current.terminalAt ? { terminalAt: current.terminalAt } : {};\n}\n\nfunction requireCurrentRef(current: DurableGoalSnapshot, ref: DurableGoalRef): void {\n\tif (current.ref.id !== ref.id || current.ref.revision !== ref.revision) {\n\t\tthrow new DurableGoalError(\"stale-ref\", \"goal reference is stale\");\n\t}\n}\n\nfunction requireMonotonicTimestamp(current: DurableGoalSnapshot, now: string): void {\n\trequireTimestamp(now, \"now\");\n\tif (Date.parse(now) < Date.parse(current.updatedAt)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"goal timestamps must be monotonic\");\n\t}\n}\n\nfunction requireGenerationAdvance(current: DurableGoalSnapshot, now: string): void {\n\tif (Date.parse(now) <= Date.parse(current.generationStartedAt)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"goal generation timestamp must advance\");\n\t}\n}\n\nfunction requireText(value: string, label: string): string {\n\tconst normalized = value.trim();\n\tif (normalized.length === 0) throw new DurableGoalError(\"invalid-input\", `${label} must not be empty`);\n\treturn normalized;\n}\n\nfunction requirePositiveInteger(value: number, label: string): number {\n\tif (!Number.isSafeInteger(value) || value < 1)\n\t\tthrow new DurableGoalError(\"invalid-input\", `${label} must be a positive integer`);\n\treturn value;\n}\n\nfunction requireTimestamp(value: string, label: string): string {\n\tif (value.length === 0 || Number.isNaN(Date.parse(value))) {\n\t\tthrow new DurableGoalError(\"invalid-input\", `${label} must be an ISO timestamp`);\n\t}\n\treturn value;\n}\n\nfunction requireDigest(value: string): string {\n\tif (!/^[0-9a-f]{64}$/u.test(value)) {\n\t\tthrow new DurableGoalError(\"invalid-input\", \"evidence digest must be lowercase SHA-256\");\n\t}\n\treturn value;\n}\n\nfunction illegal(status: DurableGoalStatus, command: DurableGoalCommand[\"kind\"]): DurableGoalError {\n\treturn new DurableGoalError(\"illegal-transition\", `cannot ${command} a ${status} goal`);\n}\n\nfunction assertNever(value: never): never {\n\tthrow new DurableGoalError(\"invalid-input\", `unsupported goal command: ${String(value)}`);\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type DurableGoalCommand, type DurableGoalSnapshot } from "./durable-goal.ts";
|
|
2
|
+
export declare const DURABLE_GOAL_JOURNAL_SCHEMA_VERSION: "omk.goal.journal.v1";
|
|
3
|
+
export interface DurableGoalJournal {
|
|
4
|
+
readonly schemaVersion: typeof DURABLE_GOAL_JOURNAL_SCHEMA_VERSION;
|
|
5
|
+
readonly goalId: string;
|
|
6
|
+
readonly revisions: readonly DurableGoalSnapshot[];
|
|
7
|
+
}
|
|
8
|
+
export declare class DurableGoalStore {
|
|
9
|
+
readonly filePath: string;
|
|
10
|
+
constructor(filePath: string);
|
|
11
|
+
readJournal(): Promise<DurableGoalJournal | null>;
|
|
12
|
+
current(): Promise<DurableGoalSnapshot | null>;
|
|
13
|
+
create(goal: DurableGoalSnapshot): Promise<DurableGoalSnapshot>;
|
|
14
|
+
transition(command: DurableGoalCommand, now: string): Promise<DurableGoalSnapshot>;
|
|
15
|
+
private withLock;
|
|
16
|
+
private readUnlocked;
|
|
17
|
+
private writeUnlocked;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=durable-goal-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-goal-store.d.ts","sourceRoot":"","sources":["../../src/core/durable-goal-store.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,kBAAkB,EAAoB,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAIxG,eAAO,MAAM,mCAAmC,uBAAiC,CAAC;AAElF,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,aAAa,EAAE,OAAO,mCAAmC,CAAC;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACnD;AAED,qBAAa,gBAAgB;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,YAAY,QAAQ,EAAE,MAAM,EAG3B;IAEK,WAAW,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAEtD;IAEK,OAAO,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAGnD;IAEK,MAAM,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwBpE;IAEK,UAAU,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CASvF;YAEa,QAAQ;YAcR,YAAY;YAgBZ,aAAa;CAU3B","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport { type DurableGoalCommand, DurableGoalError, type DurableGoalSnapshot } from \"./durable-goal.ts\";\nimport { parseDurableGoalSnapshot } from \"./durable-goal-parse.ts\";\nimport { applyDurableGoalCommand } from \"./durable-goal-reducer.ts\";\n\nexport const DURABLE_GOAL_JOURNAL_SCHEMA_VERSION = \"omk.goal.journal.v1\" as const;\n\nexport interface DurableGoalJournal {\n\treadonly schemaVersion: typeof DURABLE_GOAL_JOURNAL_SCHEMA_VERSION;\n\treadonly goalId: string;\n\treadonly revisions: readonly DurableGoalSnapshot[];\n}\n\nexport class DurableGoalStore {\n\treadonly filePath: string;\n\n\tconstructor(filePath: string) {\n\t\tif (filePath.trim().length === 0) throw new DurableGoalError(\"invalid-input\", \"goal file path must not be empty\");\n\t\tthis.filePath = filePath;\n\t}\n\n\tasync readJournal(): Promise<DurableGoalJournal | null> {\n\t\treturn this.readUnlocked();\n\t}\n\n\tasync current(): Promise<DurableGoalSnapshot | null> {\n\t\tconst journal = await this.readUnlocked();\n\t\treturn journal?.revisions.at(-1) ?? null;\n\t}\n\n\tasync create(goal: DurableGoalSnapshot): Promise<DurableGoalSnapshot> {\n\t\treturn this.withLock(async () => {\n\t\t\tif ((await this.readUnlocked()) !== null) {\n\t\t\t\tthrow new DurableGoalError(\"store-exists\", \"durable goal already exists\");\n\t\t\t}\n\t\t\tconst initial = parseDurableGoalSnapshot(goal);\n\t\t\tif (\n\t\t\t\tinitial.ref.revision !== 1 ||\n\t\t\t\tinitial.status !== \"active\" ||\n\t\t\t\tinitial.completedRounds !== 0 ||\n\t\t\t\tinitial.evidence.length !== 0 ||\n\t\t\t\tinitial.checkpoint !== undefined ||\n\t\t\t\tinitial.createdAt !== initial.updatedAt ||\n\t\t\t\tinitial.createdAt !== initial.generationStartedAt\n\t\t\t) {\n\t\t\t\tthrow invalidStore();\n\t\t\t}\n\t\t\tawait this.writeUnlocked({\n\t\t\t\tschemaVersion: DURABLE_GOAL_JOURNAL_SCHEMA_VERSION,\n\t\t\t\tgoalId: initial.ref.id,\n\t\t\t\trevisions: [initial],\n\t\t\t});\n\t\t\treturn initial;\n\t\t});\n\t}\n\n\tasync transition(command: DurableGoalCommand, now: string): Promise<DurableGoalSnapshot> {\n\t\treturn this.withLock(async () => {\n\t\t\tconst journal = await this.readUnlocked();\n\t\t\tconst current = journal?.revisions.at(-1);\n\t\t\tif (!journal || !current) throw new DurableGoalError(\"store-missing\", \"durable goal does not exist\");\n\t\t\tconst next = applyDurableGoalCommand(current, command, now);\n\t\t\tawait this.writeUnlocked({ ...journal, revisions: [...journal.revisions, next] });\n\t\t\treturn next;\n\t\t});\n\t}\n\n\tprivate async withLock<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst directory = dirname(this.filePath);\n\t\tawait mkdir(directory, { recursive: true });\n\t\tconst release = await lockfile.lock(this.filePath, {\n\t\t\trealpath: false,\n\t\t\tretries: { retries: 5, factor: 1, minTimeout: 5, maxTimeout: 25 },\n\t\t});\n\t\ttry {\n\t\t\treturn await operation();\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t}\n\n\tprivate async readUnlocked(): Promise<DurableGoalJournal | null> {\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await readFile(this.filePath, \"utf8\");\n\t\t} catch (error) {\n\t\t\tif (isFileSystemError(error, \"ENOENT\")) return null;\n\t\t\tthrow error;\n\t\t}\n\t\ttry {\n\t\t\treturn parseJournal(JSON.parse(raw));\n\t\t} catch (error) {\n\t\t\tif (error instanceof DurableGoalError && error.code === \"invalid-store\") throw error;\n\t\t\tthrow new DurableGoalError(\"invalid-store\", \"durable goal journal is invalid\");\n\t\t}\n\t}\n\n\tprivate async writeUnlocked(journal: DurableGoalJournal): Promise<void> {\n\t\tconst tempPath = join(dirname(this.filePath), `.goal-${randomUUID()}.tmp`);\n\t\ttry {\n\t\t\tawait writeFile(tempPath, `${JSON.stringify(journal, null, 2)}\\n`, \"utf8\");\n\t\t\tawait rename(tempPath, this.filePath);\n\t\t} catch (error) {\n\t\t\tawait rm(tempPath, { force: true });\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\nfunction parseJournal(value: unknown): DurableGoalJournal {\n\tif (!isRecord(value) || value.schemaVersion !== DURABLE_GOAL_JOURNAL_SCHEMA_VERSION) throw invalidStore();\n\tif (typeof value.goalId !== \"string\" || value.goalId.length === 0 || !Array.isArray(value.revisions)) {\n\t\tthrow invalidStore();\n\t}\n\tconst revisions = value.revisions.map(parseDurableGoalSnapshot);\n\tif (revisions.length === 0) throw invalidStore();\n\tfor (const [index, revision] of revisions.entries()) {\n\t\tif (revision.ref.id !== value.goalId || revision.ref.revision !== index + 1) throw invalidStore();\n\t}\n\treturn { schemaVersion: DURABLE_GOAL_JOURNAL_SCHEMA_VERSION, goalId: value.goalId, revisions };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isFileSystemError(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nfunction invalidStore(): DurableGoalError {\n\treturn new DurableGoalError(\"invalid-store\", \"durable goal journal is invalid\");\n}\n"]}
|