agentic-workflow-manager 9.12.1 → 9.12.3
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/dist/src/commands/job/index.js +61 -0
- package/dist/src/commands/watch/admission-recovery.js +161 -0
- package/dist/src/commands/watch/apply.js +51 -2
- package/dist/src/commands/watch/generations.js +1 -1
- package/dist/src/commands/watch/index.js +43 -2
- package/dist/src/commands/watch/supervisor.js +202 -64
- package/dist/src/core/journal/requests.js +6 -0
- package/dist/src/core/journal/types.js +78 -4
- package/dist/tests/commands/watch/admission-recovery.test.js +278 -0
- package/dist/tests/commands/watch/plan-lifecycle.test.js +47 -1
- package/dist/tests/commands/watch/reopened-196.test.js +749 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +111 -2
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -251,6 +251,67 @@ function registerJobCommand(program) {
|
|
|
251
251
|
assertAuthenticatedCwd(repo, branch);
|
|
252
252
|
process.stdout.write(JSON.stringify((0, query_1.queryPs)(repo, branch), null, 2) + '\n');
|
|
253
253
|
});
|
|
254
|
+
job.command('ack')
|
|
255
|
+
.description('consulta read-only el acuse durable de una request por ID exacto')
|
|
256
|
+
.argument('<requestId>')
|
|
257
|
+
.action((requestId) => {
|
|
258
|
+
if (!/^req-[A-Za-z0-9-]{1,124}$/.test(requestId))
|
|
259
|
+
throw new Error('requestId invalido');
|
|
260
|
+
const repo = process.cwd();
|
|
261
|
+
const branch = branchOf(repo);
|
|
262
|
+
assertAuthenticatedCwd(repo, branch);
|
|
263
|
+
const read = (0, store_1.readJournal)(repo, branch);
|
|
264
|
+
if (read.corrupt || !read.state) {
|
|
265
|
+
process.stdout.write(JSON.stringify({ requestId, state: 'unverifiable', journal: read.corrupt ? 'corrupt' : 'absent' }) + '\n');
|
|
266
|
+
process.exitCode = 1;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
(0, lock_1.verifyBranchInvariant)(repo, read.state.branch);
|
|
270
|
+
const ack = (0, requests_1.ackFor)(read.state, requestId);
|
|
271
|
+
if (ack) {
|
|
272
|
+
process.stdout.write(JSON.stringify({ requestId, state: ack.outcome === 'applied' ? 'applied' : 'rejected',
|
|
273
|
+
outcome: ack.outcome, ...(ack.resultRef ? { resultRef: ack.resultRef } : {}) }) + '\n');
|
|
274
|
+
if (ack.outcome !== 'applied')
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const problem = read.state.requestProblems.find(item => item.requestId === requestId
|
|
279
|
+
|| (item.kind === 'corrupt' && path_1.default.basename(item.file) === `${requestId}.json`));
|
|
280
|
+
if (problem) {
|
|
281
|
+
process.stdout.write(JSON.stringify({ requestId, state: problem.kind === 'corrupt' ? 'unverifiable' : 'rejected', reasonCode: problem.kind }) + '\n');
|
|
282
|
+
process.exitCode = 1;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
let pending;
|
|
286
|
+
try {
|
|
287
|
+
pending = (0, requests_1.listPendingRequests)(repo, branch).find(item => item.requestId === requestId
|
|
288
|
+
|| (item.corrupt && path_1.default.basename(item.file) === `${requestId}.json`));
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
process.stdout.write(JSON.stringify({ requestId, state: 'unverifiable', reasonCode: 'request-directory' }) + '\n');
|
|
292
|
+
process.exitCode = 1;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// The supervisor may have consumed the request between the first
|
|
296
|
+
// journal snapshot and the queue read. Recheck before saying unknown.
|
|
297
|
+
const latest = (0, store_1.readJournal)(repo, branch);
|
|
298
|
+
if (latest.corrupt || !latest.state) {
|
|
299
|
+
process.stdout.write(JSON.stringify({ requestId, state: 'unverifiable', journal: latest.corrupt ? 'corrupt' : 'absent' }) + '\n');
|
|
300
|
+
process.exitCode = 1;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const latestAck = (0, requests_1.ackFor)(latest.state, requestId);
|
|
304
|
+
if (latestAck) {
|
|
305
|
+
process.stdout.write(JSON.stringify({ requestId, state: latestAck.outcome === 'applied' ? 'applied' : 'rejected',
|
|
306
|
+
outcome: latestAck.outcome, ...(latestAck.resultRef ? { resultRef: latestAck.resultRef } : {}) }) + '\n');
|
|
307
|
+
if (latestAck.outcome !== 'applied')
|
|
308
|
+
process.exitCode = 1;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
process.stdout.write(JSON.stringify({ requestId, state: pending?.corrupt ? 'unverifiable' : pending ? 'pending' : 'unknown' }) + '\n');
|
|
312
|
+
if (!pending || pending.corrupt)
|
|
313
|
+
process.exitCode = 1;
|
|
314
|
+
});
|
|
254
315
|
job.command('list').action(() => {
|
|
255
316
|
const repo = process.cwd();
|
|
256
317
|
const branch = branchOf(repo);
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.recoverAdmissionCustody = recoverAdmissionCustody;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const validate_1 = require("../../core/plan/validate");
|
|
9
|
+
const capabilities_1 = require("../../core/model-policy/capabilities");
|
|
10
|
+
const event_store_1 = require("../../core/model-policy/event-store");
|
|
11
|
+
const local_event_scope_1 = require("../../core/model-policy/local-event-scope");
|
|
12
|
+
const store_1 = require("../../core/journal/store");
|
|
13
|
+
const types_1 = require("../../core/journal/types");
|
|
14
|
+
const process_1 = require("../../core/journal/process");
|
|
15
|
+
const adapter_1 = require("../../core/journal/adapter");
|
|
16
|
+
const generations_1 = require("./generations");
|
|
17
|
+
const lock_1 = require("./lock");
|
|
18
|
+
const supervisor_1 = require("./supervisor");
|
|
19
|
+
function contextFor(input) {
|
|
20
|
+
if (!(0, adapter_1.isWatchProvider)(input.provider))
|
|
21
|
+
throw new Error('provider de recovery invalido');
|
|
22
|
+
if (!(0, adapter_1.isControllerAutonomy)(input.controllerAutonomy))
|
|
23
|
+
throw new Error('postura del controller invalida');
|
|
24
|
+
if (!input.routingIdentity)
|
|
25
|
+
throw new Error('runtime de recovery requerido');
|
|
26
|
+
const runtime = (0, capabilities_1.validateRuntimeKey)({ target: input.provider, ...input.routingIdentity });
|
|
27
|
+
return { provider: input.provider, runtime: { kind: runtime.kind, version: runtime.version, accountScopeDigest: runtime.accountScopeDigest }, controllerAutonomy: input.controllerAutonomy };
|
|
28
|
+
}
|
|
29
|
+
function sameContext(a, b) {
|
|
30
|
+
return a.provider === b.provider && a.controllerAutonomy === b.controllerAutonomy
|
|
31
|
+
&& a.runtime?.kind === b.runtime?.kind && a.runtime?.version === b.runtime?.version
|
|
32
|
+
&& a.runtime?.accountScopeDigest === b.runtime?.accountScopeDigest;
|
|
33
|
+
}
|
|
34
|
+
function admissionCustodyCause(reason) {
|
|
35
|
+
if (!reason || (reason.match(/ADMISSION_[A-Z_]+/g) ?? []).length !== 1)
|
|
36
|
+
return undefined;
|
|
37
|
+
const old = reason.startsWith('admisión compacta desatendida bloqueada antes de dispatch: ');
|
|
38
|
+
if ((old || reason.startsWith('admisión currentness no verificable tras 3 reintentos: '))
|
|
39
|
+
&& reason.includes('ADMISSION_CURRENTNESS_BLOCKED:'))
|
|
40
|
+
return 'currentness';
|
|
41
|
+
if ((old || reason.startsWith('admisión sensors no verificable tras 3 reintentos: '))
|
|
42
|
+
&& reason.includes('ADMISSION_SENSORS_BLOCKED:'))
|
|
43
|
+
return 'sensors';
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
function assertNoAmbiguousWork(repoRoot, branch, state) {
|
|
47
|
+
if ((0, types_1.activeRequestProblems)(state).length > 0)
|
|
48
|
+
throw new Error('recovery bloqueado: request rechazada o corrupta activa');
|
|
49
|
+
for (const generation of state.generations) {
|
|
50
|
+
if ((0, generations_1.controllerGenerationHasUnresolvedClaim)(repoRoot, branch, generation))
|
|
51
|
+
throw new Error('recovery bloqueado: claim de controller sin identidad demostrable');
|
|
52
|
+
for (const ref of [generation.processRef, generation.wrapperRef]) {
|
|
53
|
+
if (ref && ((0, process_1.refIsAlive)(ref) || !(0, process_1.groupIsGone)(ref.processGroup)))
|
|
54
|
+
throw new Error('recovery bloqueado: controller vivo o ambiguo');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const job of Object.values(state.jobs)) {
|
|
58
|
+
if (!['exited', 'cancelled'].includes(job.executionState))
|
|
59
|
+
throw new Error(`recovery bloqueado: job ${job.id} activo o ambiguo`);
|
|
60
|
+
for (const ref of [job.processRef, job.wrapperRef]) {
|
|
61
|
+
if (ref && ((0, process_1.refIsAlive)(ref) || !(0, process_1.groupIsGone)(ref.processGroup)))
|
|
62
|
+
throw new Error(`recovery bloqueado: job ${job.id} vivo o ambiguo`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Explicit offline transition. The journal record is the authoritative audit;
|
|
67
|
+
* the event is supplementary, so retry after a crash cannot duplicate the
|
|
68
|
+
* state transition or silently discard the original custody reason. */
|
|
69
|
+
async function recoverAdmissionCustody(repoRoot, branch, input, dispatchAdmission) {
|
|
70
|
+
if (typeof repoRoot !== 'string' || !path_1.default.isAbsolute(repoRoot) || typeof branch !== 'string' || branch.length === 0
|
|
71
|
+
|| !input || typeof input !== 'object' || typeof input.reason !== 'string'
|
|
72
|
+
|| input.reason.trim().length === 0 || input.reason.length > 1024
|
|
73
|
+
|| (input.generationToken !== undefined && (typeof input.generationToken !== 'string' || input.generationToken.length === 0))) {
|
|
74
|
+
throw new Error('parametros de recovery invalidos');
|
|
75
|
+
}
|
|
76
|
+
const context = contextFor(input);
|
|
77
|
+
const lock = (0, lock_1.acquireLock)(repoRoot);
|
|
78
|
+
try {
|
|
79
|
+
const read = (0, store_1.readJournal)(repoRoot, branch);
|
|
80
|
+
if (read.corrupt || !read.state || read.state.schema !== 2 || !read.state.planBinding)
|
|
81
|
+
throw new Error('recovery requiere journal schema-2 vigente');
|
|
82
|
+
const state = read.state;
|
|
83
|
+
(0, lock_1.verifyBranchInvariant)(repoRoot, state.branch);
|
|
84
|
+
const previous = state.admissionRecoveries?.at(-1);
|
|
85
|
+
if (state.cycle.status === 'IN_PROGRESS' && previous
|
|
86
|
+
&& previous.reason === input.reason && previous.generationToken === input.generationToken
|
|
87
|
+
&& sameContext(previous.context, context))
|
|
88
|
+
return 'already-recovered';
|
|
89
|
+
if (state.cycle.status !== 'BLOCKED' || !admissionCustodyCause(state.cycle.blockedReason)) {
|
|
90
|
+
throw new Error('recovery solo admite custodia causada por admisión currentness o sensores');
|
|
91
|
+
}
|
|
92
|
+
const latest = state.generations.at(-1);
|
|
93
|
+
if (latest?.token !== input.generationToken || (latest === undefined && input.generationToken !== undefined)) {
|
|
94
|
+
throw new Error('generation de recovery no es la vigente');
|
|
95
|
+
}
|
|
96
|
+
if (latest?.provider && latest.provider !== context.provider)
|
|
97
|
+
throw new Error('provider de generation no coincide con identidad de recovery');
|
|
98
|
+
if (state.admissionContext && !sameContext(state.admissionContext, context))
|
|
99
|
+
throw new Error('identidad de runtime o postura no coincide con la admisión original');
|
|
100
|
+
assertNoAmbiguousWork(repoRoot, branch, state);
|
|
101
|
+
const prospective = structuredClone(state);
|
|
102
|
+
prospective.cycle.status = 'IN_PROGRESS';
|
|
103
|
+
prospective.cycle.blockedReason = undefined;
|
|
104
|
+
const independentBlocker = (0, supervisor_1.recoveryWhitelistBlocker)(prospective);
|
|
105
|
+
if (independentBlocker)
|
|
106
|
+
throw new Error(`recovery bloqueado por causa independiente: ${independentBlocker}`);
|
|
107
|
+
const binding = state.planBinding;
|
|
108
|
+
if (!binding)
|
|
109
|
+
throw new Error('binding del plan ausente');
|
|
110
|
+
const plan = (0, validate_1.validatePlanFile)(binding.path, repoRoot);
|
|
111
|
+
if (plan.state !== 'valid' || plan.planDigest !== binding.digest || plan.schema !== binding.schema
|
|
112
|
+
|| (binding.executionDigest !== undefined && plan.executionDigest !== binding.executionDigest)) {
|
|
113
|
+
throw new Error('binding del plan alterado: recovery denegado');
|
|
114
|
+
}
|
|
115
|
+
// A v1 plan does not ask the admission engine to evaluate routing.
|
|
116
|
+
// Recovery still promises a current native receipt for the asserted
|
|
117
|
+
// provider/runtime, so check its machine seal and live local scope
|
|
118
|
+
// explicitly; an operator assertion alone is not evidence.
|
|
119
|
+
const runtime = (0, capabilities_1.validateRuntimeKey)({ target: context.provider, ...context.runtime });
|
|
120
|
+
const stored = (0, event_store_1.readStoredEventReceipt)(runtime);
|
|
121
|
+
if (stored.state !== 'present')
|
|
122
|
+
throw new Error(`recibo nativo ausente o invalido para ${context.provider}: ejecuta awm model-policy setup --provider ${context.provider}`);
|
|
123
|
+
const local = await (0, local_event_scope_1.queryLocalEventScope)(repoRoot, context.provider, runtime.kind);
|
|
124
|
+
if (local.state !== 'current' || local.scope.runtime.target !== runtime.target
|
|
125
|
+
|| local.scope.runtime.kind !== runtime.kind || local.scope.runtime.version !== runtime.version
|
|
126
|
+
|| local.scope.runtime.accountScopeDigest !== runtime.accountScopeDigest
|
|
127
|
+
|| stored.receipt.runtime.target !== runtime.target || stored.receipt.runtime.kind !== runtime.kind
|
|
128
|
+
|| stored.receipt.runtime.version !== runtime.version || stored.receipt.runtime.accountScopeDigest !== runtime.accountScopeDigest
|
|
129
|
+
|| stored.receipt.binaryDigest !== local.scope.binaryDigest || stored.receipt.configDigest !== local.scope.configDigest
|
|
130
|
+
|| Date.parse(stored.receipt.recordedAt) > Date.now()) {
|
|
131
|
+
throw new Error(`recibo nativo no coincide con runtime/cuenta local de ${context.provider}`);
|
|
132
|
+
}
|
|
133
|
+
const cfg = { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, provider: context.provider,
|
|
134
|
+
routingIdentity: context.runtime, controllerAutonomy: context.controllerAutonomy };
|
|
135
|
+
const report = await (dispatchAdmission ?? (0, supervisor_1.admissionForConfig)(repoRoot, branch, cfg))();
|
|
136
|
+
if (report.state !== 'admitted' || report.planState !== 'valid' || report.executionMode !== 'desatendido'
|
|
137
|
+
|| report.journal !== 'current' || report.currentness !== 'current' || report.sensors !== 'pass'
|
|
138
|
+
|| (report.planDigest !== undefined && report.planDigest !== binding.digest)) {
|
|
139
|
+
throw new Error(`admisión estricta todavía bloqueada: ${report.diagnostics.map(item => `${item.code}: ${item.message}`).join('; ')}`);
|
|
140
|
+
}
|
|
141
|
+
const candidate = structuredClone(state);
|
|
142
|
+
candidate.admissionRecoveries ??= [];
|
|
143
|
+
candidate.admissionRecoveries.push({ at: new Date().toISOString(), reason: input.reason,
|
|
144
|
+
blockedReason: state.cycle.blockedReason, generationToken: input.generationToken,
|
|
145
|
+
context, planDigest: binding.digest,
|
|
146
|
+
assertion: state.admissionContext ? 'recorded' : 'legacy-operator-asserted' });
|
|
147
|
+
candidate.admissionContext = context;
|
|
148
|
+
candidate.admissionRetry = undefined;
|
|
149
|
+
candidate.cycle.status = 'IN_PROGRESS';
|
|
150
|
+
candidate.cycle.blockedReason = undefined;
|
|
151
|
+
// Existing generations and nextAction remain untouched. The next watch
|
|
152
|
+
// tick performs strict admission again before reconciling either.
|
|
153
|
+
(0, store_1.writeJournal)(repoRoot, branch, candidate);
|
|
154
|
+
(0, store_1.appendEvent)(repoRoot, branch, { kind: 'admission-recovered', generationToken: input.generationToken,
|
|
155
|
+
reason: input.reason, blockedReason: state.cycle.blockedReason, planDigest: binding.digest });
|
|
156
|
+
return 'recovered';
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
(0, lock_1.releaseLock)(repoRoot, lock);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -155,6 +155,14 @@ function applyRequestToState(s, env, digest, repoRoot, eventScope) {
|
|
|
155
155
|
const capabilities = (0, capabilities_1.readCapabilities)((0, capabilities_1.validateRuntimeKey)(envelope.runtime), checkedAt);
|
|
156
156
|
return { checkedAt, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined };
|
|
157
157
|
} }, p.at ?? now());
|
|
158
|
+
const routingAttempt = reserved.state.routingAttempts?.find(item => item.id === reserved.attemptId);
|
|
159
|
+
if (!routingAttempt)
|
|
160
|
+
throw new Error('routing reservation produced no attempt');
|
|
161
|
+
const priorReservationAck = routingAttempt.reservationRequestId === undefined ? undefined
|
|
162
|
+
: reserved.state.appliedRequests[routingAttempt.reservationRequestId];
|
|
163
|
+
if (!priorReservationAck || priorReservationAck.outcome !== 'applied' || priorReservationAck.resultRef !== routingAttempt.id) {
|
|
164
|
+
routingAttempt.reservationRequestId = env.requestId;
|
|
165
|
+
}
|
|
158
166
|
Object.assign(s, reserved.state);
|
|
159
167
|
(0, requests_1.applyOutcome)(s, { ...base, outcome: 'applied', resultRef: reserved.attemptId });
|
|
160
168
|
return;
|
|
@@ -166,6 +174,15 @@ function applyRequestToState(s, env, digest, repoRoot, eventScope) {
|
|
|
166
174
|
const attempt = s.routingAttempts?.find(item => item.id === p.attemptId);
|
|
167
175
|
if (!attempt)
|
|
168
176
|
throw new Error('routing-observe attempt is unknown');
|
|
177
|
+
if (s.planBinding?.schema === 'compact-slices/v2' && attempt.envelope.role === 'implementer') {
|
|
178
|
+
if (attempt.nativeEvidenceRequired !== true)
|
|
179
|
+
throw new Error('routing-observe v2 requires a native event receipt');
|
|
180
|
+
const dispatch = s.dispatches.find(item => item.routingAttemptId === attempt.id);
|
|
181
|
+
const ack = dispatch?.dispatchRequestId ? s.appliedRequests[dispatch.dispatchRequestId] : undefined;
|
|
182
|
+
if (!dispatch || ack?.outcome !== 'applied' || ack.requestId !== dispatch.dispatchRequestId
|
|
183
|
+
|| ack.resultRef !== dispatch.id)
|
|
184
|
+
throw new Error('routing-observe requires applied dispatch ack');
|
|
185
|
+
}
|
|
169
186
|
let key = null;
|
|
170
187
|
if (attempt.nativeEvidenceRequired) {
|
|
171
188
|
try {
|
|
@@ -454,8 +471,40 @@ function applyRequestToState(s, env, digest, repoRoot, eventScope) {
|
|
|
454
471
|
const taskId = p.taskId;
|
|
455
472
|
if (!s.tasks.some((task) => task.id === taskId))
|
|
456
473
|
throw new Error('register --entity dispatch: taskId desconocido');
|
|
457
|
-
|
|
458
|
-
|
|
474
|
+
const v2 = s.planBinding?.schema === 'compact-slices/v2';
|
|
475
|
+
let routingAttemptId;
|
|
476
|
+
let reservedForNewDispatch = true;
|
|
477
|
+
if (v2) {
|
|
478
|
+
if (typeof p.routingAttemptId !== 'string' || p.routingAttemptId.length === 0)
|
|
479
|
+
throw new Error('register --entity dispatch v2 requiere routingAttemptId');
|
|
480
|
+
const attempt = s.routingAttempts?.find(candidate => candidate.id === p.routingAttemptId);
|
|
481
|
+
if (!attempt || attempt.envelope.role !== 'implementer'
|
|
482
|
+
|| attempt.envelope.sliceId !== taskId || attempt.envelope.planDigest !== s.planBinding.digest
|
|
483
|
+
|| attempt.envelope.executionDigest !== s.planBinding.executionDigest
|
|
484
|
+
|| !attempt.reservationRequestId
|
|
485
|
+
|| s.appliedRequests[attempt.reservationRequestId]?.outcome !== 'applied'
|
|
486
|
+
|| s.appliedRequests[attempt.reservationRequestId]?.requestId !== attempt.reservationRequestId
|
|
487
|
+
|| s.appliedRequests[attempt.reservationRequestId]?.resultRef !== attempt.id) {
|
|
488
|
+
throw new Error('register --entity dispatch: routing reservation for this slice and plan lacks applied ack');
|
|
489
|
+
}
|
|
490
|
+
routingAttemptId = attempt.id;
|
|
491
|
+
if (attempt.nativeEvidenceRequired !== true)
|
|
492
|
+
throw new Error('register --entity dispatch v2 requires native event receipt');
|
|
493
|
+
reservedForNewDispatch = attempt.state === 'reserved';
|
|
494
|
+
}
|
|
495
|
+
else if (p.routingAttemptId !== undefined)
|
|
496
|
+
throw new Error('register --entity dispatch: routingAttemptId solo para compact v2');
|
|
497
|
+
const existing = s.dispatches.find(d => d.id === dispatchId);
|
|
498
|
+
if (existing && (existing.taskId !== taskId || existing.routingAttemptId !== routingAttemptId)) {
|
|
499
|
+
throw new Error('register --entity dispatch: dispatchId conflictivo');
|
|
500
|
+
}
|
|
501
|
+
if (!existing) {
|
|
502
|
+
if (routingAttemptId && s.dispatches.some(d => d.routingAttemptId === routingAttemptId)) {
|
|
503
|
+
throw new Error('register --entity dispatch: routing reservation ya tiene un dispatch');
|
|
504
|
+
}
|
|
505
|
+
if (!reservedForNewDispatch)
|
|
506
|
+
throw new Error('register --entity dispatch: routing attempt ya no esta reservado');
|
|
507
|
+
s.dispatches.push({ id: dispatchId, taskId, at: now(), ...(routingAttemptId ? { routingAttemptId, dispatchRequestId: env.requestId } : {}) });
|
|
459
508
|
const task = s.tasks.find((t) => t.id === taskId);
|
|
460
509
|
if (task !== undefined)
|
|
461
510
|
task.attempts += 1;
|
|
@@ -95,7 +95,7 @@ function promptForGeneration(gen) {
|
|
|
95
95
|
'resume-next-action': 'Retoma el next_action vigente del journal.',
|
|
96
96
|
'reconcile-active-jobs': 'Reconcilia los jobs activos existentes del journal sin duplicarlos.',
|
|
97
97
|
};
|
|
98
|
-
return `${instructions[gen.resumeAction.kind]}\nGeneracion activa: ${gen.token}. Incluye --generation ${gen.token} en
|
|
98
|
+
return `${instructions[gen.resumeAction.kind]}\nGeneracion activa: ${gen.token}. Incluye --generation ${gen.token} solo en awm job request, register, verdict, controller-heartbeat, routing-reserve y routing-observe. Ejecuta awm job reconcile sin --generation; awm job ack <requestId> tambien es de solo lectura y no acepta ese flag. En compact v2: espera el ack applied de routing-reserve, toma su resultRef como routingAttemptId, registra dispatch con ese routingAttemptId y espera su ack applied antes de lanzar al implementador nativo. Ningun implementador escribe antes de ese ack. Luego emite routing-observe y espera su ack; un recibo de emision no es un ack aplicado.`;
|
|
99
99
|
}
|
|
100
100
|
function recoveryAction(action) {
|
|
101
101
|
if (!(0, types_1.isControllerRecoveryAction)(action))
|
|
@@ -7,6 +7,7 @@ exports.registerWatchCommand = registerWatchCommand;
|
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const init_1 = require("./init");
|
|
9
9
|
const request_recovery_1 = require("./request-recovery");
|
|
10
|
+
const admission_recovery_1 = require("./admission-recovery");
|
|
10
11
|
const supervisor_1 = require("./supervisor");
|
|
11
12
|
const capabilities_1 = require("../../core/model-policy/capabilities");
|
|
12
13
|
const adapter_1 = require("../../core/journal/adapter");
|
|
@@ -142,8 +143,18 @@ function registerWatchCommand(program) {
|
|
|
142
143
|
...(opts.controllerAutonomy === undefined ? {} : { controllerAutonomy: opts.controllerAutonomy }),
|
|
143
144
|
};
|
|
144
145
|
process.stdout.write(`awm watch: supervisor activo (${cfg.provider}) — Ctrl-C para terminar\n`);
|
|
145
|
-
await (0, supervisor_1.runSupervisorLoop)(repo, branch, cfg);
|
|
146
|
-
|
|
146
|
+
const outcome = await (0, supervisor_1.runSupervisorLoop)(repo, branch, cfg);
|
|
147
|
+
const final = (0, archive_unused_1.watchJournalStatus)(repo, branch);
|
|
148
|
+
if (outcome === 'complete' && final.state === 'present' && final.cycleState === 'COMPLETE') {
|
|
149
|
+
process.stdout.write('gate verde: ciclo COMPLETE — drenado, lock liberado, apagando\n');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
process.stdout.write(`awm watch: ${outcome === 'stopped' ? 'detenido' : 'track congelado'}; ciclo ${final.cycleState ?? final.state} — sin certificacion de cierre\n`);
|
|
153
|
+
if (outcome === 'stopped')
|
|
154
|
+
process.exitCode = 130;
|
|
155
|
+
else if (outcome === 'complete' || final.state !== 'present')
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
}
|
|
147
158
|
});
|
|
148
159
|
watch.command('recover-request')
|
|
149
160
|
.description('reconcilia una task rechazada con una request correctiva aplicada bajo lock exclusivo')
|
|
@@ -168,6 +179,36 @@ function registerWatchCommand(program) {
|
|
|
168
179
|
process.exitCode = 1;
|
|
169
180
|
}
|
|
170
181
|
});
|
|
182
|
+
watch.command('recover-admission')
|
|
183
|
+
.description('reanuda offline una custodia por admisión tras una nueva comprobación estricta')
|
|
184
|
+
.option('--generation <token>', 'token de la ultima generacion; omitir solo si no existe')
|
|
185
|
+
.requiredOption('--reason <text>', 'motivo explícito de la decisión del operador')
|
|
186
|
+
.action(async (opts, command) => {
|
|
187
|
+
const repo = process.cwd();
|
|
188
|
+
try {
|
|
189
|
+
const branch = currentBranch(repo);
|
|
190
|
+
(0, context_1.resolveCommandContext)(repo, branch);
|
|
191
|
+
const parent = command.parent?.opts() ?? {};
|
|
192
|
+
if (command.parent?.getOptionValueSource('provider') !== 'cli')
|
|
193
|
+
throw new Error('recover-admission requiere --provider explícito');
|
|
194
|
+
if (!(0, adapter_1.isControllerAutonomy)(parent.controllerAutonomy))
|
|
195
|
+
throw new Error('recover-admission requiere --controller-autonomy valido');
|
|
196
|
+
if (parent.runtimeKind === undefined || parent.runtimeVersion === undefined || parent.accountScopeDigest === undefined) {
|
|
197
|
+
throw new Error('recover-admission requiere --runtime-kind, --runtime-version y --account-scope-digest');
|
|
198
|
+
}
|
|
199
|
+
const outcome = await (0, admission_recovery_1.recoverAdmissionCustody)(repo, branch, {
|
|
200
|
+
provider: parent.provider,
|
|
201
|
+
routingIdentity: { kind: parent.runtimeKind, version: parent.runtimeVersion, accountScopeDigest: parent.accountScopeDigest },
|
|
202
|
+
controllerAutonomy: parent.controllerAutonomy,
|
|
203
|
+
generationToken: opts.generation, reason: opts.reason,
|
|
204
|
+
});
|
|
205
|
+
process.stdout.write(JSON.stringify({ recovered: true, outcome, generationToken: opts.generation ?? null }) + '\n');
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
process.stderr.write(`${error.message}\n`);
|
|
209
|
+
process.exitCode = 1;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
171
212
|
watch
|
|
172
213
|
.command('rebind')
|
|
173
214
|
.description('reconcilia intencionalmente el binding desatendido tras un cambio válido del ciclo del plan')
|