@tacuchi/agent-workflow-cli 21.6.0 → 21.7.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 (97) hide show
  1. package/dist/application/artifacts-service.js +10 -0
  2. package/dist/application/artifacts-service.js.map +1 -1
  3. package/dist/application/capability/design-handler.js +224 -12
  4. package/dist/application/capability/design-handler.js.map +1 -1
  5. package/dist/application/capability/dispatcher.js +20 -6
  6. package/dist/application/capability/dispatcher.js.map +1 -1
  7. package/dist/application/capability/durable-effect.js +23 -157
  8. package/dist/application/capability/durable-effect.js.map +1 -1
  9. package/dist/application/checkpoint-service.js +49 -3
  10. package/dist/application/checkpoint-service.js.map +1 -1
  11. package/dist/application/checkpoint-write-service.js +5 -0
  12. package/dist/application/checkpoint-write-service.js.map +1 -1
  13. package/dist/application/design/design-gate-service.js +18 -0
  14. package/dist/application/design/design-gate-service.js.map +1 -1
  15. package/dist/application/design/design-index-service.js +2 -0
  16. package/dist/application/design/design-index-service.js.map +1 -1
  17. package/dist/application/design/design-resolver-service.js +18 -1
  18. package/dist/application/design/design-resolver-service.js.map +1 -1
  19. package/dist/application/design/design-simple-service.js +217 -0
  20. package/dist/application/design/design-simple-service.js.map +1 -0
  21. package/dist/application/flow/advance.js +96 -11
  22. package/dist/application/flow/advance.js.map +1 -1
  23. package/dist/application/flow/flow-service.js +12 -1
  24. package/dist/application/flow/flow-service.js.map +1 -1
  25. package/dist/application/flow/internal-actions.js +221 -0
  26. package/dist/application/flow/internal-actions.js.map +1 -0
  27. package/dist/application/flow/internal-drive.js +293 -0
  28. package/dist/application/flow/internal-drive.js.map +1 -0
  29. package/dist/application/flow/submit.js +242 -84
  30. package/dist/application/flow/submit.js.map +1 -1
  31. package/dist/application/local-proposal.js +171 -0
  32. package/dist/application/local-proposal.js.map +1 -0
  33. package/dist/application/parsers/decisiones.js +13 -0
  34. package/dist/application/parsers/decisiones.js.map +1 -1
  35. package/dist/application/paths-service.js +11 -1
  36. package/dist/application/paths-service.js.map +1 -1
  37. package/dist/application/resume-service.js +22 -26
  38. package/dist/application/resume-service.js.map +1 -1
  39. package/dist/application/semantic-operation/protocol.js +10 -2
  40. package/dist/application/semantic-operation/protocol.js.map +1 -1
  41. package/dist/application/session-artifacts.js +53 -0
  42. package/dist/application/session-artifacts.js.map +1 -1
  43. package/dist/application/session-close-service.js +5 -0
  44. package/dist/application/session-close-service.js.map +1 -1
  45. package/dist/application/session-narrative.js +294 -0
  46. package/dist/application/session-narrative.js.map +1 -0
  47. package/dist/application/status-service.js +1 -0
  48. package/dist/application/status-service.js.map +1 -1
  49. package/dist/application/workline-index-service.js +2 -0
  50. package/dist/application/workline-index-service.js.map +1 -1
  51. package/dist/cli/commands/capability.js +1 -1
  52. package/dist/cli/commands/capability.js.map +1 -1
  53. package/dist/cli/commands/designs.js +4 -1
  54. package/dist/cli/commands/designs.js.map +1 -1
  55. package/dist/cli/commands/flow.js +14 -0
  56. package/dist/cli/commands/flow.js.map +1 -1
  57. package/dist/cli/commands/session-artifacts.js +55 -2
  58. package/dist/cli/commands/session-artifacts.js.map +1 -1
  59. package/dist/domain/design/capability.js +35 -4
  60. package/dist/domain/design/capability.js.map +1 -1
  61. package/dist/domain/design/direct.js.map +1 -1
  62. package/dist/domain/design/expansion.js +137 -0
  63. package/dist/domain/design/expansion.js.map +1 -0
  64. package/dist/domain/design/manifest.js +61 -8
  65. package/dist/domain/design/manifest.js.map +1 -1
  66. package/dist/domain/design/reference.js +57 -10
  67. package/dist/domain/design/reference.js.map +1 -1
  68. package/dist/domain/design/simple.js +164 -0
  69. package/dist/domain/design/simple.js.map +1 -0
  70. package/dist/domain/flow/answer.js +90 -12
  71. package/dist/domain/flow/answer.js.map +1 -1
  72. package/dist/domain/flow/authority.js +266 -60
  73. package/dist/domain/flow/authority.js.map +1 -1
  74. package/dist/domain/flow/authorization.js +36 -15
  75. package/dist/domain/flow/authorization.js.map +1 -1
  76. package/dist/domain/flow/directive.js +10 -6
  77. package/dist/domain/flow/directive.js.map +1 -1
  78. package/dist/domain/flow/execution-result.js +72 -0
  79. package/dist/domain/flow/execution-result.js.map +1 -0
  80. package/dist/domain/flow/run-state.js +165 -13
  81. package/dist/domain/flow/run-state.js.map +1 -1
  82. package/dist/domain/host-verification.js +8 -8
  83. package/dist/domain/host-verification.js.map +1 -1
  84. package/dist/domain/proposal.js +91 -0
  85. package/dist/domain/proposal.js.map +1 -0
  86. package/dist/domain/session/narrative.js +126 -0
  87. package/dist/domain/session/narrative.js.map +1 -0
  88. package/package.json +1 -1
  89. package/skills/w/SKILL.md +1 -1
  90. package/skills/w/loops/CHASSIS.md +1 -1
  91. package/skills/w/loops/plan-new-loop/LOOP.md +5 -6
  92. package/skills/w/loops/plan-refine-loop/LOOP.md +7 -7
  93. package/skills/w/loops/spec-refine-loop/LOOP.md +6 -4
  94. package/skills/w/modules/DESIGN-REFERENCES.md +50 -56
  95. package/skills/w/roles/design/CONTRACT.md +73 -6
  96. package/skills/w/roles/design/ROLE.md +21 -3
  97. package/skills/w/schemas/design/design-manifest.v1.schema.json +4 -0
@@ -31,6 +31,14 @@ export const CHASSIS_SCOPE = "chassis";
31
31
  export const COMMAND_SCOPE_PREFIX = "cmd:";
32
32
  /** The four migration tranches the plan cuts over one at a time. */
33
33
  export const FLOW_TRANCHES = ["quick", "spec", "plan", "chassis"];
34
+ /** The proposal contract of an authoring row, or `null` when it proposes nothing. */
35
+ export function proposalContractOf(decision) {
36
+ return decision.proposes ?? null;
37
+ }
38
+ /** The approve label of a row that decides a standing proposal, or `null`. */
39
+ export function publishApprovalOf(decision) {
40
+ return decision.publishes?.approve ?? null;
41
+ }
34
42
  /** A decision computes a verdict; writing is the exception that declares itself. */
35
43
  export const DEFAULT_TRANSITION_EFFECTS = ["read_only"];
36
44
  /** What applying this transition does. */
@@ -46,10 +54,75 @@ export function effectsOf(decision) {
46
54
  * folder and its correlative — and both are facts the engine owns.
47
55
  */
48
56
  export const RUN_PLACEHOLDERS = ["{session}", "{code}"];
57
+ /**
58
+ * The Workline operations this CLI materializes inside its own process.
59
+ *
60
+ * A CLOSED union, and closed is the whole point of it. An action executed
61
+ * internally is one nobody read as a command line first, so what may run that way
62
+ * has to be enumerable, auditable and impossible to widen with data: the row names
63
+ * an operation, never a program. `invocation.program` and `invocation.args` are
64
+ * NEVER interpreted — they stay as the equivalent command a person would run to
65
+ * obtain the same reading, which is what keeps the two comparable.
66
+ *
67
+ * The four members are exactly the deterministic surface the plan names: the
68
+ * board, the sessions' own artifacts (their reading and their seeding), the close
69
+ * and the publication of an already approved proposal. Everything that runs code,
70
+ * touches git or produces a judgment is deliberately NOT here.
71
+ *
72
+ * Publishing is the one that writes documents the engine does not author, and it
73
+ * is admissible for a reason the others do not need: it writes NOTHING of its own
74
+ * — only the exact bytes a person approved, under the seal they approved them by.
75
+ */
76
+ export const INTERNAL_ACTION_OPERATIONS = [
77
+ /** Project the workspace board — what `aw status --json` returns. */
78
+ "workspace.board",
79
+ /** Read, and where the command seeds them, a session's own artifacts. */
80
+ "session.artifacts",
81
+ /** Close the session and upsert its HISTORY row. */
82
+ "session.close",
83
+ /** Write the run's approved proposal, all of it or none of it. */
84
+ "proposal.publish",
85
+ ];
86
+ /**
87
+ * The effect classes each internal operation can really apply.
88
+ *
89
+ * Read by the guard, not by the runtime: at runtime the executor reports what it
90
+ * ACTUALLY applied and the same `executionVerdict` that judges an external result
91
+ * refuses anything short of the row's declared effects. This table is what makes
92
+ * the mismatch a failing test instead of a run that discovers it — a row declaring
93
+ * `mutate_overwrite` while its operation only reads can never be satisfied, and
94
+ * finding that out at the boundary is finding it out too late.
95
+ */
96
+ export const INTERNAL_OPERATION_EFFECTS = {
97
+ "workspace.board": ["read_only"],
98
+ "session.artifacts": ["read_only", "local_additive"],
99
+ "session.close": ["read_only", "local_additive", "mutate_overwrite"],
100
+ // Creating and replacing, both real — and which of the two happens is decided
101
+ // by the proposal, not by the row: what the row declares here is the ceiling.
102
+ "proposal.publish": ["local_additive", "mutate_overwrite"],
103
+ };
49
104
  /** The delegated action of a transition, or `null` when the engine applies it itself. */
50
105
  export function actionOf(decision) {
51
106
  return decision.action ?? null;
52
107
  }
108
+ /**
109
+ * What this transition runs in-process, or `null` when whoever called it must.
110
+ *
111
+ * One predicate for every caller — the walk, the driver and the guard — so none of
112
+ * them can answer "does the CLI run this itself?" differently from the others.
113
+ *
114
+ * An action with no `execution` at all cannot happen through the type, and it is
115
+ * still read defensively: the answer for a malformed row is "not the CLI's", which
116
+ * fails closed. Throwing instead would take down every caller that merely ASKS the
117
+ * question — and asking it is now on the path of every boundary.
118
+ */
119
+ export function internalActionOf(decision) {
120
+ const action = actionOf(decision);
121
+ if (action === null || action.execution?.kind !== "internal")
122
+ return null;
123
+ const { kind: _internal, ...plan } = action.execution;
124
+ return plan;
125
+ }
53
126
  /** What makes this transition happen at all, or `null` when it always does. */
54
127
  export function conditionOf(decision) {
55
128
  return decision.condition ?? null;
@@ -397,9 +470,14 @@ export const FLOW_DECISIONS = [
397
470
  // judged. `quick` has none because it has no document to write it into, and
398
471
  // the two plan-authoring flows converge on a document they hand to the next
399
472
  // flow rather than on one they mark green.
473
+ //
474
+ // SPEC's used to be a promotion of its own, `status: ready-for-plan` written
475
+ // after the save. It is now the publication: the stamp travels inside the
476
+ // approved bytes, so the row that marks the criteria green is the row that
477
+ // writes them — one document, one write.
400
478
  realized_by: {
401
479
  kind: "transitions",
402
- ids: ["spec-refine.status-promotion", "plan-exec.plan-done"],
480
+ ids: ["spec-refine.publication", "plan-exec.plan-done"],
403
481
  },
404
482
  },
405
483
  {
@@ -459,6 +537,7 @@ export const FLOW_DECISIONS = [
459
537
  target: ".",
460
538
  input: null,
461
539
  },
540
+ execution: { kind: "internal", operation: "session.close" },
462
541
  evidence: ["chassis.sesion-cerrada"],
463
542
  idempotent: true,
464
543
  recovery: "una sesión que no cerró deja la corrida abierta: no la marques finalizada — reparás la fila del registro con 'aw history-update' y volvés a cerrar",
@@ -519,6 +598,7 @@ export const FLOW_DECISIONS = [
519
598
  // "I looked". Read-only, so it never stops to be authorized.
520
599
  action: {
521
600
  invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
601
+ execution: { kind: "internal", operation: "workspace.board" },
522
602
  evidence: ["quick.board-listed"],
523
603
  idempotent: true,
524
604
  recovery: "volvé a correr 'aw status --json' y devolvé su salida real; si el tablero no se puede leer, resolvé eso antes de seguir",
@@ -576,6 +656,7 @@ export const FLOW_DECISIONS = [
576
656
  target: SESSION_TARGET,
577
657
  input: null,
578
658
  },
659
+ execution: { kind: "internal", operation: "session.artifacts" },
579
660
  evidence: ["quick.session-present"],
580
661
  idempotent: true,
581
662
  recovery: "creá la sesión con 'aw session-create --type quick --name <slug>-quick --objetivo \"<objetivo>\"' y volvé a devolver la lectura",
@@ -625,6 +706,11 @@ export const FLOW_DECISIONS = [
625
706
  target: SESSION_TARGET,
626
707
  input: null,
627
708
  },
709
+ execution: {
710
+ kind: "internal",
711
+ operation: "session.artifacts",
712
+ dump: ["objetivo", "checkpoint"],
713
+ },
628
714
  evidence: [
629
715
  "quick.objetivo-sembrado",
630
716
  "quick.criterios-sembrados",
@@ -646,6 +732,10 @@ export const FLOW_DECISIONS = [
646
732
  // --source resolves no target and passes unconditionally.
647
733
  action: {
648
734
  invocation: { program: "aw", args: ["sources", "--verbose"], target: ".", input: null },
735
+ execution: {
736
+ kind: "external",
737
+ reason: "la rama esperada de cada fuente es un veredicto sobre git, y un workspace sin fuentes declaradas no lo tiene: leerlo desde adentro diría 'verificada' donde no hay nada que verificar",
738
+ },
649
739
  evidence: ["quick.rama-verificada"],
650
740
  idempotent: true,
651
741
  recovery: "resolvé la rama de la fuente que no coincide y volvé a leer las fuentes: nunca limpies ni cambies de rama sin confirmación",
@@ -699,6 +789,7 @@ export const FLOW_DECISIONS = [
699
789
  target: SESSION_TARGET,
700
790
  input: null,
701
791
  },
792
+ execution: { kind: "internal", operation: "session.artifacts", dump: ["scripts"] },
702
793
  evidence: ["quick.scripts-derivados"],
703
794
  idempotent: true,
704
795
  recovery: "escribí el DDL o DML en el SCRIPTS.sql de la sesión y volvé a devolver el dump; ejecutarlo no es una alternativa que este contrato admita",
@@ -745,6 +836,10 @@ export const FLOW_DECISIONS = [
745
836
  target: SESSION_TARGET,
746
837
  input: null,
747
838
  },
839
+ execution: {
840
+ kind: "external",
841
+ reason: "los criterios verdes exigen haber CORRIDO la prueba del entregable, y correr código nunca es una operación interna",
842
+ },
748
843
  evidence: ["quick.criterios-verdes"],
749
844
  idempotent: true,
750
845
  recovery: "arreglá lo que el criterio reprobó y volvé a correr sus validaciones: la transición sigue pendiente hasta que su salida real vuelva en verde",
@@ -809,6 +904,7 @@ export const FLOW_DECISIONS = [
809
904
  target: SESSION_TARGET,
810
905
  input: null,
811
906
  },
907
+ execution: { kind: "internal", operation: "session.artifacts" },
812
908
  evidence: ["spec.session-present"],
813
909
  idempotent: true,
814
910
  recovery: "creá la sesión con 'aw session-create --type refine --name <slug>-spec-refine --objetivo \"<objetivo>\"' y volvé a devolver la lectura",
@@ -1025,15 +1121,37 @@ export const FLOW_DECISIONS = [
1025
1121
  target: SESSION_TARGET,
1026
1122
  input: null,
1027
1123
  },
1124
+ execution: {
1125
+ kind: "external",
1126
+ reason: "el checklist de ready-for-plan es un juicio sobre lo que dice la spec, no la lectura del artefacto que lo contiene",
1127
+ },
1028
1128
  evidence: ["spec.ready-for-plan-checklist"],
1029
1129
  idempotent: true,
1030
1130
  recovery: "lo que el checklist reprobó vuelve al loop como gap: resolvelo y volvé a evaluar el gate con su estado real",
1031
1131
  },
1032
1132
  },
1133
+ {
1134
+ id: "spec-refine.save-proposal",
1135
+ scope: "spec-refine",
1136
+ title: "entregar los bytes exactos de la spec refinada, con su status ya sellado",
1137
+ authority: "agent",
1138
+ ownership: "cli-owned",
1139
+ document: SPEC_LOOP,
1140
+ attribution: SPEC_ATTRIBUTION,
1141
+ // The stamp travels INSIDE these bytes, and that is the whole reason the
1142
+ // separate promotion row is gone: `status: ready-for-plan` is a projection of
1143
+ // the same save, so writing it apart made the person confirm one half and
1144
+ // authorize the other. One document, one proposal, one write.
1145
+ proposes: {
1146
+ destinations: ["docs/specs"],
1147
+ effects: ["local_additive", "mutate_overwrite"],
1148
+ limits: { maxArtifacts: 8, maxArtifactBytes: 256 * 1024 },
1149
+ },
1150
+ },
1033
1151
  {
1034
1152
  id: "spec-refine.save-confirmation",
1035
1153
  scope: "spec-refine",
1036
- title: "confirmar la sobreescritura de la spec",
1154
+ title: "aprobar la vista previa de la spec y guardarla",
1037
1155
  authority: "human",
1038
1156
  ownership: "cli-owned",
1039
1157
  document: SPEC_LOOP,
@@ -1042,37 +1160,35 @@ export const FLOW_DECISIONS = [
1042
1160
  // was promoting the status and asking for the overwrite afterwards, so the
1043
1161
  // person would have been confirming a write that already happened. The
1044
1162
  // doctrine's own line is `edit_in_place_with_confirm(spec) + stamp`.
1163
+ publishes: { approve: "Aprobar y guardar" },
1045
1164
  alternatives: [
1046
1165
  {
1047
- label: "Guardar especificación refinada",
1048
- consequence: "la spec se sobrescribe en su lugar y queda sellada como ready-for-plan",
1166
+ label: "Aprobar y guardar",
1167
+ consequence: "se escriben exactamente los archivos de la vista previa —la spec en su lugar, sellada como ready-for-plan— y no se vuelve a preguntar por esos efectos",
1049
1168
  recommended: true,
1050
1169
  },
1051
1170
  {
1052
- label: "Preguntar algo más",
1053
- consequence: "el refinamiento sigue abierto y la spec queda como está",
1171
+ label: "Refinar",
1172
+ consequence: "el refinamiento sigue abierto, no se escribe nada y la spec queda como está",
1054
1173
  recommended: false,
1055
1174
  },
1056
1175
  ],
1057
1176
  },
1058
1177
  {
1059
- id: "spec-refine.status-promotion",
1178
+ id: "spec-refine.publication",
1060
1179
  scope: "spec-refine",
1061
- title: "promover el status de la spec a ready-for-plan",
1180
+ title: "publicar la propuesta aprobada de la spec en un solo acto",
1062
1181
  authority: "cli",
1063
1182
  ownership: "cli-owned",
1064
1183
  document: SPEC_LOOP,
1065
1184
  attribution: SPEC_ATTRIBUTION,
1066
- effects: ["mutate_overwrite"],
1067
- // The stamp is a write on a document the engine does not edit, so what it
1068
- // demands back is the board's own reading of that document's status. The
1069
- // effect is not self-authorizable: the run stops to be authorized before this
1070
- // invocation is ever named.
1185
+ effects: ["local_additive", "mutate_overwrite"],
1071
1186
  action: {
1072
- invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1073
- evidence: ["spec.status-ready-for-plan"],
1187
+ invocation: { program: "aw", args: ["flow", "advance"], target: ".", input: null },
1188
+ execution: { kind: "internal", operation: "proposal.publish" },
1189
+ evidence: ["spec.propuesta-publicada"],
1074
1190
  idempotent: true,
1075
- recovery: "sellá 'status: ready-for-plan' en la spec y volvé a devolver la lectura del tablero; si el sello no está, la transición sigue pendiente",
1191
+ recovery: "la publicación es todo-o-nada: si falló, nada quedó escrito y se reintenta el mismo contenido sin volver a aprobar; si la base cambió, volvé a redactar la propuesta sobre el documento vigente",
1076
1192
  },
1077
1193
  },
1078
1194
  // ── PLAN — new ────────────────────────────────────────────────────────────
@@ -1094,6 +1210,7 @@ export const FLOW_DECISIONS = [
1094
1210
  // that somebody looked. Suggesting is the outcome; it never blocks.
1095
1211
  action: {
1096
1212
  invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1213
+ execution: { kind: "internal", operation: "workspace.board" },
1097
1214
  evidence: ["plan.spec-status-leido"],
1098
1215
  idempotent: true,
1099
1216
  recovery: "volvé a correr 'aw status --json' y devolvé su salida real; sin el status de la spec no hay nada que sugerir ni que dar por listo",
@@ -1115,6 +1232,7 @@ export const FLOW_DECISIONS = [
1115
1232
  target: SESSION_TARGET,
1116
1233
  input: null,
1117
1234
  },
1235
+ execution: { kind: "internal", operation: "session.artifacts" },
1118
1236
  evidence: ["plan.session-present"],
1119
1237
  idempotent: true,
1120
1238
  recovery: "creá la sesión con 'aw session-create --type refine --name <slug>-plan-new --objetivo \"<objetivo>\"' y volvé a devolver la lectura",
@@ -1248,32 +1366,72 @@ export const FLOW_DECISIONS = [
1248
1366
  target: SESSION_TARGET,
1249
1367
  input: null,
1250
1368
  },
1369
+ execution: {
1370
+ kind: "external",
1371
+ reason: "la coherencia del plan es un juicio sobre lo que dice, no la lectura del artefacto que lo contiene",
1372
+ },
1251
1373
  evidence: ["plan.coherence-checklist"],
1252
1374
  idempotent: true,
1253
1375
  recovery: "lo que el checklist reprobó vuelve al loop como gap: resolvelo y volvé a evaluar el gate con su estado real",
1254
1376
  },
1255
1377
  },
1378
+ {
1379
+ id: "plan-new.save-proposal",
1380
+ scope: "plan-new",
1381
+ title: "entregar los bytes exactos del plan y de los hermanos que se extraigan",
1382
+ authority: "agent",
1383
+ ownership: "cli-owned",
1384
+ document: PLAN_NEW_LOOP,
1385
+ attribution: PLAN_ATTRIBUTION,
1386
+ // The generation used to write with NO effect row at all: the person confirmed
1387
+ // and the document appeared, with the engine crediting nothing. The proposal
1388
+ // is what makes that write visible — enumerated, weighed and sealed before it
1389
+ // is approved.
1390
+ proposes: {
1391
+ destinations: ["docs/plans"],
1392
+ effects: ["local_additive"],
1393
+ limits: { maxArtifacts: 8, maxArtifactBytes: 256 * 1024 },
1394
+ },
1395
+ },
1256
1396
  {
1257
1397
  id: "plan-new.save-confirmation",
1258
1398
  scope: "plan-new",
1259
- title: "confirmar la escritura del plan o de sus hermanos",
1399
+ title: "aprobar la vista previa del plan y guardarlo",
1260
1400
  authority: "human",
1261
1401
  ownership: "cli-owned",
1262
1402
  document: PLAN_NEW_LOOP,
1263
1403
  attribution: PLAN_ATTRIBUTION,
1404
+ publishes: { approve: "Aprobar y guardar" },
1264
1405
  alternatives: [
1265
1406
  {
1266
- label: "Guardar plan",
1267
- consequence: "el plan se escribe en docs/plans, y si el split fue aceptado se escriben también sus hermanos",
1407
+ label: "Aprobar y guardar",
1408
+ consequence: "se escriben exactamente los archivos de la vista previa en docs/plans, hermanos incluidos si el split fue aceptado",
1268
1409
  recommended: true,
1269
1410
  },
1270
1411
  {
1271
- label: "Preguntar algo más",
1412
+ label: "Refinar",
1272
1413
  consequence: "la generación sigue abierta y no se escribe ningún documento",
1273
1414
  recommended: false,
1274
1415
  },
1275
1416
  ],
1276
1417
  },
1418
+ {
1419
+ id: "plan-new.publication",
1420
+ scope: "plan-new",
1421
+ title: "publicar la propuesta aprobada del plan en un solo acto",
1422
+ authority: "cli",
1423
+ ownership: "cli-owned",
1424
+ document: PLAN_NEW_LOOP,
1425
+ attribution: PLAN_ATTRIBUTION,
1426
+ effects: ["local_additive"],
1427
+ action: {
1428
+ invocation: { program: "aw", args: ["flow", "advance"], target: ".", input: null },
1429
+ execution: { kind: "internal", operation: "proposal.publish" },
1430
+ evidence: ["plan.propuesta-publicada"],
1431
+ idempotent: true,
1432
+ recovery: "la publicación es todo-o-nada: si falló, no quedó ningún documento a medias y se reintenta el mismo contenido sin volver a aprobar",
1433
+ },
1434
+ },
1277
1435
  {
1278
1436
  id: "plan-new.adoption",
1279
1437
  scope: "plan-new",
@@ -1300,6 +1458,7 @@ export const FLOW_DECISIONS = [
1300
1458
  target: SESSION_TARGET,
1301
1459
  input: null,
1302
1460
  },
1461
+ execution: { kind: "internal", operation: "session.artifacts" },
1303
1462
  evidence: ["plan.session-present"],
1304
1463
  idempotent: true,
1305
1464
  recovery: "creá o reabrí la sesión con 'aw session-create' o 'aw session-resume --code <NNN> --reopen' y volvé a devolver la lectura",
@@ -1384,75 +1543,89 @@ export const FLOW_DECISIONS = [
1384
1543
  target: SESSION_TARGET,
1385
1544
  input: null,
1386
1545
  },
1546
+ execution: {
1547
+ kind: "external",
1548
+ reason: "la forma ejecutable del plan es un juicio sobre lo que dice, no la lectura del artefacto que lo contiene",
1549
+ },
1387
1550
  evidence: ["plan.executability-checklist"],
1388
1551
  idempotent: true,
1389
1552
  recovery: "lo que el checklist reprobó vuelve al loop como gap: resolvelo y volvé a evaluar el gate con su estado real",
1390
1553
  },
1391
1554
  },
1555
+ {
1556
+ id: "plan-refine.split-in-place",
1557
+ scope: "plan-refine",
1558
+ title: "reducir el plan original y extraer los hermanos sin mover trabajo completado",
1559
+ authority: "agent",
1560
+ ownership: "cli-owned",
1561
+ document: PLAN_REFINE_SPLIT,
1562
+ attribution: PLAN_ATTRIBUTION,
1563
+ // It used to be a delegated WRITE after the confirmation, which is what made
1564
+ // the refinement ask twice: once to confirm the plan and again to authorize
1565
+ // the siblings. Reducing the original and extracting the siblings is drafting
1566
+ // — it decides what the bytes are — so it happens BEFORE the proposal and its
1567
+ // result is visible where it belongs, as files enumerated in the preview.
1568
+ // Nothing is credited here: this row no longer claims a write happened.
1569
+ condition: {
1570
+ threshold: splitThreshold("plan-refine.split-signal"),
1571
+ otherwise: "el umbral de dos señales no disparó: el plan conserva su número y su alcance, y no se extrae ningún hermano",
1572
+ },
1573
+ },
1574
+ {
1575
+ id: "plan-refine.save-proposal",
1576
+ scope: "plan-refine",
1577
+ title: "entregar los bytes exactos del plan refinado, ya normalizado, sin bloques condicionales vacíos y sin tocar estados ni casillas",
1578
+ authority: "agent",
1579
+ ownership: "cli-owned",
1580
+ document: PLAN_REFINE_LOOP,
1581
+ attribution: PLAN_ATTRIBUTION,
1582
+ // The normalization rule lives in this row's own contract now, and that is the
1583
+ // merge that removes a write: normalizing the form is not a second edit of the
1584
+ // plan, it is what the drafted bytes already are. A separate row for it wrote
1585
+ // the same document twice and asked to be authorized for the second half.
1586
+ proposes: {
1587
+ destinations: ["docs/plans"],
1588
+ effects: ["local_additive", "mutate_overwrite"],
1589
+ limits: { maxArtifacts: 8, maxArtifactBytes: 256 * 1024 },
1590
+ },
1591
+ },
1392
1592
  {
1393
1593
  id: "plan-refine.save-confirmation",
1394
1594
  scope: "plan-refine",
1395
- title: "confirmar la sobreescritura del plan refinado",
1595
+ title: "aprobar la vista previa del plan refinado y guardarlo",
1396
1596
  authority: "human",
1397
1597
  ownership: "cli-owned",
1398
1598
  document: PLAN_REFINE_LOOP,
1399
1599
  attribution: PLAN_ATTRIBUTION,
1600
+ publishes: { approve: "Aprobar y guardar" },
1400
1601
  alternatives: [
1401
1602
  {
1402
- label: "Guardar plan refinado",
1403
- consequence: "el plan se edita en su lugar con su traza de refinamiento, y si el split fue aceptado se escriben los hermanos extraídos",
1603
+ label: "Aprobar y guardar",
1604
+ consequence: "se escriben exactamente los archivos de la vista previa —el plan en su lugar y los hermanos extraídos, si los hay— y no se vuelve a preguntar por esos efectos",
1404
1605
  recommended: true,
1405
1606
  },
1406
1607
  {
1407
- label: "Preguntar algo más",
1408
- consequence: "el refinamiento sigue abierto y el plan queda como está",
1608
+ label: "Refinar",
1609
+ consequence: "el refinamiento sigue abierto, no se escribe nada y el plan queda como está",
1409
1610
  recommended: false,
1410
1611
  },
1411
1612
  ],
1412
1613
  },
1413
1614
  {
1414
- id: "plan-refine.split-in-place",
1415
- scope: "plan-refine",
1416
- title: "reducir el plan original y extraer los hermanos sin mover trabajo completado",
1417
- authority: "cli",
1418
- ownership: "cli-owned",
1419
- document: PLAN_REFINE_SPLIT,
1420
- attribution: PLAN_ATTRIBUTION,
1421
- effects: ["local_additive", "mutate_overwrite"],
1422
- // AFTER the confirmation, and the doctrine's own sequence is why: the save
1423
- // branch reads `Guardar planes → edit original reduced (confirmation) + write
1424
- // extracted siblings`. The registry had both writes ahead of the gate and of
1425
- // the confirmation — the same defect the SPEC tranche found in its stamp, so
1426
- // the order moved to where the document puts it.
1427
- action: {
1428
- invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1429
- evidence: ["plan.hermanos-extraidos"],
1430
- idempotent: true,
1431
- recovery: "reducí el original en su lugar, escribí los hermanos extraídos y volvé a devolver la lectura; una tarea ya marcada nunca se muda a un hermano",
1432
- },
1433
- condition: {
1434
- threshold: splitThreshold("plan-refine.split-signal"),
1435
- otherwise: "el umbral de dos señales no disparó: el plan conserva su número y su alcance, y no se extrae ningún hermano",
1436
- },
1437
- },
1438
- {
1439
- id: "plan-refine.normalize-on-write",
1615
+ id: "plan-refine.publication",
1440
1616
  scope: "plan-refine",
1441
- title: "normalizar la forma sin escribir bloques condicionales vacíos ni tocar estados",
1617
+ title: "publicar la propuesta aprobada del plan refinado en un solo acto",
1442
1618
  authority: "cli",
1443
1619
  ownership: "cli-owned",
1444
1620
  document: PLAN_REFINE_LOOP,
1445
1621
  attribution: PLAN_ATTRIBUTION,
1446
- effects: ["mutate_overwrite"],
1447
- // A write on a document the engine does not edit, so it is delegated like
1448
- // every other one. It had neither action nor evidence, which meant the run
1449
- // recorded "normalized" for something nothing performed — the phantom
1450
- // confirmation this whole contract exists to refuse.
1622
+ effects: ["local_additive", "mutate_overwrite"],
1451
1623
  action: {
1452
- invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1453
- evidence: ["plan.forma-normalizada"],
1624
+ invocation: { program: "aw", args: ["flow", "advance"], target: ".", input: null },
1625
+ execution: { kind: "internal", operation: "proposal.publish" },
1626
+ evidence: ["plan.propuesta-publicada"],
1454
1627
  idempotent: true,
1455
- recovery: "normalizá la forma sin tocar estados ni casillas y volvé a devolver la lectura; normalizar de nuevo lo ya normalizado no rompe nada",
1628
+ recovery: "la publicación es todo-o-nada: el original reducido y sus hermanos entran juntos o no entra ninguno; si la base cambió, volvé a redactar la propuesta sobre el plan vigente",
1456
1629
  },
1457
1630
  },
1458
1631
  // ── PLAN — exec ───────────────────────────────────────────────────────────
@@ -1472,6 +1645,7 @@ export const FLOW_DECISIONS = [
1472
1645
  target: SESSION_TARGET,
1473
1646
  input: null,
1474
1647
  },
1648
+ execution: { kind: "internal", operation: "session.artifacts" },
1475
1649
  evidence: ["plan.session-present"],
1476
1650
  idempotent: true,
1477
1651
  recovery: "creá la sesión con 'aw session-create --type exec --name <slug>-plan-exec --objetivo \"<objetivo>\"' y volvé a devolver la lectura",
@@ -1491,6 +1665,10 @@ export const FLOW_DECISIONS = [
1491
1665
  // the gate's finding, not a detail to wave through.
1492
1666
  action: {
1493
1667
  invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1668
+ execution: {
1669
+ kind: "external",
1670
+ reason: "la forma ejecutable del plan es un juicio sobre lo que dice, no la lectura del tablero que lo lista",
1671
+ },
1494
1672
  evidence: ["plan.forma-ejecutable"],
1495
1673
  idempotent: true,
1496
1674
  recovery: "volvé a correr 'aw status --json' y devolvé su salida real; si el plan no se puede leer, eso ES el hallazgo del gate",
@@ -1615,6 +1793,10 @@ export const FLOW_DECISIONS = [
1615
1793
  // its current branch, the expected one and whether they match.
1616
1794
  action: {
1617
1795
  invocation: { program: "aw", args: ["sources", "--verbose"], target: ".", input: null },
1796
+ execution: {
1797
+ kind: "external",
1798
+ reason: "la rama esperada de cada fuente es un veredicto sobre git, y un workspace sin fuentes declaradas no lo tiene",
1799
+ },
1618
1800
  evidence: ["plan.rama-verificada"],
1619
1801
  idempotent: true,
1620
1802
  recovery: "resolvé la rama de la fuente que no coincide y volvé a leer las fuentes: nunca limpies ni cambies de rama sin confirmación",
@@ -1698,6 +1880,10 @@ export const FLOW_DECISIONS = [
1698
1880
  // ticked boxes. "I marked it" is the one thing this contract will not take.
1699
1881
  action: {
1700
1882
  invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1883
+ execution: {
1884
+ kind: "external",
1885
+ reason: "marcar la casilla reescribe el plan-doc, y este motor no lo edita",
1886
+ },
1701
1887
  evidence: ["plan.casillas-marcadas"],
1702
1888
  idempotent: true,
1703
1889
  recovery: "marcá la casilla de la tarea cuyo trabajo local terminó y volvé a devolver la lectura del tablero; marcar de nuevo lo ya marcado no rompe nada",
@@ -1719,6 +1905,10 @@ export const FLOW_DECISIONS = [
1719
1905
  // when a state and its boxes disagree.
1720
1906
  action: {
1721
1907
  invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1908
+ execution: {
1909
+ kind: "external",
1910
+ reason: "escribir el '> Estado:' de la fase reescribe el plan-doc, y este motor no lo edita",
1911
+ },
1722
1912
  evidence: ["plan.estado-de-fase-aplicado"],
1723
1913
  idempotent: true,
1724
1914
  recovery: "escribí el '> Estado:' que la fase realmente tiene —con su '> Bloqueo:' si quedó bloqueada— y volvé a devolver la lectura; una fase sin su prueba corrida no pasa a validada",
@@ -1745,6 +1935,10 @@ export const FLOW_DECISIONS = [
1745
1935
  target: SESSION_TARGET,
1746
1936
  input: null,
1747
1937
  },
1938
+ execution: {
1939
+ kind: "external",
1940
+ reason: "correr las pruebas de fase es ejecutar código, nunca una operación interna",
1941
+ },
1748
1942
  evidence: ["plan.validaciones-de-fase-verdes"],
1749
1943
  idempotent: true,
1750
1944
  recovery: "arreglá lo que la validación reprobó y volvé a correr las pruebas afectadas: la transición sigue pendiente hasta que su salida real vuelva en verde",
@@ -1787,6 +1981,10 @@ export const FLOW_DECISIONS = [
1787
1981
  // so it is read, not asserted.
1788
1982
  action: {
1789
1983
  invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1984
+ execution: {
1985
+ kind: "external",
1986
+ reason: "la validación final es el gate de convergencia del recorrido: darlo por verde desde una lectura sería aprobarlo sin correrlo",
1987
+ },
1790
1988
  evidence: ["plan.validacion-final-verde"],
1791
1989
  idempotent: true,
1792
1990
  recovery: "un chequeo diferido nunca cuenta como aprobado: deja su fase bloqueada y el plan abierto, así que corré lo que falte y volvé a leer el tablero",
@@ -1845,6 +2043,10 @@ export const FLOW_DECISIONS = [
1845
2043
  },
1846
2044
  action: {
1847
2045
  invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
2046
+ execution: {
2047
+ kind: "external",
2048
+ reason: "sellar el done reescribe el plan-doc, y este motor no lo edita",
2049
+ },
1848
2050
  evidence: ["plan.estado-done-sellado"],
1849
2051
  idempotent: true,
1850
2052
  recovery: "escribí '> Estado: done' y su '> Cierre:' en la línea de abajo y volvé a devolver la lectura; si el tablero no lo lee cerrado, la transición sigue pendiente",
@@ -1873,6 +2075,10 @@ export const FLOW_DECISIONS = [
1873
2075
  // clean or explicitly acknowledged").
1874
2076
  action: {
1875
2077
  invocation: { program: "aw", args: ["sources", "--verbose"], target: ".", input: null },
2078
+ execution: {
2079
+ kind: "external",
2080
+ reason: "crear un commit es un efecto sobre git que este ejecutor no aplica",
2081
+ },
1876
2082
  evidence: ["plan.commits-por-fuente"],
1877
2083
  idempotent: false,
1878
2084
  recovery: "una fuente que quedó con cambios sin commitear deja el batch SIN commitear: registralo así en CHECKPOINT y BACKLOG en vez de commitear a medias",