@tacuchi/agent-workflow-cli 25.7.0 → 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.
Files changed (189) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/adapters/git-cli.js +86 -7
  3. package/dist/adapters/git-cli.js.map +1 -1
  4. package/dist/application/capability/readiness.js +26 -10
  5. package/dist/application/capability/readiness.js.map +1 -1
  6. package/dist/application/capability/wrapper.js +10 -3
  7. package/dist/application/capability/wrapper.js.map +1 -1
  8. package/dist/application/dev-only-services.js +5 -7
  9. package/dist/application/dev-only-services.js.map +1 -1
  10. package/dist/application/doctor/provider-mcps.js +1 -1
  11. package/dist/application/doctor/provider-mcps.js.map +1 -1
  12. package/dist/application/flow/advance.js +41 -7
  13. package/dist/application/flow/advance.js.map +1 -1
  14. package/dist/application/flow/checkout-observation.js +49 -0
  15. package/dist/application/flow/checkout-observation.js.map +1 -1
  16. package/dist/application/flow/close-at-boundary.js +144 -0
  17. package/dist/application/flow/close-at-boundary.js.map +1 -0
  18. package/dist/application/flow/flow-service.js +4 -3
  19. package/dist/application/flow/flow-service.js.map +1 -1
  20. package/dist/application/flow/internal-actions.js +64 -1
  21. package/dist/application/flow/internal-actions.js.map +1 -1
  22. package/dist/application/flow/internal-drive.js +23 -18
  23. package/dist/application/flow/internal-drive.js.map +1 -1
  24. package/dist/application/flow/reopen-run.js +152 -0
  25. package/dist/application/flow/reopen-run.js.map +1 -0
  26. package/dist/application/flow/run-journey.js +11 -8
  27. package/dist/application/flow/run-journey.js.map +1 -1
  28. package/dist/application/flow/run-projection.js +16 -5
  29. package/dist/application/flow/run-projection.js.map +1 -1
  30. package/dist/application/flow/run-state-service.js +1 -1
  31. package/dist/application/flow/run-state-service.js.map +1 -1
  32. package/dist/application/flow/submit.js +158 -48
  33. package/dist/application/flow/submit.js.map +1 -1
  34. package/dist/application/hook-sql-mutation-guard.js +147 -12
  35. package/dist/application/hook-sql-mutation-guard.js.map +1 -1
  36. package/dist/application/self/install-skill.js +35 -11
  37. package/dist/application/self/install-skill.js.map +1 -1
  38. package/dist/application/self/install-targets.js +73 -2
  39. package/dist/application/self/install-targets.js.map +1 -1
  40. package/dist/application/self/uninstall.js +21 -7
  41. package/dist/application/self/uninstall.js.map +1 -1
  42. package/dist/application/session-narrative.js +9 -0
  43. package/dist/application/session-narrative.js.map +1 -1
  44. package/dist/application/session-resume-service.js +65 -16
  45. package/dist/application/session-resume-service.js.map +1 -1
  46. package/dist/cli/ascii.js +84 -0
  47. package/dist/cli/ascii.js.map +1 -0
  48. package/dist/cli/commands/amend.js +1 -0
  49. package/dist/cli/commands/amend.js.map +1 -1
  50. package/dist/cli/commands/capability.js +15 -0
  51. package/dist/cli/commands/capability.js.map +1 -1
  52. package/dist/cli/commands/check-branch.js +1 -0
  53. package/dist/cli/commands/check-branch.js.map +1 -1
  54. package/dist/cli/commands/checkpoint-read.js +1 -0
  55. package/dist/cli/commands/checkpoint-read.js.map +1 -1
  56. package/dist/cli/commands/checkpoint-write.js +4 -1
  57. package/dist/cli/commands/checkpoint-write.js.map +1 -1
  58. package/dist/cli/commands/claims.js +1 -0
  59. package/dist/cli/commands/claims.js.map +1 -1
  60. package/dist/cli/commands/code-scan.js +1 -0
  61. package/dist/cli/commands/code-scan.js.map +1 -1
  62. package/dist/cli/commands/context-budget.js +1 -0
  63. package/dist/cli/commands/context-budget.js.map +1 -1
  64. package/dist/cli/commands/context-plan.js +1 -0
  65. package/dist/cli/commands/context-plan.js.map +1 -1
  66. package/dist/cli/commands/cut-intent.js +1 -0
  67. package/dist/cli/commands/cut-intent.js.map +1 -1
  68. package/dist/cli/commands/designs.js +1 -0
  69. package/dist/cli/commands/designs.js.map +1 -1
  70. package/dist/cli/commands/dev-only.js +4 -0
  71. package/dist/cli/commands/dev-only.js.map +1 -1
  72. package/dist/cli/commands/doctor.js +5 -0
  73. package/dist/cli/commands/doctor.js.map +1 -1
  74. package/dist/cli/commands/export.js +13 -0
  75. package/dist/cli/commands/export.js.map +1 -1
  76. package/dist/cli/commands/fix-git.js +1 -0
  77. package/dist/cli/commands/fix-git.js.map +1 -1
  78. package/dist/cli/commands/flow.js +11 -0
  79. package/dist/cli/commands/flow.js.map +1 -1
  80. package/dist/cli/commands/generate-launch.js +1 -0
  81. package/dist/cli/commands/generate-launch.js.map +1 -1
  82. package/dist/cli/commands/git-flow.js +1 -0
  83. package/dist/cli/commands/git-flow.js.map +1 -1
  84. package/dist/cli/commands/history-update.js +7 -9
  85. package/dist/cli/commands/history-update.js.map +1 -1
  86. package/dist/cli/commands/hook.js +11 -3
  87. package/dist/cli/commands/hook.js.map +1 -1
  88. package/dist/cli/commands/host-doctor.js +1 -0
  89. package/dist/cli/commands/host-doctor.js.map +1 -1
  90. package/dist/cli/commands/host-memory.js +2 -9
  91. package/dist/cli/commands/host-memory.js.map +1 -1
  92. package/dist/cli/commands/mcp.js +30 -4
  93. package/dist/cli/commands/mcp.js.map +1 -1
  94. package/dist/cli/commands/merge-state.js +1 -0
  95. package/dist/cli/commands/merge-state.js.map +1 -1
  96. package/dist/cli/commands/multiroot.js +16 -0
  97. package/dist/cli/commands/multiroot.js.map +1 -1
  98. package/dist/cli/commands/persist.js +1 -0
  99. package/dist/cli/commands/persist.js.map +1 -1
  100. package/dist/cli/commands/plan.js +1 -0
  101. package/dist/cli/commands/plan.js.map +1 -1
  102. package/dist/cli/commands/plugin-cache.js +4 -0
  103. package/dist/cli/commands/plugin-cache.js.map +1 -1
  104. package/dist/cli/commands/plugin-doctor.js +3 -0
  105. package/dist/cli/commands/plugin-doctor.js.map +1 -1
  106. package/dist/cli/commands/project-md-upsert.js +3 -0
  107. package/dist/cli/commands/project-md-upsert.js.map +1 -1
  108. package/dist/cli/commands/release-data.js +12 -0
  109. package/dist/cli/commands/release-data.js.map +1 -1
  110. package/dist/cli/commands/release-pass.js +10 -0
  111. package/dist/cli/commands/release-pass.js.map +1 -1
  112. package/dist/cli/commands/remove-source.js +1 -0
  113. package/dist/cli/commands/remove-source.js.map +1 -1
  114. package/dist/cli/commands/reseal.js +1 -0
  115. package/dist/cli/commands/reseal.js.map +1 -1
  116. package/dist/cli/commands/resume-summary.js +5 -0
  117. package/dist/cli/commands/resume-summary.js.map +1 -1
  118. package/dist/cli/commands/resume.js +1 -0
  119. package/dist/cli/commands/resume.js.map +1 -1
  120. package/dist/cli/commands/retirement.js +1 -0
  121. package/dist/cli/commands/retirement.js.map +1 -1
  122. package/dist/cli/commands/self.js +33 -0
  123. package/dist/cli/commands/self.js.map +1 -1
  124. package/dist/cli/commands/session-artifacts.js +1 -0
  125. package/dist/cli/commands/session-artifacts.js.map +1 -1
  126. package/dist/cli/commands/session-close.js +129 -30
  127. package/dist/cli/commands/session-close.js.map +1 -1
  128. package/dist/cli/commands/session-create.js +1 -0
  129. package/dist/cli/commands/session-create.js.map +1 -1
  130. package/dist/cli/commands/session-resume.js +1 -0
  131. package/dist/cli/commands/session-resume.js.map +1 -1
  132. package/dist/cli/commands/sessions.js +1 -0
  133. package/dist/cli/commands/sessions.js.map +1 -1
  134. package/dist/cli/commands/set-branch.js +1 -0
  135. package/dist/cli/commands/set-branch.js.map +1 -1
  136. package/dist/cli/commands/settle.js +1 -0
  137. package/dist/cli/commands/settle.js.map +1 -1
  138. package/dist/cli/commands/skill-index.js +1 -0
  139. package/dist/cli/commands/skill-index.js.map +1 -1
  140. package/dist/cli/commands/skills.js +1 -0
  141. package/dist/cli/commands/skills.js.map +1 -1
  142. package/dist/cli/commands/sources.js +1 -0
  143. package/dist/cli/commands/sources.js.map +1 -1
  144. package/dist/cli/commands/stack.js +1 -0
  145. package/dist/cli/commands/stack.js.map +1 -1
  146. package/dist/cli/commands/status.js +1 -0
  147. package/dist/cli/commands/status.js.map +1 -1
  148. package/dist/cli/commands/tool.js +5 -21
  149. package/dist/cli/commands/tool.js.map +1 -1
  150. package/dist/cli/commands/unknown-flags.js +67 -16
  151. package/dist/cli/commands/unknown-flags.js.map +1 -1
  152. package/dist/cli/commands/visibility.js +1 -0
  153. package/dist/cli/commands/visibility.js.map +1 -1
  154. package/dist/cli/commands/workspace-init.js +12 -0
  155. package/dist/cli/commands/workspace-init.js.map +1 -1
  156. package/dist/cli/commands/workspace-migrate.js +2 -13
  157. package/dist/cli/commands/workspace-migrate.js.map +1 -1
  158. package/dist/cli/commands/worktree.js +1 -0
  159. package/dist/cli/commands/worktree.js.map +1 -1
  160. package/dist/cli/help-groups.js +35 -0
  161. package/dist/cli/help-groups.js.map +1 -1
  162. package/dist/cli/main.js +39 -57
  163. package/dist/cli/main.js.map +1 -1
  164. package/dist/cli/output-mode.js +11 -2
  165. package/dist/cli/output-mode.js.map +1 -1
  166. package/dist/cli/parser.js +14 -5
  167. package/dist/cli/parser.js.map +1 -1
  168. package/dist/cli/registry.js.map +1 -1
  169. package/dist/cli/render.js +30 -1
  170. package/dist/cli/render.js.map +1 -1
  171. package/dist/domain/flow/answer.js +8 -0
  172. package/dist/domain/flow/answer.js.map +1 -1
  173. package/dist/domain/flow/authority.js +141 -14
  174. package/dist/domain/flow/authority.js.map +1 -1
  175. package/dist/domain/flow/execution-result.js +78 -7
  176. package/dist/domain/flow/execution-result.js.map +1 -1
  177. package/dist/domain/flow/route.js.map +1 -1
  178. package/dist/domain/flow/rules.js +5 -6
  179. package/dist/domain/flow/rules.js.map +1 -1
  180. package/dist/domain/flow/run-state.js +186 -30
  181. package/dist/domain/flow/run-state.js.map +1 -1
  182. package/dist/domain/harnesses.js +8 -1
  183. package/dist/domain/harnesses.js.map +1 -1
  184. package/dist/domain/sql-lexer.js +14 -1
  185. package/dist/domain/sql-lexer.js.map +1 -1
  186. package/package.json +1 -1
  187. package/skills/w/harness/HARNESS.md +3 -1
  188. package/skills/w/loops/CHASSIS.md +1 -1
  189. package/skills/w/roles/design/CONTRACT.md +10 -3
@@ -25,12 +25,12 @@ import { authorizeEffects, touchesTheWorld, } from "../../domain/capability/effe
25
25
  import { AttemptLedger } from "../../domain/capability/protocol.js";
26
26
  import { DEFAULT_CORE_DOCS_CANON, coreDocumentKindForPath } from "../../domain/docs-canon.js";
27
27
  import { claimedSeal, parseFlowAnswer, spendsAttempt, } from "../../domain/flow/answer.js";
28
- import { FIX_PREVIEW_TRANSITION, SETTLEMENT_READINGS, actionOf, alternativesOf, approvalGrantOf, effectsOf, internalActionOf, isRouteEvaluation, proposalContractOf, publishApprovalOf, routeControlOf, scopesSources, } from "../../domain/flow/authority.js";
28
+ import { FIX_PREVIEW_TRANSITION, SETTLEMENT_READINGS, actionOf, alternativesOf, approvalGrantOf, effectsOf, internalActionOf, isRouteEvaluation, occurrenceAt, proposalContractOf, publishApprovalOf, reentryOf, routeControlOf, scopesSources, } from "../../domain/flow/authority.js";
29
29
  import { effectApprovalDigest } from "../../domain/flow/authorization.js";
30
30
  import { PAUSE_LABEL, STOP_LABEL, stepOf, } from "../../domain/flow/directive.js";
31
- import { executionVerdict } from "../../domain/flow/execution-result.js";
31
+ import { batchCreditVerdict, executionVerdict, } from "../../domain/flow/execution-result.js";
32
32
  import { ROUTE_ACCEPT_LABEL, ROUTE_ADJUST_LABEL, isRouteDisposition, } from "../../domain/flow/route.js";
33
- import { applyTransition, checkAgainstJourney, currentBatchIteration, restatesLastEvent, settlementAmbiguous, withApproval, withAttempt, withBoundary, withContinuation, withDecisionPreparation, withEvent, withFixPreview, withHandoff, withObservation, withPlanExecBatchStageForTransition, withProposal, withRouteDecisions, withRouteProposal, withScope, withSelectedChoice, withSettlementDeclarations, } from "../../domain/flow/run-state.js";
33
+ import { applyTransition, checkAgainstJourney, iterationOf, restartInvocation, restatesLastEvent, sameIteration, settlementAmbiguous, withApproval, withAttempt, withBoundary, withContinuation, withDecisionPreparation, withEvent, withFixPreview, withHandoff, withObservation, withPlanExecBatchCredit, withPlanExecBatchStageForTransition, withProposal, withReentry, withRouteDecisions, withRouteProposal, withScope, withSelectedChoice, withSettlementDeclarations, } from "../../domain/flow/run-state.js";
34
34
  import { withSpecBaseline } from "../../domain/lineage.js";
35
35
  import { destinationsOf, observedEffects, sealProposal } from "../../domain/proposal.js";
36
36
  import { baseDigest } from "../../domain/proposal.js";
@@ -46,11 +46,11 @@ import { semanticDigest } from "../semantic-operation/protocol.js";
46
46
  import { resolveSessionTarget } from "../session-resolver.js";
47
47
  import { sourceAliasesOfPlan, } from "../source-boundary-policy.js";
48
48
  import { actionDigest, advanceFlowRun, awaitingCliRerun, directiveFor, effectsOfTransition, resolveBoundary, } from "./advance.js";
49
- import { observeCheckout, withObservedCheckouts } from "./checkout-observation.js";
49
+ import { observeCheckout, observeScopedFingerprints, withObservedCheckouts, } from "./checkout-observation.js";
50
50
  import { resolveCheckoutCandidates } from "./checkout-observation.js";
51
51
  import { driveInternalActions } from "./internal-drive.js";
52
52
  import { journeyForRun } from "./run-journey.js";
53
- import { applyUnderLock, locateRun } from "./run-state-service.js";
53
+ import { applyUnderLock, locateRun, readRun } from "./run-state-service.js";
54
54
  export async function submitFlow(fs, paths, input) {
55
55
  const canon = await resolveCoreDocsCanon(fs, paths);
56
56
  if (!canon.ok) {
@@ -126,9 +126,30 @@ async function observe(fs, paths, raw, session, git) {
126
126
  scope: await observeScope(fs, paths, raw),
127
127
  plans: plans.evidence,
128
128
  checkouts: await observeCheckouts(fs, paths, session, git),
129
+ scoped: await observeBatchSources(fs, paths, session, git),
129
130
  baselines: plans.baselines,
130
131
  };
131
132
  }
133
+ /**
134
+ * The scoped fingerprints a phase validation is judged against, read before the
135
+ * lock like every other observation. The run read here only decides WHETHER to
136
+ * measure: the decision under the lock re-reads it, and a run that moved in
137
+ * between answers a different seal and is refused as stale anyway.
138
+ */
139
+ async function observeBatchSources(fs, paths, session, git) {
140
+ if (git === undefined)
141
+ return null;
142
+ // With a reader present, "not measured" is an empty map and never `null`: the
143
+ // judgment then finds every source unobservable and refuses, instead of
144
+ // skipping the one comparison the phase validation exists for.
145
+ const read = await readRun(fs, locateRun(paths, session));
146
+ if (!read.ok || read.state.boundary !== PHASE_VALIDATION || read.state.scope === null) {
147
+ return {};
148
+ }
149
+ const observed = await observeScopedFingerprints(fs, git, paths, session, read.state.scope.sources);
150
+ return observed.ok ? observed.base : {};
151
+ }
152
+ const PHASE_VALIDATION = "plan-exec.validation-execution";
132
153
  /**
133
154
  * Judge the plan bytes a proposal is carrying, and derive the seal each one gets.
134
155
  *
@@ -385,6 +406,16 @@ async function decide(fs, paths, state, input, snapshot) {
385
406
  action: "corré 'aw flow advance': el CLI corre una vez más la operación interna y, si reconoce su efecto, avanza sin cobrar",
386
407
  });
387
408
  }
409
+ // An internal action is credited only by the driver that ran it: that is what
410
+ // leaves its `executed` event in the trace. A result from outside, however well
411
+ // formed, is a claim nobody verified — and applying it used to publish nothing
412
+ // while marking the step done.
413
+ if (resolved.kind === "execution" && internalActionOf(resolved.stopped) !== null) {
414
+ return reject(state, resolved, `'${resolved.stopped.id}' la ejecuta el CLI: un resultado externo no la acredita`, {
415
+ code: "FLOW_INTERNAL_ACTION_EXTERNAL_RESULT",
416
+ action: `corré 'aw flow advance --session ${state.session}': el CLI corre la operación y la acredita; si la corrida no sale de esta frontera, '${restartInvocation(state.session)}' la destraba`,
417
+ }, cost);
418
+ }
388
419
  // The action the run is waiting on is the one that was EMITTED, and the seal
389
420
  // would already refuse a result about any other. What the persisted digest adds
390
421
  // is the diagnosis: when the two differ, the invocation changed underneath a run
@@ -394,7 +425,7 @@ async function decide(fs, paths, state, input, snapshot) {
394
425
  if (drifted !== null)
395
426
  return drifted;
396
427
  // 2 · The boundary in force decides what is admissible.
397
- const admissible = admit(state, resolved, resolved.stopped, input, cost, snapshot.checkouts);
428
+ const admissible = admit(state, resolved, resolved.stopped, input, cost, snapshot.checkouts, snapshot.scoped);
398
429
  if ("decision" in admissible)
399
430
  return admissible.decision;
400
431
  const parsed = admissible;
@@ -408,7 +439,12 @@ async function decide(fs, paths, state, input, snapshot) {
408
439
  if (route.kind === "adjust") {
409
440
  return adjustRouteProposal(state, journey);
410
441
  }
411
- const routed = route.kind === "accept" ? withRouteDecisions(state, route.decisions) : state;
442
+ const accepted = route.kind === "accept" ? withRouteDecisions(state, route.decisions) : state;
443
+ // Sealed with the transition it earned, in the same state publication: a batch
444
+ // is never seen validated without the proofs that validated it.
445
+ const routed = parsed.credit === undefined
446
+ ? accepted
447
+ : withPlanExecBatchCredit(accepted, parsed.credit.batch, parsed.credit.proofs);
412
448
  // The registry, not the Spanish consequence text, owns what a selected
413
449
  // alternative does. A handoff must stop before any later plan-exec row can
414
450
  // mark work, validate or commit; a decision selection is durably recorded
@@ -502,9 +538,12 @@ async function decide(fs, paths, state, input, snapshot) {
502
538
  if (preview.preview !== null) {
503
539
  selectedState = withFixPreview(selectedState, preview.preview);
504
540
  }
541
+ // A redraft recorded by `grantOf` inserted rows after this boundary, so the
542
+ // transition is applied against the journey as it now reads.
543
+ const walked = journeyForRun(selectedState);
505
544
  const outcome = holds
506
- ? holdAfterApproval(selectedState, journey, identity)
507
- : applyAndAdvance(selectedState, journey, resolved.stopped, identity, parsed.answer);
545
+ ? holdAfterApproval(selectedState, walked, identity)
546
+ : applyAndAdvance(selectedState, walked, resolved.stopped, identity, parsed.answer);
508
547
  return standalone === null ? outcome : withStandaloneGuidance(outcome, standalone);
509
548
  }
510
549
  /**
@@ -679,17 +718,28 @@ function routeAnswer(state, stopped, journey, answer) {
679
718
  },
680
719
  };
681
720
  }
721
+ const { decision, control } = configuredControl;
722
+ const consequence = control.consequences[raw.disposition];
723
+ if (consequence === undefined) {
724
+ return {
725
+ kind: "failure",
726
+ failure: {
727
+ code: "FLOW_ROUTE_HARD_GATE",
728
+ message: `'${raw.transition}' no admite '${raw.disposition}': el registro no deja que una ruta la saltee`,
729
+ action: "declará apply o substitute para ese control; la evidencia que acredita el paso no se omite",
730
+ },
731
+ };
732
+ }
682
733
  const substitution = routeSubstitution(raw.disposition, raw.substitution);
683
734
  if ("failure" in substitution)
684
735
  return { kind: "failure", failure: substitution.failure };
685
- const { decision, control } = configuredControl;
686
736
  controls.push({
687
737
  transition: decision.id,
688
738
  title: decision.title,
689
739
  disposition: raw.disposition,
690
740
  recommendation: control.recommendation,
691
741
  alternatives: control.consequences,
692
- consequence: control.consequences[raw.disposition],
742
+ consequence,
693
743
  risk: control.risk,
694
744
  reason: raw.reason.trim(),
695
745
  substitution: substitution.value,
@@ -1216,11 +1266,11 @@ async function registerSelectedDecision(fs, paths, state, snapshot, resolved, co
1216
1266
  * run that merely advanced would be indistinguishable from one that never decided.
1217
1267
  */
1218
1268
  function standaloneDecisionEvent(state, transition, record) {
1219
- const batchIteration = currentBatchIteration(state, transition);
1269
+ const rowIteration = iterationOf(state, transition);
1220
1270
  return {
1221
1271
  kind: "executed",
1222
1272
  transition,
1223
- ...(batchIteration === null ? {} : { batch_iteration: batchIteration }),
1273
+ ...rowIteration,
1224
1274
  operation: "plan-exec.standalone-decision",
1225
1275
  summary: record.decision,
1226
1276
  output_digest: record.digest,
@@ -1396,6 +1446,26 @@ function sealFrom(state, stopped, answer, snapshot, baselines) {
1396
1446
  })),
1397
1447
  };
1398
1448
  }
1449
+ /**
1450
+ * The redraft a confirmation row's alternative asked for, or `null`.
1451
+ *
1452
+ * It unseats any standing proposal and records a reentry from the flow's
1453
+ * authoring row, so the run walks the redraft and its gate again instead of
1454
+ * carrying on to a publication with nothing to publish and a finalize that
1455
+ * closes the session. Independent of whether a proposal is seated: a proposal
1456
+ * the run gave up on is exactly when redrafting matters most.
1457
+ */
1458
+ function redraftOf(state, stopped, answer, journey) {
1459
+ const redraft = reentryOf(stopped);
1460
+ if (redraft === null || answer.choice !== redraft.label)
1461
+ return null;
1462
+ return withReentry(withProposal(state, null), {
1463
+ kind: "refine",
1464
+ transition: stopped.id,
1465
+ occurrence: occurrenceAt(journey, state.applied.length),
1466
+ from: redraft.from,
1467
+ });
1468
+ }
1399
1469
  /**
1400
1470
  * The grant this answer produced, scoped to what it was given over.
1401
1471
  *
@@ -1410,8 +1480,13 @@ function sealFrom(state, stopped, answer, snapshot, baselines) {
1410
1480
  */
1411
1481
  function grantOf(state, resolved, answer, granted, journey) {
1412
1482
  const stopped = resolved.stopped;
1483
+ if (stopped === null)
1484
+ return state;
1485
+ const redrafted = redraftOf(state, stopped, answer, journey);
1486
+ if (redrafted !== null)
1487
+ return redrafted;
1413
1488
  const proposal = state.proposal;
1414
- const approve = stopped === null ? null : publishApprovalOf(stopped);
1489
+ const approve = publishApprovalOf(stopped);
1415
1490
  if (approve !== null && proposal !== null) {
1416
1491
  if (answer.choice === approve) {
1417
1492
  return withApproval(state, {
@@ -1420,13 +1495,11 @@ function grantOf(state, resolved, answer, granted, journey) {
1420
1495
  classes: [...proposal.requires_approval],
1421
1496
  });
1422
1497
  }
1423
- // Declining unseats the proposal, and that is what makes `Refinar` cost
1424
- // nothing: the publication downstream finds nothing to publish and is skipped
1425
- // saying so, instead of stopping to ask for an authorization over bytes the
1498
+ // Declining unseats the proposal: no authorization is asked over bytes the
1426
1499
  // person just turned down.
1427
1500
  return withProposal(state, null);
1428
1501
  }
1429
- const link = stopped === null ? null : approvalGrantOf(stopped);
1502
+ const link = approvalGrantOf(stopped);
1430
1503
  if (link !== null && answer.choice === link.approve) {
1431
1504
  const target = journey.find((row) => row.id === link.transition);
1432
1505
  // A link to a transition this journey does not walk grants nothing: the
@@ -1457,7 +1530,7 @@ function grantOf(state, resolved, answer, granted, journey) {
1457
1530
  * a payload the boundary's own contract rejects, a decline (a real answer that
1458
1531
  * applies nothing), and an execution result that did not earn its transition.
1459
1532
  */
1460
- function admit(state, resolved, stopped, input, cost, checkouts) {
1533
+ function admit(state, resolved, stopped, input, cost, checkouts, scoped) {
1461
1534
  const expectedApproval = resolved.kind === "authorization"
1462
1535
  ? effectApprovalDigest(stopped.id, resolved.authorization?.planned ?? [])
1463
1536
  : null;
@@ -1520,22 +1593,53 @@ function admit(state, resolved, stopped, input, cost, checkouts) {
1520
1593
  // An execution result has to EARN the transition. Anything short of a completed
1521
1594
  // run with its evidence and its whole effect keeps the boundary standing: the
1522
1595
  // work stays pending, with the recovery the action declared.
1523
- if (resolved.kind === "execution") {
1524
- // The row's `effects` are the ceiling and the sealed proposal is what really
1525
- // happens: demanding an overwrite from a publication that only creates files
1526
- // would leave the transition pending for an effect nobody could produce.
1527
- const verdict = executionVerdict(parsed.answer.result, resolved.action, effectsOfTransition(state, stopped), checkouts);
1528
- if (verdict !== null) {
1529
- const trace = declaredTrace(state, stopped, resolved, parsed.answer, verdict);
1530
- return {
1531
- decision: reject(state, resolved, verdict.message, verdict.detail, {
1532
- ...cost,
1533
- ...(trace === null ? {} : { trace }),
1534
- }),
1535
- };
1536
- }
1596
+ if (resolved.kind !== "execution")
1597
+ return parsed;
1598
+ return earned(state, resolved, stopped, parsed, cost, checkouts, scoped);
1599
+ }
1600
+ /** The execution verdict, then — at the phase validation — the batch's own credit. */
1601
+ function earned(state, resolved, stopped, parsed, cost, checkouts, scoped) {
1602
+ // The row's `effects` are the ceiling and the sealed proposal is what really
1603
+ // happens: demanding an overwrite from a publication that only creates files
1604
+ // would leave the transition pending for an effect nobody could produce.
1605
+ const verdict = executionVerdict(parsed.answer.result, resolved.action, effectsOfTransition(state, stopped), checkouts);
1606
+ if (verdict !== null) {
1607
+ const trace = declaredTrace(state, stopped, resolved, parsed.answer, verdict);
1608
+ return {
1609
+ decision: reject(state, resolved, verdict.message, verdict.detail, {
1610
+ ...cost,
1611
+ ...(trace === null ? {} : { trace }),
1612
+ }),
1613
+ };
1614
+ }
1615
+ const credited = batchCredit(state, stopped, parsed.answer, resolved, scoped);
1616
+ if (credited === null)
1617
+ return parsed;
1618
+ if (!credited.ok) {
1619
+ return {
1620
+ decision: reject(state, resolved, credited.refusal.message, credited.refusal.detail, cost),
1621
+ };
1537
1622
  }
1538
- return parsed;
1623
+ return { ...parsed, credit: credited.credit };
1624
+ }
1625
+ /**
1626
+ * The phase validation's own judgment: the batch it closes must bring a proof of
1627
+ * each of its sources, of its own checkout. `null` on every other boundary, and
1628
+ * on a run with no batch standing, where there is nothing to credit.
1629
+ */
1630
+ function batchCredit(state, stopped, answer, resolved, scoped) {
1631
+ if (stopped.id !== PHASE_VALIDATION || answer.result === null || state.scope === null) {
1632
+ return null;
1633
+ }
1634
+ const iteration = state.batch_loop?.iteration ?? null;
1635
+ const batches = state.batches ?? [];
1636
+ const batch = batches.find((entry) => entry.iteration === iteration);
1637
+ if (batch === undefined)
1638
+ return null;
1639
+ const verdict = batchCreditVerdict(answer.result, { batch, batches, sources: state.scope.sources, scoped }, resolved.action?.recovery ?? "");
1640
+ if (!verdict.ok)
1641
+ return verdict;
1642
+ return { ok: true, credit: { batch: batch.id, proofs: verdict.credit } };
1539
1643
  }
1540
1644
  /**
1541
1645
  * The material fact a REFUSED external result still leaves behind — or `null`.
@@ -1557,11 +1661,11 @@ function declaredTrace(state, stopped, resolved, answer, verdict) {
1557
1661
  if (!touchesTheWorld(applied) && verdict.detail.code !== "FLOW_EFFECT_PARTIAL")
1558
1662
  return null;
1559
1663
  const invocation = resolved.action?.invocation;
1560
- const batchIteration = currentBatchIteration(state, stopped.id);
1664
+ const rowIteration = iterationOf(state, stopped.id);
1561
1665
  return {
1562
1666
  kind: "failed",
1563
1667
  transition: stopped.id,
1564
- ...(batchIteration === null ? {} : { batch_iteration: batchIteration }),
1668
+ ...rowIteration,
1565
1669
  // What was really run, named the way whoever ran it would recognize it: an
1566
1670
  // external execution has no internal operation id to quote.
1567
1671
  operation: invocation === undefined ? stopped.id : [invocation.program, ...invocation.args].join(" "),
@@ -1618,13 +1722,6 @@ function applyAndAdvance(approved, journey, stopped, identity, answer) {
1618
1722
  ? withObservation(approved, observationFor(approved, stopped.id, answer.signals))
1619
1723
  : approved;
1620
1724
  next = withPlanExecBatchStageForTransition(applyTransition(next, stopped.id, effectsOfTransition(next, stopped)), stopped.id);
1621
- // A published proposal is spent, whoever published it. This is the degraded
1622
- // path — no internal executor, so the caller ran the write and returned its
1623
- // result — and leaving the proposal seated would keep a preview of bytes that
1624
- // are already on disk standing in front of the next boundary.
1625
- if (internalActionOf(stopped)?.operation === "proposal.publish") {
1626
- next = withProposal(next, null);
1627
- }
1628
1725
  next = withAttempt(next, identity);
1629
1726
  // The boundary follows the position, always: handing the engine a state whose
1630
1727
  // boundary still names the transition just applied is exactly the incoherence
@@ -1779,7 +1876,7 @@ function resendCheck(state, resolved, cost) {
1779
1876
  // The twin was refused: this is a retry of the same wrong answer, so it gets
1780
1877
  // the same real diagnosis again and counts toward the cap, which is what
1781
1878
  // eventually degrades the boundary instead of leaving the caller looping.
1782
- if (twin !== undefined && !state.applied.includes(twin.transition))
1879
+ if (twin !== undefined && twinWasRefused(state, twin))
1783
1880
  return null;
1784
1881
  return reject(state, resolved, "esta respuesta ya se había aplicado: el recorrido no avanza dos veces", {
1785
1882
  code: "FLOW_ANSWER_RESENT",
@@ -1787,6 +1884,19 @@ function resendCheck(state, resolved, cost) {
1787
1884
  outcome: "completed",
1788
1885
  }, cost);
1789
1886
  }
1887
+ /**
1888
+ * Whether the attempt a resend matches never advanced anything.
1889
+ *
1890
+ * Its transition missing from `applied` says so. So does its transition being
1891
+ * the boundary still standing at the same iteration: a repeated row (a batch or
1892
+ * a redraft copy) has its id in `applied` from an earlier walk, and that earlier
1893
+ * walk is not this one.
1894
+ */
1895
+ function twinWasRefused(state, twin) {
1896
+ if (!state.applied.includes(twin.transition))
1897
+ return true;
1898
+ return (twin.transition === state.boundary && sameIteration(twin, iterationOf(state, twin.transition)));
1899
+ }
1790
1900
  /**
1791
1901
  * A rejection, expressed as the recalculated directive.
1792
1902
  *
@@ -1848,7 +1958,7 @@ function reject(state, resolved, message, detail, cost) {
1848
1958
  */
1849
1959
  function attemptIdentity(state, input, seal, transition) {
1850
1960
  const digest = semanticDigest({ payload: input.raw, approval: input.approval });
1851
- const batchIteration = currentBatchIteration(state, transition);
1961
+ const rowIteration = iterationOf(state, transition);
1852
1962
  const prior = state.attempts.filter((past) => past.invocation_id === seal);
1853
1963
  const twin = prior.find((past) => past.request_digest === digest);
1854
1964
  // The next ordinal, asked with the SAME expression `AttemptLedger.record`
@@ -1872,18 +1982,18 @@ function attemptIdentity(state, input, seal, transition) {
1872
1982
  // The seal moves with the state; the transition does not while the run
1873
1983
  // stands there. It is what the cap counts over.
1874
1984
  transition,
1875
- ...(batchIteration === null ? {} : { batch_iteration: batchIteration }),
1985
+ ...rowIteration,
1876
1986
  };
1877
1987
  }
1878
1988
  function isRecord(value) {
1879
1989
  return typeof value === "object" && value !== null && !Array.isArray(value);
1880
1990
  }
1881
1991
  function observationFor(state, transition, signals) {
1882
- const batchIteration = currentBatchIteration(state, transition);
1992
+ const rowIteration = iterationOf(state, transition);
1883
1993
  return {
1884
1994
  transition,
1885
1995
  signals,
1886
- ...(batchIteration === null ? {} : { batch_iteration: batchIteration }),
1996
+ ...rowIteration,
1887
1997
  };
1888
1998
  }
1889
1999
  //# sourceMappingURL=submit.js.map