@tacuchi/agent-workflow-cli 25.1.2 → 25.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/adapters/node-process.js +35 -0
- package/dist/adapters/node-process.js.map +1 -1
- package/dist/application/amend-service.js +393 -0
- package/dist/application/amend-service.js.map +1 -0
- package/dist/application/decision-note-service.js +20 -3
- package/dist/application/decision-note-service.js.map +1 -1
- package/dist/application/doctor/actions.js +158 -0
- package/dist/application/doctor/actions.js.map +1 -0
- package/dist/application/doctor/apply.js +399 -0
- package/dist/application/doctor/apply.js.map +1 -0
- package/dist/application/doctor/auth-dsn.js +143 -0
- package/dist/application/doctor/auth-dsn.js.map +1 -0
- package/dist/application/doctor/auth-flow.js +28 -0
- package/dist/application/doctor/auth-flow.js.map +1 -0
- package/dist/application/doctor/auth-registry.js +12 -0
- package/dist/application/doctor/auth-registry.js.map +1 -0
- package/dist/application/doctor/hosts.js +41 -0
- package/dist/application/doctor/hosts.js.map +1 -0
- package/dist/application/doctor/native-host-state.js +211 -0
- package/dist/application/doctor/native-host-state.js.map +1 -0
- package/dist/application/doctor/prepare.js +262 -0
- package/dist/application/doctor/prepare.js.map +1 -0
- package/dist/application/doctor/provider-installation.js +124 -0
- package/dist/application/doctor/provider-installation.js.map +1 -0
- package/dist/application/doctor/provider-mcps.js +650 -0
- package/dist/application/doctor/provider-mcps.js.map +1 -0
- package/dist/application/doctor/provider-plugins-hooks.js +112 -0
- package/dist/application/doctor/provider-plugins-hooks.js.map +1 -0
- package/dist/application/doctor/provider-skills.js +212 -0
- package/dist/application/doctor/provider-skills.js.map +1 -0
- package/dist/application/doctor/provider-tools-auth.js +201 -0
- package/dist/application/doctor/provider-tools-auth.js.map +1 -0
- package/dist/application/doctor/provider-visibility.js +149 -0
- package/dist/application/doctor/provider-visibility.js.map +1 -0
- package/dist/application/doctor/repair-runner.js +169 -0
- package/dist/application/doctor/repair-runner.js.map +1 -0
- package/dist/application/doctor/report.js +155 -0
- package/dist/application/doctor/report.js.map +1 -0
- package/dist/application/doctor/types.js +5 -0
- package/dist/application/doctor/types.js.map +1 -0
- package/dist/application/flow/advance.js +152 -13
- package/dist/application/flow/advance.js.map +1 -1
- package/dist/application/flow/flow-service.js +18 -7
- package/dist/application/flow/flow-service.js.map +1 -1
- package/dist/application/flow/internal-actions.js +167 -7
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/flow/run-projection.js +6 -1
- package/dist/application/flow/run-projection.js.map +1 -1
- package/dist/application/flow/submit.js +330 -7
- package/dist/application/flow/submit.js.map +1 -1
- package/dist/application/mcp-doctor-service.js +1 -1
- package/dist/application/mcp-doctor-service.js.map +1 -1
- package/dist/application/mcp-host-reader.js +134 -26
- package/dist/application/mcp-host-reader.js.map +1 -1
- package/dist/application/mcp-host-receipt-service.js +9 -7
- package/dist/application/mcp-host-receipt-service.js.map +1 -1
- package/dist/application/plan-current-point.js +54 -0
- package/dist/application/plan-current-point.js.map +1 -0
- package/dist/application/plan-exec-decision-service.js +143 -4
- package/dist/application/plan-exec-decision-service.js.map +1 -1
- package/dist/application/plan-locator.js +78 -0
- package/dist/application/plan-locator.js.map +1 -0
- package/dist/application/plan-open-run.js +129 -0
- package/dist/application/plan-open-run.js.map +1 -0
- package/dist/application/reseal-service.js +14 -44
- package/dist/application/reseal-service.js.map +1 -1
- package/dist/application/resume-service.js +9 -3
- package/dist/application/resume-service.js.map +1 -1
- package/dist/application/self/install-skill.js +10 -2
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/mcp-config.js +1 -1
- package/dist/application/self/mcp-config.js.map +1 -1
- package/dist/application/session-narrative.js +15 -0
- package/dist/application/session-narrative.js.map +1 -1
- package/dist/application/settle-service.js +442 -0
- package/dist/application/settle-service.js.map +1 -0
- package/dist/application/source-boundary-policy.js +87 -6
- package/dist/application/source-boundary-policy.js.map +1 -1
- package/dist/application/workline-index-service.js +165 -62
- package/dist/application/workline-index-service.js.map +1 -1
- package/dist/cli/commands/amend.js +122 -0
- package/dist/cli/commands/amend.js.map +1 -0
- package/dist/cli/commands/doctor.js +300 -0
- package/dist/cli/commands/doctor.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/settle.js +197 -0
- package/dist/cli/commands/settle.js.map +1 -0
- package/dist/cli/commands/status.js +2 -0
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/dispatch-plan.js +47 -0
- package/dist/cli/dispatch-plan.js.map +1 -0
- package/dist/cli/help-groups.js +15 -0
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/main.js +54 -10
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parser.js +26 -0
- package/dist/cli/parser.js.map +1 -1
- package/dist/domain/decision-note.js +166 -3
- package/dist/domain/decision-note.js.map +1 -1
- package/dist/domain/decision-preview.js.map +1 -1
- package/dist/domain/doctor/auth.js +65 -0
- package/dist/domain/doctor/auth.js.map +1 -0
- package/dist/domain/doctor/model.js +82 -0
- package/dist/domain/doctor/model.js.map +1 -0
- package/dist/domain/doctor/operations.js +114 -0
- package/dist/domain/doctor/operations.js.map +1 -0
- package/dist/domain/effective-contract.js +4 -3
- package/dist/domain/effective-contract.js.map +1 -1
- package/dist/domain/flow/answer.js +10 -0
- package/dist/domain/flow/answer.js.map +1 -1
- package/dist/domain/flow/authority.js +124 -0
- package/dist/domain/flow/authority.js.map +1 -1
- package/dist/domain/flow/directive.js +8 -1
- package/dist/domain/flow/directive.js.map +1 -1
- package/dist/domain/flow/run-state.js +276 -34
- package/dist/domain/flow/run-state.js.map +1 -1
- package/dist/domain/obligation-correspondence.js +95 -0
- package/dist/domain/obligation-correspondence.js.map +1 -0
- package/dist/domain/reconciliation.js +142 -25
- package/dist/domain/reconciliation.js.map +1 -1
- package/dist/domain/redaction.js +40 -0
- package/dist/domain/redaction.js.map +1 -1
- package/dist/domain/settlement.js +129 -0
- package/dist/domain/settlement.js.map +1 -0
- package/package.json +1 -1
- package/skills/w/SKILL.md +2 -1
- package/skills/w/commands/README.md +2 -1
- package/skills/w/commands/doctor.md +31 -0
- package/skills/w/commands/resume.md +2 -2
- package/skills/w/context/MANIFEST.json +4 -0
- package/skills/w/loops/plan-exec-loop/LOOP.md +31 -0
|
@@ -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, 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, proposalContractOf, publishApprovalOf, 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
31
|
import { 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, withApproval, withAttempt, withBoundary, withContinuation, withDecisionPreparation, withEvent, withFixPreview, withHandoff, withObservation, withPlanExecBatchStageForTransition, withProposal, withRouteDecisions, withRouteProposal, withScope, withSelectedChoice, } from "../../domain/flow/run-state.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";
|
|
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";
|
|
@@ -44,7 +44,7 @@ import { resolveWorkspaceRootFrom } from "../paths-service.js";
|
|
|
44
44
|
import { commitStoredPlanExecDecision, preparePlanExecDecision, } from "../plan-exec-decision-service.js";
|
|
45
45
|
import { semanticDigest } from "../semantic-operation/protocol.js";
|
|
46
46
|
import { resolveSessionTarget } from "../session-resolver.js";
|
|
47
|
-
import { sourceAliasesOfPlan, validatePlanSourceBoundary, } from "../source-boundary-policy.js";
|
|
47
|
+
import { sourceAliasesOfPlan, validatePlanSourceBoundary, validateSourceBoundedSemantics, } from "../source-boundary-policy.js";
|
|
48
48
|
import { actionDigest, advanceFlowRun, directiveFor, effectsOfTransition, resolveBoundary, } from "./advance.js";
|
|
49
49
|
import { observeCheckout, withObservedCheckouts } from "./checkout-observation.js";
|
|
50
50
|
import { resolveCheckoutCandidates } from "./checkout-observation.js";
|
|
@@ -123,6 +123,7 @@ async function observe(fs, paths, raw, session, git) {
|
|
|
123
123
|
root,
|
|
124
124
|
destinations: await observeDestinations(fs, paths, raw),
|
|
125
125
|
scope: await observeScope(fs, paths, raw),
|
|
126
|
+
plans: await observePlanEvidence(fs, paths, raw),
|
|
126
127
|
checkouts: await observeCheckouts(fs, paths, session, git),
|
|
127
128
|
baselines: await observeSpecBaselines(fs, paths, raw),
|
|
128
129
|
};
|
|
@@ -198,6 +199,71 @@ async function observeSpecBaselines(fs, paths, raw) {
|
|
|
198
199
|
}
|
|
199
200
|
return out;
|
|
200
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Run the source-boundary policy over the plan bytes a proposal is carrying.
|
|
204
|
+
*
|
|
205
|
+
* The same judgment the execution entry makes, one moment earlier — over the
|
|
206
|
+
* artifact's content instead of a file on disk — so a plan accepted when it
|
|
207
|
+
* closes cannot be rejected for the same cause when somebody tries to run it.
|
|
208
|
+
* Nothing is read when the payload carries no artifact.
|
|
209
|
+
*
|
|
210
|
+
* When the WORKSPACE block cannot be read, only the SEMANTIC half runs. The
|
|
211
|
+
* clause-level judgment needs no alias table, and answering an unreadable block
|
|
212
|
+
* with "that alias does not exist" would reject a plan for something the plan
|
|
213
|
+
* did not do.
|
|
214
|
+
*/
|
|
215
|
+
async function observePlanEvidence(fs, paths, raw) {
|
|
216
|
+
const proposed = proposedArtifacts(raw);
|
|
217
|
+
if (proposed.length === 0)
|
|
218
|
+
return [];
|
|
219
|
+
const canon = await resolveCoreDocsCanon(fs, paths);
|
|
220
|
+
// An unreadable canon cannot tell a plan from anything else, and judging a
|
|
221
|
+
// spec's prose by the plan's closure rule would reject what it never promised.
|
|
222
|
+
if (!canon.ok)
|
|
223
|
+
return [];
|
|
224
|
+
const root = await resolveWorkspaceRootFrom(fs, paths);
|
|
225
|
+
const block = await readWorkspaceBlock(fs, root, paths.blockMarkers());
|
|
226
|
+
const declared = block === null ? null : block.fuentes.map((source) => source.alias);
|
|
227
|
+
const out = [];
|
|
228
|
+
for (const artifact of proposed) {
|
|
229
|
+
if (!checkSafeRelativePath(artifact.path).ok)
|
|
230
|
+
continue;
|
|
231
|
+
if (coreDocumentKindForPath(artifact.path, canon.canon) !== "plan")
|
|
232
|
+
continue;
|
|
233
|
+
out.push({
|
|
234
|
+
path: artifact.path,
|
|
235
|
+
failures: declared === null
|
|
236
|
+
? validateSourceBoundedSemantics(artifact.content)
|
|
237
|
+
: validatePlanSourceBoundary(artifact.content, declared),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
/** The `path`/`content` pairs a payload proposes, read as data and judged by nobody. */
|
|
243
|
+
function proposedArtifacts(raw) {
|
|
244
|
+
let parsed;
|
|
245
|
+
try {
|
|
246
|
+
parsed = JSON.parse(raw);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
const artifacts = parsed?.artifacts;
|
|
252
|
+
if (!Array.isArray(artifacts))
|
|
253
|
+
return [];
|
|
254
|
+
const out = [];
|
|
255
|
+
for (const entry of artifacts) {
|
|
256
|
+
const path = entry?.path;
|
|
257
|
+
const content = entry?.content;
|
|
258
|
+
if (typeof path !== "string" || typeof content !== "string")
|
|
259
|
+
continue;
|
|
260
|
+
const relative = path.trim();
|
|
261
|
+
if (relative.length === 0 || out.some((artifact) => artifact.path === relative))
|
|
262
|
+
continue;
|
|
263
|
+
out.push({ path: relative, content });
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
201
267
|
/**
|
|
202
268
|
* The workspace's declared aliases and the named plan's text, when the payload
|
|
203
269
|
* declares a scope at all.
|
|
@@ -433,6 +499,13 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
433
499
|
if ("failure" in scoped) {
|
|
434
500
|
return reject(state, resolved, scoped.failure.message, { code: scoped.failure.code, action: scoped.failure.action }, cost);
|
|
435
501
|
}
|
|
502
|
+
// The closure-evidence gate runs over the proposed bytes BEFORE the seal: a
|
|
503
|
+
// plan whose validation names no observable check of the checkout is refused
|
|
504
|
+
// while it is being written, not when somebody tries to execute it.
|
|
505
|
+
const evidence = planEvidenceFrom(resolved.stopped, parsed.answer, snapshot.plans);
|
|
506
|
+
if (evidence !== null) {
|
|
507
|
+
return reject(state, resolved, evidence.failure.message, { code: evidence.failure.code, action: evidence.failure.action }, cost);
|
|
508
|
+
}
|
|
436
509
|
const sealed = sealFrom(scoped.state, resolved.stopped, parsed.answer, snapshot.destinations, snapshot.baselines);
|
|
437
510
|
if ("failure" in sealed) {
|
|
438
511
|
return reject(state, resolved, sealed.failure.message, { code: sealed.failure.code, action: sealed.failure.action }, cost);
|
|
@@ -440,8 +513,11 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
440
513
|
const preparedForGate = await prepareDecisionForGate(fs, sealed.state, snapshot, parsed.answer, resolved, cost);
|
|
441
514
|
if (!preparedForGate.ok)
|
|
442
515
|
return preparedForGate.decision;
|
|
516
|
+
const settlement = recordSettlement(preparedForGate.state, parsed.answer, resolved, cost);
|
|
517
|
+
if (!settlement.ok)
|
|
518
|
+
return settlement.decision;
|
|
443
519
|
const granted = resolved.kind === "authorization" ? (resolved.authorization?.planned ?? []) : [];
|
|
444
|
-
const approved = grantOf(
|
|
520
|
+
const approved = grantOf(settlement.state, resolved, parsed.answer, granted, journey);
|
|
445
521
|
// All pure scope/proposal checks have now passed. A durable decision is a
|
|
446
522
|
// material write, so it must never land when a later validation of the same
|
|
447
523
|
// answer would refuse the boundary and leave its cursor standing.
|
|
@@ -821,7 +897,7 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
821
897
|
failure: {
|
|
822
898
|
code: structural.code,
|
|
823
899
|
message: structural.message,
|
|
824
|
-
action: "
|
|
900
|
+
action: planBoundaryAction(structural.code, "execution-entry"),
|
|
825
901
|
},
|
|
826
902
|
};
|
|
827
903
|
}
|
|
@@ -843,6 +919,186 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
843
919
|
}
|
|
844
920
|
return { state: withScope(state, { plan, sources }) };
|
|
845
921
|
}
|
|
922
|
+
const SETTLEMENT_OUTCOMES = SETTLEMENT_READINGS.map((reading) => reading.outcome);
|
|
923
|
+
/** The reading each label of the question stands for — from the row's own table. */
|
|
924
|
+
const SETTLEMENT_BY_LABEL = new Map(SETTLEMENT_READINGS.map((reading) => [reading.label, reading.outcome]));
|
|
925
|
+
function recordSettlement(state, answer, resolved, cost) {
|
|
926
|
+
const id = resolved.stopped?.id;
|
|
927
|
+
if (id === "plan-exec.settlement-authoring") {
|
|
928
|
+
return recordSettlementAuthoring(state, answer, resolved, cost);
|
|
929
|
+
}
|
|
930
|
+
if (id === "plan-exec.settlement-question") {
|
|
931
|
+
return recordSettlementAnswer(state, answer, resolved, cost);
|
|
932
|
+
}
|
|
933
|
+
return { ok: true, state };
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* The agent's declaration, checked against what the plan actually owes.
|
|
937
|
+
*
|
|
938
|
+
* Every live compensation, exactly once: a declaration that skips one would let
|
|
939
|
+
* the closure publish a settlement silently missing an obligation, and one that
|
|
940
|
+
* names an obligation nobody owes would decide about work that is not there.
|
|
941
|
+
*
|
|
942
|
+
* `pending` is read two ways, and the difference is the whole design. On an
|
|
943
|
+
* obligation with a single reading it means the work is NOT DONE, and the
|
|
944
|
+
* closure stops here until it is — the boundary stays open and the refusal costs
|
|
945
|
+
* no attempt, because telling the truth must not spend the run's retries. On an
|
|
946
|
+
* ambiguous one it is a PROPOSAL, and the next row is where a person settles it.
|
|
947
|
+
*/
|
|
948
|
+
function recordSettlementAuthoring(state, answer, resolved, cost) {
|
|
949
|
+
const declared = readDeclarations(answer.decisions.settlement);
|
|
950
|
+
const refusal = checkDeclarations(state, declared);
|
|
951
|
+
if (refusal !== null) {
|
|
952
|
+
return { ok: false, decision: reject(state, resolved, refusal.message, refusal, cost) };
|
|
953
|
+
}
|
|
954
|
+
return { ok: true, state: withSettlementDeclarations(state, declared ?? []) };
|
|
955
|
+
}
|
|
956
|
+
/** Every way a declaration can fail to answer for what the plan owes. */
|
|
957
|
+
function checkDeclarations(state, declared) {
|
|
958
|
+
const owed = state.settlement?.compensations ?? [];
|
|
959
|
+
if (declared === null) {
|
|
960
|
+
return {
|
|
961
|
+
code: "FLOW_SETTLEMENT_INVALID",
|
|
962
|
+
message: "la declaración de saldo no es una lista de { note, index, outcome, evidence? }",
|
|
963
|
+
action: `declará una entrada por cada compensación vigente: ${owed.map((o) => `${o.note}[${o.index}]`).join(", ") || "ninguna"}`,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
const missing = owed.filter((obligation) => matching(declared, obligation).length !== 1);
|
|
967
|
+
if (missing.length > 0) {
|
|
968
|
+
return {
|
|
969
|
+
code: "FLOW_SETTLEMENT_INCOMPLETE",
|
|
970
|
+
message: `falta declarar exactamente una vez: ${missing.map((o) => `${o.note}[${o.index}] ${o.text}`).join("; ")}`,
|
|
971
|
+
action: "cada compensación vigente lleva una declaración y sólo una: saltear una publicaría un saldo al que le falta trabajo",
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
const foreign = declared.filter((declaration) => !owed.some((obligation) => obligation.note === declaration.note && obligation.index === declaration.index));
|
|
975
|
+
if (foreign.length > 0) {
|
|
976
|
+
return {
|
|
977
|
+
code: "FLOW_SETTLEMENT_INVALID",
|
|
978
|
+
message: `se declara sobre obligaciones que el plan no debe: ${foreign.map((d) => `${d.note}[${d.index}]`).join(", ")}`,
|
|
979
|
+
action: "el saldo alcanza a las compensaciones vigentes que el tablero enumera, y a ninguna otra",
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
const undone = owed.filter((obligation) => !obligation.legacy && matching(declared, obligation)[0]?.outcome === "pending");
|
|
983
|
+
if (undone.length === 0)
|
|
984
|
+
return null;
|
|
985
|
+
return {
|
|
986
|
+
code: "PLAN_EXEC_SETTLEMENT_PENDING",
|
|
987
|
+
message: `queda compensación sin saldar: ${undone.map((o) => `${o.note}: ${o.text}`).join("; ")}`,
|
|
988
|
+
action: `hacé ese trabajo y volvé con 'aw flow advance --code ${state.session}' para declararla cumplida con su evidencia; el cierre espera acá`,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
/** The obligations of `declared` that name this exact one. */
|
|
992
|
+
function matching(declared, obligation) {
|
|
993
|
+
return declared.filter((declaration) => declaration.note === obligation.note && declaration.index === obligation.index);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* The person's reading, applied to every ambiguous obligation at once.
|
|
997
|
+
*
|
|
998
|
+
* One answer for all of them, which is the degradation the plan declared before
|
|
999
|
+
* this was built: repeating a row is the batch loop's mechanism and it is keyed
|
|
1000
|
+
* to the loop's own transitions. What the answer may NOT do is discharge work
|
|
1001
|
+
* with no evidence behind it — "cumplida" over an obligation nobody proved is
|
|
1002
|
+
* the one reading this boundary must refuse, and it refuses for free.
|
|
1003
|
+
*/
|
|
1004
|
+
function recordSettlementAnswer(state, answer, resolved, cost) {
|
|
1005
|
+
const outcome = SETTLEMENT_BY_LABEL.get(answer.choice ?? "");
|
|
1006
|
+
if (outcome === undefined)
|
|
1007
|
+
return { ok: true, state };
|
|
1008
|
+
const ambiguous = settlementAmbiguous(state);
|
|
1009
|
+
const declared = state.settlement?.declared ?? [];
|
|
1010
|
+
// The row's own consequence says the run stays here, and this is what makes
|
|
1011
|
+
// that true: choosing "still pending" is choosing NOT to close, so advancing
|
|
1012
|
+
// would contradict the sentence the person read before answering.
|
|
1013
|
+
if (outcome === "pending") {
|
|
1014
|
+
return {
|
|
1015
|
+
ok: false,
|
|
1016
|
+
decision: reject(state, resolved, `queda como compensación vigente: ${ambiguous.map((o) => `${o.note}: ${o.text}`).join("; ")}`, {
|
|
1017
|
+
code: "PLAN_EXEC_SETTLEMENT_PENDING",
|
|
1018
|
+
action: `hacé ese trabajo y volvé con 'aw flow advance --code ${state.session}': el cierre espera en la frontera de saldo`,
|
|
1019
|
+
}, cost),
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
if (outcome === "settled") {
|
|
1023
|
+
const unproven = ambiguous.filter((obligation) => matching(declared, obligation)[0]?.evidence === undefined);
|
|
1024
|
+
if (unproven.length > 0) {
|
|
1025
|
+
return {
|
|
1026
|
+
ok: false,
|
|
1027
|
+
decision: reject(state, resolved, `no hay evidencia declarada para: ${unproven.map((o) => `${o.note}: ${o.text}`).join("; ")}`, {
|
|
1028
|
+
code: "PLAN_EXEC_SETTLEMENT_PENDING",
|
|
1029
|
+
action: "una obligación se salda con lo que lo prueba: volvé a la frontera anterior y declará la evidencia, o elegí otra lectura",
|
|
1030
|
+
}, cost),
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
1035
|
+
ok: true,
|
|
1036
|
+
state: withSettlementDeclarations(state, declared.map((declaration) => ambiguous.some((obligation) => obligation.note === declaration.note && obligation.index === declaration.index)
|
|
1037
|
+
? ratified(declaration, outcome)
|
|
1038
|
+
: declaration)),
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* The person's reading, applied — and the agent's evidence dropped with it.
|
|
1043
|
+
*
|
|
1044
|
+
* The class the person ratifies REPLACES the one the run proposed, and evidence
|
|
1045
|
+
* belongs to the proposal it came with: evidence accredits a settlement, so on a
|
|
1046
|
+
* handoff it is evidence of work this lineage did not do. Carrying it across the
|
|
1047
|
+
* ratification was a dead end with no exit — `checkSettlements` refuses an
|
|
1048
|
+
* obligation that brings evidence without being declared settled, so a run whose
|
|
1049
|
+
* agent proposed "done, here is the proof" and whose person answered "that was
|
|
1050
|
+
* somebody else's" could never publish its settlement and never close: the plan
|
|
1051
|
+
* stayed neither executable nor closable, which is the incident this whole plan
|
|
1052
|
+
* exists to end. The proposal is not lost — it is in the run's own trace.
|
|
1053
|
+
*/
|
|
1054
|
+
function ratified(declaration, outcome) {
|
|
1055
|
+
return outcome === "settled"
|
|
1056
|
+
? { ...declaration, outcome }
|
|
1057
|
+
: { note: declaration.note, index: declaration.index, outcome };
|
|
1058
|
+
}
|
|
1059
|
+
function readDeclarations(value) {
|
|
1060
|
+
if (!Array.isArray(value))
|
|
1061
|
+
return null;
|
|
1062
|
+
const out = [];
|
|
1063
|
+
for (const entry of value) {
|
|
1064
|
+
const read = readDeclaration(entry);
|
|
1065
|
+
if (read === null)
|
|
1066
|
+
return null;
|
|
1067
|
+
out.push(read);
|
|
1068
|
+
}
|
|
1069
|
+
return out;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* One declaration, or `null` when it is not one.
|
|
1073
|
+
*
|
|
1074
|
+
* Evidence and outcome are checked together on purpose: "cumplida" without what
|
|
1075
|
+
* proves it is not a slightly worse declaration, it is a discharge nobody can
|
|
1076
|
+
* audit — and evidence attached to work that was NOT declared done is evidence
|
|
1077
|
+
* of nothing, filed where somebody would later read it as a settlement.
|
|
1078
|
+
*/
|
|
1079
|
+
function readDeclaration(entry) {
|
|
1080
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
1081
|
+
return null;
|
|
1082
|
+
const raw = entry;
|
|
1083
|
+
if (typeof raw.note !== "string" || raw.note.length === 0)
|
|
1084
|
+
return null;
|
|
1085
|
+
if (!Number.isInteger(raw.index))
|
|
1086
|
+
return null;
|
|
1087
|
+
if (!SETTLEMENT_OUTCOMES.includes(raw.outcome))
|
|
1088
|
+
return null;
|
|
1089
|
+
const outcome = raw.outcome;
|
|
1090
|
+
const evidence = typeof raw.evidence === "string" ? raw.evidence.trim() : "";
|
|
1091
|
+
if (outcome === "settled" && evidence.length === 0)
|
|
1092
|
+
return null;
|
|
1093
|
+
if (outcome !== "settled" && evidence.length > 0)
|
|
1094
|
+
return null;
|
|
1095
|
+
return {
|
|
1096
|
+
note: raw.note,
|
|
1097
|
+
index: raw.index,
|
|
1098
|
+
outcome,
|
|
1099
|
+
...(outcome === "settled" ? { evidence } : {}),
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
846
1102
|
/**
|
|
847
1103
|
* Prepare the complete decision view one boundary BEFORE the human choice.
|
|
848
1104
|
*
|
|
@@ -985,7 +1241,7 @@ async function registerSelectedDecision(fs, paths, state, snapshot, resolved, co
|
|
|
985
1241
|
}, cost),
|
|
986
1242
|
};
|
|
987
1243
|
}
|
|
988
|
-
const committed = await commitStoredPlanExecDecision(fs, paths, snapshot.root, preparation);
|
|
1244
|
+
const committed = await commitStoredPlanExecDecision(fs, paths, snapshot.root, preparation, state.scope?.plan);
|
|
989
1245
|
if (!committed.ok) {
|
|
990
1246
|
return {
|
|
991
1247
|
ok: false,
|
|
@@ -1066,6 +1322,64 @@ function withStandaloneGuidance(decision, record) {
|
|
|
1066
1322
|
function invalidScope(message, action) {
|
|
1067
1323
|
return { code: "FLOW_SCOPE_INVALID", message, action };
|
|
1068
1324
|
}
|
|
1325
|
+
/**
|
|
1326
|
+
* What to do about a source-boundary failure, said at the moment it was found.
|
|
1327
|
+
*
|
|
1328
|
+
* One generic sentence used to answer all five codes, and it was wrong twice
|
|
1329
|
+
* over: it sent somebody to declare sources that were already declared, and it
|
|
1330
|
+
* sent a sentence of prose to a refinement. Structure goes to the refinement
|
|
1331
|
+
* that owns it; a clause that names no observable check is a phrase to fix where
|
|
1332
|
+
* it is being written, and at a save proposal nothing is even saved yet.
|
|
1333
|
+
*/
|
|
1334
|
+
function planBoundaryAction(code, moment) {
|
|
1335
|
+
const then = moment === "proposal"
|
|
1336
|
+
? "corregilo en los bytes y volvé a proponer la vista previa"
|
|
1337
|
+
: "corregilo con /w:plan-refine antes de volver a entrar a ejecución";
|
|
1338
|
+
switch (code) {
|
|
1339
|
+
case "PLAN_SOURCE_BOUNDARY_MISSING":
|
|
1340
|
+
return `falta la declaración estructural: '> Límite de ejecución: checkout' bajo el título y '> Fuentes:' en cada fase — ${then}`;
|
|
1341
|
+
case "PLAN_SOURCE_UNKNOWN":
|
|
1342
|
+
return `ese alias no está en la tabla Fuentes del bloque WORKSPACE: declaralo ahí, o usá uno de los que ya están — ${then}`;
|
|
1343
|
+
case "PLAN_TASK_SOURCE_OUTSIDE_PHASE":
|
|
1344
|
+
return `la fuente de una tarea es un subconjunto de la de su fase: ajustá una de las dos — ${then}`;
|
|
1345
|
+
case "PLAN_SOURCE_EXTERNAL_CLOSURE":
|
|
1346
|
+
return `una cláusula de cierre no puede apoyarse en una superficie externa: llevá esa comprobación a '## Handoff operativo' y dejá en la cláusula una del checkout — ${then}`;
|
|
1347
|
+
case "PLAN_SOURCE_LOCAL_PROOF_MISSING":
|
|
1348
|
+
return moment === "proposal"
|
|
1349
|
+
? "nombrá en esa cláusula el comando, el archivo o la ruta que produce la comprobación, y volvé a proponer la vista previa"
|
|
1350
|
+
: "es una frase del documento y no su estructura: nombrá en esa cláusula el comando, el archivo o la ruta que produce la comprobación";
|
|
1351
|
+
default:
|
|
1352
|
+
return then;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* The closure-evidence gate, run BEFORE the preview is sealed.
|
|
1357
|
+
*
|
|
1358
|
+
* Position is the whole rule: the rejection has to arrive while the plan is
|
|
1359
|
+
* still being written, so nobody is asked to approve a document the execution
|
|
1360
|
+
* entry would refuse. The judgment is literally the same function, which is what
|
|
1361
|
+
* makes the two moments impossible to disagree.
|
|
1362
|
+
*/
|
|
1363
|
+
function planEvidenceFrom(stopped, answer, observed) {
|
|
1364
|
+
if (proposalContractOf(stopped) === null || answer.artifacts.length === 0)
|
|
1365
|
+
return null;
|
|
1366
|
+
const proposed = new Set(answer.artifacts.map((artifact) => artifact.path.trim()));
|
|
1367
|
+
for (const plan of observed) {
|
|
1368
|
+
if (!proposed.has(plan.path))
|
|
1369
|
+
continue;
|
|
1370
|
+
const failure = plan.failures[0];
|
|
1371
|
+
if (failure === undefined)
|
|
1372
|
+
continue;
|
|
1373
|
+
return {
|
|
1374
|
+
failure: {
|
|
1375
|
+
code: failure.code,
|
|
1376
|
+
message: `'${plan.path}': ${failure.message}`,
|
|
1377
|
+
action: planBoundaryAction(failure.code, "proposal"),
|
|
1378
|
+
},
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1069
1383
|
/**
|
|
1070
1384
|
* Seat the proposal an authoring answer just handed over — sealed, not believed.
|
|
1071
1385
|
*
|
|
@@ -1618,7 +1932,16 @@ function attemptIdentity(state, input, seal, transition) {
|
|
|
1618
1932
|
const batchIteration = currentBatchIteration(state, transition);
|
|
1619
1933
|
const prior = state.attempts.filter((past) => past.invocation_id === seal);
|
|
1620
1934
|
const twin = prior.find((past) => past.request_digest === digest);
|
|
1621
|
-
|
|
1935
|
+
// The next ordinal, asked with the SAME expression `AttemptLedger.record`
|
|
1936
|
+
// validates it against. Counting rows instead is a second reading of one fact,
|
|
1937
|
+
// and the two part the moment a resend is persisted: a recognized resend is
|
|
1938
|
+
// re-evaluated and its row kept wearing the twin's ordinal — deliberately, so
|
|
1939
|
+
// it still counts toward the cap — so the rows say two while the highest
|
|
1940
|
+
// ordinal says one, and the next answer arrives as attempt 3 where the ledger
|
|
1941
|
+
// accepts only 2. Nobody can answer that boundary, and `recover` reads the
|
|
1942
|
+
// chain as coherent because replaying two identical rows never fails.
|
|
1943
|
+
const highest = Math.max(0, ...prior.map((past) => past.attempt));
|
|
1944
|
+
const attempt = twin?.attempt ?? highest + 1;
|
|
1622
1945
|
const parent = attempt === 1
|
|
1623
1946
|
? null
|
|
1624
1947
|
: (prior.find((past) => past.attempt === attempt - 1)?.request_digest ?? null);
|