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.
@@ -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
- if (!s.dispatches.some((d) => d.id === dispatchId)) {
458
- s.dispatches.push({ id: dispatchId, taskId, at: now() });
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 cada comando awm job.`;
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
- process.stdout.write('gate verde: ciclo COMPLETE — drenado, lock liberado, apagando\n');
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')