agentic-workflow-manager 9.7.0 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/src/commands/doctor.js +4 -2
- package/dist/src/commands/export.js +3 -0
- package/dist/src/commands/init.js +10 -2
- package/dist/src/commands/job/exec-wrapper.js +53 -3
- package/dist/src/commands/plan/index.js +124 -6
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +7 -5
- package/dist/src/commands/registry/install-bundles.js +4 -4
- package/dist/src/commands/registry/status.js +4 -4
- package/dist/src/commands/sensors/bootstrap.js +36 -5
- package/dist/src/commands/sensors/compatibility/contract.js +19 -4
- package/dist/src/commands/sensors/compatibility/discovery.js +23 -3
- package/dist/src/commands/sensors/compatibility/live.js +18 -1
- package/dist/src/commands/sensors/compatibility/probe.js +8 -2
- package/dist/src/commands/sensors/compatibility/resolve.js +5 -2
- package/dist/src/commands/sensors/coverage/evidence.js +19 -10
- package/dist/src/commands/sensors/coverage/index.js +11 -2
- package/dist/src/commands/sensors/coverage/resolve.js +10 -0
- package/dist/src/commands/sensors/exec.js +16 -1
- package/dist/src/commands/sensors/migrate.js +62 -2
- package/dist/src/commands/sensors/prepare.js +2 -0
- package/dist/src/commands/sensors/result.js +2 -0
- package/dist/src/commands/sensors/run.js +64 -1
- package/dist/src/commands/sensors/status.js +6 -4
- package/dist/src/commands/sync.js +2 -1
- package/dist/src/commands/update.js +3 -1
- package/dist/src/commands/watch/archive-unused.js +138 -0
- package/dist/src/commands/watch/generations.js +32 -9
- package/dist/src/commands/watch/index.js +108 -2
- package/dist/src/commands/watch/init.js +149 -18
- package/dist/src/commands/watch/supervisor.js +167 -13
- package/dist/src/commands/watch/teardown-driver.js +13 -13
- package/dist/src/commands/watch/track-state.js +59 -0
- package/dist/src/commands/watch/tracks.js +38 -102
- package/dist/src/core/admission/index.js +181 -0
- package/dist/src/core/admission/registry-contracts.js +290 -0
- package/dist/src/core/bundle-install.js +2 -2
- package/dist/src/core/bundles.js +92 -19
- package/dist/src/core/diagnostics/context.js +1 -1
- package/dist/src/core/export/index.js +1 -1
- package/dist/src/core/export/resolve.js +2 -2
- package/dist/src/core/journal/fingerprint.js +35 -1
- package/dist/src/core/journal/paths.js +11 -0
- package/dist/src/core/journal/store.js +146 -4
- package/dist/src/core/journal/types.js +35 -1
- package/dist/src/core/migration/historical-provenance.js +18 -0
- package/dist/src/core/migration/index.js +322 -0
- package/dist/src/core/plan/identity.js +75 -0
- package/dist/src/core/plan/validate.js +178 -64
- package/dist/src/core/reconciliation.js +1 -1
- package/dist/src/index.js +46 -39
- package/dist/src/utils/grouping.js +1 -1
- package/dist/src/utils/registry-view.js +1 -1
- package/dist/tests/commands/bundle-diagnostics.test.js +145 -0
- package/dist/tests/commands/export.test.js +28 -2
- package/dist/tests/commands/job/exec-wrapper.test.js +16 -0
- package/dist/tests/commands/job/gate-reconcile.test.js +24 -10
- package/dist/tests/commands/multi-agent-targeting.test.js +1 -1
- package/dist/tests/commands/pin.test.js +23 -23
- package/dist/tests/commands/plan/index.test.js +630 -14
- package/dist/tests/commands/sensors/bootstrap.test.js +57 -4
- package/dist/tests/commands/sensors/compatibility/contract.test.js +21 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +19 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +14 -1
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +17 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +51 -27
- package/dist/tests/commands/sensors/coverage/resolve.test.js +51 -0
- package/dist/tests/commands/sensors/exec.test.js +112 -0
- package/dist/tests/commands/sensors/run-changed.test.js +11 -0
- package/dist/tests/commands/sensors/run.test.js +15 -2
- package/dist/tests/commands/sensors/status.test.js +3 -3
- package/dist/tests/commands/watch/e2e-crash.test.js +126 -11
- package/dist/tests/commands/watch/generations.test.js +113 -4
- package/dist/tests/commands/watch/plan-lifecycle.test.js +263 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +247 -49
- package/dist/tests/commands/watch/track-finalize.test.js +20 -3
- package/dist/tests/commands/watch/track-freeze.test.js +43 -30
- package/dist/tests/commands/watch/track-state-dependency.test.js +15 -0
- package/dist/tests/commands/watch/watch-init.test.js +244 -0
- package/dist/tests/core/admission/index.test.js +87 -0
- package/dist/tests/core/bundle-install.test.js +36 -0
- package/dist/tests/core/bundles-multiroot.test.js +24 -2
- package/dist/tests/core/bundles-overrides.test.js +10 -7
- package/dist/tests/core/bundles.test.js +75 -6
- package/dist/tests/core/diagnostics/context.test.js +2 -2
- package/dist/tests/core/export/engine.test.js +1 -1
- package/dist/tests/core/export/resolve.test.js +1 -1
- package/dist/tests/core/init/mutation-targets.test.js +1 -1
- package/dist/tests/core/init/orchestrator.test.js +1 -1
- package/dist/tests/core/init/steps.test.js +1 -1
- package/dist/tests/core/journal/fingerprint.test.js +22 -0
- package/dist/tests/core/journal/store.test.js +53 -0
- package/dist/tests/core/journal/types.test.js +7 -0
- package/dist/tests/core/migration/facts.test.js +289 -0
- package/dist/tests/core/migration/historical-provenance.test.js +25 -0
- package/dist/tests/core/plan/validate.test.js +320 -6
- package/dist/tests/core/reconciliation.test.js +1 -1
- package/dist/tests/core/sync-profile-atomicity.test.js +1 -1
- package/dist/tests/integration/pack-e2e.test.js +4 -1
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +7 -3
- package/dist/tests/integration/sensor-bootstrap.e2e.test.js +37 -0
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +12 -10
- package/dist/tests/providers/injection-config.test.js +14 -6
- package/dist/tests/structural/bundle-skill-reference-contract.test.js +56 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +29 -24
- package/dist/tests/structural/native-release-artifacts.test.js +16 -0
- package/dist/tests/utils/grouping.test.js +2 -2
- package/dist/tests/utils/registry-view.test.js +7 -7
- package/package.json +2 -2
- 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
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Supervisor = exports.DEFAULT_SUPERVISOR_CONFIG = void 0;
|
|
4
4
|
exports.runSupervisorLoop = runSupervisorLoop;
|
|
5
|
-
// Loop foreground (R4.4/R4.5): tick = apply -> collect/spawn -> stall -> gate.
|
|
6
|
-
// COMPLETE exige gate verde (que exige cero vivos): drenaje ANTES de declarar.
|
|
7
|
-
// Custodia BLOCKED: el loop sigue, el lock NO se libera, nada se mata.
|
|
8
5
|
const store_1 = require("../../core/journal/store");
|
|
9
6
|
const fingerprint_1 = require("../../core/journal/fingerprint");
|
|
7
|
+
const validate_1 = require("../../core/plan/validate");
|
|
8
|
+
const registry_contracts_1 = require("../../core/admission/registry-contracts");
|
|
9
|
+
const config_1 = require("../../utils/config");
|
|
10
10
|
const adapter_1 = require("../../core/journal/adapter");
|
|
11
11
|
const process_1 = require("../../core/journal/process");
|
|
12
12
|
const gate_1 = require("../job/gate");
|
|
@@ -16,6 +16,20 @@ const apply_1 = require("./apply");
|
|
|
16
16
|
const runner_1 = require("./runner");
|
|
17
17
|
const tracks_1 = require("./tracks");
|
|
18
18
|
const generations_1 = require("./generations");
|
|
19
|
+
function defaultDispatchAdmission(repoRoot, branch, provider) {
|
|
20
|
+
return async () => {
|
|
21
|
+
const observed = (0, store_1.readJournal)(repoRoot, branch);
|
|
22
|
+
const binding = observed.state?.schema === 2 ? observed.state.planBinding : undefined;
|
|
23
|
+
if (!binding || binding.executionMode !== 'desatendido') {
|
|
24
|
+
return { state: 'blocked', planState: 'invalid', journal: observed.corrupt ? 'corrupt' : 'missing', currentness: 'not-checked', sensors: 'not-required', diagnostics: [{ code: 'ADMISSION_JOURNAL_BINDING_REQUIRED', message: 'Unattended dispatch requires an exact compact plan binding.' }] };
|
|
25
|
+
}
|
|
26
|
+
const plan = (0, validate_1.validatePlanFile)(binding.path, repoRoot);
|
|
27
|
+
const preferences = (0, config_1.readPreferences)();
|
|
28
|
+
return (0, registry_contracts_1.admitRegistryPlan)({ plan, provider, cwd: repoRoot, enabledAgents: preferences.enabledAgents,
|
|
29
|
+
executionMode: 'desatendido', requireCurrent: true, verifySensors: true,
|
|
30
|
+
journalState: observed.state, journalCorrupt: observed.corrupt, planPath: binding.path });
|
|
31
|
+
};
|
|
32
|
+
}
|
|
19
33
|
/** R7/C3/C4 (Task 12) + fix post-review #2 (re-derivado desde cero tras
|
|
20
34
|
* encontrar que la justificación original no probaba lo que decía — ver
|
|
21
35
|
* finding 1 del segundo round de re-review): fases de `CohortPhase` en las
|
|
@@ -107,21 +121,43 @@ exports.DEFAULT_SUPERVISOR_CONFIG = {
|
|
|
107
121
|
maxParallelTracks: 1, // overridden en tiempo de ejecución con loadDefaultParallelism() (watch/index.ts)
|
|
108
122
|
};
|
|
109
123
|
const LIVE = ['received', 'spawn-intent', 'claimed', 'running', 'cancel-requested'];
|
|
124
|
+
function recoveryWhitelistBlocker(state) {
|
|
125
|
+
if (state.cycle.status === 'BLOCKED')
|
|
126
|
+
return 'el ciclo está bloqueado';
|
|
127
|
+
if (state.requestProblems.length > 0)
|
|
128
|
+
return 'hay conflictos durables de requests';
|
|
129
|
+
// Pending work is the normal reason to launch (or retry launching) the
|
|
130
|
+
// controller. It is not recovery evidence and therefore cannot turn a
|
|
131
|
+
// transient launch failure into permanent custody. Conversely, once a
|
|
132
|
+
// non-empty task set claims completion, missing required evidence is an
|
|
133
|
+
// unsafe recovery fact and must remain fail-closed.
|
|
134
|
+
if (state.tasks.length === 0 || state.tasks.some(task => task.status !== 'done'))
|
|
135
|
+
return undefined;
|
|
136
|
+
const verificationItems = [...state.cycleVerificationPlan, ...state.tasks.flatMap(task => task.verificationPlan)];
|
|
137
|
+
for (const required of state.requiredVerifiers) {
|
|
138
|
+
const requiredItems = verificationItems.filter(item => item.kind === required);
|
|
139
|
+
if (requiredItems.length === 0 || requiredItems.some(item => item.satisfiedBy === undefined))
|
|
140
|
+
return `falta evidencia del verificador requerido: ${required}`;
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
110
144
|
class Supervisor {
|
|
111
145
|
repoRoot;
|
|
112
146
|
branch;
|
|
113
147
|
cfg;
|
|
114
148
|
spawner;
|
|
149
|
+
dispatchAdmission;
|
|
115
150
|
backoff = new generations_1.Backoff();
|
|
116
151
|
relaunchNotBefore = 0;
|
|
117
152
|
lastActivity = null;
|
|
118
153
|
lastGenerationToken = null;
|
|
119
154
|
trackRuntime;
|
|
120
|
-
constructor(repoRoot, branch, cfg, spawner, trackRuntime) {
|
|
155
|
+
constructor(repoRoot, branch, cfg, spawner, trackRuntime, dispatchAdmission = defaultDispatchAdmission(repoRoot, branch, cfg.provider)) {
|
|
121
156
|
this.repoRoot = repoRoot;
|
|
122
157
|
this.branch = branch;
|
|
123
158
|
this.cfg = cfg;
|
|
124
159
|
this.spawner = spawner;
|
|
160
|
+
this.dispatchAdmission = dispatchAdmission;
|
|
125
161
|
this.trackRuntime = trackRuntime ?? (0, tracks_1.defaultTrackRuntime)(repoRoot, branch, { termGraceMs: cfg.termGraceMs, killGraceMs: cfg.killGraceMs });
|
|
126
162
|
}
|
|
127
163
|
fingerprintNow = (argv, paths, cwd) => {
|
|
@@ -132,7 +168,7 @@ class Supervisor {
|
|
|
132
168
|
return null;
|
|
133
169
|
} // no recomputable => el gate NO certifica (fail-closed)
|
|
134
170
|
};
|
|
135
|
-
ensureController(
|
|
171
|
+
ensureController(action) {
|
|
136
172
|
if (Date.now() < this.relaunchNotBefore)
|
|
137
173
|
return 'deferred';
|
|
138
174
|
if (this.backoff.exhausted()) {
|
|
@@ -140,7 +176,7 @@ class Supervisor {
|
|
|
140
176
|
return 'custody';
|
|
141
177
|
}
|
|
142
178
|
try {
|
|
143
|
-
(0, generations_1.ensureControllerGeneration)(this.repoRoot, this.branch, this.cfg.provider,
|
|
179
|
+
(0, generations_1.ensureControllerGeneration)(this.repoRoot, this.branch, this.cfg.provider, action, this.spawner, this.cfg.reconcileGraceMs);
|
|
144
180
|
return 'ok';
|
|
145
181
|
}
|
|
146
182
|
catch (error) {
|
|
@@ -161,6 +197,121 @@ class Supervisor {
|
|
|
161
197
|
const before0 = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
162
198
|
if (before0.corrupt || before0.state === null)
|
|
163
199
|
throw new Error('journal corrupto: el supervisor no opera sobre corrupcion (R1.6)');
|
|
200
|
+
// Historical/schema-1 journals are intentionally readable by migration
|
|
201
|
+
// commands, never executable. Do this before controller recovery,
|
|
202
|
+
// request consumption, tracks, or runner reconciliation: each can cause
|
|
203
|
+
// a dispatch directly or indirectly.
|
|
204
|
+
if (before0.state.schema !== 2 || !before0.state.planBinding)
|
|
205
|
+
return 'custody';
|
|
206
|
+
// COMPLETE is terminal, not a recovery attempt. It cannot dispatch and
|
|
207
|
+
// must retain its normal no-op result even if old work evidence is no
|
|
208
|
+
// longer reconstructible.
|
|
209
|
+
if (before0.state.cycle.status === 'COMPLETE')
|
|
210
|
+
return 'complete';
|
|
211
|
+
// A controller can create jobs and runnerTick can start them. Both are
|
|
212
|
+
// downstream of the same full compact unattended admission, so it must
|
|
213
|
+
// complete before any reconciliation path that could dispatch either.
|
|
214
|
+
if (before0.state.schema === 2 && before0.state.planBinding) {
|
|
215
|
+
let admission;
|
|
216
|
+
try {
|
|
217
|
+
admission = await this.dispatchAdmission();
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `admisión desatendida no verificable: ${error.message}`);
|
|
221
|
+
return 'custody';
|
|
222
|
+
}
|
|
223
|
+
if (admission.state !== 'admitted' || admission.executionMode !== 'desatendido'
|
|
224
|
+
|| admission.currentness !== 'current' || admission.sensors !== 'pass' || admission.journal !== 'current') {
|
|
225
|
+
// A first empirical sensor observation can be inconclusive while
|
|
226
|
+
// the already-started controller is still settling. It never
|
|
227
|
+
// authorizes dispatch; retry the read-only admission next tick
|
|
228
|
+
// instead of permanently custodying a healthy cycle.
|
|
229
|
+
if (admission.planState === 'valid' && admission.currentness === 'current'
|
|
230
|
+
&& admission.sensors === 'not-certified' && admission.journal === 'not-required')
|
|
231
|
+
return 'continue';
|
|
232
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `admisión compacta desatendida bloqueada antes de dispatch: ${admission.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join('; ')}`);
|
|
233
|
+
return 'custody';
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
let recoveryResumeAction;
|
|
237
|
+
// Schema-2 custody is reconciled before any controller launch. This is
|
|
238
|
+
// read-only: existing active jobs are reused, never re-requested.
|
|
239
|
+
if (before0.state.schema === 2 && before0.state.planBinding) {
|
|
240
|
+
const whitelistBlocker = recoveryWhitelistBlocker(before0.state);
|
|
241
|
+
if (whitelistBlocker) {
|
|
242
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `recovery no autorizado: ${whitelistBlocker}`);
|
|
243
|
+
return 'custody';
|
|
244
|
+
}
|
|
245
|
+
const activeJobIds = Object.values(before0.state.jobs)
|
|
246
|
+
.filter(job => LIVE.includes(job.executionState) || job.executionState === 'orphaned').map(job => job.id);
|
|
247
|
+
const staleJob = Object.values(before0.state.jobs).filter(job => LIVE.includes(job.executionState) || job.executionState === 'orphaned').some(job => {
|
|
248
|
+
try {
|
|
249
|
+
return (0, fingerprint_1.computeFingerprint)(this.repoRoot, job.argv, job.paths, job.cwd).fingerprint !== job.fingerprint;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const boundPlan = (0, validate_1.validatePlanFile)(before0.state.planBinding.path, this.repoRoot);
|
|
256
|
+
const planChanged = boundPlan.state !== 'valid' || boundPlan.planDigest !== before0.state.planBinding.digest || boundPlan.schema !== before0.state.planBinding.schema;
|
|
257
|
+
const passed = (id) => {
|
|
258
|
+
if (!id)
|
|
259
|
+
return false;
|
|
260
|
+
const job = before0.state.jobs[id];
|
|
261
|
+
return job !== undefined && job.verdict === 'pass' && !staleJob;
|
|
262
|
+
};
|
|
263
|
+
const verificationItems = [...before0.state.cycleVerificationPlan, ...before0.state.tasks.flatMap(task => task.verificationPlan)];
|
|
264
|
+
const tests = verificationItems.filter(item => item.kind === 'test');
|
|
265
|
+
const sensorItems = verificationItems.filter(item => item.kind === 'sensors');
|
|
266
|
+
// `computeGate` is the single authority for every verification
|
|
267
|
+
// kind (review, QA and interlock included). Reuse its evidence
|
|
268
|
+
// semantics instead of maintaining a weaker recovery subset.
|
|
269
|
+
const evidenceGate = (0, gate_1.computeGate)(before0.state, false, this.fingerprintNow);
|
|
270
|
+
const terminalTaskClaims = before0.state.tasks.length > 0 && before0.state.tasks.every(task => task.status === 'done');
|
|
271
|
+
const unresolvedVerification = terminalTaskClaims && evidenceGate.reasons.some(reason => [
|
|
272
|
+
'dangling-reference', 'unsatisfied-plan', 'adverse-verdict',
|
|
273
|
+
'stale-fingerprint', 'open-obligation', 'open-fix',
|
|
274
|
+
].includes(reason.category));
|
|
275
|
+
const hasStaleReview = before0.state.verdicts.some(verdict => verdict.fingerprint === '' || (verdict.argv.length > 0 && (() => {
|
|
276
|
+
try {
|
|
277
|
+
return (0, fingerprint_1.computeFingerprint)(this.repoRoot, verdict.argv, verdict.paths, verdict.cwd).fingerprint !== verdict.fingerprint;
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
})()));
|
|
283
|
+
// Keep the same fail-closed review/fix semantics as job/gate.ts:
|
|
284
|
+
// missing required kinds, dangling verdict references, adverse
|
|
285
|
+
// verdicts, and an adverse verdict without a closed fix all stop
|
|
286
|
+
// recovery before a controller can create more work.
|
|
287
|
+
const verdictById = new Map(before0.state.verdicts.map(verdict => [verdict.id, verdict]));
|
|
288
|
+
const openReviewOrFix = before0.state.tasks.some(task => {
|
|
289
|
+
const obligations = task.reviewObligations;
|
|
290
|
+
return !['spec', 'quality'].every(kind => obligations.some(obligation => obligation.kind === kind))
|
|
291
|
+
|| obligations.some(obligation => {
|
|
292
|
+
const verdict = obligation.verdictId === undefined ? undefined : verdictById.get(obligation.verdictId);
|
|
293
|
+
return verdict === undefined || verdict.result !== 'pass';
|
|
294
|
+
});
|
|
295
|
+
}) || before0.state.verdicts.some(verdict => verdict.result !== 'pass'
|
|
296
|
+
&& !before0.state.fixes.some(fix => fix.verdictId === verdict.id && fix.closed));
|
|
297
|
+
const recovery = (0, fingerprint_1.reconcileUnattendedRecovery)({
|
|
298
|
+
journal: before0.state, journalCorrupt: false, plan: before0.state.planBinding,
|
|
299
|
+
git: staleJob || planChanged ? 'changed' : 'current', activeJobIds,
|
|
300
|
+
tests: !terminalTaskClaims || tests.length === 0 || tests.every(item => passed(item.satisfiedBy)) ? 'pass' : 'missing',
|
|
301
|
+
sensors: !terminalTaskClaims || sensorItems.length === 0 || sensorItems.every(item => passed(item.satisfiedBy)) ? 'pass' : 'missing',
|
|
302
|
+
verdicts: hasStaleReview ? 'stale' : (openReviewOrFix || unresolvedVerification) ? 'missing' : 'current',
|
|
303
|
+
});
|
|
304
|
+
(0, store_1.appendEvent)(this.repoRoot, this.branch, { kind: 'unattended-recovery', nextAction: recovery.nextAction, activeJobIds: recovery.activeJobIds, diagnostics: recovery.diagnostics });
|
|
305
|
+
if (recovery.state !== 'ready') {
|
|
306
|
+
const staleReasons = terminalTaskClaims ? evidenceGate.reasons.filter(reason => reason.category === 'stale-fingerprint') : [];
|
|
307
|
+
const remedy = staleReasons.length > 0
|
|
308
|
+
? `; stale-fingerprint: ${staleReasons.map(reason => reason.detail).join('; ')}. Re-ejecutar verificaciones mediante awm job request con generation, paths y satisfies originales, y repetir reviews independientes cuando corresponda; no repetir watch/rebind ni actualizar fingerprints de PASS históricos`
|
|
309
|
+
: '';
|
|
310
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `recovery no autorizado: ${recovery.diagnostics.join(', ')}${remedy}`);
|
|
311
|
+
return 'custody';
|
|
312
|
+
}
|
|
313
|
+
recoveryResumeAction = { schema: 'controller-recovery/v1', kind: recovery.nextAction === 'reconcile-active-jobs' ? 'reconcile-active-jobs' : 'resume-next-action' };
|
|
314
|
+
}
|
|
164
315
|
// R6.2/R6.8/C7 (Task 11): reconciliar un `MERGE_HEAD` abierto por un
|
|
165
316
|
// crash a mitad de un merge ANTES de cualquier guard general — hoy
|
|
166
317
|
// ningún guard existente (`verifyBranchInvariant` incluido) rechaza
|
|
@@ -181,8 +332,8 @@ class Supervisor {
|
|
|
181
332
|
if (before.frozen !== undefined)
|
|
182
333
|
return 'frozen';
|
|
183
334
|
const pending = before.cycle.nextAction;
|
|
184
|
-
const
|
|
185
|
-
if (this.ensureController(
|
|
335
|
+
const resumeAction = recoveryResumeAction ?? { schema: 'controller-recovery/v1', kind: pending !== undefined ? 'resume-next-action' : 'resume-cycle' };
|
|
336
|
+
if (this.ensureController(resumeAction) === 'custody')
|
|
186
337
|
return 'custody';
|
|
187
338
|
const r0 = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
188
339
|
if (r0.corrupt || r0.state === null)
|
|
@@ -221,7 +372,7 @@ class Supervisor {
|
|
|
221
372
|
if (!finalizing && afterRequests.state !== null && (0, generations_1.activeGeneration)(afterRequests.state) === undefined
|
|
222
373
|
&& afterRequests.state.generations.length > 0 && afterRequests.state.cycle.status === 'IN_PROGRESS') {
|
|
223
374
|
(0, generations_1.beginGeneration)(this.repoRoot, this.branch);
|
|
224
|
-
if (this.ensureController(
|
|
375
|
+
if (this.ensureController(resumeAction) === 'custody')
|
|
225
376
|
return 'custody';
|
|
226
377
|
}
|
|
227
378
|
// R5.2/R6.3 (Task 10): recién leído tras `consumePendingRequests` —
|
|
@@ -400,8 +551,8 @@ class Supervisor {
|
|
|
400
551
|
return false; // esperando backoff, auditando
|
|
401
552
|
(0, generations_1.beginGeneration)(this.repoRoot, this.branch);
|
|
402
553
|
const nextAction = (0, store_1.readJournal)(this.repoRoot, this.branch).state.cycle.nextAction;
|
|
403
|
-
const
|
|
404
|
-
const launched = this.ensureController(
|
|
554
|
+
const action = { schema: 'controller-recovery/v1', kind: nextAction !== undefined ? 'resume-next-action' : 'resume-cycle' };
|
|
555
|
+
const launched = this.ensureController(action);
|
|
405
556
|
if (launched === 'custody')
|
|
406
557
|
return true;
|
|
407
558
|
if (launched === 'ok') {
|
|
@@ -415,10 +566,13 @@ exports.Supervisor = Supervisor;
|
|
|
415
566
|
/** Foreground, visible, terminable (R2.4): sin daemons. SIGINT/SIGTERM libera
|
|
416
567
|
* el lock y sale; COMPLETE => auto-exit liberando lock y terminando la
|
|
417
568
|
* generacion propia (cero huerfanos). */
|
|
418
|
-
async function runSupervisorLoop(repoRoot, branch, cfg, spawner = (0, runner_1.defaultWrapperSpawner)(), trackRuntime) {
|
|
569
|
+
async function runSupervisorLoop(repoRoot, branch, cfg, spawner = (0, runner_1.defaultWrapperSpawner)(), trackRuntime, dispatchAdmission) {
|
|
419
570
|
const r = (0, store_1.readJournal)(repoRoot, branch);
|
|
420
571
|
if (r.corrupt || r.state === null)
|
|
421
572
|
throw new Error('journal ausente o corrupto: corre `awm watch --init` primero');
|
|
573
|
+
if (r.state.schema !== 2 || !r.state.planBinding) {
|
|
574
|
+
throw new Error('journal legacy o sin binding compacto: `awm watch` no puede ejecutar ni despachar trabajo');
|
|
575
|
+
}
|
|
422
576
|
(0, lock_1.verifyBranchInvariant)(repoRoot, r.state.branch);
|
|
423
577
|
if (r.state.cycle.status === 'COMPLETE')
|
|
424
578
|
return;
|
|
@@ -429,7 +583,7 @@ async function runSupervisorLoop(repoRoot, branch, cfg, spawner = (0, runner_1.d
|
|
|
429
583
|
const onSignal = () => { shutdownRequested = true; wakeSleep?.(); };
|
|
430
584
|
process.on('SIGINT', onSignal);
|
|
431
585
|
process.on('SIGTERM', onSignal);
|
|
432
|
-
const sup = new Supervisor(repoRoot, branch, cfg, spawner, trackRuntime);
|
|
586
|
+
const sup = new Supervisor(repoRoot, branch, cfg, spawner, trackRuntime, dispatchAdmission);
|
|
433
587
|
try {
|
|
434
588
|
const s0 = (0, store_1.readJournal)(repoRoot, branch).state;
|
|
435
589
|
if ((0, generations_1.activeGeneration)(s0) === undefined) {
|
|
@@ -21,7 +21,7 @@ const protocol_1 = require("../../core/tracks/protocol");
|
|
|
21
21
|
const git_1 = require("../../core/tracks/git");
|
|
22
22
|
const teardown_1 = require("../../core/tracks/teardown");
|
|
23
23
|
const store_1 = require("../../core/journal/store");
|
|
24
|
-
const
|
|
24
|
+
const track_state_1 = require("./track-state");
|
|
25
25
|
/** Task 13 (R4.2/R4.3/R4.6/R4.10/C2/C9): gathering READ-ONLY del estado real
|
|
26
26
|
* de UN track en teardown — jamás cuenta como el side effect del tick
|
|
27
27
|
* (mismo criterio que `ownedWorktreeExists`/`foreignPathExists` en
|
|
@@ -94,7 +94,7 @@ function gatherTeardownObservation(planRoot, s, ref) {
|
|
|
94
94
|
* haya funcionado, la relectura es la única fuente de verdad). */
|
|
95
95
|
async function runBeginTeardown(planRoot, branch, s, protocol, effect, runtime) {
|
|
96
96
|
const trackId = effect.trackId;
|
|
97
|
-
const ref = (0,
|
|
97
|
+
const ref = (0, track_state_1.refOf)(s, trackId);
|
|
98
98
|
const observed = gatherTeardownObservation(planRoot, s, ref);
|
|
99
99
|
const decision = (0, protocol_1.decideTeardown)(protocol.tracks[trackId], observed);
|
|
100
100
|
if (decision === 'persist-intent') {
|
|
@@ -104,16 +104,16 @@ async function runBeginTeardown(planRoot, branch, s, protocol, effect, runtime)
|
|
|
104
104
|
// dato del journal, no del protocolo puro, así que se aplica con
|
|
105
105
|
// `withRef` en la MISMA persistencia (una sola frontera, no dos).
|
|
106
106
|
const applied = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'effect-applied', effect });
|
|
107
|
-
const withIntent = (0,
|
|
107
|
+
const withIntent = (0, track_state_1.withRef)((0, track_state_1.applyProtocolToState)(s, applied), trackId, {
|
|
108
108
|
teardownIntent: { worktreePath: ref.worktreePath, branch: ref.branch, supervisorNonce: ref.supervisorProcessRef?.spawnNonce },
|
|
109
109
|
});
|
|
110
|
-
const next = (0,
|
|
110
|
+
const next = (0, track_state_1.persist)(planRoot, branch, withIntent);
|
|
111
111
|
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-teardown-step', trackId, step: 'TEARDOWN_INTENT' });
|
|
112
112
|
return { state: next, stop: true, executed: effect.kind };
|
|
113
113
|
}
|
|
114
114
|
if (decision === 'block-foreign') {
|
|
115
115
|
const blocked = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'teardown-observation', trackId, ...observed });
|
|
116
|
-
const next = (0,
|
|
116
|
+
const next = (0, track_state_1.persist)(planRoot, branch, (0, track_state_1.applyProtocolToState)(s, blocked));
|
|
117
117
|
(0, store_1.appendEvent)(planRoot, branch, {
|
|
118
118
|
kind: 'track-blocked', trackId,
|
|
119
119
|
reason: observed.foreignSupervisor ? 'identidad de supervisor ajena' : 'worktree preexistente ajeno',
|
|
@@ -125,8 +125,8 @@ async function runBeginTeardown(planRoot, branch, s, protocol, effect, runtime)
|
|
|
125
125
|
// `runtime` este tick (mismo criterio que 'accept-worktree' en
|
|
126
126
|
// `runCreateWorktree`).
|
|
127
127
|
const applied = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'teardown-observation', trackId, ...observed });
|
|
128
|
-
const next = (0,
|
|
129
|
-
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-teardown-step', trackId, step: (0,
|
|
128
|
+
const next = (0, track_state_1.persist)(planRoot, branch, (0, track_state_1.applyProtocolToState)(s, applied));
|
|
129
|
+
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-teardown-step', trackId, step: (0, track_state_1.refOf)(next, trackId).phase });
|
|
130
130
|
return { state: next, stop: true, executed: effect.kind };
|
|
131
131
|
}
|
|
132
132
|
if (decision === 'stop-own-supervisor') {
|
|
@@ -135,7 +135,7 @@ async function runBeginTeardown(planRoot, branch, s, protocol, effect, runtime)
|
|
|
135
135
|
const confirmed = await runtime.stopOwnSupervisor(ref);
|
|
136
136
|
const after = { ownSupervisorAlive: !confirmed };
|
|
137
137
|
const applied = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'teardown-observation', trackId, ...after });
|
|
138
|
-
const next = (0,
|
|
138
|
+
const next = (0, track_state_1.persist)(planRoot, branch, (0, track_state_1.applyProtocolToState)(s, applied));
|
|
139
139
|
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-teardown-step', trackId, step: confirmed ? 'SUPERVISOR_STOPPED' : 'TEARDOWN_INTENT' });
|
|
140
140
|
return { state: next, stop: true, executed: effect.kind };
|
|
141
141
|
}
|
|
@@ -149,12 +149,12 @@ async function runBeginTeardown(planRoot, branch, s, protocol, effect, runtime)
|
|
|
149
149
|
}
|
|
150
150
|
catch (error) {
|
|
151
151
|
const failed = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'effect-failed', trackId, effect: effect.kind, detail: error.message });
|
|
152
|
-
const next = (0,
|
|
152
|
+
const next = (0, track_state_1.persist)(planRoot, branch, (0, track_state_1.applyProtocolToState)(s, failed));
|
|
153
153
|
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-effect-failed', trackId, effect: effect.kind, detail: error.message });
|
|
154
154
|
return { state: next, stop: true, executed: effect.kind };
|
|
155
155
|
}
|
|
156
156
|
const applied = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'teardown-observation', trackId, ownedWorktreeExists: false });
|
|
157
|
-
const next = (0,
|
|
157
|
+
const next = (0, track_state_1.persist)(planRoot, branch, (0, track_state_1.applyProtocolToState)(s, applied));
|
|
158
158
|
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-teardown-step', trackId, step: 'WORKTREE_REMOVED' });
|
|
159
159
|
return { state: next, stop: true, executed: effect.kind };
|
|
160
160
|
}
|
|
@@ -168,7 +168,7 @@ async function runBeginTeardown(planRoot, branch, s, protocol, effect, runtime)
|
|
|
168
168
|
// próximo tick, ya en esa fase, decide la branch de nuevo con una
|
|
169
169
|
// observación fresca.
|
|
170
170
|
const applied = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'teardown-observation', trackId, ownedWorktreeExists: false });
|
|
171
|
-
const next = (0,
|
|
171
|
+
const next = (0, track_state_1.persist)(planRoot, branch, (0, track_state_1.applyProtocolToState)(s, applied));
|
|
172
172
|
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-teardown-step', trackId, step: 'WORKTREE_REMOVED' });
|
|
173
173
|
return { state: next, stop: true, executed: effect.kind };
|
|
174
174
|
}
|
|
@@ -178,12 +178,12 @@ async function runBeginTeardown(planRoot, branch, s, protocol, effect, runtime)
|
|
|
178
178
|
}
|
|
179
179
|
catch (error) {
|
|
180
180
|
const failed = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'effect-failed', trackId, effect: effect.kind, detail: error.message });
|
|
181
|
-
const next = (0,
|
|
181
|
+
const next = (0, track_state_1.persist)(planRoot, branch, (0, track_state_1.applyProtocolToState)(s, failed));
|
|
182
182
|
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-effect-failed', trackId, effect: effect.kind, detail: error.message });
|
|
183
183
|
return { state: next, stop: true, executed: effect.kind };
|
|
184
184
|
}
|
|
185
185
|
const applied = (0, protocol_1.observeProtocolEffect)(protocol, effect, { kind: 'teardown-observation', trackId, ownedBranchExists: false });
|
|
186
|
-
const next = (0,
|
|
186
|
+
const next = (0, track_state_1.persist)(planRoot, branch, (0, track_state_1.applyProtocolToState)(s, applied));
|
|
187
187
|
(0, store_1.appendEvent)(planRoot, branch, { kind: 'track-teardown-step', trackId, step: 'BRANCH_REMOVED' });
|
|
188
188
|
return { state: next, stop: true, executed: effect.kind };
|
|
189
189
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyProtocolToState = applyProtocolToState;
|
|
4
|
+
exports.persist = persist;
|
|
5
|
+
exports.refOf = refOf;
|
|
6
|
+
exports.withRef = withRef;
|
|
7
|
+
const store_1 = require("../../core/journal/store");
|
|
8
|
+
const types_1 = require("../../core/tracks/types");
|
|
9
|
+
function applyProtocolToState(state, protocol) {
|
|
10
|
+
const next = structuredClone(state);
|
|
11
|
+
next.cohortPhase = protocol.cohortPhase;
|
|
12
|
+
if (protocol.planHeadSha !== undefined)
|
|
13
|
+
next.cohortPlanHeadSha = protocol.planHeadSha;
|
|
14
|
+
if (protocol.cohortPhase === 'FALLBACK_PENDING') {
|
|
15
|
+
next.globalQaHeadSha = undefined;
|
|
16
|
+
next.finalIntegrationJobId = undefined;
|
|
17
|
+
next.qaFinalizeRequested = undefined;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
if (protocol.globalQaHeadSha !== undefined)
|
|
21
|
+
next.globalQaHeadSha = protocol.globalQaHeadSha;
|
|
22
|
+
if (protocol.finalIntegrationJobId !== undefined)
|
|
23
|
+
next.finalIntegrationJobId = protocol.finalIntegrationJobId;
|
|
24
|
+
}
|
|
25
|
+
if (protocol.fallbackReason !== undefined)
|
|
26
|
+
next.cohortFallbackReason = protocol.fallbackReason;
|
|
27
|
+
next.tracks = (next.tracks ?? []).map((ref) => {
|
|
28
|
+
const track = protocol.tracks[ref.trackId];
|
|
29
|
+
if (track === undefined)
|
|
30
|
+
return ref;
|
|
31
|
+
return {
|
|
32
|
+
...ref,
|
|
33
|
+
phase: track.phase,
|
|
34
|
+
frozenHeadSha: track.frozenHeadSha,
|
|
35
|
+
blockedReason: track.blockedReason,
|
|
36
|
+
joinedCommitSha: track.joinedCommitSha,
|
|
37
|
+
joinIntent: track.expectedPlanHeadSha !== undefined && track.expectedTrackHeadSha !== undefined
|
|
38
|
+
? { expectedPlanHeadSha: track.expectedPlanHeadSha, expectedTrackHeadSha: track.expectedTrackHeadSha, strategy: types_1.JOIN_STRATEGY_NO_FF }
|
|
39
|
+
: ref.joinIntent,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
44
|
+
function persist(planRoot, branch, state) {
|
|
45
|
+
(0, store_1.writeJournal)(planRoot, branch, state);
|
|
46
|
+
const result = (0, store_1.readJournal)(planRoot, branch);
|
|
47
|
+
if (result.corrupt || result.state === null)
|
|
48
|
+
throw new Error('journal corrupto tras persistir tracks (R1.6)');
|
|
49
|
+
return result.state;
|
|
50
|
+
}
|
|
51
|
+
function refOf(state, trackId) {
|
|
52
|
+
const ref = state.tracks?.find((track) => track.trackId === trackId);
|
|
53
|
+
if (ref === undefined)
|
|
54
|
+
throw new Error(`invariante rota: TrackRef ausente para ${trackId}`);
|
|
55
|
+
return ref;
|
|
56
|
+
}
|
|
57
|
+
function withRef(state, trackId, patch) {
|
|
58
|
+
return { ...state, tracks: (state.tracks ?? []).map((track) => (track.trackId === trackId ? { ...track, ...patch } : track)) };
|
|
59
|
+
}
|