@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,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.dispatchConfirmedContinuation = dispatchConfirmedContinuation;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const state_js_1 = require("../state.js");
|
|
10
|
+
const envelope_js_1 = require("../verification/envelope.js");
|
|
11
|
+
const checkpoint_js_1 = require("../checkpoint.js");
|
|
12
|
+
const logger_js_1 = require("../audit/logger.js");
|
|
13
|
+
const reader_js_1 = require("../audit/reader.js");
|
|
14
|
+
const execution_adapter_js_1 = require("../../loop/execution-adapter.js");
|
|
15
|
+
const agent_subtask_js_1 = require("../../loop/adapters/agent-subtask.js");
|
|
16
|
+
const execution_window_js_1 = require("../execution-window.js");
|
|
17
|
+
const fingerprint_js_1 = require("../verification/fingerprint.js");
|
|
18
|
+
/**
|
|
19
|
+
* Dispatch a confirmed continuation: validate, checkpoint, acquire active_child lease,
|
|
20
|
+
* and stub the adapter call. Full adapter dispatch wired in Issue C (POL-93).
|
|
21
|
+
*/
|
|
22
|
+
async function dispatchConfirmedContinuation(request) {
|
|
23
|
+
const { artifact_dir, envelope } = request;
|
|
24
|
+
// Step 1: fresh state read — never use a caller-supplied cached copy
|
|
25
|
+
const rawState = await (0, state_js_1.loadState)(artifact_dir);
|
|
26
|
+
if (rawState === null) {
|
|
27
|
+
return { ok: false, rejection: { check: "state", reason: "state_not_found" } };
|
|
28
|
+
}
|
|
29
|
+
// Step 4: recovery state detection (before envelope validation — active_child may need clearing first)
|
|
30
|
+
const auditLog = await (0, reader_js_1.readAuditLog)(artifact_dir);
|
|
31
|
+
const lastDispatch = (0, reader_js_1.findLastEvent)(auditLog, "worker_dispatched", rawState.run_id, rawState.step_cursor ?? "");
|
|
32
|
+
const lastResult = (0, reader_js_1.findLastEvent)(auditLog, "worker_result_received", rawState.run_id, rawState.step_cursor ?? "");
|
|
33
|
+
if (lastDispatch && (!lastResult || lastDispatch.timestamp > lastResult.timestamp)) {
|
|
34
|
+
// dispatched-awaiting-result: a worker was dispatched but result not yet received — do NOT re-dispatch
|
|
35
|
+
return { ok: false, rejection: { check: "recovery", reason: "dispatched-awaiting-result" } };
|
|
36
|
+
}
|
|
37
|
+
let state = rawState;
|
|
38
|
+
if (rawState.active_child && !lastDispatch) {
|
|
39
|
+
// interrupted-before-dispatch: active_child set but no dispatch event — clear and proceed
|
|
40
|
+
const cleared = { ...rawState, active_child: null };
|
|
41
|
+
await (0, state_js_1.writeState)(artifact_dir, cleared);
|
|
42
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
43
|
+
event_type: "recovery_attempted",
|
|
44
|
+
run_id: rawState.run_id,
|
|
45
|
+
step_cursor: rawState.step_cursor ?? "",
|
|
46
|
+
operator: "mcp",
|
|
47
|
+
operation: "interrupted_before_dispatch_recovery",
|
|
48
|
+
result: "ok",
|
|
49
|
+
metadata: { cleared_active_child: rawState.active_child },
|
|
50
|
+
});
|
|
51
|
+
state = cleared;
|
|
52
|
+
}
|
|
53
|
+
// Step 2: paranoid re-validation after the pendingConfirmations gate
|
|
54
|
+
const validation = (0, envelope_js_1.validateEnvelope)(state, envelope);
|
|
55
|
+
if (!validation.ok) {
|
|
56
|
+
return { ok: false, rejection: validation.failure };
|
|
57
|
+
}
|
|
58
|
+
// Step 3: execution window validation
|
|
59
|
+
if (request.executionWindow) {
|
|
60
|
+
const fingerprint = (0, fingerprint_js_1.computeStateFingerprint)({ state, approvalNonce: request.executionWindow.run_id });
|
|
61
|
+
const windowResult = (0, execution_window_js_1.validateWindow)(state, request.executionWindow, fingerprint);
|
|
62
|
+
if (!windowResult.ok) {
|
|
63
|
+
return { ok: false, rejection: { check: "execution_window", reason: windowResult.reason } };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Step 5: pre-dispatch checkpoint
|
|
67
|
+
await (0, checkpoint_js_1.writeCheckpoint)(artifact_dir, state.step_cursor);
|
|
68
|
+
const nextChild = validation.next_child;
|
|
69
|
+
// Step 6: acquire active_child lease — single writeState call sets both fields atomically
|
|
70
|
+
await (0, state_js_1.writeState)(artifact_dir, {
|
|
71
|
+
...state,
|
|
72
|
+
active_child: nextChild,
|
|
73
|
+
continuation_epoch: (state.continuation_epoch ?? 0) + 1,
|
|
74
|
+
});
|
|
75
|
+
// Decrement window after lease acquired — persist as side-file
|
|
76
|
+
if (request.executionWindow) {
|
|
77
|
+
const decremented = (0, execution_window_js_1.decrementWindow)(request.executionWindow);
|
|
78
|
+
// Persist decremented window to side-file (not in CurrentState)
|
|
79
|
+
const windowFile = node_path_1.default.join((0, state_js_1.getArtifactDir)(artifact_dir), "execution-window.json");
|
|
80
|
+
await (0, promises_1.writeFile)(windowFile, JSON.stringify(decremented, null, 2) + "\n", "utf-8");
|
|
81
|
+
}
|
|
82
|
+
// Step 7: record mutation_approved after state is durably written
|
|
83
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
84
|
+
event_type: "mutation_approved",
|
|
85
|
+
run_id: state.run_id,
|
|
86
|
+
step_cursor: state.step_cursor,
|
|
87
|
+
operator: "mcp",
|
|
88
|
+
operation: "loop_continue_confirmed",
|
|
89
|
+
approval_fingerprint: envelope.fingerprint,
|
|
90
|
+
result: "ok",
|
|
91
|
+
metadata: { next_child: nextChild },
|
|
92
|
+
});
|
|
93
|
+
// Step 8: adapter selection + autoDispatch gating
|
|
94
|
+
const selection = (0, execution_adapter_js_1.selectExecutionAdapter)({
|
|
95
|
+
explicitAdapter: request.adapterOverride,
|
|
96
|
+
insideAgentSession: true,
|
|
97
|
+
nativeSubtaskAvailable: true,
|
|
98
|
+
crossAgentConfigured: false,
|
|
99
|
+
tokenBudgetLow: false,
|
|
100
|
+
});
|
|
101
|
+
if (!selection.autoDispatch) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
rejection: {
|
|
105
|
+
check: "adapter_mode",
|
|
106
|
+
reason: "manual_dispatch_required",
|
|
107
|
+
detail: `Adapter "${selection.mode}" requires manual operator dispatch`,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// Step 9: dispatch boundary assertion
|
|
112
|
+
if (!nextChild)
|
|
113
|
+
throw new Error("dispatch_invariant_violated: nextChild must be non-empty at dispatch boundary");
|
|
114
|
+
// Step 10: emit worker_dispatched audit event before adapter call
|
|
115
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
116
|
+
event_type: "worker_dispatched",
|
|
117
|
+
run_id: state.run_id,
|
|
118
|
+
step_cursor: state.step_cursor,
|
|
119
|
+
child_id: nextChild,
|
|
120
|
+
operator: "mcp",
|
|
121
|
+
operation: "confirmed_dispatch",
|
|
122
|
+
result: "ok",
|
|
123
|
+
metadata: { adapter_mode: selection.mode },
|
|
124
|
+
});
|
|
125
|
+
// Step 11: build bootstrap packet and call adapter.dispatch()
|
|
126
|
+
const packet = {
|
|
127
|
+
schema_version: "1.0",
|
|
128
|
+
run_id: state.run_id,
|
|
129
|
+
cluster_id: state.cluster_id,
|
|
130
|
+
active_child: nextChild,
|
|
131
|
+
state_file: node_path_1.default.resolve(artifact_dir, "current-state.json"),
|
|
132
|
+
telemetry_file: node_path_1.default.resolve(artifact_dir, "telemetry.jsonl"),
|
|
133
|
+
};
|
|
134
|
+
const adapter = request._adapterFactory ? request._adapterFactory() : new agent_subtask_js_1.AgentSubtaskAdapter();
|
|
135
|
+
let compactReturn;
|
|
136
|
+
let dispatchResultCode = 0;
|
|
137
|
+
try {
|
|
138
|
+
const dispatchResult = await adapter.dispatch(packet, { provider: "agent-subtask" });
|
|
139
|
+
dispatchResultCode = dispatchResult.exit_code;
|
|
140
|
+
// Parse summary if available; fall back to inferring done state from exit_code
|
|
141
|
+
if (dispatchResult.summary) {
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(dispatchResult.summary);
|
|
144
|
+
compactReturn = {
|
|
145
|
+
status: typeof parsed["status"] === "string" ? parsed["status"] : "done",
|
|
146
|
+
state_updated: typeof parsed["state_updated"] === "boolean" ? parsed["state_updated"] : dispatchResult.exit_code === 0,
|
|
147
|
+
exit_code: dispatchResult.exit_code,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
compactReturn = { status: "done", state_updated: dispatchResult.exit_code === 0, exit_code: dispatchResult.exit_code };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
compactReturn = { status: "done", state_updated: dispatchResult.exit_code === 0, exit_code: dispatchResult.exit_code };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Adapter dispatch failed — clear active_child, write recovery audit events, and
|
|
160
|
+
// return ok: false so MCP callers are not misled into treating a failed dispatch
|
|
161
|
+
// as successful execution.
|
|
162
|
+
// Read the latest persisted state to avoid clobbering newer fields like continuation_epoch
|
|
163
|
+
const latestState = await (0, state_js_1.loadState)(artifact_dir);
|
|
164
|
+
if (latestState !== null) {
|
|
165
|
+
await (0, state_js_1.writeState)(artifact_dir, { ...latestState, active_child: "" });
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Fallback: use the stale state if persisted state is missing (should not happen)
|
|
169
|
+
await (0, state_js_1.writeState)(artifact_dir, { ...state, active_child: "" });
|
|
170
|
+
}
|
|
171
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
172
|
+
event_type: "worker_result_received",
|
|
173
|
+
run_id: state.run_id,
|
|
174
|
+
step_cursor: state.step_cursor,
|
|
175
|
+
child_id: nextChild,
|
|
176
|
+
operator: "mcp",
|
|
177
|
+
operation: "confirmed_dispatch",
|
|
178
|
+
result: "error",
|
|
179
|
+
metadata: { reason: "adapter_throw" },
|
|
180
|
+
});
|
|
181
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
182
|
+
event_type: "recovery_attempted",
|
|
183
|
+
run_id: state.run_id,
|
|
184
|
+
step_cursor: state.step_cursor,
|
|
185
|
+
operator: "mcp",
|
|
186
|
+
operation: "confirmed_dispatch_recovery",
|
|
187
|
+
result: "ok",
|
|
188
|
+
metadata: { reason: "adapter_dispatch_failed" },
|
|
189
|
+
});
|
|
190
|
+
return { ok: false, rejection: { check: "adapter_dispatch", reason: "dispatch_failed" } };
|
|
191
|
+
}
|
|
192
|
+
// Step 12: emit worker_result_received audit event
|
|
193
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
194
|
+
event_type: "worker_result_received",
|
|
195
|
+
run_id: state.run_id,
|
|
196
|
+
step_cursor: state.step_cursor,
|
|
197
|
+
child_id: nextChild,
|
|
198
|
+
operator: "mcp",
|
|
199
|
+
operation: "confirmed_dispatch",
|
|
200
|
+
result: "ok",
|
|
201
|
+
metadata: { exit_code: dispatchResultCode, status: compactReturn.status },
|
|
202
|
+
});
|
|
203
|
+
// Step 13: handle state_updated: false — defensively clear active_child
|
|
204
|
+
if (compactReturn.state_updated === false) {
|
|
205
|
+
// Read the latest persisted state to avoid clobbering newer fields like continuation_epoch
|
|
206
|
+
const currentPersistedState = await (0, state_js_1.loadState)(artifact_dir);
|
|
207
|
+
if (currentPersistedState !== null) {
|
|
208
|
+
await (0, state_js_1.writeState)(artifact_dir, { ...currentPersistedState, active_child: "" });
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Fallback: use the stale state if persisted state is missing (should not happen)
|
|
212
|
+
await (0, state_js_1.writeState)(artifact_dir, { ...state, active_child: "" });
|
|
213
|
+
}
|
|
214
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
215
|
+
event_type: "recovery_attempted",
|
|
216
|
+
run_id: state.run_id,
|
|
217
|
+
step_cursor: state.step_cursor,
|
|
218
|
+
operator: "mcp",
|
|
219
|
+
operation: "confirmed_dispatch_recovery",
|
|
220
|
+
result: "ok",
|
|
221
|
+
metadata: { reason: "state_updated_false" },
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return { ok: true, child_id: nextChild, compact_return: compactReturn };
|
|
225
|
+
}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* POL-92: Unit tests for adapter selection + autoDispatch gating in confirmed.ts
|
|
4
|
+
*
|
|
5
|
+
* Tests the selectExecutionAdapter integration and autoDispatch gate.
|
|
6
|
+
* Each test gets an isolated artifact directory in .taskchain_artifacts.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const vitest_1 = require("vitest");
|
|
13
|
+
const promises_1 = require("node:fs/promises");
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
16
|
+
const fingerprint_js_1 = require("../verification/fingerprint.js");
|
|
17
|
+
const confirmed_js_1 = require("./confirmed.js");
|
|
18
|
+
const logger_js_1 = require("../audit/logger.js");
|
|
19
|
+
async function readAuditLog(artifactDir) {
|
|
20
|
+
const filePath = node_path_1.default.join(ARTIFACTS_ROOT, artifactDir, "audit.jsonl");
|
|
21
|
+
try {
|
|
22
|
+
const raw = await (0, promises_1.readFile)(filePath, "utf-8");
|
|
23
|
+
return raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function makeMockAdapter(result) {
|
|
30
|
+
return () => ({
|
|
31
|
+
name: "mock-adapter",
|
|
32
|
+
// eslint-disable-next-line no-unused-vars
|
|
33
|
+
async dispatch(_packet, _options) {
|
|
34
|
+
return {
|
|
35
|
+
exit_code: 0,
|
|
36
|
+
provider_used: "mock",
|
|
37
|
+
command_run: "mock",
|
|
38
|
+
summary: JSON.stringify(result),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const ARTIFACTS_ROOT = node_path_1.default.join(process.cwd(), ".taskchain_artifacts");
|
|
44
|
+
function makeRunningState(overrides = {}) {
|
|
45
|
+
return {
|
|
46
|
+
schema_version: "1.0",
|
|
47
|
+
run_id: "polaris-run-test-pol92",
|
|
48
|
+
cluster_id: "POL-88",
|
|
49
|
+
active_child: null,
|
|
50
|
+
completed_children: ["POL-89", "POL-90", "POL-91"],
|
|
51
|
+
open_children: ["POL-92"],
|
|
52
|
+
step_cursor: "06-decide-continuation",
|
|
53
|
+
context_budget: { children_completed: 3, max_children_per_session: 4 },
|
|
54
|
+
status: "running",
|
|
55
|
+
runtime_generation: 1,
|
|
56
|
+
orchestration_mode: "bootstrap",
|
|
57
|
+
continuation_epoch: 0,
|
|
58
|
+
...overrides,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function writeStateToDir(artifactDir, state) {
|
|
62
|
+
const dir = node_path_1.default.join(ARTIFACTS_ROOT, artifactDir);
|
|
63
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
64
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(dir, "current-state.json"), JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
65
|
+
}
|
|
66
|
+
function buildEnvelope(state) {
|
|
67
|
+
const nonce = (0, node_crypto_1.randomUUID)();
|
|
68
|
+
const fingerprint = (0, fingerprint_js_1.computeStateFingerprint)({ state, approvalNonce: nonce });
|
|
69
|
+
const now = new Date();
|
|
70
|
+
return {
|
|
71
|
+
run_id: state.run_id,
|
|
72
|
+
expected_step_cursor: state.step_cursor,
|
|
73
|
+
fingerprint,
|
|
74
|
+
runtime_generation: state.runtime_generation ?? 1,
|
|
75
|
+
nonce,
|
|
76
|
+
issued_at: now.toISOString(),
|
|
77
|
+
expires_at: new Date(now.getTime() + 5 * 60 * 1000).toISOString(),
|
|
78
|
+
requested_action: "loop_continue",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
let testArtifactDir;
|
|
82
|
+
(0, vitest_1.beforeEach)(() => {
|
|
83
|
+
testArtifactDir = `test-pol92-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.afterEach)(async () => {
|
|
86
|
+
const dir = node_path_1.default.join(ARTIFACTS_ROOT, testArtifactDir);
|
|
87
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.describe)("confirmed.ts: adapter selection + autoDispatch gating", () => {
|
|
90
|
+
(0, vitest_1.it)("agent-subtask mode → autoDispatch true → proceeds past gating (ok: true)", async () => {
|
|
91
|
+
const state = makeRunningState();
|
|
92
|
+
await writeStateToDir(testArtifactDir, state);
|
|
93
|
+
const envelope = buildEnvelope(state);
|
|
94
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
95
|
+
artifact_dir: testArtifactDir,
|
|
96
|
+
envelope,
|
|
97
|
+
adapterOverride: "agent-subtask",
|
|
98
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
101
|
+
if (result.ok) {
|
|
102
|
+
(0, vitest_1.expect)(result.child_id).toBe("POL-92");
|
|
103
|
+
(0, vitest_1.expect)(result.compact_return.status).toBe("done");
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
(0, vitest_1.it)("terminal-cli mode → autoDispatch false → returns manual_dispatch_required rejection", async () => {
|
|
107
|
+
const state = makeRunningState();
|
|
108
|
+
await writeStateToDir(testArtifactDir, state);
|
|
109
|
+
const envelope = buildEnvelope(state);
|
|
110
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
111
|
+
artifact_dir: testArtifactDir,
|
|
112
|
+
envelope,
|
|
113
|
+
adapterOverride: "terminal-cli",
|
|
114
|
+
});
|
|
115
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
116
|
+
if (!result.ok) {
|
|
117
|
+
(0, vitest_1.expect)(result.rejection.check).toBe("adapter_mode");
|
|
118
|
+
(0, vitest_1.expect)(result.rejection.reason).toBe("manual_dispatch_required");
|
|
119
|
+
(0, vitest_1.expect)(result.rejection.detail).toContain("terminal-cli");
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
(0, vitest_1.it)("ci mode → autoDispatch false → returns manual_dispatch_required rejection", async () => {
|
|
123
|
+
const state = makeRunningState();
|
|
124
|
+
await writeStateToDir(testArtifactDir, state);
|
|
125
|
+
const envelope = buildEnvelope(state);
|
|
126
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
127
|
+
artifact_dir: testArtifactDir,
|
|
128
|
+
envelope,
|
|
129
|
+
adapterOverride: "ci",
|
|
130
|
+
});
|
|
131
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
132
|
+
if (!result.ok) {
|
|
133
|
+
(0, vitest_1.expect)(result.rejection.check).toBe("adapter_mode");
|
|
134
|
+
(0, vitest_1.expect)(result.rejection.reason).toBe("manual_dispatch_required");
|
|
135
|
+
(0, vitest_1.expect)(result.rejection.detail).toContain("ci");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
(0, vitest_1.it)("no adapterOverride → auto-detects agent-subtask (insideAgentSession + nativeSubtaskAvailable) → autoDispatch true → ok", async () => {
|
|
139
|
+
const state = makeRunningState();
|
|
140
|
+
await writeStateToDir(testArtifactDir, state);
|
|
141
|
+
const envelope = buildEnvelope(state);
|
|
142
|
+
// No adapterOverride: selectExecutionAdapter called with insideAgentSession: true, nativeSubtaskAvailable: true
|
|
143
|
+
// → resolves to agent-subtask → autoDispatch: true
|
|
144
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
145
|
+
artifact_dir: testArtifactDir,
|
|
146
|
+
envelope,
|
|
147
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
148
|
+
});
|
|
149
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
150
|
+
if (result.ok) {
|
|
151
|
+
(0, vitest_1.expect)(result.child_id).toBe("POL-92");
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
function makeWindow(state, overrides = {}) {
|
|
156
|
+
return {
|
|
157
|
+
run_id: state.run_id,
|
|
158
|
+
max_continuations: 3,
|
|
159
|
+
valid_from: new Date(Date.now() - 60000).toISOString(),
|
|
160
|
+
valid_until: new Date(Date.now() + 3600000).toISOString(),
|
|
161
|
+
allowed_child_types: [],
|
|
162
|
+
state_fingerprint_at_issue: (0, fingerprint_js_1.computeStateFingerprint)({ state, approvalNonce: state.run_id }),
|
|
163
|
+
...overrides,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
(0, vitest_1.describe)("execution window validation", () => {
|
|
167
|
+
(0, vitest_1.it)("window present and valid → proceeds to dispatch (ok: true)", async () => {
|
|
168
|
+
const state = makeRunningState();
|
|
169
|
+
await writeStateToDir(testArtifactDir, state);
|
|
170
|
+
const envelope = buildEnvelope(state);
|
|
171
|
+
const executionWindow = makeWindow(state);
|
|
172
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
173
|
+
artifact_dir: testArtifactDir,
|
|
174
|
+
envelope,
|
|
175
|
+
adapterOverride: "agent-subtask",
|
|
176
|
+
executionWindow,
|
|
177
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
178
|
+
});
|
|
179
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
180
|
+
if (result.ok) {
|
|
181
|
+
(0, vitest_1.expect)(result.child_id).toBe("POL-92");
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
(0, vitest_1.it)("window present and exhausted (max_continuations: 0) → returns execution_window/window_exhausted rejection", async () => {
|
|
185
|
+
const state = makeRunningState();
|
|
186
|
+
await writeStateToDir(testArtifactDir, state);
|
|
187
|
+
const envelope = buildEnvelope(state);
|
|
188
|
+
const executionWindow = makeWindow(state, { max_continuations: 0 });
|
|
189
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
190
|
+
artifact_dir: testArtifactDir,
|
|
191
|
+
envelope,
|
|
192
|
+
executionWindow,
|
|
193
|
+
});
|
|
194
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
195
|
+
if (!result.ok) {
|
|
196
|
+
(0, vitest_1.expect)(result.rejection.check).toBe("execution_window");
|
|
197
|
+
(0, vitest_1.expect)(result.rejection.reason).toBe("window_exhausted");
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
(0, vitest_1.it)("window absent → skipped entirely (proceeds normally, ok: true)", async () => {
|
|
201
|
+
const state = makeRunningState();
|
|
202
|
+
await writeStateToDir(testArtifactDir, state);
|
|
203
|
+
const envelope = buildEnvelope(state);
|
|
204
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
205
|
+
artifact_dir: testArtifactDir,
|
|
206
|
+
envelope,
|
|
207
|
+
adapterOverride: "agent-subtask",
|
|
208
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
209
|
+
});
|
|
210
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
(0, vitest_1.it)("valid window with non-empty allowed_child_types → proceeds (check deferred)", async () => {
|
|
213
|
+
const state = makeRunningState();
|
|
214
|
+
await writeStateToDir(testArtifactDir, state);
|
|
215
|
+
const envelope = buildEnvelope(state);
|
|
216
|
+
const executionWindow = makeWindow(state, { allowed_child_types: ["implement"] });
|
|
217
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
218
|
+
artifact_dir: testArtifactDir,
|
|
219
|
+
envelope,
|
|
220
|
+
adapterOverride: "agent-subtask",
|
|
221
|
+
executionWindow,
|
|
222
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
223
|
+
});
|
|
224
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
(0, vitest_1.it)("decremented window is written to side-file after lease acquired", async () => {
|
|
227
|
+
const state = makeRunningState();
|
|
228
|
+
await writeStateToDir(testArtifactDir, state);
|
|
229
|
+
const envelope = buildEnvelope(state);
|
|
230
|
+
const executionWindow = makeWindow(state, { max_continuations: 3 });
|
|
231
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
232
|
+
artifact_dir: testArtifactDir,
|
|
233
|
+
envelope,
|
|
234
|
+
adapterOverride: "agent-subtask",
|
|
235
|
+
executionWindow,
|
|
236
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
237
|
+
});
|
|
238
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
239
|
+
const windowFilePath = node_path_1.default.join(ARTIFACTS_ROOT, testArtifactDir, "execution-window.json");
|
|
240
|
+
const raw = await (0, promises_1.readFile)(windowFilePath, "utf-8");
|
|
241
|
+
const persisted = JSON.parse(raw);
|
|
242
|
+
(0, vitest_1.expect)(persisted.max_continuations).toBe(2); // decremented from 3
|
|
243
|
+
(0, vitest_1.expect)(persisted.run_id).toBe(state.run_id);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
(0, vitest_1.describe)("confirmed.ts: POL-93 dispatch + CompactReturn handling", () => {
|
|
247
|
+
(0, vitest_1.it)("mock adapter state_updated: true → active_child NOT cleared, worker_dispatched and worker_result_received in audit log", async () => {
|
|
248
|
+
const state = makeRunningState();
|
|
249
|
+
await writeStateToDir(testArtifactDir, state);
|
|
250
|
+
const envelope = buildEnvelope(state);
|
|
251
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
252
|
+
artifact_dir: testArtifactDir,
|
|
253
|
+
envelope,
|
|
254
|
+
adapterOverride: "agent-subtask",
|
|
255
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
256
|
+
});
|
|
257
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
258
|
+
if (result.ok) {
|
|
259
|
+
(0, vitest_1.expect)(result.child_id).toBe("POL-92");
|
|
260
|
+
(0, vitest_1.expect)(result.compact_return.status).toBe("done");
|
|
261
|
+
(0, vitest_1.expect)(result.compact_return.state_updated).toBe(true);
|
|
262
|
+
}
|
|
263
|
+
const events = await readAuditLog(testArtifactDir);
|
|
264
|
+
const dispatchedIdx = events.findIndex((e) => e["event_type"] === "worker_dispatched");
|
|
265
|
+
const receivedIdx = events.findIndex((e) => e["event_type"] === "worker_result_received");
|
|
266
|
+
(0, vitest_1.expect)(dispatchedIdx).toBeGreaterThanOrEqual(0);
|
|
267
|
+
(0, vitest_1.expect)(receivedIdx).toBeGreaterThanOrEqual(0);
|
|
268
|
+
// worker_dispatched must appear before worker_result_received
|
|
269
|
+
(0, vitest_1.expect)(dispatchedIdx).toBeLessThan(receivedIdx);
|
|
270
|
+
const dispatched = events[dispatchedIdx];
|
|
271
|
+
(0, vitest_1.expect)(dispatched["child_id"]).toBe("POL-92");
|
|
272
|
+
(0, vitest_1.expect)(dispatched["operator"]).toBe("mcp");
|
|
273
|
+
(0, vitest_1.expect)(dispatched["operation"]).toBe("confirmed_dispatch");
|
|
274
|
+
(0, vitest_1.expect)(dispatched["result"]).toBe("ok");
|
|
275
|
+
const received = events[receivedIdx];
|
|
276
|
+
(0, vitest_1.expect)(received["child_id"]).toBe("POL-92");
|
|
277
|
+
(0, vitest_1.expect)(received["metadata"]["status"]).toBe("done");
|
|
278
|
+
// active_child should NOT be cleared by confirmed.ts (state_updated: true means adapter handled it)
|
|
279
|
+
// We can verify no recovery_attempted event was emitted
|
|
280
|
+
const recovery = events.find((e) => e["event_type"] === "recovery_attempted");
|
|
281
|
+
(0, vitest_1.expect)(recovery).toBeUndefined();
|
|
282
|
+
});
|
|
283
|
+
(0, vitest_1.it)("mock adapter state_updated: false → active_child defensively cleared + recovery_attempted emitted", async () => {
|
|
284
|
+
const state = makeRunningState();
|
|
285
|
+
await writeStateToDir(testArtifactDir, state);
|
|
286
|
+
const envelope = buildEnvelope(state);
|
|
287
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
288
|
+
artifact_dir: testArtifactDir,
|
|
289
|
+
envelope,
|
|
290
|
+
adapterOverride: "agent-subtask",
|
|
291
|
+
_adapterFactory: makeMockAdapter({ status: "dispatched-stub", state_updated: false }),
|
|
292
|
+
});
|
|
293
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
294
|
+
if (result.ok) {
|
|
295
|
+
(0, vitest_1.expect)(result.compact_return.state_updated).toBe(false);
|
|
296
|
+
}
|
|
297
|
+
const events = await readAuditLog(testArtifactDir);
|
|
298
|
+
const recovery = events.find((e) => e["event_type"] === "recovery_attempted");
|
|
299
|
+
(0, vitest_1.expect)(recovery).toBeDefined();
|
|
300
|
+
(0, vitest_1.expect)(recovery["result"]).toBe("ok");
|
|
301
|
+
(0, vitest_1.expect)(recovery["metadata"]["reason"]).toBe("state_updated_false");
|
|
302
|
+
});
|
|
303
|
+
(0, vitest_1.it)("worker_dispatched event appears before worker_result_received in audit log", async () => {
|
|
304
|
+
const state = makeRunningState();
|
|
305
|
+
await writeStateToDir(testArtifactDir, state);
|
|
306
|
+
const envelope = buildEnvelope(state);
|
|
307
|
+
await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
308
|
+
artifact_dir: testArtifactDir,
|
|
309
|
+
envelope,
|
|
310
|
+
adapterOverride: "agent-subtask",
|
|
311
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
312
|
+
});
|
|
313
|
+
const events = await readAuditLog(testArtifactDir);
|
|
314
|
+
const types = events.map((e) => e["event_type"]);
|
|
315
|
+
const dispatchedIdx = types.indexOf("worker_dispatched");
|
|
316
|
+
const receivedIdx = types.indexOf("worker_result_received");
|
|
317
|
+
(0, vitest_1.expect)(dispatchedIdx).toBeGreaterThanOrEqual(0);
|
|
318
|
+
(0, vitest_1.expect)(receivedIdx).toBeGreaterThanOrEqual(0);
|
|
319
|
+
(0, vitest_1.expect)(dispatchedIdx).toBeLessThan(receivedIdx);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
(0, vitest_1.describe)("recovery state detection", () => {
|
|
323
|
+
(0, vitest_1.it)("clean state (no prior dispatch events) → proceeds normally (ok: true)", async () => {
|
|
324
|
+
const state = makeRunningState();
|
|
325
|
+
await writeStateToDir(testArtifactDir, state);
|
|
326
|
+
const envelope = buildEnvelope(state);
|
|
327
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
328
|
+
artifact_dir: testArtifactDir,
|
|
329
|
+
envelope,
|
|
330
|
+
adapterOverride: "agent-subtask",
|
|
331
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
332
|
+
});
|
|
333
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
334
|
+
if (result.ok) {
|
|
335
|
+
(0, vitest_1.expect)(result.child_id).toBe("POL-92");
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
(0, vitest_1.it)("dispatched-awaiting-result: worker_dispatched with no worker_result_received → returns dispatched-awaiting-result rejection", async () => {
|
|
339
|
+
const state = makeRunningState();
|
|
340
|
+
await writeStateToDir(testArtifactDir, state);
|
|
341
|
+
// Pre-seed worker_dispatched but no worker_result_received
|
|
342
|
+
await (0, logger_js_1.appendAuditEvent)(testArtifactDir, {
|
|
343
|
+
event_type: "worker_dispatched",
|
|
344
|
+
run_id: state.run_id,
|
|
345
|
+
step_cursor: state.step_cursor,
|
|
346
|
+
operator: "mcp",
|
|
347
|
+
operation: "confirmed_dispatch",
|
|
348
|
+
result: "ok",
|
|
349
|
+
metadata: { adapter_mode: "agent-subtask" },
|
|
350
|
+
});
|
|
351
|
+
const envelope = buildEnvelope(state);
|
|
352
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
353
|
+
artifact_dir: testArtifactDir,
|
|
354
|
+
envelope,
|
|
355
|
+
adapterOverride: "agent-subtask",
|
|
356
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
357
|
+
});
|
|
358
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
359
|
+
if (!result.ok) {
|
|
360
|
+
(0, vitest_1.expect)(result.rejection.check).toBe("recovery");
|
|
361
|
+
(0, vitest_1.expect)(result.rejection.reason).toBe("dispatched-awaiting-result");
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
(0, vitest_1.it)("interrupted-before-dispatch: active_child set but no worker_dispatched → clears active_child, emits recovery_attempted, then proceeds", async () => {
|
|
365
|
+
// Envelope issued against clean state (active_child null), then active_child got set without dispatch
|
|
366
|
+
const cleanState = makeRunningState({ active_child: null });
|
|
367
|
+
const envelope = buildEnvelope(cleanState);
|
|
368
|
+
// Now simulate an interrupted state: active_child got set but no worker_dispatched was emitted
|
|
369
|
+
const state = makeRunningState({ active_child: "POL-92" });
|
|
370
|
+
await writeStateToDir(testArtifactDir, state);
|
|
371
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
372
|
+
artifact_dir: testArtifactDir,
|
|
373
|
+
envelope,
|
|
374
|
+
adapterOverride: "agent-subtask",
|
|
375
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
376
|
+
});
|
|
377
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
378
|
+
const events = await readAuditLog(testArtifactDir);
|
|
379
|
+
const recovery = events.find((e) => e["event_type"] === "recovery_attempted");
|
|
380
|
+
(0, vitest_1.expect)(recovery).toBeDefined();
|
|
381
|
+
(0, vitest_1.expect)(recovery["result"]).toBe("ok");
|
|
382
|
+
(0, vitest_1.expect)(recovery["metadata"]["cleared_active_child"]).toBe("POL-92");
|
|
383
|
+
(0, vitest_1.expect)(recovery["metadata"]["operation"]).toBeUndefined();
|
|
384
|
+
// Verify operation field on the recovery event
|
|
385
|
+
(0, vitest_1.expect)(recovery["operation"]).toBe("interrupted_before_dispatch_recovery");
|
|
386
|
+
});
|
|
387
|
+
(0, vitest_1.it)("idempotency: both worker_dispatched and worker_result_received present → proceeds normally", async () => {
|
|
388
|
+
const state = makeRunningState();
|
|
389
|
+
await writeStateToDir(testArtifactDir, state);
|
|
390
|
+
// Pre-seed both events (previous successful dispatch)
|
|
391
|
+
await (0, logger_js_1.appendAuditEvent)(testArtifactDir, {
|
|
392
|
+
event_type: "worker_dispatched",
|
|
393
|
+
run_id: state.run_id,
|
|
394
|
+
step_cursor: state.step_cursor,
|
|
395
|
+
operator: "mcp",
|
|
396
|
+
operation: "confirmed_dispatch",
|
|
397
|
+
result: "ok",
|
|
398
|
+
metadata: {},
|
|
399
|
+
});
|
|
400
|
+
await (0, logger_js_1.appendAuditEvent)(testArtifactDir, {
|
|
401
|
+
event_type: "worker_result_received",
|
|
402
|
+
run_id: state.run_id,
|
|
403
|
+
step_cursor: state.step_cursor,
|
|
404
|
+
operator: "mcp",
|
|
405
|
+
operation: "confirmed_dispatch",
|
|
406
|
+
result: "ok",
|
|
407
|
+
metadata: {},
|
|
408
|
+
});
|
|
409
|
+
const envelope = buildEnvelope(state);
|
|
410
|
+
const result = await (0, confirmed_js_1.dispatchConfirmedContinuation)({
|
|
411
|
+
artifact_dir: testArtifactDir,
|
|
412
|
+
envelope,
|
|
413
|
+
adapterOverride: "agent-subtask",
|
|
414
|
+
_adapterFactory: makeMockAdapter({ status: "done", state_updated: true }),
|
|
415
|
+
});
|
|
416
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
417
|
+
if (result.ok) {
|
|
418
|
+
(0, vitest_1.expect)(result.child_id).toBe("POL-92");
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
});
|