@tacuchi/agent-workflow-cli 25.6.1 → 25.8.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/CHANGELOG.md +4398 -0
- package/README.md +1 -1
- package/dist/adapters/git-cli.js +160 -13
- package/dist/adapters/git-cli.js.map +1 -1
- package/dist/adapters/node-file-system.js +39 -2
- package/dist/adapters/node-file-system.js.map +1 -1
- package/dist/application/branch-resolver.js +34 -1
- package/dist/application/branch-resolver.js.map +1 -1
- package/dist/application/capability/readiness.js +26 -10
- package/dist/application/capability/readiness.js.map +1 -1
- package/dist/application/capability/wrapper.js +10 -3
- package/dist/application/capability/wrapper.js.map +1 -1
- package/dist/application/checkpoint-write-service.js +161 -39
- package/dist/application/checkpoint-write-service.js.map +1 -1
- package/dist/application/dates.js +12 -0
- package/dist/application/dates.js.map +1 -1
- package/dist/application/dev-only-services.js +5 -7
- package/dist/application/dev-only-services.js.map +1 -1
- package/dist/application/doctor/provider-mcps.js +1 -1
- package/dist/application/doctor/provider-mcps.js.map +1 -1
- package/dist/application/fix-git-service.js +83 -13
- package/dist/application/fix-git-service.js.map +1 -1
- package/dist/application/flow/advance.js +64 -9
- package/dist/application/flow/advance.js.map +1 -1
- package/dist/application/flow/annul-service.js +159 -0
- package/dist/application/flow/annul-service.js.map +1 -0
- package/dist/application/flow/checkout-observation.js +49 -0
- package/dist/application/flow/checkout-observation.js.map +1 -1
- package/dist/application/flow/close-at-boundary.js +144 -0
- package/dist/application/flow/close-at-boundary.js.map +1 -0
- package/dist/application/flow/flow-service.js +213 -49
- package/dist/application/flow/flow-service.js.map +1 -1
- package/dist/application/flow/internal-actions.js +87 -4
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/flow/internal-drive.js +70 -33
- package/dist/application/flow/internal-drive.js.map +1 -1
- package/dist/application/flow/reopen-run.js +152 -0
- package/dist/application/flow/reopen-run.js.map +1 -0
- package/dist/application/flow/run-journey.js +11 -8
- package/dist/application/flow/run-journey.js.map +1 -1
- package/dist/application/flow/run-projection.js +37 -10
- package/dist/application/flow/run-projection.js.map +1 -1
- package/dist/application/flow/run-state-service.js +109 -36
- package/dist/application/flow/run-state-service.js.map +1 -1
- package/dist/application/flow/submit.js +217 -188
- package/dist/application/flow/submit.js.map +1 -1
- package/dist/application/git-flow-service.js +325 -72
- package/dist/application/git-flow-service.js.map +1 -1
- package/dist/application/hook-sql-mutation-guard.js +290 -27
- package/dist/application/hook-sql-mutation-guard.js.map +1 -1
- package/dist/application/host-memory/claude-code.js +65 -0
- package/dist/application/host-memory/claude-code.js.map +1 -0
- package/dist/application/host-memory/codex.js +225 -0
- package/dist/application/host-memory/codex.js.map +1 -0
- package/dist/application/host-memory/destination.js +52 -0
- package/dist/application/host-memory/destination.js.map +1 -0
- package/dist/application/host-memory/reader.js +9 -0
- package/dist/application/host-memory/reader.js.map +1 -0
- package/dist/application/host-memory/report.js +136 -0
- package/dist/application/host-memory/report.js.map +1 -0
- package/dist/application/lifecycle-target.js +3 -1
- package/dist/application/lifecycle-target.js.map +1 -1
- package/dist/application/local-proposal.js +13 -3
- package/dist/application/local-proposal.js.map +1 -1
- package/dist/application/logging/log-events.js +22 -0
- package/dist/application/logging/log-events.js.map +1 -1
- package/dist/application/parsers/spec-relation.js +20 -7
- package/dist/application/parsers/spec-relation.js.map +1 -1
- package/dist/application/persist-service.js +45 -1
- package/dist/application/persist-service.js.map +1 -1
- package/dist/application/plan-exec-batch-service.js +47 -2
- package/dist/application/plan-exec-batch-service.js.map +1 -1
- package/dist/application/plan-lineage-seal.js +93 -0
- package/dist/application/plan-lineage-seal.js.map +1 -0
- package/dist/application/plan-lint-service.js +153 -0
- package/dist/application/plan-lint-service.js.map +1 -0
- package/dist/application/prod-consent.js +75 -0
- package/dist/application/prod-consent.js.map +1 -0
- package/dist/application/reseal-service.js +9 -3
- package/dist/application/reseal-service.js.map +1 -1
- package/dist/application/self/install-skill.js +40 -12
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/install-targets.js +73 -2
- package/dist/application/self/install-targets.js.map +1 -1
- package/dist/application/self/uninstall.js +21 -7
- package/dist/application/self/uninstall.js.map +1 -1
- package/dist/application/self/update-self.js +204 -8
- package/dist/application/self/update-self.js.map +1 -1
- package/dist/application/session-create-service.js +49 -0
- package/dist/application/session-create-service.js.map +1 -1
- package/dist/application/session-custody-recorder.js +4 -0
- package/dist/application/session-custody-recorder.js.map +1 -1
- package/dist/application/session-narrative.js +36 -0
- package/dist/application/session-narrative.js.map +1 -1
- package/dist/application/session-resolver.js +28 -9
- package/dist/application/session-resolver.js.map +1 -1
- package/dist/application/session-resume-service.js +65 -16
- package/dist/application/session-resume-service.js.map +1 -1
- package/dist/application/source-boundary-policy.js +168 -34
- package/dist/application/source-boundary-policy.js.map +1 -1
- package/dist/application/working-branch-service.js +68 -0
- package/dist/application/working-branch-service.js.map +1 -0
- package/dist/application/workline-graph.js +7 -2
- package/dist/application/workline-graph.js.map +1 -1
- package/dist/cli/ascii.js +84 -0
- package/dist/cli/ascii.js.map +1 -0
- package/dist/cli/commands/amend.js +1 -0
- package/dist/cli/commands/amend.js.map +1 -1
- package/dist/cli/commands/capability.js +15 -0
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/check-branch.js +1 -0
- package/dist/cli/commands/check-branch.js.map +1 -1
- package/dist/cli/commands/checkpoint-read.js +1 -0
- package/dist/cli/commands/checkpoint-read.js.map +1 -1
- package/dist/cli/commands/checkpoint-write.js +22 -9
- package/dist/cli/commands/checkpoint-write.js.map +1 -1
- package/dist/cli/commands/claims.js +1 -0
- package/dist/cli/commands/claims.js.map +1 -1
- package/dist/cli/commands/code-scan.js +1 -0
- package/dist/cli/commands/code-scan.js.map +1 -1
- package/dist/cli/commands/context-budget.js +1 -0
- package/dist/cli/commands/context-budget.js.map +1 -1
- package/dist/cli/commands/context-plan.js +1 -0
- package/dist/cli/commands/context-plan.js.map +1 -1
- package/dist/cli/commands/cut-intent.js +1 -0
- package/dist/cli/commands/cut-intent.js.map +1 -1
- package/dist/cli/commands/designs.js +1 -0
- package/dist/cli/commands/designs.js.map +1 -1
- package/dist/cli/commands/dev-only.js +4 -0
- package/dist/cli/commands/dev-only.js.map +1 -1
- package/dist/cli/commands/doctor.js +5 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/export.js +13 -0
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/cli/commands/fix-git.js +46 -6
- package/dist/cli/commands/fix-git.js.map +1 -1
- package/dist/cli/commands/flow.js +61 -5
- package/dist/cli/commands/flow.js.map +1 -1
- package/dist/cli/commands/generate-launch.js +1 -0
- package/dist/cli/commands/generate-launch.js.map +1 -1
- package/dist/cli/commands/git-flow.js +104 -29
- package/dist/cli/commands/git-flow.js.map +1 -1
- package/dist/cli/commands/history-update.js +7 -9
- package/dist/cli/commands/history-update.js.map +1 -1
- package/dist/cli/commands/hook.js +11 -3
- package/dist/cli/commands/hook.js.map +1 -1
- package/dist/cli/commands/host-doctor.js +1 -0
- package/dist/cli/commands/host-doctor.js.map +1 -1
- package/dist/cli/commands/host-memory.js +26 -0
- package/dist/cli/commands/host-memory.js.map +1 -0
- package/dist/cli/commands/index.js +6 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.js +30 -4
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/merge-state.js +1 -0
- package/dist/cli/commands/merge-state.js.map +1 -1
- package/dist/cli/commands/multiroot.js +16 -0
- package/dist/cli/commands/multiroot.js.map +1 -1
- package/dist/cli/commands/persist.js +6 -0
- package/dist/cli/commands/persist.js.map +1 -1
- package/dist/cli/commands/plan.js +51 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/plugin-cache.js +4 -0
- package/dist/cli/commands/plugin-cache.js.map +1 -1
- package/dist/cli/commands/plugin-doctor.js +3 -0
- package/dist/cli/commands/plugin-doctor.js.map +1 -1
- package/dist/cli/commands/project-md-upsert.js +3 -0
- package/dist/cli/commands/project-md-upsert.js.map +1 -1
- package/dist/cli/commands/release-data.js +12 -0
- package/dist/cli/commands/release-data.js.map +1 -1
- package/dist/cli/commands/release-pass.js +10 -0
- package/dist/cli/commands/release-pass.js.map +1 -1
- package/dist/cli/commands/remove-source.js +1 -0
- package/dist/cli/commands/remove-source.js.map +1 -1
- package/dist/cli/commands/reseal.js +1 -0
- package/dist/cli/commands/reseal.js.map +1 -1
- package/dist/cli/commands/resume-summary.js +9 -2
- package/dist/cli/commands/resume-summary.js.map +1 -1
- package/dist/cli/commands/resume.js +1 -0
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/retirement.js +1 -0
- package/dist/cli/commands/retirement.js.map +1 -1
- package/dist/cli/commands/self.js +33 -0
- package/dist/cli/commands/self.js.map +1 -1
- package/dist/cli/commands/session-artifacts.js +1 -0
- package/dist/cli/commands/session-artifacts.js.map +1 -1
- package/dist/cli/commands/session-close.js +129 -30
- package/dist/cli/commands/session-close.js.map +1 -1
- package/dist/cli/commands/session-create.js +1 -0
- package/dist/cli/commands/session-create.js.map +1 -1
- package/dist/cli/commands/session-resume.js +1 -0
- package/dist/cli/commands/session-resume.js.map +1 -1
- package/dist/cli/commands/sessions.js +1 -0
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/commands/set-branch.js +41 -6
- package/dist/cli/commands/set-branch.js.map +1 -1
- package/dist/cli/commands/settle.js +1 -0
- package/dist/cli/commands/settle.js.map +1 -1
- package/dist/cli/commands/skill-index.js +1 -0
- package/dist/cli/commands/skill-index.js.map +1 -1
- package/dist/cli/commands/skills.js +1 -0
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/commands/sources.js +1 -0
- package/dist/cli/commands/sources.js.map +1 -1
- package/dist/cli/commands/stack.js +1 -0
- package/dist/cli/commands/stack.js.map +1 -1
- package/dist/cli/commands/status.js +1 -0
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/tool.js +5 -21
- package/dist/cli/commands/tool.js.map +1 -1
- package/dist/cli/commands/unknown-flags.js +67 -16
- package/dist/cli/commands/unknown-flags.js.map +1 -1
- package/dist/cli/commands/visibility.js +1 -0
- package/dist/cli/commands/visibility.js.map +1 -1
- package/dist/cli/commands/workspace-init.js +12 -0
- package/dist/cli/commands/workspace-init.js.map +1 -1
- package/dist/cli/commands/workspace-migrate.js +2 -13
- package/dist/cli/commands/workspace-migrate.js.map +1 -1
- package/dist/cli/commands/worktree.js +1 -0
- package/dist/cli/commands/worktree.js.map +1 -1
- package/dist/cli/help-groups.js +41 -0
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/main.js +41 -58
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output-mode.js +11 -2
- package/dist/cli/output-mode.js.map +1 -1
- package/dist/cli/parser.js +14 -5
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/render.js +30 -1
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/tui/components/git-flow-actions.js +65 -20
- package/dist/cli/tui/components/git-flow-actions.js.map +1 -1
- package/dist/cli/tui/tabs/project-tab.js +101 -12
- package/dist/cli/tui/tabs/project-tab.js.map +1 -1
- package/dist/domain/changelog-contract.js +172 -0
- package/dist/domain/changelog-contract.js.map +1 -0
- package/dist/domain/database-tools.js +1 -189
- package/dist/domain/database-tools.js.map +1 -1
- package/dist/domain/flow/answer.js +11 -0
- package/dist/domain/flow/answer.js.map +1 -1
- package/dist/domain/flow/authority.js +153 -14
- package/dist/domain/flow/authority.js.map +1 -1
- package/dist/domain/flow/execution-result.js +78 -7
- package/dist/domain/flow/execution-result.js.map +1 -1
- package/dist/domain/flow/journey-baseline.js +131 -0
- package/dist/domain/flow/journey-baseline.js.map +1 -0
- package/dist/domain/flow/route.js.map +1 -1
- package/dist/domain/flow/rules.js +5 -6
- package/dist/domain/flow/rules.js.map +1 -1
- package/dist/domain/flow/run-state.js +440 -89
- package/dist/domain/flow/run-state.js.map +1 -1
- package/dist/domain/harnesses.js +8 -1
- package/dist/domain/harnesses.js.map +1 -1
- package/dist/domain/host-memory/model.js +2 -0
- package/dist/domain/host-memory/model.js.map +1 -0
- package/dist/domain/host-memory/provenance.js +43 -0
- package/dist/domain/host-memory/provenance.js.map +1 -0
- package/dist/domain/host-memory/topic.js +26 -0
- package/dist/domain/host-memory/topic.js.map +1 -0
- package/dist/domain/lineage.js +11 -3
- package/dist/domain/lineage.js.map +1 -1
- package/dist/domain/proposal.js +7 -0
- package/dist/domain/proposal.js.map +1 -1
- package/dist/domain/session/custody.js.map +1 -1
- package/dist/domain/sql-lexer.js +240 -0
- package/dist/domain/sql-lexer.js.map +1 -0
- package/package.json +2 -2
- package/skills/w/SKILL.md +9 -9
- package/skills/w/commands/README.md +2 -1
- package/skills/w/commands/recall.md +39 -0
- package/skills/w/context/MANIFEST.json +4 -0
- package/skills/w/harness/HARNESS.md +12 -6
- package/skills/w/hooks/README.md +27 -16
- package/skills/w/loops/CHASSIS.md +16 -12
- package/skills/w/loops/CODE-POLICIES.md +11 -6
- package/skills/w/loops/plan-exec-loop/LOOP.md +9 -9
- package/skills/w/modules/COMPACTION.md +1 -1
- package/skills/w/roles/design/CONTRACT.md +10 -3
|
@@ -26,6 +26,8 @@ import { canonicalJson, semanticDigest } from "../../application/semantic-operat
|
|
|
26
26
|
import { isEffectClass, touchesTheWorld } from "../capability/effects.js";
|
|
27
27
|
import { AttemptLedger } from "../capability/protocol.js";
|
|
28
28
|
import { normalizeObligations, validateDecisionNote } from "../decision-note.js";
|
|
29
|
+
import { expandJourney, journeyOfFlow, } from "./authority.js";
|
|
30
|
+
import { V11_JOURNEY_BASE } from "./journey-baseline.js";
|
|
29
31
|
import { assuranceForRoute, isAssuranceStatus, isRouteDisposition, } from "./route.js";
|
|
30
32
|
/**
|
|
31
33
|
* The version this CLI WRITES. Every state it persists carries it.
|
|
@@ -38,17 +40,50 @@ import { assuranceForRoute, isAssuranceStatus, isRouteDisposition, } from "./rou
|
|
|
38
40
|
* turning the cap off in silence while somebody alternates CLI versions over one
|
|
39
41
|
* run. Failing with a cause is the requirement; failing silently is the defect.
|
|
40
42
|
*/
|
|
41
|
-
export const FLOW_RUN_STATE_VERSION =
|
|
43
|
+
export const FLOW_RUN_STATE_VERSION = 13;
|
|
44
|
+
/**
|
|
45
|
+
* The versions this CLI CONTINUES without adoption, newest first.
|
|
46
|
+
*
|
|
47
|
+
* v12 added the `aligned`, `restarted` and `annulled` trace kinds and `journey_base`, whose truthful value
|
|
48
|
+
* for a v11 run is the journey 25.6.1 walked. So a v11 run keeps walking after
|
|
49
|
+
* an upgrade: {@link upgradeContinuable} re-stamps it without touching the
|
|
50
|
+
* cursor, the answers or the attempts. Every later version joins this set with its own
|
|
51
|
+
* upgrade step, which is what keeps "v11 or later survives an update" true
|
|
52
|
+
* without an exception per release.
|
|
53
|
+
*
|
|
54
|
+
* v13 added `reentries` and the `reentry_iteration` of attempts, observations and
|
|
55
|
+
* events. All three are absent on a run that never re-entered, which is every
|
|
56
|
+
* v12 run, so its upgrade step only re-stamps the version.
|
|
57
|
+
* It also added each batch's `base` and `credit`, a reseated run's `inherited_bases`
|
|
58
|
+
* and the `route-refused` trace kind. A v12 run has none of them either, and a
|
|
59
|
+
* batch without `base` is read as the one that was in flight when the CLI updated.
|
|
60
|
+
*/
|
|
61
|
+
export const FLOW_RUN_STATE_CONTINUABLE = [FLOW_RUN_STATE_VERSION, 12, 11];
|
|
42
62
|
/**
|
|
43
63
|
* The versions this CLI READS, newest first.
|
|
44
64
|
*
|
|
45
|
-
* Versions
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* An active legacy run must be adopted explicitly before any mutation.
|
|
65
|
+
* Versions 10 to 7 remain readable for status and recovery evidence only. They
|
|
66
|
+
* are NOT continuable execution state: continuing an old cursor by merely
|
|
67
|
+
* changing `version` would invent a batch boundary the old run never declared,
|
|
68
|
+
* so their way out is `aw flow restart`.
|
|
50
69
|
*/
|
|
51
|
-
export const FLOW_RUN_STATE_READABLE = [
|
|
70
|
+
export const FLOW_RUN_STATE_READABLE = [
|
|
71
|
+
...FLOW_RUN_STATE_CONTINUABLE,
|
|
72
|
+
10,
|
|
73
|
+
9,
|
|
74
|
+
8,
|
|
75
|
+
7,
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* One upgrade step per continuable version, keyed by the version it upgrades FROM.
|
|
79
|
+
*
|
|
80
|
+
* A step may only add what the next version introduced with a truthful default;
|
|
81
|
+
* the cursor, the answers, the approvals and the attempts pass through untouched.
|
|
82
|
+
*/
|
|
83
|
+
const CONTINUABLE_UPGRADES = {
|
|
84
|
+
11: (state) => ({ ...state, version: 12, journey_base: [...V11_JOURNEY_BASE[state.flow]] }),
|
|
85
|
+
12: (state) => ({ ...state, version: 13 }),
|
|
86
|
+
};
|
|
52
87
|
/** The CLI-owned run state inside the session folder. Machine-local, dotted. */
|
|
53
88
|
export const FLOW_RUN_STATE_FILE = ".flow-run.json";
|
|
54
89
|
/**
|
|
@@ -84,12 +119,47 @@ export function attemptsAt(state, transition) {
|
|
|
84
119
|
}
|
|
85
120
|
/** The monotone-counter key for one boundary, scoped when that boundary repeats in a batch. */
|
|
86
121
|
export function attemptCounterKey(state, transition) {
|
|
87
|
-
|
|
88
|
-
return attemptCounterKeyForIteration(transition, iteration ?? undefined);
|
|
122
|
+
return attemptCounterKeyForIteration(transition, iterationOf(state, transition));
|
|
89
123
|
}
|
|
90
|
-
/** Stable counter key for a historical attempt whose
|
|
124
|
+
/** Stable counter key for a historical attempt whose iteration is recorded on the row. */
|
|
91
125
|
export function attemptCounterKeyForIteration(transition, iteration) {
|
|
92
|
-
|
|
126
|
+
const batch = iteration.batch_iteration === undefined ? "" : `@batch-${iteration.batch_iteration}`;
|
|
127
|
+
const reentry = iteration.reentry_iteration === undefined ? "" : `@reentry-${iteration.reentry_iteration}`;
|
|
128
|
+
return `${transition}${batch}${reentry}`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The iteration of `transition` where the run stands: its batch copy and its
|
|
132
|
+
* reentry copy. Spread it into any record keyed per boundary.
|
|
133
|
+
*/
|
|
134
|
+
export function iterationOf(state, transition) {
|
|
135
|
+
const batch = currentBatchIteration(state, transition);
|
|
136
|
+
const reentry = currentReentryIteration(state, transition);
|
|
137
|
+
return {
|
|
138
|
+
...(batch === null ? {} : { batch_iteration: batch }),
|
|
139
|
+
...(reentry === null ? {} : { reentry_iteration: reentry }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Whether a persisted record belongs to this iteration of its boundary. */
|
|
143
|
+
export function sameIteration(record, iteration) {
|
|
144
|
+
return (record.batch_iteration === iteration.batch_iteration &&
|
|
145
|
+
record.reentry_iteration === iteration.reentry_iteration);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Which reentry inserted the row of `transition` the cursor is on, or `null` on
|
|
149
|
+
* the row's own place. Read at the cursor and backwards from it, so it answers
|
|
150
|
+
* the same before the row is applied and right after.
|
|
151
|
+
*/
|
|
152
|
+
export function currentReentryIteration(state, transition) {
|
|
153
|
+
if ((state.reentries ?? []).length === 0)
|
|
154
|
+
return null;
|
|
155
|
+
const { rows, copies } = expandJourney(state);
|
|
156
|
+
for (let index = Math.min(state.applied.length, rows.length - 1); index >= 0; index -= 1) {
|
|
157
|
+
if (rows[index]?.id !== transition)
|
|
158
|
+
continue;
|
|
159
|
+
const copy = copies[index] ?? 0;
|
|
160
|
+
return copy === 0 ? null : copy;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
93
163
|
}
|
|
94
164
|
/**
|
|
95
165
|
* The three numbers and the difference between them, computed once.
|
|
@@ -100,11 +170,8 @@ export function attemptCounterKeyForIteration(transition, iteration) {
|
|
|
100
170
|
* directive and the recovery guard would end up disagreeing about the same run.
|
|
101
171
|
*/
|
|
102
172
|
function spendAt(state, transition) {
|
|
103
|
-
const iteration =
|
|
104
|
-
const rows = state.attempts.filter((attempt) => attempt.transition === transition &&
|
|
105
|
-
(iteration === null
|
|
106
|
-
? attempt.batch_iteration === undefined
|
|
107
|
-
: attempt.batch_iteration === iteration));
|
|
173
|
+
const iteration = iterationOf(state, transition);
|
|
174
|
+
const rows = state.attempts.filter((attempt) => attempt.transition === transition && sameIteration(attempt, iteration));
|
|
108
175
|
const key = attemptCounterKey(state, transition);
|
|
109
176
|
const floor = state.attempt_floor?.[key] ?? 0;
|
|
110
177
|
const granted = state.attempt_grants?.[key] ?? 0;
|
|
@@ -289,11 +356,10 @@ export function applyAttemptReconciliation(state, reconciliation) {
|
|
|
289
356
|
grants[key] = repair.after;
|
|
290
357
|
next = sealRunState({ ...withoutSeal(next), attempt_grants: grants });
|
|
291
358
|
}
|
|
292
|
-
const iteration = currentBatchIteration(next, reconciliation.transition);
|
|
293
359
|
return withEvent(next, {
|
|
294
360
|
kind: "reconciled",
|
|
295
361
|
transition: reconciliation.transition,
|
|
296
|
-
...(
|
|
362
|
+
...iterationOf(next, reconciliation.transition),
|
|
297
363
|
operation: "flow.attempt-reconciliation",
|
|
298
364
|
repairs: reconciliation.repairs,
|
|
299
365
|
});
|
|
@@ -323,11 +389,8 @@ function unanswerableAt(state) {
|
|
|
323
389
|
return null;
|
|
324
390
|
}
|
|
325
391
|
export function recoveryBlockedAt(state, transition) {
|
|
326
|
-
const iteration =
|
|
327
|
-
const trace = state.events.filter((event) => event.transition === transition &&
|
|
328
|
-
(iteration === null
|
|
329
|
-
? event.batch_iteration === undefined
|
|
330
|
-
: event.batch_iteration === iteration));
|
|
392
|
+
const iteration = iterationOf(state, transition);
|
|
393
|
+
const trace = state.events.filter((event) => event.transition === transition && sameIteration(event, iteration));
|
|
331
394
|
const material = [...trace].reverse().find(declaresMaterialEffect);
|
|
332
395
|
if (material !== undefined)
|
|
333
396
|
return { reason: "materialized", event: material };
|
|
@@ -348,8 +411,13 @@ export function recoveryBlockedAt(state, transition) {
|
|
|
348
411
|
function declaresMaterialEffect(event) {
|
|
349
412
|
// A reconciliation moved the run's own counters and nothing else: it can no
|
|
350
413
|
// more block a recovery than reading the file could.
|
|
351
|
-
if (event.kind === "reconciled"
|
|
414
|
+
if (event.kind === "reconciled" ||
|
|
415
|
+
event.kind === "aligned" ||
|
|
416
|
+
event.kind === "route-refused" ||
|
|
417
|
+
event.kind === "restarted" ||
|
|
418
|
+
event.kind === "annulled") {
|
|
352
419
|
return false;
|
|
420
|
+
}
|
|
353
421
|
if (event.kind !== "failed")
|
|
354
422
|
return touchesTheWorld(event.effects);
|
|
355
423
|
if (event.effects === undefined)
|
|
@@ -405,6 +473,7 @@ export function newRunState(flow, session) {
|
|
|
405
473
|
flow,
|
|
406
474
|
session,
|
|
407
475
|
scope: null,
|
|
476
|
+
journey_base: journeyOfFlow(flow).map((decision) => decision.id),
|
|
408
477
|
applied: [],
|
|
409
478
|
skipped: [],
|
|
410
479
|
boundary: null,
|
|
@@ -525,32 +594,43 @@ export function grantAttempts(state, transition, attempts) {
|
|
|
525
594
|
granted[key] = (granted[key] ?? 0) + attempts;
|
|
526
595
|
return sealRunState({ ...withoutSeal(state), attempt_grants: granted });
|
|
527
596
|
}
|
|
597
|
+
/** Whether this exhausted boundary already spent the one re-run the CLI gives it. */
|
|
598
|
+
export function exhaustedRerunSpent(state, transition) {
|
|
599
|
+
return (state.exhausted_reruns ?? []).includes(attemptCounterKey(state, transition));
|
|
600
|
+
}
|
|
601
|
+
/** Record that the CLI's one re-run of this exhausted boundary came back refused. */
|
|
602
|
+
export function withExhaustedRerunSpent(state, transition) {
|
|
603
|
+
if (exhaustedRerunSpent(state, transition))
|
|
604
|
+
return state;
|
|
605
|
+
return sealRunState({
|
|
606
|
+
...withoutSeal(state),
|
|
607
|
+
exhausted_reruns: [...(state.exhausted_reruns ?? []), attemptCounterKey(state, transition)],
|
|
608
|
+
});
|
|
609
|
+
}
|
|
528
610
|
/**
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
* This function is intentionally NOT called by the reader. Calling it is the
|
|
532
|
-
* durable act of adoption after the caller has named the flow and accepted that
|
|
533
|
-
* no pre-v10 batch is being reconstructed. Empty batch arrays mean exactly
|
|
534
|
-
* "legacy history preserved; no v10 batch claimed", never "the old run had no
|
|
535
|
-
* batches".
|
|
611
|
+
* Forget a spent re-run: `aw flow recover` gives the boundary a fresh budget,
|
|
612
|
+
* and a later exhaustion is a new episode that is owed its own re-run.
|
|
536
613
|
*/
|
|
537
|
-
export function
|
|
538
|
-
if (state
|
|
614
|
+
export function withoutExhaustedRerun(state, transition) {
|
|
615
|
+
if (!exhaustedRerunSpent(state, transition))
|
|
539
616
|
return state;
|
|
617
|
+
const key = attemptCounterKey(state, transition);
|
|
540
618
|
return sealRunState({
|
|
541
619
|
...withoutSeal(state),
|
|
542
|
-
|
|
543
|
-
batches: [],
|
|
544
|
-
batch_trace: [],
|
|
545
|
-
batch_loop: { pending: true, iteration: 1 },
|
|
546
|
-
handoff: null,
|
|
547
|
-
selected_choice: null,
|
|
548
|
-
decision_preparation: null,
|
|
549
|
-
route_proposal: null,
|
|
550
|
-
route_decisions: null,
|
|
551
|
-
assurance: "verified",
|
|
620
|
+
exhausted_reruns: (state.exhausted_reruns ?? []).filter((spent) => spent !== key),
|
|
552
621
|
});
|
|
553
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* The one exit from a run the CLI cannot continue: archive it inside the session
|
|
625
|
+
* and adopt a new one. Named in every refusal whose state it resolves.
|
|
626
|
+
*/
|
|
627
|
+
export function restartInvocation(session) {
|
|
628
|
+
return `aw flow restart --session ${session}`;
|
|
629
|
+
}
|
|
630
|
+
/** The action of every refusal over a registry this CLI cannot use as it is. */
|
|
631
|
+
function unusableRemedy(session) {
|
|
632
|
+
return `corré '${restartInvocation(session)}': archiva el registro dentro de la sesión, re-adopta su flow y lo deja en la traza — o restaurá una copia íntegra del archivo`;
|
|
633
|
+
}
|
|
554
634
|
/** Keep the sealed preview the person is choosing over; no cursor moves here. */
|
|
555
635
|
export function withRouteProposal(state, proposal) {
|
|
556
636
|
return sealRunState({
|
|
@@ -568,9 +648,68 @@ export function withRouteDecisions(state, decisions) {
|
|
|
568
648
|
assurance: assuranceForRoute(decisions, state.applied),
|
|
569
649
|
});
|
|
570
650
|
}
|
|
571
|
-
/** Whether a parsed state
|
|
651
|
+
/** Whether a parsed state is outside the continuable set and cannot be mutated. */
|
|
572
652
|
export function legacyRunNeedsAdoption(state) {
|
|
573
|
-
return state.version
|
|
653
|
+
return !FLOW_RUN_STATE_CONTINUABLE.includes(state.version);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Walk a continuable state up to the version this CLI writes, one step at a time.
|
|
657
|
+
*
|
|
658
|
+
* Re-sealed once at the end: the file on disk was already verified against its
|
|
659
|
+
* own seal, and the upgraded state is persisted by the next write, never here.
|
|
660
|
+
* A legacy state comes back unchanged — reading one must never re-stamp it.
|
|
661
|
+
*/
|
|
662
|
+
export function upgradeContinuable(state) {
|
|
663
|
+
if (legacyRunNeedsAdoption(state) || state.version === FLOW_RUN_STATE_VERSION)
|
|
664
|
+
return state;
|
|
665
|
+
let current = state;
|
|
666
|
+
while (current.version !== FLOW_RUN_STATE_VERSION) {
|
|
667
|
+
const step = CONTINUABLE_UPGRADES[current.version];
|
|
668
|
+
if (step === undefined)
|
|
669
|
+
return state;
|
|
670
|
+
current = step(current);
|
|
671
|
+
}
|
|
672
|
+
return sealRunState(withoutSeal(current));
|
|
673
|
+
}
|
|
674
|
+
/** Record a reentry of the journey, after every one already recorded. */
|
|
675
|
+
export function withReentry(state, reentry) {
|
|
676
|
+
return sealRunState({
|
|
677
|
+
...withoutSeal(state),
|
|
678
|
+
reentries: [...(state.reentries ?? []), reentry],
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Take back the last reentry, for the close that recorded its intention and then
|
|
683
|
+
* could not happen. The field goes back to absent when it empties, so the run
|
|
684
|
+
* serializes the bytes it had before the intention.
|
|
685
|
+
*/
|
|
686
|
+
export function withoutLastReentry(state) {
|
|
687
|
+
const { reentries: _all, ...rest } = withoutSeal(state);
|
|
688
|
+
const kept = (state.reentries ?? []).slice(0, -1);
|
|
689
|
+
return sealRunState(kept.length === 0 ? rest : { ...rest, reentries: kept });
|
|
690
|
+
}
|
|
691
|
+
/** Seal the base of one batch; a batch that already has one keeps it. */
|
|
692
|
+
export function withPlanExecBatchBase(state, batchId, base) {
|
|
693
|
+
const batches = state.batches ?? [];
|
|
694
|
+
const target = batches.find((batch) => batch.id === batchId);
|
|
695
|
+
// A re-run of the acquisition on the same batch must not move its base: the
|
|
696
|
+
// batch would then be compared against its own half-done work.
|
|
697
|
+
if (target === undefined || target.base !== undefined)
|
|
698
|
+
return state;
|
|
699
|
+
return sealRunState({
|
|
700
|
+
...withoutSeal(state),
|
|
701
|
+
batches: batches.map((batch) => (batch.id === batchId ? { ...batch, base } : batch)),
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
/** Record the proofs that credited one batch. */
|
|
705
|
+
export function withPlanExecBatchCredit(state, batchId, credit) {
|
|
706
|
+
const batches = state.batches ?? [];
|
|
707
|
+
if (!batches.some((batch) => batch.id === batchId))
|
|
708
|
+
return state;
|
|
709
|
+
return sealRunState({
|
|
710
|
+
...withoutSeal(state),
|
|
711
|
+
batches: batches.map((batch) => (batch.id === batchId ? { ...batch, credit } : batch)),
|
|
712
|
+
});
|
|
574
713
|
}
|
|
575
714
|
/** Record one inferred or declared batch, refusing duplicate ids at the caller's boundary. */
|
|
576
715
|
export function withPlanExecBatch(state, batch) {
|
|
@@ -892,7 +1031,7 @@ export function restatesLastEvent(state, event) {
|
|
|
892
1031
|
if (last === undefined || last.kind !== "failed" || event.kind !== "failed")
|
|
893
1032
|
return false;
|
|
894
1033
|
return (last.transition === event.transition &&
|
|
895
|
-
last
|
|
1034
|
+
sameIteration(last, event) &&
|
|
896
1035
|
last.code === event.code &&
|
|
897
1036
|
last.message === event.message);
|
|
898
1037
|
}
|
|
@@ -913,7 +1052,7 @@ export function restatesLastEvent(state, event) {
|
|
|
913
1052
|
*/
|
|
914
1053
|
export function withObservation(state, observation) {
|
|
915
1054
|
const repeated = state.observations.some((past) => past.transition === observation.transition &&
|
|
916
|
-
past
|
|
1055
|
+
sameIteration(past, observation) &&
|
|
917
1056
|
sameSignals(past.signals, observation.signals));
|
|
918
1057
|
if (repeated)
|
|
919
1058
|
return state;
|
|
@@ -1021,20 +1160,26 @@ function union(current, added) {
|
|
|
1021
1160
|
*
|
|
1022
1161
|
* Order matters: shape before version before seal. A file whose version is
|
|
1023
1162
|
* unknown must not be judged by a digest rule that version may not even use.
|
|
1163
|
+
*
|
|
1164
|
+
* Every refusal names the command that works IN that state: `--adopt` only adopts
|
|
1165
|
+
* a session with no registry at all, so an unreadable, tampered or too-old one
|
|
1166
|
+
* leaves through `aw flow restart`, and one written by a newer CLI through
|
|
1167
|
+
* updating this one. `session` names the run in those commands.
|
|
1024
1168
|
*/
|
|
1025
|
-
export function parseRunState(raw) {
|
|
1169
|
+
export function parseRunState(raw, session = "<código>") {
|
|
1170
|
+
const restart = unusableRemedy(session);
|
|
1026
1171
|
if (raw.trim().length === 0) {
|
|
1027
|
-
return refuse("FLOW_RUN_INVALID", "el estado de corrida está vacío",
|
|
1172
|
+
return refuse("FLOW_RUN_INVALID", "el estado de corrida está vacío", restart);
|
|
1028
1173
|
}
|
|
1029
1174
|
let parsed;
|
|
1030
1175
|
try {
|
|
1031
1176
|
parsed = JSON.parse(raw);
|
|
1032
1177
|
}
|
|
1033
1178
|
catch {
|
|
1034
|
-
return refuse("FLOW_RUN_INVALID", "el estado de corrida no es JSON válido",
|
|
1179
|
+
return refuse("FLOW_RUN_INVALID", "el estado de corrida no es JSON válido", restart);
|
|
1035
1180
|
}
|
|
1036
1181
|
if (!isRecord(parsed)) {
|
|
1037
|
-
return refuse("FLOW_RUN_INVALID", "el estado de corrida no es un objeto JSON",
|
|
1182
|
+
return refuse("FLOW_RUN_INVALID", "el estado de corrida no es un objeto JSON", restart);
|
|
1038
1183
|
}
|
|
1039
1184
|
if (!FLOW_RUN_STATE_READABLE.includes(parsed.version)) {
|
|
1040
1185
|
// No silent migration, in either direction: a version outside the readable
|
|
@@ -1042,14 +1187,17 @@ export function parseRunState(raw) {
|
|
|
1042
1187
|
// fabricate the very history this file exists to make trustworthy — or comes
|
|
1043
1188
|
// from a build ahead of this one, whose fields this build would ignore.
|
|
1044
1189
|
// Refusing with the cause is what keeps the second case from being silent.
|
|
1045
|
-
|
|
1190
|
+
const newer = typeof parsed.version === "number" && parsed.version > FLOW_RUN_STATE_VERSION;
|
|
1191
|
+
return refuse("FLOW_RUN_VERSION_UNSUPPORTED", `versión de estado de corrida no soportada: ${String(parsed.version)} (este CLI lee de la ${FLOW_RUN_STATE_READABLE.at(-1)} a la ${FLOW_RUN_STATE_VERSION})`, newer
|
|
1192
|
+
? `la escribió un CLI más nuevo que este: actualizalo con 'aw self update' y seguí la corrida — reiniciarla descartaría lo que esa versión sí continúa`
|
|
1193
|
+
: restart);
|
|
1046
1194
|
}
|
|
1047
|
-
const shape = checkShape(parsed);
|
|
1195
|
+
const shape = checkShape(parsed, restart);
|
|
1048
1196
|
if (shape !== null)
|
|
1049
1197
|
return { ok: false, failure: shape };
|
|
1050
1198
|
const state = parsed;
|
|
1051
1199
|
if (semanticDigest(withoutSeal(state)) !== state.digest) {
|
|
1052
|
-
return refuse("FLOW_RUN_TAMPERED", "el estado de corrida no coincide con su propio sello
|
|
1200
|
+
return refuse("FLOW_RUN_TAMPERED", "el estado de corrida no coincide con su propio sello: fue editado fuera del CLI", restart);
|
|
1053
1201
|
}
|
|
1054
1202
|
return { ok: true, state };
|
|
1055
1203
|
}
|
|
@@ -1069,10 +1217,15 @@ export function checkAgainstJourney(state, journey) {
|
|
|
1069
1217
|
for (const [index, applied] of state.applied.entries()) {
|
|
1070
1218
|
if (ids[index] === applied)
|
|
1071
1219
|
continue;
|
|
1220
|
+
// The cause decides the repair, so it is named: a row this build no longer
|
|
1221
|
+
// has was renamed or retired, one it has elsewhere was reordered.
|
|
1222
|
+
const cause = ids.includes(applied)
|
|
1223
|
+
? "el recorrido instalado la tiene en otra posición"
|
|
1224
|
+
: "el recorrido instalado ya no tiene esa frontera";
|
|
1072
1225
|
return {
|
|
1073
1226
|
code: "FLOW_RUN_AHEAD_OF_JOURNEY",
|
|
1074
|
-
message: `el estado dice haber aplicado '${applied}' donde el recorrido tiene '${ids[index] ?? "(nada)"}'`,
|
|
1075
|
-
action:
|
|
1227
|
+
message: `el estado dice haber aplicado '${applied}' donde el recorrido tiene '${ids[index] ?? "(nada)"}': ${cause}`,
|
|
1228
|
+
action: unusableRemedy(state.session),
|
|
1076
1229
|
};
|
|
1077
1230
|
}
|
|
1078
1231
|
const next = ids[state.applied.length] ?? null;
|
|
@@ -1080,16 +1233,111 @@ export function checkAgainstJourney(state, journey) {
|
|
|
1080
1233
|
return {
|
|
1081
1234
|
code: "FLOW_RUN_AHEAD_OF_JOURNEY",
|
|
1082
1235
|
message: `la frontera vigente dice '${state.boundary}' y el recorrido sigue en '${next ?? "(nada)"}'`,
|
|
1083
|
-
action:
|
|
1236
|
+
action: unusableRemedy(state.session),
|
|
1084
1237
|
};
|
|
1085
1238
|
}
|
|
1086
1239
|
return null;
|
|
1087
1240
|
}
|
|
1088
|
-
|
|
1241
|
+
/** The trace operation that names an alignment, so status and tests read one spelling. */
|
|
1242
|
+
export const JOURNEY_ALIGNMENT_OPERATION = "flow.journey-alignment";
|
|
1243
|
+
const ALIGNMENT_REASON = "la versión instalada agregó esta frontera en un tramo que la corrida ya había recorrido: entra omitida, sin pedirse";
|
|
1244
|
+
/**
|
|
1245
|
+
* Align a continuable run's cursor with the journey the installed build walks.
|
|
1246
|
+
*
|
|
1247
|
+
* The only difference this repairs is a boundary the installed build ADDED to
|
|
1248
|
+
* the base journey the run recorded (`journey_base`) and that sits before the
|
|
1249
|
+
* run's position. "Before the position" includes the rows ahead of the boundary
|
|
1250
|
+
* the run is standing on: that boundary is the one it was answering, and a
|
|
1251
|
+
* sealed or attempted action there must not be thrown away for a row nobody
|
|
1252
|
+
* asked it about. A row added AFTER the boundary is left alone and asked when
|
|
1253
|
+
* the run gets there.
|
|
1254
|
+
*
|
|
1255
|
+
* Every other difference comes back untouched, for {@link checkAgainstJourney}
|
|
1256
|
+
* to refuse with its cause: a row the recorded base already had and the cursor
|
|
1257
|
+
* lacks is a hole, not an addition; a reorder or a retirement moves an id that
|
|
1258
|
+
* keys attempts, counters, trace and approvals.
|
|
1259
|
+
*
|
|
1260
|
+
* `base` is the installed REGISTRY journey; `journey` is what the run walks
|
|
1261
|
+
* (plan-exec's expanded copies included). Pure: the aligned state is persisted
|
|
1262
|
+
* by the run's next write, never by a read.
|
|
1263
|
+
*/
|
|
1264
|
+
export function alignToJourney(state, journey, base) {
|
|
1265
|
+
const recorded = state.journey_base;
|
|
1266
|
+
if (recorded === undefined || sameIds(recorded, base))
|
|
1267
|
+
return state;
|
|
1268
|
+
const addedIds = new Set(base.filter((id) => !recorded.includes(id)));
|
|
1269
|
+
const ids = journey.map((decision) => decision.id);
|
|
1270
|
+
const applied = [];
|
|
1271
|
+
const added = [];
|
|
1272
|
+
let cursor = 0;
|
|
1273
|
+
// Everything from `cursor` up to the next occurrence of `target` must be rows
|
|
1274
|
+
// the installed build added; only then are they inserted, in journey order.
|
|
1275
|
+
const insertUntil = (target) => {
|
|
1276
|
+
let next = cursor;
|
|
1277
|
+
while (next < ids.length && ids[next] !== target) {
|
|
1278
|
+
if (!addedIds.has(ids[next]))
|
|
1279
|
+
return false;
|
|
1280
|
+
next += 1;
|
|
1281
|
+
}
|
|
1282
|
+
if (next >= ids.length)
|
|
1283
|
+
return false;
|
|
1284
|
+
for (; cursor < next; cursor += 1) {
|
|
1285
|
+
added.push({ transition: ids[cursor], position: applied.length });
|
|
1286
|
+
applied.push(ids[cursor]);
|
|
1287
|
+
}
|
|
1288
|
+
return true;
|
|
1289
|
+
};
|
|
1290
|
+
for (const id of state.applied) {
|
|
1291
|
+
if (ids[cursor] !== id && !insertUntil(id))
|
|
1292
|
+
return state;
|
|
1293
|
+
applied.push(id);
|
|
1294
|
+
cursor += 1;
|
|
1295
|
+
}
|
|
1296
|
+
if (state.boundary !== null && ids[cursor] !== state.boundary && !insertUntil(state.boundary)) {
|
|
1297
|
+
return state;
|
|
1298
|
+
}
|
|
1299
|
+
const events = added.map((row) => {
|
|
1300
|
+
const iteration = segmentIterationAt(state.flow, applied, row.position);
|
|
1301
|
+
return {
|
|
1302
|
+
kind: "aligned",
|
|
1303
|
+
transition: row.transition,
|
|
1304
|
+
...(iteration === null ? {} : { batch_iteration: iteration }),
|
|
1305
|
+
operation: JOURNEY_ALIGNMENT_OPERATION,
|
|
1306
|
+
position: row.position,
|
|
1307
|
+
reason: ALIGNMENT_REASON,
|
|
1308
|
+
};
|
|
1309
|
+
});
|
|
1310
|
+
return sealRunState({
|
|
1311
|
+
...withoutSeal(state),
|
|
1312
|
+
journey_base: [...base],
|
|
1313
|
+
applied,
|
|
1314
|
+
skipped: [...state.skipped, ...added.map((row) => row.transition)],
|
|
1315
|
+
events: [...state.events, ...events],
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
function sameIds(left, right) {
|
|
1319
|
+
return left.length === right.length && left.every((id, index) => id === right[index]);
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Which plan-exec batch copy a cursor position falls inside, or null outside one.
|
|
1323
|
+
*
|
|
1324
|
+
* Two copies of the same added row differ only by the batch they belong to, so
|
|
1325
|
+
* the trace names it the way every other per-batch event does.
|
|
1326
|
+
*/
|
|
1327
|
+
function segmentIterationAt(flow, applied, position) {
|
|
1328
|
+
if (flow !== "plan-exec")
|
|
1329
|
+
return null;
|
|
1330
|
+
const before = applied.slice(0, position);
|
|
1331
|
+
const opened = before.lastIndexOf("plan-exec.batch-eligibility-signal");
|
|
1332
|
+
if (opened < 0 || before.lastIndexOf("plan-exec.batch-close") > opened)
|
|
1333
|
+
return null;
|
|
1334
|
+
return before.filter((id) => id === "plan-exec.batch-eligibility-signal").length;
|
|
1335
|
+
}
|
|
1336
|
+
function checkShape(parsed, remedy) {
|
|
1089
1337
|
const invalid = (why) => ({
|
|
1090
1338
|
code: "FLOW_RUN_INVALID",
|
|
1091
1339
|
message: `el estado de corrida ${why}`,
|
|
1092
|
-
action:
|
|
1340
|
+
action: remedy,
|
|
1093
1341
|
});
|
|
1094
1342
|
if (!WORKLINE_FLOWS.includes(parsed.flow)) {
|
|
1095
1343
|
return invalid(`declara un flow desconocido: ${String(parsed.flow)}`);
|
|
@@ -1125,16 +1373,26 @@ function checkRecordShape(parsed, invalid) {
|
|
|
1125
1373
|
const common = checkCommonRecordShape(parsed, invalid);
|
|
1126
1374
|
if (common !== null)
|
|
1127
1375
|
return common;
|
|
1128
|
-
|
|
1376
|
+
const version = parsed.version;
|
|
1377
|
+
if (version >= 10) {
|
|
1129
1378
|
const v10 = checkV10RecordShape(parsed, invalid);
|
|
1130
1379
|
if (v10 !== null)
|
|
1131
1380
|
return v10;
|
|
1132
1381
|
}
|
|
1133
|
-
if (
|
|
1382
|
+
if (version >= 11) {
|
|
1134
1383
|
const v11 = checkV11RecordShape(parsed, invalid);
|
|
1135
1384
|
if (v11 !== null)
|
|
1136
1385
|
return v11;
|
|
1137
1386
|
}
|
|
1387
|
+
if (version >= 12 && !isStringArray(parsed.journey_base)) {
|
|
1388
|
+
return invalid("no dice contra qué recorrido se escribió su cursor");
|
|
1389
|
+
}
|
|
1390
|
+
if (!isReentryArray(parsed.reentries)) {
|
|
1391
|
+
return invalid("declara una reentrada sin tipo, frontera, ocurrencia u origen");
|
|
1392
|
+
}
|
|
1393
|
+
if (parsed.exhausted_reruns !== undefined && !isStringArray(parsed.exhausted_reruns)) {
|
|
1394
|
+
return invalid("declara vueltas de fronteras agotadas que no son transiciones");
|
|
1395
|
+
}
|
|
1138
1396
|
if (typeof parsed.digest !== "string")
|
|
1139
1397
|
return invalid("no trae su sello");
|
|
1140
1398
|
return null;
|
|
@@ -1172,8 +1430,55 @@ function checkCommonRecordShape(parsed, invalid) {
|
|
|
1172
1430
|
if (!isCounterMap(parsed.attempt_floor) || !isCounterMap(parsed.attempt_grants)) {
|
|
1173
1431
|
return invalid("trae una contabilidad de intentos que no es un contador por transición");
|
|
1174
1432
|
}
|
|
1433
|
+
if (!isInheritedBaseArray(parsed.inherited_bases)) {
|
|
1434
|
+
return invalid("hereda una base de batch sin sus fases o sin sus fuentes");
|
|
1435
|
+
}
|
|
1175
1436
|
return null;
|
|
1176
1437
|
}
|
|
1438
|
+
function isInheritedBaseArray(value) {
|
|
1439
|
+
if (value === undefined)
|
|
1440
|
+
return true;
|
|
1441
|
+
return (Array.isArray(value) &&
|
|
1442
|
+
value.every((entry) => isRecord(entry) &&
|
|
1443
|
+
Array.isArray(entry.phases) &&
|
|
1444
|
+
entry.phases.length > 0 &&
|
|
1445
|
+
entry.phases.every((phase) => Number.isInteger(phase) && phase > 0) &&
|
|
1446
|
+
isSourceMap(entry.base, true) &&
|
|
1447
|
+
entry.base !== undefined));
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* The bases a reseated run inherits from the archived registry, read tolerantly:
|
|
1451
|
+
* the archive may be the very state that could not be continued, so anything
|
|
1452
|
+
* that is not a well-formed batch base is left behind rather than trusted.
|
|
1453
|
+
*/
|
|
1454
|
+
export function inheritedBasesFrom(archived) {
|
|
1455
|
+
if (archived === null)
|
|
1456
|
+
return [];
|
|
1457
|
+
let parsed;
|
|
1458
|
+
try {
|
|
1459
|
+
parsed = JSON.parse(archived);
|
|
1460
|
+
}
|
|
1461
|
+
catch {
|
|
1462
|
+
return [];
|
|
1463
|
+
}
|
|
1464
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.batches))
|
|
1465
|
+
return [];
|
|
1466
|
+
const inherited = parsed.batches.flatMap((batch) => {
|
|
1467
|
+
if (!isRecord(batch) || batch.base === undefined)
|
|
1468
|
+
return [];
|
|
1469
|
+
const candidate = { phases: batch.phases, base: batch.base };
|
|
1470
|
+
return isInheritedBaseArray([candidate]) ? [candidate] : [];
|
|
1471
|
+
});
|
|
1472
|
+
const earlier = isInheritedBaseArray(parsed.inherited_bases)
|
|
1473
|
+
? (parsed.inherited_bases ?? [])
|
|
1474
|
+
: [];
|
|
1475
|
+
return [...earlier, ...inherited];
|
|
1476
|
+
}
|
|
1477
|
+
export function withInheritedBases(state, inherited) {
|
|
1478
|
+
if (inherited.length === 0)
|
|
1479
|
+
return state;
|
|
1480
|
+
return sealRunState({ ...withoutSeal(state), inherited_bases: [...inherited] });
|
|
1481
|
+
}
|
|
1177
1482
|
/** Fields added by v10 that have no truthful default on a persisted run. */
|
|
1178
1483
|
function checkV10RecordShape(parsed, invalid) {
|
|
1179
1484
|
if (!isPlanExecBatchArray(parsed.batches)) {
|
|
@@ -1262,7 +1567,7 @@ function isRouteProposal(value) {
|
|
|
1262
1567
|
!isRouteDisposition(control.recommendation) ||
|
|
1263
1568
|
!isRecord(control.alternatives) ||
|
|
1264
1569
|
!isNonEmptyString(control.alternatives.apply) ||
|
|
1265
|
-
!isNonEmptyString(control.alternatives.omit) ||
|
|
1570
|
+
(control.alternatives.omit !== undefined && !isNonEmptyString(control.alternatives.omit)) ||
|
|
1266
1571
|
!isNonEmptyString(control.alternatives.substitute) ||
|
|
1267
1572
|
!isNonEmptyString(control.consequence) ||
|
|
1268
1573
|
!isNonEmptyString(control.risk) ||
|
|
@@ -1364,7 +1669,9 @@ function isPlanExecBatchArray(value) {
|
|
|
1364
1669
|
(entry.publication !== undefined &&
|
|
1365
1670
|
entry.publication.status === "applied" &&
|
|
1366
1671
|
entry.published_plan_digest !== entry.publication.after_plan_digest) ||
|
|
1367
|
-
!PLAN_EXEC_BATCH_STAGES.includes(entry.stage)
|
|
1672
|
+
!PLAN_EXEC_BATCH_STAGES.includes(entry.stage) ||
|
|
1673
|
+
!isSourceMap(entry.base, true) ||
|
|
1674
|
+
!isSourceMap(entry.credit, false)) {
|
|
1368
1675
|
return false;
|
|
1369
1676
|
}
|
|
1370
1677
|
ids.add(entry.id);
|
|
@@ -1372,6 +1679,14 @@ function isPlanExecBatchArray(value) {
|
|
|
1372
1679
|
return true;
|
|
1373
1680
|
});
|
|
1374
1681
|
}
|
|
1682
|
+
/** Absent, or a non-empty alias → digest map; `nullable` admits an unobserved source. */
|
|
1683
|
+
function isSourceMap(value, nullable) {
|
|
1684
|
+
if (value === undefined)
|
|
1685
|
+
return true;
|
|
1686
|
+
if (!isRecord(value) || Object.keys(value).length === 0)
|
|
1687
|
+
return false;
|
|
1688
|
+
return Object.values(value).every((digest) => (nullable && digest === null) || (typeof digest === "string" && digest.length > 0));
|
|
1689
|
+
}
|
|
1375
1690
|
function isPlanExecBatchPublication(value) {
|
|
1376
1691
|
if (value === undefined)
|
|
1377
1692
|
return true;
|
|
@@ -1599,7 +1914,8 @@ function isDecisionPreview(value, baseline) {
|
|
|
1599
1914
|
return value.effects.entries.every((entry) => isRecord(entry) &&
|
|
1600
1915
|
typeof entry.path === "string" &&
|
|
1601
1916
|
Number.isInteger(entry.bytes) &&
|
|
1602
|
-
typeof entry.overwrite === "boolean"
|
|
1917
|
+
typeof entry.overwrite === "boolean" &&
|
|
1918
|
+
isReservedMark(entry.reserved));
|
|
1603
1919
|
}
|
|
1604
1920
|
/**
|
|
1605
1921
|
* The persisted preview's obligations, in EITHER form.
|
|
@@ -1634,7 +1950,12 @@ function isProposalArtifacts(value) {
|
|
|
1634
1950
|
return value.every((entry) => isRecord(entry) &&
|
|
1635
1951
|
typeof entry.path === "string" &&
|
|
1636
1952
|
typeof entry.content === "string" &&
|
|
1637
|
-
typeof entry.overwrite === "boolean"
|
|
1953
|
+
typeof entry.overwrite === "boolean" &&
|
|
1954
|
+
isReservedMark(entry.reserved));
|
|
1955
|
+
}
|
|
1956
|
+
/** The optional reservation mark: absent, or exactly `true`. */
|
|
1957
|
+
function isReservedMark(value) {
|
|
1958
|
+
return value === undefined || value === true;
|
|
1638
1959
|
}
|
|
1639
1960
|
function isProposalBases(value) {
|
|
1640
1961
|
if (!Array.isArray(value))
|
|
@@ -1678,28 +1999,38 @@ function isEvent(entry) {
|
|
|
1678
1999
|
return false;
|
|
1679
2000
|
if (typeof entry.transition !== "string" || typeof entry.operation !== "string")
|
|
1680
2001
|
return false;
|
|
1681
|
-
if (entry
|
|
1682
|
-
(!Number.isInteger(entry.batch_iteration) || entry.batch_iteration < 1)) {
|
|
2002
|
+
if (!isIterationRecord(entry))
|
|
1683
2003
|
return false;
|
|
1684
|
-
|
|
1685
|
-
if (entry.kind === "executed") {
|
|
1686
|
-
return (typeof entry.summary === "string" &&
|
|
1687
|
-
typeof entry.output_digest === "string" &&
|
|
1688
|
-
isEffectClassArray(entry.effects) &&
|
|
1689
|
-
isStringArray(entry.evidence));
|
|
1690
|
-
}
|
|
1691
|
-
if (entry.kind === "reconciled")
|
|
1692
|
-
return isRepairArray(entry.repairs);
|
|
1693
|
-
if (entry.kind === "failed") {
|
|
1694
|
-
return (typeof entry.code === "string" &&
|
|
1695
|
-
typeof entry.message === "string" &&
|
|
1696
|
-
typeof entry.recovery === "string" &&
|
|
1697
|
-
// ABSENT-or-well-formed, never defaulted: a version 7 trace has no such
|
|
1698
|
-
// field, and the guard that reads it treats its absence as the refusal.
|
|
1699
|
-
(entry.effects === undefined || isEffectClassArray(entry.effects)));
|
|
1700
|
-
}
|
|
1701
|
-
return false;
|
|
2004
|
+
return EVENT_BODIES.get(entry.kind)?.(entry) === true;
|
|
1702
2005
|
}
|
|
2006
|
+
const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2007
|
+
/**
|
|
2008
|
+
* What each trace kind demands beyond the common fields; an unknown kind has no
|
|
2009
|
+
* entry. A Map, not an object: a kind spelled like an Object.prototype member
|
|
2010
|
+
* must not find a function nobody declared.
|
|
2011
|
+
*/
|
|
2012
|
+
const EVENT_BODIES = new Map(Object.entries({
|
|
2013
|
+
executed: (entry) => typeof entry.summary === "string" &&
|
|
2014
|
+
typeof entry.output_digest === "string" &&
|
|
2015
|
+
isEffectClassArray(entry.effects) &&
|
|
2016
|
+
isStringArray(entry.evidence),
|
|
2017
|
+
reconciled: (entry) => isRepairArray(entry.repairs),
|
|
2018
|
+
restarted: (entry) => nonEmpty(entry.archive) && nonEmpty(entry.cause),
|
|
2019
|
+
annulled: (entry) => isStringArray(entry.batches) &&
|
|
2020
|
+
entry.batches.length > 0 &&
|
|
2021
|
+
Array.isArray(entry.phases) &&
|
|
2022
|
+
entry.phases.every((phase) => Number.isInteger(phase) && phase > 0) &&
|
|
2023
|
+
isStringArray(entry.tasks) &&
|
|
2024
|
+
nonEmpty(entry.digest),
|
|
2025
|
+
aligned: (entry) => Number.isInteger(entry.position) && entry.position >= 0 && nonEmpty(entry.reason),
|
|
2026
|
+
"route-refused": (entry) => isRouteDisposition(entry.disposition) && nonEmpty(entry.reason),
|
|
2027
|
+
failed: (entry) => typeof entry.code === "string" &&
|
|
2028
|
+
typeof entry.message === "string" &&
|
|
2029
|
+
typeof entry.recovery === "string" &&
|
|
2030
|
+
// ABSENT-or-well-formed, never defaulted: a version 7 trace has no such
|
|
2031
|
+
// field, and the guard that reads it treats its absence as the refusal.
|
|
2032
|
+
(entry.effects === undefined || isEffectClassArray(entry.effects)),
|
|
2033
|
+
}));
|
|
1703
2034
|
/** A repair is well-formed when its rule is one of the three and its values are numbers. */
|
|
1704
2035
|
function isRepairArray(value) {
|
|
1705
2036
|
if (!Array.isArray(value))
|
|
@@ -1718,8 +2049,29 @@ function isObservationArray(value) {
|
|
|
1718
2049
|
return value.every((entry) => isRecord(entry) &&
|
|
1719
2050
|
typeof entry.transition === "string" &&
|
|
1720
2051
|
isStringArray(entry.signals) &&
|
|
1721
|
-
(entry
|
|
1722
|
-
|
|
2052
|
+
isIterationRecord(entry));
|
|
2053
|
+
}
|
|
2054
|
+
const REENTRY_KINDS = ["refine", "close", "reopen"];
|
|
2055
|
+
/** Absent, or reentries each with its kind, its boundary, its occurrence and its origin. */
|
|
2056
|
+
function isReentryArray(value) {
|
|
2057
|
+
if (value === undefined)
|
|
2058
|
+
return true;
|
|
2059
|
+
if (!Array.isArray(value))
|
|
2060
|
+
return false;
|
|
2061
|
+
return value.every((entry) => isRecord(entry) &&
|
|
2062
|
+
REENTRY_KINDS.includes(entry.kind) &&
|
|
2063
|
+
nonEmpty(entry.transition) &&
|
|
2064
|
+
Number.isInteger(entry.occurrence) &&
|
|
2065
|
+
entry.occurrence >= 1 &&
|
|
2066
|
+
// A redraft names where it re-enters; the other kinds repeat from their row.
|
|
2067
|
+
(entry.kind === "refine"
|
|
2068
|
+
? nonEmpty(entry.from)
|
|
2069
|
+
: entry.from === null || nonEmpty(entry.from)));
|
|
2070
|
+
}
|
|
2071
|
+
/** Both iteration fields, each absent or a positive integer. */
|
|
2072
|
+
function isIterationRecord(entry) {
|
|
2073
|
+
const ordinal = (value) => value === undefined || (Number.isInteger(value) && value >= 1);
|
|
2074
|
+
return ordinal(entry.batch_iteration) && ordinal(entry.reentry_iteration);
|
|
1723
2075
|
}
|
|
1724
2076
|
/**
|
|
1725
2077
|
* The two optional fields are checked as ABSENT-or-well-formed, never defaulted.
|
|
@@ -1755,8 +2107,7 @@ function isAttemptArray(value) {
|
|
|
1755
2107
|
Number.isInteger(entry.attempt) &&
|
|
1756
2108
|
typeof entry.request_digest === "string" &&
|
|
1757
2109
|
typeof entry.transition === "string" &&
|
|
1758
|
-
(entry
|
|
1759
|
-
(Number.isInteger(entry.batch_iteration) && entry.batch_iteration >= 1)) &&
|
|
2110
|
+
isIterationRecord(entry) &&
|
|
1760
2111
|
(entry.parent_request_digest === null || typeof entry.parent_request_digest === "string"));
|
|
1761
2112
|
}
|
|
1762
2113
|
//# sourceMappingURL=run-state.js.map
|