@lsctech/polaris 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -0
- package/dist/__mocks__/tool-server-linear.js +34 -0
- package/dist/cli/adopt-approve.js +60 -0
- package/dist/cli/adopt-cognition.js +199 -0
- package/dist/cli/adopt-instructions.js +143 -0
- package/dist/cli/adopt-rules.js +169 -0
- package/dist/cli/adopt-scan.js +25 -0
- package/dist/cli/adopt-smartdocs.js +192 -0
- package/dist/cli/adoption-inventory.js +350 -0
- package/dist/cli/adoption-plan.js +222 -0
- package/dist/cli/args.js +28 -0
- package/dist/cli/args.test.js +30 -0
- package/dist/cli/branding.js +41 -0
- package/dist/cli/graph.js +358 -0
- package/dist/cli/index.js +127 -0
- package/dist/cli/index.test.js +162 -0
- package/dist/cli/init-detect.js +61 -0
- package/dist/cli/init.js +492 -0
- package/dist/cli/init.test.js +259 -0
- package/dist/cli/librarian.js +32 -0
- package/dist/cli/medic.js +96 -0
- package/dist/cli/startup.test.js +23 -0
- package/dist/cli/tracker.js +130 -0
- package/dist/cli/tracker.test.js +126 -0
- package/dist/cli/version.js +10 -0
- package/dist/cli/version.test.js +11 -0
- package/dist/cli/worker.js +287 -0
- package/dist/cluster-state/fixtures/test-clusters.json +19 -0
- package/dist/cluster-state/store.js +302 -0
- package/dist/cluster-state/store.test.js +145 -0
- package/dist/cluster-state/types.js +2 -0
- package/dist/cognition/archive.js +158 -0
- package/dist/cognition/closeout-librarian-types.js +161 -0
- package/dist/cognition/cognition.test.js +623 -0
- package/dist/cognition/index.js +41 -0
- package/dist/cognition/librarian-dispatch.js +391 -0
- package/dist/cognition/librarian-dispatch.test.js +247 -0
- package/dist/cognition/librarian-packet.js +313 -0
- package/dist/cognition/librarian-types.js +57 -0
- package/dist/cognition/route-cognition-delta.js +350 -0
- package/dist/cognition/summary-delta.js +279 -0
- package/dist/cognition/validate.js +162 -0
- package/dist/config/defaults.js +98 -0
- package/dist/config/doctor.js +273 -0
- package/dist/config/index.js +14 -0
- package/dist/config/loader.js +58 -0
- package/dist/config/provider-detect.js +60 -0
- package/dist/config/provider-detect.test.js +119 -0
- package/dist/config/schema.js +2 -0
- package/dist/config/show.js +44 -0
- package/dist/config/show.test.js +42 -0
- package/dist/config/validator.js +675 -0
- package/dist/config/validator.test.js +77 -0
- package/dist/docs/canon-check.js +334 -0
- package/dist/docs/canon-check.test.js +207 -0
- package/dist/docs/doctrine.js +133 -0
- package/dist/docs/doctrine.test.js +139 -0
- package/dist/docs/index.js +168 -0
- package/dist/docs/index.test.js +83 -0
- package/dist/docs/ingest.js +295 -0
- package/dist/docs/ingest.test.js +71 -0
- package/dist/docs/migrate.js +165 -0
- package/dist/docs/migrate.test.js +136 -0
- package/dist/docs/seed-instructions.js +147 -0
- package/dist/docs/seed-instructions.test.js +137 -0
- package/dist/docs/validate-instructions.js +267 -0
- package/dist/docs/validate-instructions.test.js +238 -0
- package/dist/finalize/artifact-policy.js +214 -0
- package/dist/finalize/artifact-policy.test.js +84 -0
- package/dist/finalize/delivery-integrity.js +141 -0
- package/dist/finalize/finalize.test.js +210 -0
- package/dist/finalize/github.js +41 -0
- package/dist/finalize/index.js +474 -0
- package/dist/finalize/linear.js +213 -0
- package/dist/finalize/run-report.js +47 -0
- package/dist/finalize/steps/01-map-update.js +11 -0
- package/dist/finalize/steps/02-map-validate.js +11 -0
- package/dist/finalize/steps/03-schema-validate.js +11 -0
- package/dist/finalize/steps/04-run-checks.js +118 -0
- package/dist/finalize/steps/05-generate-report.js +14 -0
- package/dist/finalize/steps/06-commit.js +39 -0
- package/dist/finalize/steps/07-push.js +10 -0
- package/dist/finalize/steps/08-create-pr.js +9 -0
- package/dist/finalize/steps/09-update-state.js +9 -0
- package/dist/finalize/steps/10-append-jsonl.js +23 -0
- package/dist/finalize/steps/11-update-linear.js +22 -0
- package/dist/finalize/steps/12-archive.js +21 -0
- package/dist/graph/adapter/c/adapter.js +45 -0
- package/dist/graph/adapter/c/extract.js +135 -0
- package/dist/graph/adapter/c/index.js +10 -0
- package/dist/graph/adapter/c/runtime.js +67 -0
- package/dist/graph/adapter/cpp/adapter.js +45 -0
- package/dist/graph/adapter/cpp/extract.js +159 -0
- package/dist/graph/adapter/cpp/index.js +10 -0
- package/dist/graph/adapter/cpp/runtime.js +61 -0
- package/dist/graph/adapter/csharp/adapter.js +45 -0
- package/dist/graph/adapter/csharp/extract.js +127 -0
- package/dist/graph/adapter/csharp/index.js +10 -0
- package/dist/graph/adapter/csharp/runtime.js +61 -0
- package/dist/graph/adapter/dart/adapter.js +56 -0
- package/dist/graph/adapter/dart/extract.js +130 -0
- package/dist/graph/adapter/dart/index.js +10 -0
- package/dist/graph/adapter/dart/runtime.js +61 -0
- package/dist/graph/adapter/go/adapter.js +45 -0
- package/dist/graph/adapter/go/extract.js +112 -0
- package/dist/graph/adapter/go/index.js +10 -0
- package/dist/graph/adapter/go/runtime.js +58 -0
- package/dist/graph/adapter/kotlin-java/extract-common.js +83 -0
- package/dist/graph/adapter/kotlin-java/index.js +15 -0
- package/dist/graph/adapter/kotlin-java/java-adapter.js +45 -0
- package/dist/graph/adapter/kotlin-java/java-extract.js +42 -0
- package/dist/graph/adapter/kotlin-java/kotlin-adapter.js +48 -0
- package/dist/graph/adapter/kotlin-java/kotlin-extract.js +58 -0
- package/dist/graph/adapter/kotlin-java/runtime.js +75 -0
- package/dist/graph/adapter/python/adapter.js +45 -0
- package/dist/graph/adapter/python/extract.js +121 -0
- package/dist/graph/adapter/python/index.js +10 -0
- package/dist/graph/adapter/python/runtime.js +70 -0
- package/dist/graph/adapter/registry.js +82 -0
- package/dist/graph/adapter/rust/adapter.js +45 -0
- package/dist/graph/adapter/rust/extract.js +110 -0
- package/dist/graph/adapter/rust/index.js +10 -0
- package/dist/graph/adapter/rust/runtime.js +74 -0
- package/dist/graph/adapter/shell/adapter.js +34 -0
- package/dist/graph/adapter/shell/extract.js +98 -0
- package/dist/graph/adapter/shell/index.js +8 -0
- package/dist/graph/adapter/svelte/adapter.js +91 -0
- package/dist/graph/adapter/svelte/extract.js +41 -0
- package/dist/graph/adapter/svelte/index.js +8 -0
- package/dist/graph/adapter/swift/adapter.js +45 -0
- package/dist/graph/adapter/swift/extract.js +120 -0
- package/dist/graph/adapter/swift/index.js +10 -0
- package/dist/graph/adapter/swift/runtime.js +70 -0
- package/dist/graph/adapter/types.js +2 -0
- package/dist/graph/adapter/typescript-javascript/adapter.js +72 -0
- package/dist/graph/adapter/typescript-javascript/extract.js +115 -0
- package/dist/graph/adapter/typescript-javascript/index.js +10 -0
- package/dist/graph/adapter/typescript-javascript/runtime.js +97 -0
- package/dist/graph/capability/index.js +105 -0
- package/dist/graph/governance.js +104 -0
- package/dist/graph/parser/extract.js +5 -0
- package/dist/graph/parser/loader.js +2 -0
- package/dist/graph/parser/pipeline.js +194 -0
- package/dist/graph/query/index.js +277 -0
- package/dist/graph/query/types.js +5 -0
- package/dist/graph/resolver/build-edges.js +211 -0
- package/dist/graph/resolver/index.js +23 -0
- package/dist/graph/resolver/resolve-imports.js +168 -0
- package/dist/graph/store/adapter.js +132 -0
- package/dist/graph/store/queries.js +124 -0
- package/dist/graph/store/types.js +4 -0
- package/dist/ignore/defaults.js +41 -0
- package/dist/ignore/parser.js +39 -0
- package/dist/lint/map-reference-validator.js +72 -0
- package/dist/loop/abort.js +188 -0
- package/dist/loop/abort.test.js +151 -0
- package/dist/loop/adapters/__tests__/terminal-cli.test.js +92 -0
- package/dist/loop/adapters/agent-subtask.js +137 -0
- package/dist/loop/adapters/agent-subtask.test.js +151 -0
- package/dist/loop/adapters/cli-subtask-bridge.js +108 -0
- package/dist/loop/adapters/index.js +9 -0
- package/dist/loop/adapters/registry.js +17 -0
- package/dist/loop/adapters/terminal-cli.js +403 -0
- package/dist/loop/adapters/types.js +2 -0
- package/dist/loop/adapters/worker-instructions.js +85 -0
- package/dist/loop/body-parser.js +226 -0
- package/dist/loop/bootstrap-packet.js +97 -0
- package/dist/loop/bootstrap-packet.test.js +171 -0
- package/dist/loop/branch-governance.js +54 -0
- package/dist/loop/budget.js +63 -0
- package/dist/loop/budget.test.js +163 -0
- package/dist/loop/checkpoint.js +325 -0
- package/dist/loop/compact-return.js +59 -0
- package/dist/loop/continue.js +645 -0
- package/dist/loop/continue.test.js +337 -0
- package/dist/loop/dispatch-boundary.js +335 -0
- package/dist/loop/dispatch-state.js +223 -0
- package/dist/loop/dispatch-state.test.js +186 -0
- package/dist/loop/dispatch.js +1236 -0
- package/dist/loop/dispatch.test.js +499 -0
- package/dist/loop/ephemeral-smoke.test.js +160 -0
- package/dist/loop/evidence-backfill.js +216 -0
- package/dist/loop/execution-adapter.js +141 -0
- package/dist/loop/execution-adapter.test.js +66 -0
- package/dist/loop/finalize-evidence.js +169 -0
- package/dist/loop/git-custody.js +250 -0
- package/dist/loop/impl-packet-input.js +38 -0
- package/dist/loop/index.js +270 -0
- package/dist/loop/ledger.js +49 -0
- package/dist/loop/ledger.test.js +127 -0
- package/dist/loop/lifecycle-dispatch.js +348 -0
- package/dist/loop/lifecycle-dispatch.test.js +318 -0
- package/dist/loop/lifecycle.js +93 -0
- package/dist/loop/lifecycle.test.js +150 -0
- package/dist/loop/orphan-recovery.js +276 -0
- package/dist/loop/orphan-recovery.test.js +236 -0
- package/dist/loop/parent.js +1566 -0
- package/dist/loop/parent.test.js +738 -0
- package/dist/loop/provider-sealed-packet.test.js +164 -0
- package/dist/loop/resume.js +295 -0
- package/dist/loop/resume.test.js +172 -0
- package/dist/loop/run-bootstrap.js +234 -0
- package/dist/loop/run-preflight.js +203 -0
- package/dist/loop/status.js +665 -0
- package/dist/loop/status.test.js +235 -0
- package/dist/loop/wait.js +86 -0
- package/dist/loop/worker-packet.js +406 -0
- package/dist/loop/worker-packet.test.js +243 -0
- package/dist/loop/worker-prompt.js +260 -0
- package/dist/loop/worker-prompt.test.js +247 -0
- package/dist/loop/worker.js +498 -0
- package/dist/loop/worker.test.js +372 -0
- package/dist/map/atlas.js +77 -0
- package/dist/map/backfill.js +171 -0
- package/dist/map/backfill.test.js +120 -0
- package/dist/map/index.js +255 -0
- package/dist/map/inference.js +73 -0
- package/dist/map/inference.test.js +35 -0
- package/dist/map/query.js +157 -0
- package/dist/map/query.test.js +174 -0
- package/dist/map/update.js +355 -0
- package/dist/map/update.test.js +67 -0
- package/dist/map/validate.js +153 -0
- package/dist/map/validate.test.js +90 -0
- package/dist/map/welfare.js +86 -0
- package/dist/mcp/lib/invoke.js +35 -0
- package/dist/mcp/lib/redact.js +24 -0
- package/dist/mcp/lib/redact.test.js +112 -0
- package/dist/mcp/lib/root.js +41 -0
- package/dist/mcp/lib/root.test.js +114 -0
- package/dist/mcp/server.js +15 -0
- package/dist/mcp/tools/claim-child.js +119 -0
- package/dist/mcp/tools/claim-child.test.js +69 -0
- package/dist/mcp/tools/current-state.js +73 -0
- package/dist/mcp/tools/current-state.test.js +114 -0
- package/dist/mcp/tools/dispatch-result.js +186 -0
- package/dist/mcp/tools/dispatch-result.test.js +109 -0
- package/dist/mcp/tools/index.js +222 -0
- package/dist/mcp/tools/index.test.js +172 -0
- package/dist/mcp/tools/linear.js +74 -0
- package/dist/mcp/tools/loop-continue.js +159 -0
- package/dist/mcp/tools/loop-continue.test.js +186 -0
- package/dist/mcp/tools/loop-dry-run.js +15 -0
- package/dist/mcp/tools/status.js +65 -0
- package/dist/medic/chart-id.js +75 -0
- package/dist/medic/chart-schema.js +139 -0
- package/dist/runs/index.js +119 -0
- package/dist/runs/index.test.js +184 -0
- package/dist/runs/ledger-tail.js +15 -0
- package/dist/runs/list.js +29 -0
- package/dist/runs/reconcile.js +74 -0
- package/dist/runs/show.js +10 -0
- package/dist/runtime/audit/logger.js +15 -0
- package/dist/runtime/audit/reader.js +42 -0
- package/dist/runtime/checkpoint.js +89 -0
- package/dist/runtime/continuation/confirmed.js +225 -0
- package/dist/runtime/continuation/confirmed.test.js +421 -0
- package/dist/runtime/continuation/dry-run.js +74 -0
- package/dist/runtime/execution-window.js +106 -0
- package/dist/runtime/scheduling/child-selector.js +16 -0
- package/dist/runtime/state.js +43 -0
- package/dist/runtime/status.js +46 -0
- package/dist/runtime/verification/envelope.js +80 -0
- package/dist/runtime/verification/fingerprint.js +26 -0
- package/dist/skill-packet/cli.test.js +105 -0
- package/dist/skill-packet/generator.js +225 -0
- package/dist/skill-packet/generator.test.js +159 -0
- package/dist/skill-packet/index.js +49 -0
- package/dist/skill-packet/types.js +2 -0
- package/dist/smartdocs-engine/audit.js +194 -0
- package/dist/smartdocs-engine/audit.test.js +122 -0
- package/dist/smartdocs-engine/canon-check.js +340 -0
- package/dist/smartdocs-engine/canon-check.test.js +234 -0
- package/dist/smartdocs-engine/doctrine.js +633 -0
- package/dist/smartdocs-engine/doctrine.test.js +374 -0
- package/dist/smartdocs-engine/index.js +456 -0
- package/dist/smartdocs-engine/index.test.js +82 -0
- package/dist/smartdocs-engine/ingest.js +495 -0
- package/dist/smartdocs-engine/ingest.test.js +163 -0
- package/dist/smartdocs-engine/migrate.js +217 -0
- package/dist/smartdocs-engine/migrate.test.js +200 -0
- package/dist/smartdocs-engine/seed-instructions.js +335 -0
- package/dist/smartdocs-engine/seed-instructions.test.js +275 -0
- package/dist/smartdocs-engine/smartdoc-ignore.js +258 -0
- package/dist/smartdocs-engine/smartdoc-ignore.test.js +146 -0
- package/dist/smartdocs-engine/validate-instructions.js +366 -0
- package/dist/smartdocs-engine/validate-instructions.test.js +283 -0
- package/dist/tracker/adapters/github/index.js +305 -0
- package/dist/tracker/adapters/jira/index.js +293 -0
- package/dist/tracker/adapters/linear/http-errors.test.js +51 -0
- package/dist/tracker/adapters/linear/index.js +621 -0
- package/dist/tracker/adapters/linear/index.test.js +238 -0
- package/dist/tracker/adapters/linear.js +50 -0
- package/dist/tracker/adapters/local/index.js +10 -0
- package/dist/tracker/adapters/local-file/index.js +159 -0
- package/dist/tracker/adapters/mcp-bridge.js +158 -0
- package/dist/tracker/adapters/mcp-bridge.test.js +12 -0
- package/dist/tracker/adapters/spec/index.js +146 -0
- package/dist/tracker/capabilities.js +2 -0
- package/dist/tracker/index.js +49 -0
- package/dist/tracker/lifecycle-policy.js +139 -0
- package/dist/tracker/lifecycle-transition.js +164 -0
- package/dist/tracker/local-graph.js +98 -0
- package/dist/tracker/migration.js +56 -0
- package/dist/tracker/schema.js +86 -0
- package/dist/tracker/sync/index.js +404 -0
- package/dist/tracker/sync/queue-store.js +95 -0
- package/dist/tracker/sync/queue-store.test.js +104 -0
- package/dist/tracker/types.js +11 -0
- package/dist/types/linear.js +3 -0
- package/dist/types/result-packet.js +12 -0
- package/dist/types/runtime-state.js +2 -0
- package/dist/types/work-contract.js +2 -0
- package/dist/utils/monotonic-timestamp.js +13 -0
- package/dist/utils/monotonic-timestamp.test.js +19 -0
- package/package.json +64 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Worker dispatch state machine types and transition logic.
|
|
4
|
+
*
|
|
5
|
+
* This module defines the runtime state machine for tracking worker execution
|
|
6
|
+
* from packet creation through completion, failure, or orphaning.
|
|
7
|
+
*
|
|
8
|
+
* @module dispatch-state
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.DEFAULT_TIMEOUTS = exports.PRE_LAUNCH_STATES = exports.ACTIVE_STATES = exports.TERMINAL_STATES = void 0;
|
|
12
|
+
exports.isTerminalState = isTerminalState;
|
|
13
|
+
exports.isActiveState = isActiveState;
|
|
14
|
+
exports.isPreLaunchState = isPreLaunchState;
|
|
15
|
+
exports.resolveTimeoutConfig = resolveTimeoutConfig;
|
|
16
|
+
exports.deriveDispatchState = deriveDispatchState;
|
|
17
|
+
exports.isValidTransition = isValidTransition;
|
|
18
|
+
exports.createStateHistoryEntry = createStateHistoryEntry;
|
|
19
|
+
/**
|
|
20
|
+
* Terminal states - once reached, no further transitions.
|
|
21
|
+
*/
|
|
22
|
+
exports.TERMINAL_STATES = ["completed", "failed", "orphaned"];
|
|
23
|
+
/**
|
|
24
|
+
* Active states - worker is still expected to produce telemetry.
|
|
25
|
+
*/
|
|
26
|
+
exports.ACTIVE_STATES = [
|
|
27
|
+
"launching",
|
|
28
|
+
"acknowledged",
|
|
29
|
+
"running",
|
|
30
|
+
"waiting-for-approval",
|
|
31
|
+
"blocked",
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Pre-launch states - worker has not yet started.
|
|
35
|
+
*/
|
|
36
|
+
exports.PRE_LAUNCH_STATES = ["packet-created", "delegated"];
|
|
37
|
+
/**
|
|
38
|
+
* Default timeout values.
|
|
39
|
+
*/
|
|
40
|
+
exports.DEFAULT_TIMEOUTS = {
|
|
41
|
+
launch_to_first_heartbeat_ms: 30000, // 30 seconds
|
|
42
|
+
heartbeat_interval_ms: 300000, // 5 minutes
|
|
43
|
+
orphan_timeout_ms: 600000, // 10 minutes
|
|
44
|
+
approval_timeout_ms: 3600000, // 1 hour
|
|
45
|
+
ack_to_first_heartbeat_ms: 120000, // 2 minutes
|
|
46
|
+
};
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
// State Machine Logic
|
|
49
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Check if a state is terminal (no further transitions expected).
|
|
52
|
+
*/
|
|
53
|
+
function isTerminalState(state) {
|
|
54
|
+
return exports.TERMINAL_STATES.includes(state);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if a state represents an active worker (telemetry expected).
|
|
58
|
+
*/
|
|
59
|
+
function isActiveState(state) {
|
|
60
|
+
return exports.ACTIVE_STATES.includes(state);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if state is pre-launch (worker not yet started).
|
|
64
|
+
*/
|
|
65
|
+
function isPreLaunchState(state) {
|
|
66
|
+
return exports.PRE_LAUNCH_STATES.includes(state);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get the timeout configuration with defaults applied.
|
|
70
|
+
*/
|
|
71
|
+
function resolveTimeoutConfig(config) {
|
|
72
|
+
return {
|
|
73
|
+
launch_to_first_heartbeat_ms: config?.launch_to_first_heartbeat_ms ?? exports.DEFAULT_TIMEOUTS.launch_to_first_heartbeat_ms,
|
|
74
|
+
heartbeat_interval_ms: config?.heartbeat_interval_ms ?? exports.DEFAULT_TIMEOUTS.heartbeat_interval_ms,
|
|
75
|
+
orphan_timeout_ms: config?.orphan_timeout_ms ?? exports.DEFAULT_TIMEOUTS.orphan_timeout_ms,
|
|
76
|
+
approval_timeout_ms: config?.approval_timeout_ms ?? exports.DEFAULT_TIMEOUTS.approval_timeout_ms,
|
|
77
|
+
ack_to_first_heartbeat_ms: config?.ack_to_first_heartbeat_ms ?? exports.DEFAULT_TIMEOUTS.ack_to_first_heartbeat_ms,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Derive the current dispatch state from events and configuration.
|
|
82
|
+
*
|
|
83
|
+
* This is the core state machine function that determines the current
|
|
84
|
+
* state based on telemetry events and timeout thresholds.
|
|
85
|
+
*
|
|
86
|
+
* For delegated mode, also considers Foreman coordination events:
|
|
87
|
+
* - worker-assignment-attempted
|
|
88
|
+
* - worker-assigned
|
|
89
|
+
* - worker-assignment-failed
|
|
90
|
+
* - escalation-initiated
|
|
91
|
+
*/
|
|
92
|
+
function deriveDispatchState(events, config, now = new Date()) {
|
|
93
|
+
// Sort events by timestamp
|
|
94
|
+
const sorted = [...events].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
95
|
+
// Find result event (terminal)
|
|
96
|
+
const resultEvent = sorted.find((e) => e.event === "worker-result");
|
|
97
|
+
if (resultEvent) {
|
|
98
|
+
return resultEvent.status === "success" ? "completed" : "failed";
|
|
99
|
+
}
|
|
100
|
+
// Find blocked event
|
|
101
|
+
const blockedEvents = sorted.filter((e) => e.event === "worker-blocked");
|
|
102
|
+
const lastBlocked = blockedEvents.at(-1);
|
|
103
|
+
if (lastBlocked) {
|
|
104
|
+
// Check for approval after this block
|
|
105
|
+
const approvalEvents = sorted.filter((e) => (e.event === "worker-approved" || e.event === "worker-auto-approved") &&
|
|
106
|
+
new Date(e.timestamp) > new Date(lastBlocked.timestamp));
|
|
107
|
+
if (approvalEvents.length === 0) {
|
|
108
|
+
// Still blocked - check for timeout
|
|
109
|
+
const blockedMs = now.getTime() - new Date(lastBlocked.timestamp).getTime();
|
|
110
|
+
if (blockedMs > config.approval_timeout_ms) {
|
|
111
|
+
return "failed";
|
|
112
|
+
}
|
|
113
|
+
return "waiting-for-approval";
|
|
114
|
+
}
|
|
115
|
+
// Approved - continue checking heartbeats below
|
|
116
|
+
}
|
|
117
|
+
// Find rejection event - context aware
|
|
118
|
+
// Only treat rejection as terminal if it's relevant to current unresolved cycle
|
|
119
|
+
// A rejection is relevant if:
|
|
120
|
+
// 1. It occurred after the most recent approval (if any approvals exist), OR
|
|
121
|
+
// 2. It matches the blocker_id of the current unresolved blocked event
|
|
122
|
+
const rejectionEvents = sorted.filter((e) => e.event === "worker-rejected");
|
|
123
|
+
if (rejectionEvents.length > 0) {
|
|
124
|
+
// Find the most recent approval timestamp (if any approvals exist)
|
|
125
|
+
const lastApproval = sorted
|
|
126
|
+
.filter((e) => e.event === "worker-approved" || e.event === "worker-auto-approved")
|
|
127
|
+
.at(-1);
|
|
128
|
+
const lastApprovalTimestamp = lastApproval ? new Date(lastApproval.timestamp).getTime() : 0;
|
|
129
|
+
// Find if there's a current unresolved blocked event with a blocker_id
|
|
130
|
+
const currentBlocked = lastBlocked;
|
|
131
|
+
const currentBlockerId = currentBlocked?.blocker_id;
|
|
132
|
+
// Check for relevant rejections
|
|
133
|
+
const relevantRejection = rejectionEvents.find((rejection) => {
|
|
134
|
+
const rejectionTimestamp = new Date(rejection.timestamp).getTime();
|
|
135
|
+
// Rejection is relevant if it happened after the last approval
|
|
136
|
+
if (rejectionTimestamp > lastApprovalTimestamp) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
// Or if it matches the current unresolved blocked event's blocker_id
|
|
140
|
+
if (currentBlockerId && rejection.blocker_id === currentBlockerId) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
});
|
|
145
|
+
if (relevantRejection) {
|
|
146
|
+
return "failed";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Check heartbeats
|
|
150
|
+
const heartbeats = sorted.filter((e) => e.event === "worker-heartbeat");
|
|
151
|
+
const lastHeartbeat = heartbeats.at(-1);
|
|
152
|
+
if (lastHeartbeat) {
|
|
153
|
+
const msSinceHeartbeat = now.getTime() - new Date(lastHeartbeat.timestamp).getTime();
|
|
154
|
+
// Check for orphan timeout
|
|
155
|
+
if (msSinceHeartbeat > config.orphan_timeout_ms) {
|
|
156
|
+
return "orphaned";
|
|
157
|
+
}
|
|
158
|
+
// Check for blocked (stale heartbeat)
|
|
159
|
+
if (msSinceHeartbeat > config.heartbeat_interval_ms) {
|
|
160
|
+
return "blocked";
|
|
161
|
+
}
|
|
162
|
+
// Active worker
|
|
163
|
+
return "running";
|
|
164
|
+
}
|
|
165
|
+
// No heartbeats yet — check for worker-acknowledged event
|
|
166
|
+
const acknowledgedEvent = sorted.find((e) => e.event === "worker-acknowledged");
|
|
167
|
+
if (acknowledgedEvent) {
|
|
168
|
+
// Worker acknowledged packet but has not yet sent a heartbeat
|
|
169
|
+
// Check if the acknowledgement has aged out
|
|
170
|
+
const msSinceAck = now.getTime() - new Date(acknowledgedEvent.timestamp).getTime();
|
|
171
|
+
if (msSinceAck > config.ack_to_first_heartbeat_ms) {
|
|
172
|
+
// Ack timeout — worker never started actual work
|
|
173
|
+
return "failed";
|
|
174
|
+
}
|
|
175
|
+
return "acknowledged";
|
|
176
|
+
}
|
|
177
|
+
// No heartbeats yet - check for launch
|
|
178
|
+
const launchEvent = sorted.find((e) => e.event === "worker-launch");
|
|
179
|
+
if (launchEvent) {
|
|
180
|
+
const msSinceLaunch = now.getTime() - new Date(launchEvent.timestamp).getTime();
|
|
181
|
+
// Check for launch timeout
|
|
182
|
+
if (msSinceLaunch > config.launch_to_first_heartbeat_ms) {
|
|
183
|
+
return "blocked";
|
|
184
|
+
}
|
|
185
|
+
return "launching";
|
|
186
|
+
}
|
|
187
|
+
// No launch event - check for provider assignment
|
|
188
|
+
// This would require external knowledge, default to packet-created
|
|
189
|
+
return "packet-created";
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Determine if a state transition is valid.
|
|
193
|
+
*/
|
|
194
|
+
function isValidTransition(from, to) {
|
|
195
|
+
// Cannot transition from terminal states
|
|
196
|
+
if (isTerminalState(from)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
// Define valid transitions
|
|
200
|
+
const validTransitions = {
|
|
201
|
+
"packet-created": ["delegated", "launching", "acknowledged", "running", "failed"],
|
|
202
|
+
"delegated": ["launching", "acknowledged", "running", "completed", "failed", "blocked"],
|
|
203
|
+
"launching": ["acknowledged", "running", "waiting-for-approval", "blocked", "completed", "failed"],
|
|
204
|
+
"acknowledged": ["running", "failed"],
|
|
205
|
+
"running": ["waiting-for-approval", "blocked", "completed", "failed", "orphaned"],
|
|
206
|
+
"waiting-for-approval": ["running", "completed", "failed"],
|
|
207
|
+
"blocked": ["running", "completed", "failed", "orphaned"],
|
|
208
|
+
"completed": [],
|
|
209
|
+
"failed": [],
|
|
210
|
+
"orphaned": [],
|
|
211
|
+
};
|
|
212
|
+
return validTransitions[from]?.includes(to) ?? false;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Create a state history entry.
|
|
216
|
+
*/
|
|
217
|
+
function createStateHistoryEntry(state, evidence) {
|
|
218
|
+
return {
|
|
219
|
+
state,
|
|
220
|
+
entered_at: new Date().toISOString(),
|
|
221
|
+
evidence,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for dispatch-state.ts — state machine transitions and backward compat helpers.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const dispatch_state_js_1 = require("./dispatch-state.js");
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
// Transition tests
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
(0, vitest_1.describe)("isValidTransition — added transitions", () => {
|
|
12
|
+
(0, vitest_1.it)("packet-created → failed is valid", () => {
|
|
13
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("packet-created", "failed")).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.it)("running → orphaned is valid", () => {
|
|
16
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("running", "orphaned")).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
(0, vitest_1.it)("delegated → blocked is valid", () => {
|
|
19
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("delegated", "blocked")).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.describe)("isValidTransition — pre-existing transitions still valid", () => {
|
|
23
|
+
(0, vitest_1.it)("packet-created → delegated", () => {
|
|
24
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("packet-created", "delegated")).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.it)("delegated → launching", () => {
|
|
27
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("delegated", "launching")).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
(0, vitest_1.it)("running → completed", () => {
|
|
30
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("running", "completed")).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.it)("blocked → orphaned", () => {
|
|
33
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("blocked", "orphaned")).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
// POL-221: acknowledged state transitions
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
(0, vitest_1.describe)("isValidTransition — acknowledged state (POL-221)", () => {
|
|
40
|
+
(0, vitest_1.it)("launching → acknowledged is valid", () => {
|
|
41
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("launching", "acknowledged")).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
(0, vitest_1.it)("acknowledged → running is valid", () => {
|
|
44
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("acknowledged", "running")).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)("acknowledged → failed is valid", () => {
|
|
47
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("acknowledged", "failed")).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
(0, vitest_1.it)("launching → running is still valid for backward compat", () => {
|
|
50
|
+
// The state machine allows this, but deriveDispatchState now routes through acknowledged
|
|
51
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("launching", "running")).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
// POL-221: deriveDispatchState with worker-acknowledged event
|
|
56
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
function makeBaseEvent(event, overrides = {}) {
|
|
58
|
+
return {
|
|
59
|
+
event,
|
|
60
|
+
event_id: "evt-001",
|
|
61
|
+
dispatch_id: "dispatch-001",
|
|
62
|
+
run_id: "run-001",
|
|
63
|
+
child_id: "POL-221",
|
|
64
|
+
timestamp: new Date().toISOString(),
|
|
65
|
+
...overrides,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
(0, vitest_1.describe)("deriveDispatchState — acknowledged (POL-221)", () => {
|
|
69
|
+
const now = new Date();
|
|
70
|
+
(0, vitest_1.it)("returns 'acknowledged' when worker-acknowledged event present and no heartbeat", () => {
|
|
71
|
+
const events = [
|
|
72
|
+
makeBaseEvent("worker-launch"),
|
|
73
|
+
makeBaseEvent("worker-acknowledged", { worker_id: "worker-uuid", packet_sha: "abc123" }),
|
|
74
|
+
];
|
|
75
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.deriveDispatchState)(events, dispatch_state_js_1.DEFAULT_TIMEOUTS, now)).toBe("acknowledged");
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.it)("returns 'acknowledged' without a prior launch event if only acknowledged present", () => {
|
|
78
|
+
const events = [
|
|
79
|
+
makeBaseEvent("worker-acknowledged", { worker_id: "worker-uuid", packet_sha: "abc123" }),
|
|
80
|
+
];
|
|
81
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.deriveDispatchState)(events, dispatch_state_js_1.DEFAULT_TIMEOUTS, now)).toBe("acknowledged");
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)("returns 'running' when heartbeat present after acknowledgment", () => {
|
|
84
|
+
const events = [
|
|
85
|
+
makeBaseEvent("worker-launch"),
|
|
86
|
+
makeBaseEvent("worker-acknowledged", { worker_id: "worker-uuid", packet_sha: "abc123" }),
|
|
87
|
+
makeBaseEvent("worker-heartbeat", { step_cursor: "step-1" }),
|
|
88
|
+
];
|
|
89
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.deriveDispatchState)(events, dispatch_state_js_1.DEFAULT_TIMEOUTS, now)).toBe("running");
|
|
90
|
+
});
|
|
91
|
+
(0, vitest_1.it)("does NOT return 'running' from launching without acknowledgment (proxy retirement)", () => {
|
|
92
|
+
// Without worker-acknowledged or heartbeat, should be 'launching', not 'running'
|
|
93
|
+
const events = [
|
|
94
|
+
makeBaseEvent("worker-launch"),
|
|
95
|
+
];
|
|
96
|
+
const state = (0, dispatch_state_js_1.deriveDispatchState)(events, dispatch_state_js_1.DEFAULT_TIMEOUTS, now);
|
|
97
|
+
(0, vitest_1.expect)(state).toBe("launching");
|
|
98
|
+
(0, vitest_1.expect)(state).not.toBe("running");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.describe)("isValidTransition — terminal states cannot transition", () => {
|
|
102
|
+
(0, vitest_1.it)("completed → running is invalid", () => {
|
|
103
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("completed", "running")).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.it)("failed → running is invalid", () => {
|
|
106
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("failed", "running")).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
(0, vitest_1.it)("orphaned → running is invalid", () => {
|
|
109
|
+
(0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("orphaned", "running")).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
113
|
+
// Backward-compat helper tests for ChildDispatchRecord optional fields
|
|
114
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* Resolve worker_id with backward-compat fallback.
|
|
117
|
+
* Records created before worker_id was added fall back to dispatch_id.
|
|
118
|
+
*/
|
|
119
|
+
function resolveWorkerId(record) {
|
|
120
|
+
return record.worker_id ?? record.dispatch_id;
|
|
121
|
+
}
|
|
122
|
+
function resolveSessionId(record) {
|
|
123
|
+
return record.session_id !== undefined ? record.session_id : null;
|
|
124
|
+
}
|
|
125
|
+
function resolveAttachmentCapable(record) {
|
|
126
|
+
return record.attachment_capable ?? false;
|
|
127
|
+
}
|
|
128
|
+
function resolveHeartbeatCount(record) {
|
|
129
|
+
return record.heartbeat_count ?? 0;
|
|
130
|
+
}
|
|
131
|
+
(0, vitest_1.describe)("ChildDispatchRecord backward compat", () => {
|
|
132
|
+
const legacyRecord = {
|
|
133
|
+
dispatch_id: "legacy-dispatch-id",
|
|
134
|
+
child_id: "POL-001",
|
|
135
|
+
run_id: "run-1",
|
|
136
|
+
cluster_id: "cluster-1",
|
|
137
|
+
packet_path: "/path/to/packet",
|
|
138
|
+
expected_result_path: "/path/to/result",
|
|
139
|
+
dispatched_at: new Date().toISOString(),
|
|
140
|
+
status: "dispatched",
|
|
141
|
+
};
|
|
142
|
+
(0, vitest_1.it)("worker_id falls back to dispatch_id when absent", () => {
|
|
143
|
+
(0, vitest_1.expect)(resolveWorkerId(legacyRecord)).toBe("legacy-dispatch-id");
|
|
144
|
+
});
|
|
145
|
+
(0, vitest_1.it)("session_id defaults to null when absent", () => {
|
|
146
|
+
(0, vitest_1.expect)(resolveSessionId(legacyRecord)).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.it)("attachment_capable defaults to false when absent", () => {
|
|
149
|
+
(0, vitest_1.expect)(resolveAttachmentCapable(legacyRecord)).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
(0, vitest_1.it)("heartbeat_count defaults to 0 when absent", () => {
|
|
152
|
+
(0, vitest_1.expect)(resolveHeartbeatCount(legacyRecord)).toBe(0);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
(0, vitest_1.describe)("ChildDispatchRecord new fields when present", () => {
|
|
156
|
+
const newRecord = {
|
|
157
|
+
dispatch_id: "dispatch-abc",
|
|
158
|
+
child_id: "POL-219",
|
|
159
|
+
run_id: "run-2",
|
|
160
|
+
cluster_id: "cluster-2",
|
|
161
|
+
packet_path: "/path/to/packet",
|
|
162
|
+
expected_result_path: "/path/to/result",
|
|
163
|
+
dispatched_at: new Date().toISOString(),
|
|
164
|
+
status: "dispatched",
|
|
165
|
+
worker_id: "worker-uuid-xyz",
|
|
166
|
+
session_id: "session-123",
|
|
167
|
+
attachment_capable: true,
|
|
168
|
+
heartbeat_count: 5,
|
|
169
|
+
first_heartbeat_at: "2026-05-29T00:00:00.000Z",
|
|
170
|
+
};
|
|
171
|
+
(0, vitest_1.it)("worker_id uses explicit value", () => {
|
|
172
|
+
(0, vitest_1.expect)(resolveWorkerId(newRecord)).toBe("worker-uuid-xyz");
|
|
173
|
+
});
|
|
174
|
+
(0, vitest_1.it)("session_id returns explicit value", () => {
|
|
175
|
+
(0, vitest_1.expect)(resolveSessionId(newRecord)).toBe("session-123");
|
|
176
|
+
});
|
|
177
|
+
(0, vitest_1.it)("attachment_capable returns true when set", () => {
|
|
178
|
+
(0, vitest_1.expect)(resolveAttachmentCapable(newRecord)).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
(0, vitest_1.it)("heartbeat_count returns explicit count", () => {
|
|
181
|
+
(0, vitest_1.expect)(resolveHeartbeatCount(newRecord)).toBe(5);
|
|
182
|
+
});
|
|
183
|
+
(0, vitest_1.it)("first_heartbeat_at is present", () => {
|
|
184
|
+
(0, vitest_1.expect)(newRecord.first_heartbeat_at).toBe("2026-05-29T00:00:00.000Z");
|
|
185
|
+
});
|
|
186
|
+
});
|