@tacuchi/agent-workflow-cli 22.4.2 → 24.0.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 +39 -3
- package/dist/adapters/git-cli.js +59 -17
- package/dist/adapters/git-cli.js.map +1 -1
- package/dist/adapters/node-file-system.js +55 -1
- package/dist/adapters/node-file-system.js.map +1 -1
- package/dist/adapters/postgres-readonly-tools.js +277 -0
- package/dist/adapters/postgres-readonly-tools.js.map +1 -0
- package/dist/application/artifacts-service.js +2 -2
- package/dist/application/artifacts-service.js.map +1 -1
- package/dist/application/capability/dispatcher.js +2 -2
- package/dist/application/capability/dispatcher.js.map +1 -1
- package/dist/application/capability/installed-inventory.js +2 -2
- package/dist/application/capability/installed-inventory.js.map +1 -1
- package/dist/application/capability/readiness.js +1 -1
- package/dist/application/capability/readiness.js.map +1 -1
- package/dist/application/check-branch-service.js +2 -2
- package/dist/application/check-branch-service.js.map +1 -1
- package/dist/application/checkpoint/files-touched.js +271 -0
- package/dist/application/checkpoint/files-touched.js.map +1 -0
- package/dist/application/checkpoint/markdown.js +61 -8
- package/dist/application/checkpoint/markdown.js.map +1 -1
- package/dist/application/checkpoint/state-reader.js +8 -2
- package/dist/application/checkpoint/state-reader.js.map +1 -1
- package/dist/application/checkpoint-write-service.js +16 -5
- package/dist/application/checkpoint-write-service.js.map +1 -1
- package/dist/application/claims-ledger.js +330 -0
- package/dist/application/claims-ledger.js.map +1 -0
- package/dist/application/claims-recovery.js +338 -0
- package/dist/application/claims-recovery.js.map +1 -0
- package/dist/application/database-mcp-server.js +168 -0
- package/dist/application/database-mcp-server.js.map +1 -0
- package/dist/application/database-mcp-stdio.js +137 -0
- package/dist/application/database-mcp-stdio.js.map +1 -0
- package/dist/application/database-tool-catalog.js +226 -0
- package/dist/application/database-tool-catalog.js.map +1 -0
- package/dist/application/dev-only-services.js +154 -44
- package/dist/application/dev-only-services.js.map +1 -1
- package/dist/application/dsn-reader-service.js +1 -1
- package/dist/application/elicitation-server.js +382 -0
- package/dist/application/elicitation-server.js.map +1 -0
- package/dist/application/elicitation-stdio.js +48 -0
- package/dist/application/elicitation-stdio.js.map +1 -0
- package/dist/application/flow/advance.js +86 -5
- package/dist/application/flow/advance.js.map +1 -1
- package/dist/application/flow/checkout-observation.js +152 -0
- package/dist/application/flow/checkout-observation.js.map +1 -0
- package/dist/application/flow/flow-service.js +72 -13
- package/dist/application/flow/flow-service.js.map +1 -1
- package/dist/application/flow/internal-actions.js +460 -1
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/flow/internal-drive.js +22 -9
- package/dist/application/flow/internal-drive.js.map +1 -1
- package/dist/application/flow/prove.js +205 -0
- package/dist/application/flow/prove.js.map +1 -0
- package/dist/application/flow/run-journey.js +15 -0
- package/dist/application/flow/run-journey.js.map +1 -0
- package/dist/application/flow/run-projection.js +15 -3
- package/dist/application/flow/run-projection.js.map +1 -1
- package/dist/application/flow/run-state-service.js +19 -4
- package/dist/application/flow/run-state-service.js.map +1 -1
- package/dist/application/flow/submit.js +250 -72
- package/dist/application/flow/submit.js.map +1 -1
- package/dist/application/generate-launch-service.js +4 -4
- package/dist/application/generate-launch-service.js.map +1 -1
- package/dist/application/git-flow-service.js +9 -0
- package/dist/application/git-flow-service.js.map +1 -1
- package/dist/application/local-proposal.js +9 -0
- package/dist/application/local-proposal.js.map +1 -1
- package/dist/application/lock-service.js +96 -28
- package/dist/application/lock-service.js.map +1 -1
- package/dist/application/logging/log-events.js +19 -3
- package/dist/application/logging/log-events.js.map +1 -1
- package/dist/application/logging/logger.js +2 -2
- package/dist/application/logging/logger.js.map +1 -1
- package/dist/application/mcp-connections-service.js +115 -41
- package/dist/application/mcp-connections-service.js.map +1 -1
- package/dist/application/mcp-doctor-service.js +98 -54
- package/dist/application/mcp-doctor-service.js.map +1 -1
- package/dist/application/mcp-entry-classification.js +60 -0
- package/dist/application/mcp-entry-classification.js.map +1 -0
- package/dist/application/mcp-host-reader.js +114 -81
- package/dist/application/mcp-host-reader.js.map +1 -1
- package/dist/application/mcp-host-receipt-service.js +399 -0
- package/dist/application/mcp-host-receipt-service.js.map +1 -0
- package/dist/application/mcp-host-receipt-store.js +122 -0
- package/dist/application/mcp-host-receipt-store.js.map +1 -0
- package/dist/application/mcp-host-receipts.js +28 -0
- package/dist/application/mcp-host-receipts.js.map +1 -0
- package/dist/application/mcp-host-writer.js +321 -126
- package/dist/application/mcp-host-writer.js.map +1 -1
- package/dist/application/mcp-launch-probe-service.js +142 -0
- package/dist/application/mcp-launch-probe-service.js.map +1 -0
- package/dist/application/mcp-migration-service.js +351 -0
- package/dist/application/mcp-migration-service.js.map +1 -0
- package/dist/application/mcp-native-host-check-service.js +154 -0
- package/dist/application/mcp-native-host-check-service.js.map +1 -0
- package/dist/application/mcp-receipt-registration-service.js +105 -0
- package/dist/application/mcp-receipt-registration-service.js.map +1 -0
- package/dist/application/mcp-receipt-removal-service.js +62 -0
- package/dist/application/mcp-receipt-removal-service.js.map +1 -0
- package/dist/application/mcp-remove-service.js +84 -21
- package/dist/application/mcp-remove-service.js.map +1 -1
- package/dist/application/mcp-scope-common.js +2 -4
- package/dist/application/mcp-scope-common.js.map +1 -1
- package/dist/application/mcp-setup-service.js +60 -21
- package/dist/application/mcp-setup-service.js.map +1 -1
- package/dist/application/mcp-stdio-probe.js +526 -0
- package/dist/application/mcp-stdio-probe.js.map +1 -0
- package/dist/application/mcp-test-connection-service.js +23 -50
- package/dist/application/mcp-test-connection-service.js.map +1 -1
- package/dist/application/merge-state-service.js +10 -16
- package/dist/application/merge-state-service.js.map +1 -1
- package/dist/application/multiroot/claude.js +34 -15
- package/dist/application/multiroot/claude.js.map +1 -1
- package/dist/application/multiroot/codex.js +9 -5
- package/dist/application/multiroot/codex.js.map +1 -1
- package/dist/application/multiroot-service.js +20 -11
- package/dist/application/multiroot-service.js.map +1 -1
- package/dist/application/parsers/project-block.js +25 -2
- package/dist/application/parsers/project-block.js.map +1 -1
- package/dist/application/paths-service.js +30 -31
- package/dist/application/paths-service.js.map +1 -1
- package/dist/application/plan-exec-batch-service.js +549 -0
- package/dist/application/plan-exec-batch-service.js.map +1 -0
- package/dist/application/plan-exec-decision-service.js +273 -0
- package/dist/application/plan-exec-decision-service.js.map +1 -0
- package/dist/application/project-md-service.js +2 -2
- package/dist/application/project-md-service.js.map +1 -1
- package/dist/application/project-md-upsert-service.js +4 -4
- package/dist/application/project-md-upsert-service.js.map +1 -1
- package/dist/application/project-tab-data.js +12 -7
- package/dist/application/project-tab-data.js.map +1 -1
- package/dist/application/release-data-service.js +2 -2
- package/dist/application/release-data-service.js.map +1 -1
- package/dist/application/resume-service.js +32 -11
- package/dist/application/resume-service.js.map +1 -1
- package/dist/application/retirement/apply.js +84 -0
- package/dist/application/retirement/apply.js.map +1 -1
- package/dist/application/retirement/attribution.js +31 -1
- package/dist/application/retirement/attribution.js.map +1 -1
- package/dist/application/retirement/graph.js +19 -1
- package/dist/application/retirement/graph.js.map +1 -1
- package/dist/application/retirement/journal.js +11 -0
- package/dist/application/retirement/journal.js.map +1 -1
- package/dist/application/retirement/prepare.js +89 -3
- package/dist/application/retirement/prepare.js.map +1 -1
- package/dist/application/retirement/preview.js +16 -0
- package/dist/application/retirement/preview.js.map +1 -1
- package/dist/application/retirement/resolve.js +65 -0
- package/dist/application/retirement/resolve.js.map +1 -1
- package/dist/application/self/host-states.js +2 -0
- package/dist/application/self/host-states.js.map +1 -1
- package/dist/application/self/install-skill.js +21 -17
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/mcp-config.js +392 -84
- package/dist/application/self/mcp-config.js.map +1 -1
- package/dist/application/self/mcp-via-state.js +62 -0
- package/dist/application/self/mcp-via-state.js.map +1 -0
- package/dist/application/self/uninstall.js +15 -5
- package/dist/application/self/uninstall.js.map +1 -1
- package/dist/application/session-close-service.js +24 -1
- package/dist/application/session-close-service.js.map +1 -1
- package/dist/application/session-create-service.js +7 -0
- package/dist/application/session-create-service.js.map +1 -1
- package/dist/application/session-resume-service.js +2 -2
- package/dist/application/session-resume-service.js.map +1 -1
- package/dist/application/sessions-service.js +2 -4
- package/dist/application/sessions-service.js.map +1 -1
- package/dist/application/skills-resolver-service.js +4 -4
- package/dist/application/skills-resolver-service.js.map +1 -1
- package/dist/application/source-boundary-policy.js +18 -7
- package/dist/application/source-boundary-policy.js.map +1 -1
- package/dist/application/source-remove-service.js +12 -1
- package/dist/application/source-remove-service.js.map +1 -1
- package/dist/application/sources-service.js +2 -2
- package/dist/application/sources-service.js.map +1 -1
- package/dist/application/status-service.js +4 -0
- package/dist/application/status-service.js.map +1 -1
- package/dist/application/visibility-doctor-service.js +1 -1
- package/dist/application/visibility-doctor-service.js.map +1 -1
- package/dist/application/workline-index-service.js +182 -27
- package/dist/application/workline-index-service.js.map +1 -1
- package/dist/application/workspace-init-service.js +89 -271
- package/dist/application/workspace-init-service.js.map +1 -1
- package/dist/application/workspace-materialization-service.js +300 -0
- package/dist/application/workspace-materialization-service.js.map +1 -0
- package/dist/application/worktree-service.js +32 -3
- package/dist/application/worktree-service.js.map +1 -1
- package/dist/cli/commands/capability.js +4 -7
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/claims.js +72 -0
- package/dist/cli/commands/claims.js.map +1 -0
- package/dist/cli/commands/dev-only.js +73 -25
- package/dist/cli/commands/dev-only.js.map +1 -1
- package/dist/cli/commands/flow.js +145 -42
- package/dist/cli/commands/flow.js.map +1 -1
- package/dist/cli/commands/index.js +4 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.js +752 -76
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/multiroot.js +40 -7
- package/dist/cli/commands/multiroot.js.map +1 -1
- package/dist/cli/commands/resume.js +9 -3
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/retirement.js +9 -0
- package/dist/cli/commands/retirement.js.map +1 -1
- package/dist/cli/commands/session-create.js +1 -1
- package/dist/cli/commands/session-create.js.map +1 -1
- package/dist/cli/commands/skills.js +1 -1
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/commands/status.js +72 -24
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/tool.js +141 -0
- package/dist/cli/commands/tool.js.map +1 -0
- package/dist/cli/commands/workspace-init.js +5 -5
- package/dist/cli/commands/workspace-init.js.map +1 -1
- package/dist/cli/help-groups.js +6 -1
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/main.js +272 -67
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parser.js +8 -1
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/render.js +15 -2
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/tui/app.js +3 -2
- package/dist/cli/tui/app.js.map +1 -1
- package/dist/cli/tui/components/workspace-init-form.js +7 -7
- package/dist/cli/tui/components/workspace-init-form.js.map +1 -1
- package/dist/cli/tui/data/workflow-content.js +2 -2
- package/dist/cli/tui/data/workflow-content.js.map +1 -1
- package/dist/cli/tui/tabs/config-tab.js +4 -3
- package/dist/cli/tui/tabs/config-tab.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab-helpers.js +72 -1
- package/dist/cli/tui/tabs/mcp-tab-helpers.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab.js +101 -42
- package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
- package/dist/cli/tui/tabs/project-tab.js +18 -21
- package/dist/cli/tui/tabs/project-tab.js.map +1 -1
- package/dist/cli/tui/workspace-root.js +13 -0
- package/dist/cli/tui/workspace-root.js.map +1 -0
- package/dist/domain/capability/protocol.js +2 -2
- package/dist/domain/capability/protocol.js.map +1 -1
- package/dist/domain/database-tools.js +445 -0
- package/dist/domain/database-tools.js.map +1 -0
- package/dist/domain/degradation-notice.js +60 -0
- package/dist/domain/degradation-notice.js.map +1 -0
- package/dist/domain/elicitation.js +152 -0
- package/dist/domain/elicitation.js.map +1 -0
- package/dist/domain/flow/answer.js +173 -42
- package/dist/domain/flow/answer.js.map +1 -1
- package/dist/domain/flow/authority.js +184 -127
- package/dist/domain/flow/authority.js.map +1 -1
- package/dist/domain/flow/directive.js +47 -1
- package/dist/domain/flow/directive.js.map +1 -1
- package/dist/domain/flow/rules.js +7 -4
- package/dist/domain/flow/rules.js.map +1 -1
- package/dist/domain/flow/run-state.js +636 -31
- package/dist/domain/flow/run-state.js.map +1 -1
- package/dist/domain/harnesses.js +16 -0
- package/dist/domain/harnesses.js.map +1 -1
- package/dist/domain/mcp-entry.js +164 -28
- package/dist/domain/mcp-entry.js.map +1 -1
- package/dist/domain/postgres-object-search.js +320 -0
- package/dist/domain/postgres-object-search.js.map +1 -0
- package/dist/domain/redaction.js +47 -0
- package/dist/domain/redaction.js.map +1 -0
- package/dist/domain/retirement/proposal.js +1 -0
- package/dist/domain/retirement/proposal.js.map +1 -1
- package/dist/domain/structured-choice-stamp.js +32 -4
- package/dist/domain/structured-choice-stamp.js.map +1 -1
- package/dist/domain/workline-mcp-entry.js +34 -0
- package/dist/domain/workline-mcp-entry.js.map +1 -0
- package/dist/ports/postgres-tools.js +10 -0
- package/dist/ports/postgres-tools.js.map +1 -0
- package/dist/runtime/namespace-resolver.js +104 -18
- package/dist/runtime/namespace-resolver.js.map +1 -1
- package/package.json +5 -1
- package/skills/w/README.md +2 -2
- package/skills/w/SKILL.md +9 -10
- package/skills/w/artifacts/README.md +1 -1
- package/skills/w/commands/README.md +3 -3
- package/skills/w/commands/persist.md +3 -1
- package/skills/w/commands/resume.md +1 -1
- package/skills/w/commands/spec-new.md +2 -2
- package/skills/w/commands/status.md +1 -1
- package/skills/w/commands/workspace-init.md +9 -10
- package/skills/w/context/MANIFEST.json +1 -4
- package/skills/w/harness/HARNESS.md +1 -1
- package/skills/w/loops/quick-loop/LOOP.md +1 -1
- package/skills/w/modules/PROMPT-CONTINUITY.md +1 -1
- package/skills/w/modules/WORKSPACE-SCAFFOLD.md +17 -20
- package/skills/w/schemas/capability-descriptor.schema.json +1 -1
- package/dist/application/mcp-dbhub-launcher.js +0 -216
- package/dist/application/mcp-dbhub-launcher.js.map +0 -1
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
import { WORKLINE_FLOWS } from "../../application/capability/compose.js";
|
|
25
25
|
import { canonicalJson, semanticDigest } from "../../application/semantic-operation/protocol.js";
|
|
26
26
|
import { isEffectClass, touchesTheWorld } from "../capability/effects.js";
|
|
27
|
+
import { AttemptLedger } from "../capability/protocol.js";
|
|
28
|
+
import { validateDecisionNote } from "../decision-note.js";
|
|
27
29
|
/**
|
|
28
30
|
* The version this CLI WRITES. Every state it persists carries it.
|
|
29
31
|
*
|
|
@@ -35,21 +37,17 @@ import { isEffectClass, touchesTheWorld } from "../capability/effects.js";
|
|
|
35
37
|
* turning the cap off in silence while somebody alternates CLI versions over one
|
|
36
38
|
* run. Failing with a cause is the requirement; failing silently is the defect.
|
|
37
39
|
*/
|
|
38
|
-
export const FLOW_RUN_STATE_VERSION =
|
|
40
|
+
export const FLOW_RUN_STATE_VERSION = 10;
|
|
39
41
|
/**
|
|
40
42
|
* The versions this CLI READS, newest first.
|
|
41
43
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* would be the fabrication the version gate exists to refuse. Neither happens
|
|
50
|
-
* here.
|
|
51
|
-
*/
|
|
52
|
-
export const FLOW_RUN_STATE_READABLE = [FLOW_RUN_STATE_VERSION, 8, 7];
|
|
44
|
+
* Versions 9, 8 and 7 remain readable for status, recovery evidence and an
|
|
45
|
+
* explicit adoption. They are NOT writable execution state: v10 records batches
|
|
46
|
+
* and their append-only iteration trace, so continuing an old cursor by merely
|
|
47
|
+
* changing `version` would invent a batch boundary the old run never declared.
|
|
48
|
+
* An active legacy run must be adopted explicitly before any mutation.
|
|
49
|
+
*/
|
|
50
|
+
export const FLOW_RUN_STATE_READABLE = [FLOW_RUN_STATE_VERSION, 9, 8, 7];
|
|
53
51
|
/** The CLI-owned run state inside the session folder. Machine-local, dotted. */
|
|
54
52
|
export const FLOW_RUN_STATE_FILE = ".flow-run.json";
|
|
55
53
|
/**
|
|
@@ -81,13 +79,116 @@ export const MAX_BOUNDARY_ATTEMPTS = 3;
|
|
|
81
79
|
* as a reason to refuse a run that was walking fine.
|
|
82
80
|
*/
|
|
83
81
|
export function attemptsAt(state, transition) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
return spendAt(state, transition).spent;
|
|
83
|
+
}
|
|
84
|
+
/** The monotone-counter key for one boundary, scoped when that boundary repeats in a batch. */
|
|
85
|
+
export function attemptCounterKey(state, transition) {
|
|
86
|
+
const iteration = currentBatchIteration(state, transition);
|
|
87
|
+
return attemptCounterKeyForIteration(transition, iteration ?? undefined);
|
|
88
|
+
}
|
|
89
|
+
/** Stable counter key for a historical attempt whose batch iteration is recorded on the row. */
|
|
90
|
+
export function attemptCounterKeyForIteration(transition, iteration) {
|
|
91
|
+
return iteration === undefined ? transition : `${transition}@batch-${iteration}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The three numbers and the difference between them, computed once.
|
|
95
|
+
*
|
|
96
|
+
* Kept apart from {@link attemptAccountingAt} so the cap check stays what it was
|
|
97
|
+
* — three lookups and a subtraction — while the richer reading built on top does
|
|
98
|
+
* not restate the arithmetic. Two copies of this formula is exactly how the
|
|
99
|
+
* directive and the recovery guard would end up disagreeing about the same run.
|
|
100
|
+
*/
|
|
101
|
+
function spendAt(state, transition) {
|
|
102
|
+
const iteration = currentBatchIteration(state, transition);
|
|
103
|
+
const rows = state.attempts.filter((attempt) => attempt.transition === transition &&
|
|
104
|
+
(iteration === null
|
|
105
|
+
? attempt.batch_iteration === undefined
|
|
106
|
+
: attempt.batch_iteration === iteration));
|
|
107
|
+
const key = attemptCounterKey(state, transition);
|
|
108
|
+
const floor = state.attempt_floor?.[key] ?? 0;
|
|
109
|
+
const granted = state.attempt_grants?.[key] ?? 0;
|
|
110
|
+
return { rows, floor, granted, spent: Math.max(0, Math.max(rows.length, floor) - granted) };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The whole accounting of one transition, conflicts named and answerability read.
|
|
114
|
+
*
|
|
115
|
+
* The one reading the engine, the directive and the recovery guard share.
|
|
116
|
+
*/
|
|
117
|
+
export function attemptAccountingAt(state, transition) {
|
|
118
|
+
const { rows, floor: read, granted, spent } = spendAt(state, transition);
|
|
119
|
+
// The floor the FILE will hold, not the one this invocation happened to read.
|
|
120
|
+
// `raiseCounters` lifts it to at least the row count on every write, so the
|
|
121
|
+
// pre-raise value left the directive reporting a floor one write behind itself
|
|
122
|
+
// — and a projection that disagrees with the state it describes is the very
|
|
123
|
+
// thing this reading exists to stop. It cannot mask a real divergence: a
|
|
124
|
+
// counter that runs AHEAD of the rows stays ahead of this maximum.
|
|
125
|
+
const floor = Math.max(read, rows.length);
|
|
126
|
+
const ordinals = rows.map((attempt) => attempt.attempt);
|
|
127
|
+
const conflicts = [];
|
|
128
|
+
if (floor > rows.length) {
|
|
129
|
+
conflicts.push({
|
|
130
|
+
between: [
|
|
131
|
+
{ name: "attempt_floor", value: floor },
|
|
132
|
+
{ name: "attempts", value: rows.length },
|
|
133
|
+
],
|
|
134
|
+
cause: "el contador monótono declara más intentos que las filas persistidas: o se restauró una copia anterior del ledger, o una escritura murió entre el contador y el estado",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (granted > Math.max(rows.length, floor)) {
|
|
138
|
+
conflicts.push({
|
|
139
|
+
between: [
|
|
140
|
+
{ name: "attempt_grants", value: granted },
|
|
141
|
+
{ name: "attempts", value: Math.max(rows.length, floor) },
|
|
142
|
+
],
|
|
143
|
+
cause: "se perdonan más intentos de los que esta transición llegó a registrar",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const highest = ordinals.length === 0 ? 0 : Math.max(...ordinals);
|
|
147
|
+
if (highest !== rows.length) {
|
|
148
|
+
conflicts.push({
|
|
149
|
+
between: [
|
|
150
|
+
{ name: "attempts[].attempt", value: highest },
|
|
151
|
+
{ name: "attempts", value: rows.length },
|
|
152
|
+
],
|
|
153
|
+
cause: "el ordinal más alto que las filas declaran no coincide con cuántas filas hay: la cadena tiene un hueco, un número repetido, o se reinició bajo otro sello",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
transition,
|
|
158
|
+
rows: rows.length,
|
|
159
|
+
floor,
|
|
160
|
+
granted,
|
|
161
|
+
spent,
|
|
162
|
+
available: Math.max(0, MAX_BOUNDARY_ATTEMPTS - spent),
|
|
163
|
+
ordinals,
|
|
164
|
+
conflicts,
|
|
165
|
+
unanswerable: unanswerableAt(state),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The refusal a submit would hit replaying this run's own history, or `null`.
|
|
170
|
+
*
|
|
171
|
+
* The ledger validates ordinals keyed by the boundary SEAL rather than by
|
|
172
|
+
* transition, so the question cannot be asked about one transition in isolation:
|
|
173
|
+
* it is asked the way `submit` asks it, by replaying every row the run persisted.
|
|
174
|
+
* A failed replay is not a prediction — it is the same failure the next submit
|
|
175
|
+
* returns, produced before anybody pays an attempt to discover it.
|
|
176
|
+
*/
|
|
177
|
+
function unanswerableAt(state) {
|
|
178
|
+
const ledger = new AttemptLedger();
|
|
179
|
+
for (const past of state.attempts) {
|
|
180
|
+
const replay = ledger.record(past);
|
|
181
|
+
if (!replay.ok)
|
|
182
|
+
return replay.failure;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
88
185
|
}
|
|
89
186
|
export function recoveryBlockedAt(state, transition) {
|
|
90
|
-
const
|
|
187
|
+
const iteration = currentBatchIteration(state, transition);
|
|
188
|
+
const trace = state.events.filter((event) => event.transition === transition &&
|
|
189
|
+
(iteration === null
|
|
190
|
+
? event.batch_iteration === undefined
|
|
191
|
+
: event.batch_iteration === iteration));
|
|
91
192
|
const material = [...trace].reverse().find(declaresMaterialEffect);
|
|
92
193
|
if (material !== undefined)
|
|
93
194
|
return { reason: "materialized", event: material };
|
|
@@ -134,6 +235,19 @@ export function positionDigest(state) {
|
|
|
134
235
|
const { attempts: _spent, attempt_floor: _floor, attempt_grants: _granted, digest: _seal, ...position } = state;
|
|
135
236
|
return semanticDigest(position);
|
|
136
237
|
}
|
|
238
|
+
/** The bounded lifecycle of one repeatable plan-execution batch. */
|
|
239
|
+
export const PLAN_EXEC_BATCH_STAGES = [
|
|
240
|
+
"inferred",
|
|
241
|
+
"isolated",
|
|
242
|
+
"implementing",
|
|
243
|
+
"deviation",
|
|
244
|
+
"validating",
|
|
245
|
+
"reviewing",
|
|
246
|
+
"closed",
|
|
247
|
+
"committing",
|
|
248
|
+
"integrating",
|
|
249
|
+
"done",
|
|
250
|
+
];
|
|
137
251
|
/** The state minus its own seal — exactly what {@link sealRunState} digests. */
|
|
138
252
|
function withoutSeal(state) {
|
|
139
253
|
const { digest: _seal, ...rest } = state;
|
|
@@ -158,6 +272,12 @@ export function newRunState(flow, session) {
|
|
|
158
272
|
proposal: null,
|
|
159
273
|
effects: { planned: [], approved: [], applied: [] },
|
|
160
274
|
attempts: [],
|
|
275
|
+
batches: [],
|
|
276
|
+
batch_trace: [],
|
|
277
|
+
batch_loop: { pending: true, iteration: 1 },
|
|
278
|
+
handoff: null,
|
|
279
|
+
selected_choice: null,
|
|
280
|
+
decision_preparation: null,
|
|
161
281
|
});
|
|
162
282
|
}
|
|
163
283
|
/**
|
|
@@ -223,25 +343,257 @@ export function degradeTransition(state, decisionId, cause) {
|
|
|
223
343
|
* were forgiven, which only ever grows; the effective count is the difference.
|
|
224
344
|
* See {@link attemptsAt}.
|
|
225
345
|
*/
|
|
346
|
+
/**
|
|
347
|
+
* Relabel the attempt chain so the ledger accepts it again — losing no row.
|
|
348
|
+
*
|
|
349
|
+
* The ledger validates ordinals per boundary SEAL: contiguous from 1, each row
|
|
350
|
+
* linked to the digest of the one before it. A run whose chain was broken by an
|
|
351
|
+
* older build of this very CLI — the recovery that rewound `attempts` and then
|
|
352
|
+
* appended a duplicate — stays readable and keeps every fact worth keeping, but
|
|
353
|
+
* every future submit dies on the replay. Renumbering is the smallest repair that
|
|
354
|
+
* makes it answerable: the rows, their digests and their seals all survive in the
|
|
355
|
+
* order they happened, and only the LABELS are made consistent with that order.
|
|
356
|
+
*
|
|
357
|
+
* It is not a way around the cap, which is the reason it is safe to do at all:
|
|
358
|
+
* the monotone floor and the grants are untouched here, so what the boundary has
|
|
359
|
+
* already spent is exactly what it spent before. And it is not a repair of a
|
|
360
|
+
* TAMPERED ledger either — the seal is verified before anything gets this far.
|
|
361
|
+
*/
|
|
362
|
+
export function normalizeAttemptChain(state) {
|
|
363
|
+
const ordinal = new Map();
|
|
364
|
+
const previous = new Map();
|
|
365
|
+
const attempts = state.attempts.map((row) => {
|
|
366
|
+
const next = (ordinal.get(row.invocation_id) ?? 0) + 1;
|
|
367
|
+
ordinal.set(row.invocation_id, next);
|
|
368
|
+
const parent = next === 1 ? null : (previous.get(row.invocation_id) ?? null);
|
|
369
|
+
previous.set(row.invocation_id, row.request_digest);
|
|
370
|
+
return { ...row, attempt: next, parent_request_digest: parent };
|
|
371
|
+
});
|
|
372
|
+
return sealRunState({ ...withoutSeal(state), attempts });
|
|
373
|
+
}
|
|
226
374
|
export function grantAttempts(state, transition, attempts) {
|
|
227
375
|
const granted = { ...(state.attempt_grants ?? {}) };
|
|
228
|
-
|
|
376
|
+
const key = attemptCounterKey(state, transition);
|
|
377
|
+
granted[key] = (granted[key] ?? 0) + attempts;
|
|
229
378
|
return sealRunState({ ...withoutSeal(state), attempt_grants: granted });
|
|
230
379
|
}
|
|
231
380
|
/**
|
|
232
|
-
*
|
|
381
|
+
* Explicitly adopt a readable legacy state into v10.
|
|
233
382
|
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
* and nothing is filled in. Re-sealed because the version is inside the seal.
|
|
383
|
+
* This function is intentionally NOT called by the reader. Calling it is the
|
|
384
|
+
* durable act of adoption after the caller has named the flow and accepted that
|
|
385
|
+
* no pre-v10 batch is being reconstructed. Empty batch arrays mean exactly
|
|
386
|
+
* "legacy history preserved; no v10 batch claimed", never "the old run had no
|
|
387
|
+
* batches".
|
|
240
388
|
*/
|
|
241
389
|
export function atCurrentVersion(state) {
|
|
242
390
|
if (state.version === FLOW_RUN_STATE_VERSION)
|
|
243
391
|
return state;
|
|
244
|
-
return sealRunState({
|
|
392
|
+
return sealRunState({
|
|
393
|
+
...withoutSeal(state),
|
|
394
|
+
version: FLOW_RUN_STATE_VERSION,
|
|
395
|
+
batches: [],
|
|
396
|
+
batch_trace: [],
|
|
397
|
+
batch_loop: { pending: true, iteration: 1 },
|
|
398
|
+
handoff: null,
|
|
399
|
+
selected_choice: null,
|
|
400
|
+
decision_preparation: null,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
/** Whether a parsed state needs the explicit adoption boundary before mutation. */
|
|
404
|
+
export function legacyRunNeedsAdoption(state) {
|
|
405
|
+
return state.version !== FLOW_RUN_STATE_VERSION;
|
|
406
|
+
}
|
|
407
|
+
/** Record one inferred or declared batch, refusing duplicate ids at the caller's boundary. */
|
|
408
|
+
export function withPlanExecBatch(state, batch) {
|
|
409
|
+
const batches = state.batches ?? [];
|
|
410
|
+
if (batches.some((current) => current.id === batch.id))
|
|
411
|
+
return state;
|
|
412
|
+
const trace = state.batch_trace ?? [];
|
|
413
|
+
return sealRunState({
|
|
414
|
+
...withoutSeal(state),
|
|
415
|
+
batches: [...batches, batch],
|
|
416
|
+
batch_trace: [
|
|
417
|
+
...trace,
|
|
418
|
+
{
|
|
419
|
+
sequence: trace.length + 1,
|
|
420
|
+
batch_id: batch.id,
|
|
421
|
+
iteration: batch.iteration,
|
|
422
|
+
stage: "inferred",
|
|
423
|
+
transition: "plan-exec.batch-inference",
|
|
424
|
+
kind: "entered",
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/** Advance one batch phase and append the evidence row in the same sealed state. */
|
|
430
|
+
export function withPlanExecBatchStage(state, batchId, stage, transition, kind = "completed") {
|
|
431
|
+
const batches = state.batches ?? [];
|
|
432
|
+
const index = batches.findIndex((batch) => batch.id === batchId);
|
|
433
|
+
if (index < 0)
|
|
434
|
+
return state;
|
|
435
|
+
const current = batches[index];
|
|
436
|
+
if (current === undefined)
|
|
437
|
+
return state;
|
|
438
|
+
const nextBatches = [...batches];
|
|
439
|
+
nextBatches[index] = { ...current, stage };
|
|
440
|
+
const trace = state.batch_trace ?? [];
|
|
441
|
+
const entry = {
|
|
442
|
+
sequence: trace.length + 1,
|
|
443
|
+
batch_id: current.id,
|
|
444
|
+
iteration: current.iteration,
|
|
445
|
+
stage,
|
|
446
|
+
transition,
|
|
447
|
+
kind,
|
|
448
|
+
};
|
|
449
|
+
return sealRunState({
|
|
450
|
+
...withoutSeal(state),
|
|
451
|
+
batches: nextBatches,
|
|
452
|
+
batch_trace: [...trace, entry],
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Record the lifecycle evidence a real PLAN-exec boundary just crossed.
|
|
457
|
+
*
|
|
458
|
+
* The batch itself is sealed at `batch-inference`, before implementation. Later
|
|
459
|
+
* transitions do not re-infer it; they only append the stage that the cursor
|
|
460
|
+
* actually reached. The small mapping lives beside the state shape so CLI walks,
|
|
461
|
+
* submitted execution results and internal actions cannot drift into three
|
|
462
|
+
* different notions of "the current batch".
|
|
463
|
+
*/
|
|
464
|
+
const PLAN_EXEC_BATCH_STAGE_BY_TRANSITION = {
|
|
465
|
+
"plan-exec.batch-isolation": "isolated",
|
|
466
|
+
"plan-exec.implementation": "implementing",
|
|
467
|
+
"plan-exec.deviation-recognition": "deviation",
|
|
468
|
+
"plan-exec.validation-execution": "validating",
|
|
469
|
+
"plan-exec.review-findings": "reviewing",
|
|
470
|
+
// These only occur after the final batch publication. They therefore extend
|
|
471
|
+
// the last closed batch's trace instead of fabricating a new one for Git/done.
|
|
472
|
+
"plan-exec.final-validation": "validating",
|
|
473
|
+
"plan-exec.commit-execution": "committing",
|
|
474
|
+
"plan-exec.unit-integration": "integrating",
|
|
475
|
+
"plan-exec.plan-done": "done",
|
|
476
|
+
};
|
|
477
|
+
const PLAN_EXEC_TERMINAL_BATCH_TRANSITIONS = new Set([
|
|
478
|
+
"plan-exec.final-validation",
|
|
479
|
+
"plan-exec.commit-execution",
|
|
480
|
+
"plan-exec.unit-integration",
|
|
481
|
+
"plan-exec.plan-done",
|
|
482
|
+
]);
|
|
483
|
+
/** Append the stage for a crossed batch boundary, if that boundary owns one. */
|
|
484
|
+
export function withPlanExecBatchStageForTransition(state, transition) {
|
|
485
|
+
if (state.flow !== "plan-exec")
|
|
486
|
+
return state;
|
|
487
|
+
const stage = PLAN_EXEC_BATCH_STAGE_BY_TRANSITION[transition];
|
|
488
|
+
if (stage === undefined)
|
|
489
|
+
return state;
|
|
490
|
+
const reverse = [...(state.batches ?? [])].reverse();
|
|
491
|
+
const current = PLAN_EXEC_TERMINAL_BATCH_TRANSITIONS.has(transition)
|
|
492
|
+
? reverse.find((batch) => batch.published_plan_digest !== undefined)
|
|
493
|
+
: reverse.find((batch) => batch.published_plan_digest === undefined);
|
|
494
|
+
if (current === undefined)
|
|
495
|
+
return state;
|
|
496
|
+
const last = state.batch_trace?.at(-1);
|
|
497
|
+
if (current.stage === stage && last?.batch_id === current.id && last.transition === transition) {
|
|
498
|
+
return state;
|
|
499
|
+
}
|
|
500
|
+
return withPlanExecBatchStage(state, current.id, stage, transition, "completed");
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Seal whether the just-published batch opens another iteration.
|
|
504
|
+
*
|
|
505
|
+
* The final document bytes, not an agent assertion, decide this field. Keeping
|
|
506
|
+
* it in the same state publication as the `closed` trace makes a crash recover
|
|
507
|
+
* to one exact cursor shape.
|
|
508
|
+
*/
|
|
509
|
+
export function withPlanExecBatchLoop(state, loop) {
|
|
510
|
+
const current = state.batch_loop;
|
|
511
|
+
if (current?.pending === loop.pending && current?.iteration === loop.iteration)
|
|
512
|
+
return state;
|
|
513
|
+
return sealRunState({ ...withoutSeal(state), batch_loop: loop });
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* The iteration identity for a transition in the repeatable PLAN-exec segment.
|
|
517
|
+
*
|
|
518
|
+
* Prefix/tail rows deliberately return `null`: they run once per flow and keep
|
|
519
|
+
* their historical retry/observation identity. The loop field is absent on an
|
|
520
|
+
* older v10 state, in which case preserving its old unscoped accounting is safer
|
|
521
|
+
* than pretending we know which historical batch it belonged to.
|
|
522
|
+
*/
|
|
523
|
+
export function currentBatchIteration(state, transition) {
|
|
524
|
+
if (state.flow !== "plan-exec" || !PLAN_EXEC_BATCH_LOOP_TRANSITIONS.has(transition))
|
|
525
|
+
return null;
|
|
526
|
+
const loop = state.batch_loop;
|
|
527
|
+
return loop?.pending === true && loop.iteration !== null ? loop.iteration : null;
|
|
528
|
+
}
|
|
529
|
+
/** The rows that repeat between batch closures; kept local to the state contract. */
|
|
530
|
+
export const PLAN_EXEC_BATCH_LOOP_TRANSITIONS = new Set([
|
|
531
|
+
"plan-exec.batch-eligibility-signal",
|
|
532
|
+
"plan-exec.batch-inference",
|
|
533
|
+
"plan-exec.batch-isolation",
|
|
534
|
+
"plan-exec.design-precondition",
|
|
535
|
+
"plan-exec.unit-acquisition",
|
|
536
|
+
"plan-exec.branch-precondition",
|
|
537
|
+
"plan-exec.implementation",
|
|
538
|
+
"plan-exec.deviation-recognition",
|
|
539
|
+
"plan-exec.deviation-eligibility",
|
|
540
|
+
"plan-exec.deviation-gate",
|
|
541
|
+
"plan-exec.escalation-package",
|
|
542
|
+
"plan-exec.validation-execution",
|
|
543
|
+
"plan-exec.deferred-check",
|
|
544
|
+
"plan-exec.review-findings",
|
|
545
|
+
"plan-exec.batch-close",
|
|
546
|
+
]);
|
|
547
|
+
/** Seal the plan bytes the batch publication actually produced. */
|
|
548
|
+
export function withPlanExecBatchPublication(state, batchId, publishedPlanDigest) {
|
|
549
|
+
const batches = state.batches ?? [];
|
|
550
|
+
const index = batches.findIndex((batch) => batch.id === batchId);
|
|
551
|
+
if (index < 0)
|
|
552
|
+
return state;
|
|
553
|
+
const current = batches[index];
|
|
554
|
+
if (current === undefined)
|
|
555
|
+
return state;
|
|
556
|
+
const next = [...batches];
|
|
557
|
+
const publication = current.publication === undefined
|
|
558
|
+
? {}
|
|
559
|
+
: {
|
|
560
|
+
publication: {
|
|
561
|
+
...current.publication,
|
|
562
|
+
after_plan_digest: publishedPlanDigest,
|
|
563
|
+
status: "applied",
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
next[index] = {
|
|
567
|
+
...current,
|
|
568
|
+
published_plan_digest: publishedPlanDigest,
|
|
569
|
+
...publication,
|
|
570
|
+
};
|
|
571
|
+
return sealRunState({ ...withoutSeal(state), batches: next });
|
|
572
|
+
}
|
|
573
|
+
/** Seal the exact before/after pair before the plan document is touched. */
|
|
574
|
+
export function withPlanExecBatchPublicationPrepared(state, batchId, publication) {
|
|
575
|
+
const batches = state.batches ?? [];
|
|
576
|
+
const index = batches.findIndex((batch) => batch.id === batchId);
|
|
577
|
+
if (index < 0)
|
|
578
|
+
return state;
|
|
579
|
+
const current = batches[index];
|
|
580
|
+
if (current === undefined)
|
|
581
|
+
return state;
|
|
582
|
+
const next = [...batches];
|
|
583
|
+
next[index] = { ...current, publication: { ...publication, status: "prepared" } };
|
|
584
|
+
return sealRunState({ ...withoutSeal(state), batches: next });
|
|
585
|
+
}
|
|
586
|
+
/** Persist a selected handoff; the advancement engine then refuses to continue this run. */
|
|
587
|
+
export function withHandoff(state, handoff) {
|
|
588
|
+
return sealRunState({ ...withoutSeal(state), handoff });
|
|
589
|
+
}
|
|
590
|
+
/** Persist the gate's executable consequence before the run leaves that boundary. */
|
|
591
|
+
export function withSelectedChoice(state, selected) {
|
|
592
|
+
return sealRunState({ ...withoutSeal(state), selected_choice: selected });
|
|
593
|
+
}
|
|
594
|
+
/** Persist or clear the exact decision view offered at the human gate. */
|
|
595
|
+
export function withDecisionPreparation(state, preparation) {
|
|
596
|
+
return sealRunState({ ...withoutSeal(state), decision_preparation: preparation });
|
|
245
597
|
}
|
|
246
598
|
/**
|
|
247
599
|
* Reconcile the state with the monotone counter that lives outside its seal.
|
|
@@ -329,6 +681,7 @@ export function restatesLastEvent(state, event) {
|
|
|
329
681
|
if (last === undefined || last.kind !== "failed" || event.kind !== "failed")
|
|
330
682
|
return false;
|
|
331
683
|
return (last.transition === event.transition &&
|
|
684
|
+
last.batch_iteration === event.batch_iteration &&
|
|
332
685
|
last.code === event.code &&
|
|
333
686
|
last.message === event.message);
|
|
334
687
|
}
|
|
@@ -348,7 +701,9 @@ export function restatesLastEvent(state, event) {
|
|
|
348
701
|
* into a retry counter, which is what the attempt history is for.
|
|
349
702
|
*/
|
|
350
703
|
export function withObservation(state, observation) {
|
|
351
|
-
const repeated = state.observations.some((past) => past.transition === observation.transition &&
|
|
704
|
+
const repeated = state.observations.some((past) => past.transition === observation.transition &&
|
|
705
|
+
past.batch_iteration === observation.batch_iteration &&
|
|
706
|
+
sameSignals(past.signals, observation.signals));
|
|
352
707
|
if (repeated)
|
|
353
708
|
return state;
|
|
354
709
|
return sealRunState({
|
|
@@ -552,6 +907,20 @@ function checkShape(parsed) {
|
|
|
552
907
|
* digest matches.
|
|
553
908
|
*/
|
|
554
909
|
function checkRecordShape(parsed, invalid) {
|
|
910
|
+
const common = checkCommonRecordShape(parsed, invalid);
|
|
911
|
+
if (common !== null)
|
|
912
|
+
return common;
|
|
913
|
+
if (parsed.version === FLOW_RUN_STATE_VERSION) {
|
|
914
|
+
const v10 = checkV10RecordShape(parsed, invalid);
|
|
915
|
+
if (v10 !== null)
|
|
916
|
+
return v10;
|
|
917
|
+
}
|
|
918
|
+
if (typeof parsed.digest !== "string")
|
|
919
|
+
return invalid("no trae su sello");
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
/** Record fields shared by every readable flow-run version, in refusal order. */
|
|
923
|
+
function checkCommonRecordShape(parsed, invalid) {
|
|
555
924
|
if (!isPendingAction(parsed.pending_action)) {
|
|
556
925
|
return invalid("declara una acción pendiente sin transición o sin sello");
|
|
557
926
|
}
|
|
@@ -583,8 +952,28 @@ function checkRecordShape(parsed, invalid) {
|
|
|
583
952
|
if (!isCounterMap(parsed.attempt_floor) || !isCounterMap(parsed.attempt_grants)) {
|
|
584
953
|
return invalid("trae una contabilidad de intentos que no es un contador por transición");
|
|
585
954
|
}
|
|
586
|
-
|
|
587
|
-
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
/** Fields added by v10 that have no truthful default on a persisted run. */
|
|
958
|
+
function checkV10RecordShape(parsed, invalid) {
|
|
959
|
+
if (!isPlanExecBatchArray(parsed.batches)) {
|
|
960
|
+
return invalid("no trae batches v10 con fases, tareas y sello de plan");
|
|
961
|
+
}
|
|
962
|
+
if (!isPlanExecBatchTraceArray(parsed.batch_trace, parsed.batches)) {
|
|
963
|
+
return invalid("trae una traza de batches que no es append-only ni corresponde a sus iteraciones");
|
|
964
|
+
}
|
|
965
|
+
if (!isPlanExecBatchLoop(parsed.batch_loop)) {
|
|
966
|
+
return invalid("declara un ciclo de batches sin su próxima iteración sellada");
|
|
967
|
+
}
|
|
968
|
+
if (!isHandoff(parsed.handoff)) {
|
|
969
|
+
return invalid("declara una entrega de flow sin destino, comando o sello de paquete");
|
|
970
|
+
}
|
|
971
|
+
if (!isSelectedChoice(parsed.selected_choice)) {
|
|
972
|
+
return invalid("declara una consecuencia elegida sin frontera, etiqueta o ruta válida");
|
|
973
|
+
}
|
|
974
|
+
if (!isDecisionPreparation(parsed.decision_preparation)) {
|
|
975
|
+
return invalid("declara una vista de decisión sin nota, preview o linaje sellados");
|
|
976
|
+
}
|
|
588
977
|
return null;
|
|
589
978
|
}
|
|
590
979
|
function refuse(code, message, action) {
|
|
@@ -633,6 +1022,212 @@ function isScope(value) {
|
|
|
633
1022
|
return false;
|
|
634
1023
|
return value.sources.every((alias) => alias.trim().length > 0);
|
|
635
1024
|
}
|
|
1025
|
+
function isPlanExecBatchArray(value) {
|
|
1026
|
+
if (!Array.isArray(value))
|
|
1027
|
+
return false;
|
|
1028
|
+
const ids = new Set();
|
|
1029
|
+
const iterations = new Set();
|
|
1030
|
+
return value.every((entry) => {
|
|
1031
|
+
if (!isRecord(entry))
|
|
1032
|
+
return false;
|
|
1033
|
+
if (typeof entry.id !== "string" ||
|
|
1034
|
+
entry.id.trim().length === 0 ||
|
|
1035
|
+
ids.has(entry.id) ||
|
|
1036
|
+
typeof entry.iteration !== "number" ||
|
|
1037
|
+
!Number.isInteger(entry.iteration) ||
|
|
1038
|
+
entry.iteration < 1 ||
|
|
1039
|
+
iterations.has(entry.iteration) ||
|
|
1040
|
+
(entry.mode !== "continuous" && entry.mode !== "isolated") ||
|
|
1041
|
+
!Array.isArray(entry.phases) ||
|
|
1042
|
+
entry.phases.length === 0 ||
|
|
1043
|
+
!entry.phases.every((phase) => typeof phase === "number" && Number.isInteger(phase) && phase > 0) ||
|
|
1044
|
+
!isStringArray(entry.tasks) ||
|
|
1045
|
+
entry.tasks.length === 0 ||
|
|
1046
|
+
new Set(entry.tasks).size !== entry.tasks.length ||
|
|
1047
|
+
typeof entry.plan_digest !== "string" ||
|
|
1048
|
+
entry.plan_digest.length === 0 ||
|
|
1049
|
+
(entry.published_plan_digest !== undefined &&
|
|
1050
|
+
(typeof entry.published_plan_digest !== "string" ||
|
|
1051
|
+
entry.published_plan_digest.length === 0)) ||
|
|
1052
|
+
!isPlanExecBatchPublication(entry.publication) ||
|
|
1053
|
+
(entry.publication !== undefined &&
|
|
1054
|
+
entry.publication.status === "applied" &&
|
|
1055
|
+
entry.published_plan_digest !== entry.publication.after_plan_digest) ||
|
|
1056
|
+
!PLAN_EXEC_BATCH_STAGES.includes(entry.stage)) {
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
ids.add(entry.id);
|
|
1060
|
+
iterations.add(entry.iteration);
|
|
1061
|
+
return true;
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
function isPlanExecBatchPublication(value) {
|
|
1065
|
+
if (value === undefined)
|
|
1066
|
+
return true;
|
|
1067
|
+
return (isRecord(value) &&
|
|
1068
|
+
typeof value.plan === "string" &&
|
|
1069
|
+
value.plan.trim().length > 0 &&
|
|
1070
|
+
typeof value.before_plan_digest === "string" &&
|
|
1071
|
+
value.before_plan_digest.length > 0 &&
|
|
1072
|
+
typeof value.after_plan_digest === "string" &&
|
|
1073
|
+
value.after_plan_digest.length > 0 &&
|
|
1074
|
+
typeof value.transition === "string" &&
|
|
1075
|
+
value.transition.length > 0 &&
|
|
1076
|
+
(value.status === "prepared" || value.status === "applied"));
|
|
1077
|
+
}
|
|
1078
|
+
/** Absent is only the compatibility reading of a v10 file written before the loop cursor. */
|
|
1079
|
+
function isPlanExecBatchLoop(value) {
|
|
1080
|
+
if (value === undefined)
|
|
1081
|
+
return true;
|
|
1082
|
+
if (!isRecord(value) || typeof value.pending !== "boolean")
|
|
1083
|
+
return false;
|
|
1084
|
+
if (value.pending)
|
|
1085
|
+
return Number.isInteger(value.iteration) && value.iteration >= 1;
|
|
1086
|
+
return value.iteration === null;
|
|
1087
|
+
}
|
|
1088
|
+
function isPlanExecBatchTraceArray(value, batches) {
|
|
1089
|
+
if (!Array.isArray(value) || !Array.isArray(batches))
|
|
1090
|
+
return false;
|
|
1091
|
+
const byId = new Map(batches.map((batch) => [batch.id, batch.iteration]));
|
|
1092
|
+
return value.every((entry, index) => {
|
|
1093
|
+
if (!isRecord(entry))
|
|
1094
|
+
return false;
|
|
1095
|
+
return (entry.sequence === index + 1 &&
|
|
1096
|
+
typeof entry.batch_id === "string" &&
|
|
1097
|
+
byId.get(entry.batch_id) === entry.iteration &&
|
|
1098
|
+
typeof entry.iteration === "number" &&
|
|
1099
|
+
Number.isInteger(entry.iteration) &&
|
|
1100
|
+
typeof entry.transition === "string" &&
|
|
1101
|
+
entry.transition.length > 0 &&
|
|
1102
|
+
PLAN_EXEC_BATCH_STAGES.includes(entry.stage) &&
|
|
1103
|
+
(entry.kind === "entered" || entry.kind === "completed" || entry.kind === "blocked"));
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
function isHandoff(value) {
|
|
1107
|
+
if (value === null)
|
|
1108
|
+
return true;
|
|
1109
|
+
if (!isRecord(value))
|
|
1110
|
+
return false;
|
|
1111
|
+
return ((value.destination === "plan-refine" ||
|
|
1112
|
+
value.destination === "spec-refine" ||
|
|
1113
|
+
value.destination === "spec-new") &&
|
|
1114
|
+
typeof value.command === "string" &&
|
|
1115
|
+
value.command.trim().length > 0 &&
|
|
1116
|
+
isEscalationPackage(value.package) &&
|
|
1117
|
+
typeof value.package_digest === "string" &&
|
|
1118
|
+
value.package_digest === semanticDigest(value.package));
|
|
1119
|
+
}
|
|
1120
|
+
function isEscalationPackage(value) {
|
|
1121
|
+
return (isRecord(value) &&
|
|
1122
|
+
(value.plan === null || (typeof value.plan === "string" && value.plan.trim().length > 0)) &&
|
|
1123
|
+
isObservationArray(value.observations) &&
|
|
1124
|
+
isRecord(value.decisions) &&
|
|
1125
|
+
isJsonValue(value.decisions) &&
|
|
1126
|
+
typeof value.selection === "string" &&
|
|
1127
|
+
value.selection.trim().length > 0);
|
|
1128
|
+
}
|
|
1129
|
+
function isJsonValue(value) {
|
|
1130
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
1131
|
+
return true;
|
|
1132
|
+
if (typeof value === "number")
|
|
1133
|
+
return Number.isFinite(value);
|
|
1134
|
+
if (Array.isArray(value))
|
|
1135
|
+
return value.every(isJsonValue);
|
|
1136
|
+
return isRecord(value) && Object.values(value).every(isJsonValue);
|
|
1137
|
+
}
|
|
1138
|
+
function isSelectedChoice(value) {
|
|
1139
|
+
if (value === null)
|
|
1140
|
+
return true;
|
|
1141
|
+
if (!isRecord(value) || typeof value.transition !== "string" || typeof value.label !== "string") {
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
if (!isRecord(value.outcome))
|
|
1145
|
+
return false;
|
|
1146
|
+
return (value.outcome.kind === "register-decision" ||
|
|
1147
|
+
(value.outcome.kind === "handoff" &&
|
|
1148
|
+
(value.outcome.destination === "plan-refine" ||
|
|
1149
|
+
value.outcome.destination === "spec-refine" ||
|
|
1150
|
+
value.outcome.destination === "spec-new")));
|
|
1151
|
+
}
|
|
1152
|
+
/** A v10 file written before preview persistence may omit this field. */
|
|
1153
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: each discriminant validates one durable preparation variant.
|
|
1154
|
+
function isDecisionPreparation(value) {
|
|
1155
|
+
if (value === undefined || value === null)
|
|
1156
|
+
return true;
|
|
1157
|
+
if (!isRecord(value) || typeof value.kind !== "string")
|
|
1158
|
+
return false;
|
|
1159
|
+
if (value.kind === "settled")
|
|
1160
|
+
return typeof value.decision === "string" && value.decision.trim().length > 0;
|
|
1161
|
+
if (value.kind === "reused") {
|
|
1162
|
+
return (typeof value.note === "string" &&
|
|
1163
|
+
value.note.trim().length > 0 &&
|
|
1164
|
+
typeof value.decision === "string" &&
|
|
1165
|
+
value.decision.trim().length > 0 &&
|
|
1166
|
+
typeof value.resume_point === "string" &&
|
|
1167
|
+
value.resume_point.trim().length > 0);
|
|
1168
|
+
}
|
|
1169
|
+
if (value.kind !== "prepared" || !isRecord(value.baseline))
|
|
1170
|
+
return false;
|
|
1171
|
+
const baseline = value.baseline;
|
|
1172
|
+
if (typeof baseline.path !== "string" ||
|
|
1173
|
+
baseline.path.trim().length === 0 ||
|
|
1174
|
+
typeof baseline.number !== "string" ||
|
|
1175
|
+
baseline.number.trim().length === 0 ||
|
|
1176
|
+
typeof baseline.digest !== "string" ||
|
|
1177
|
+
baseline.digest.trim().length === 0 ||
|
|
1178
|
+
!isStringArray(baseline.criteria) ||
|
|
1179
|
+
typeof value.index_path !== "string" ||
|
|
1180
|
+
value.index_path.trim().length === 0) {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
const checkedBaseline = {
|
|
1184
|
+
path: baseline.path,
|
|
1185
|
+
number: baseline.number,
|
|
1186
|
+
digest: baseline.digest,
|
|
1187
|
+
};
|
|
1188
|
+
const note = validateDecisionNote(value.note);
|
|
1189
|
+
if (!note.ok || note.value === null)
|
|
1190
|
+
return false;
|
|
1191
|
+
if (note.value.lineage.spec.path !== checkedBaseline.path ||
|
|
1192
|
+
note.value.lineage.spec.number !== checkedBaseline.number ||
|
|
1193
|
+
note.value.lineage.spec.digest !== checkedBaseline.digest) {
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
return isDecisionPreview(value.preview, checkedBaseline);
|
|
1197
|
+
}
|
|
1198
|
+
function isDecisionPreview(value, baseline) {
|
|
1199
|
+
if (!isRecord(value) ||
|
|
1200
|
+
!isRecord(value.baseline) ||
|
|
1201
|
+
!isRecord(value.impact) ||
|
|
1202
|
+
!isRecord(value.evidence)) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
const previewBaseline = value.baseline;
|
|
1206
|
+
if (previewBaseline.path !== baseline.path ||
|
|
1207
|
+
previewBaseline.number !== baseline.number ||
|
|
1208
|
+
previewBaseline.digest !== baseline.digest ||
|
|
1209
|
+
!Array.isArray(value.effective_change) ||
|
|
1210
|
+
!isStringArray(value.consumers) ||
|
|
1211
|
+
!isStringArray(value.obligations) ||
|
|
1212
|
+
typeof value.resume_point !== "string" ||
|
|
1213
|
+
value.resume_point.trim().length === 0 ||
|
|
1214
|
+
!isStringArray(value.evidence.preserved) ||
|
|
1215
|
+
!isStringArray(value.evidence.invalidated) ||
|
|
1216
|
+
typeof value.impact.scope !== "string" ||
|
|
1217
|
+
(value.impact.scope !== "functional" && value.impact.scope !== "plan-only") ||
|
|
1218
|
+
!Number.isInteger(value.impact.assertions) ||
|
|
1219
|
+
!Number.isInteger(value.impact.consumers) ||
|
|
1220
|
+
!isRecord(value.effects) ||
|
|
1221
|
+
!isEffectClassArray(value.effects.classes) ||
|
|
1222
|
+
!Array.isArray(value.effects.entries) ||
|
|
1223
|
+
!isProposal(value.proposal)) {
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
return value.effects.entries.every((entry) => isRecord(entry) &&
|
|
1227
|
+
typeof entry.path === "string" &&
|
|
1228
|
+
Number.isInteger(entry.bytes) &&
|
|
1229
|
+
typeof entry.overwrite === "boolean");
|
|
1230
|
+
}
|
|
636
1231
|
function isProposal(value) {
|
|
637
1232
|
if (value === null)
|
|
638
1233
|
return true;
|
|
@@ -694,8 +1289,12 @@ function isEventArray(value) {
|
|
|
694
1289
|
return value.every((entry) => {
|
|
695
1290
|
if (!isRecord(entry))
|
|
696
1291
|
return false;
|
|
697
|
-
if (typeof entry.transition !== "string" ||
|
|
1292
|
+
if (typeof entry.transition !== "string" ||
|
|
1293
|
+
typeof entry.operation !== "string" ||
|
|
1294
|
+
(entry.batch_iteration !== undefined &&
|
|
1295
|
+
(!Number.isInteger(entry.batch_iteration) || entry.batch_iteration < 1))) {
|
|
698
1296
|
return false;
|
|
1297
|
+
}
|
|
699
1298
|
if (entry.kind === "executed") {
|
|
700
1299
|
return (typeof entry.summary === "string" &&
|
|
701
1300
|
typeof entry.output_digest === "string" &&
|
|
@@ -716,7 +1315,11 @@ function isEventArray(value) {
|
|
|
716
1315
|
function isObservationArray(value) {
|
|
717
1316
|
if (!Array.isArray(value))
|
|
718
1317
|
return false;
|
|
719
|
-
return value.every((entry) => isRecord(entry) &&
|
|
1318
|
+
return value.every((entry) => isRecord(entry) &&
|
|
1319
|
+
typeof entry.transition === "string" &&
|
|
1320
|
+
isStringArray(entry.signals) &&
|
|
1321
|
+
(entry.batch_iteration === undefined ||
|
|
1322
|
+
(Number.isInteger(entry.batch_iteration) && entry.batch_iteration >= 1)));
|
|
720
1323
|
}
|
|
721
1324
|
/**
|
|
722
1325
|
* The two optional fields are checked as ABSENT-or-well-formed, never defaulted.
|
|
@@ -752,6 +1355,8 @@ function isAttemptArray(value) {
|
|
|
752
1355
|
Number.isInteger(entry.attempt) &&
|
|
753
1356
|
typeof entry.request_digest === "string" &&
|
|
754
1357
|
typeof entry.transition === "string" &&
|
|
1358
|
+
(entry.batch_iteration === undefined ||
|
|
1359
|
+
(Number.isInteger(entry.batch_iteration) && entry.batch_iteration >= 1)) &&
|
|
755
1360
|
(entry.parent_request_digest === null || typeof entry.parent_request_digest === "string"));
|
|
756
1361
|
}
|
|
757
1362
|
//# sourceMappingURL=run-state.js.map
|