@aopslabs/aops 0.3.23 → 0.3.25
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/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/SKILL.md +0 -1
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/agentspace/SKILL.md +0 -6
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/collaborative-work/SKILL.md +1 -2
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/working-disciplines/SKILL.md +2 -5
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/agentspace.md +1 -16
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/aops-cli.md +4 -33
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/working-disciplines.md +32 -138
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/manifest.json +17 -22
- package/agent-assets-release/agent-assets/inventory.json +5 -5
- package/agent-assets-release/agent-assets/projection.json +1 -1
- package/agent-assets-release/agent-assets-release.json +48 -53
- package/agent-assets-release/agent-assets-release.sigstore.json +27 -31
- package/assets/agent-assets/core/SKILL.md +0 -1
- package/assets/agent-assets/core/references/agentspace/SKILL.md +0 -6
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +1 -2
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +2 -5
- package/assets/agent-assets/core/user-guides/agentspace.md +1 -16
- package/assets/agent-assets/core/user-guides/aops-cli.md +4 -33
- package/assets/agent-assets/core/user-guides/working-disciplines.md +32 -138
- package/dist/commands/checkpoint.js +4 -23
- package/dist/commands/community-server.js +212 -6
- package/dist/commands/db.js +145 -0
- package/dist/commands/experience.js +0 -2
- package/dist/commands/global-update.js +447 -22
- package/dist/commands/memory.js +8 -2
- package/dist/commands/official-catalog.js +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/pm/index.js +39 -3
- package/dist/commands/pm/projectman.js +281 -8
- package/dist/commands/start-disciplines.js +8 -21
- package/dist/commands/start.js +20 -504
- package/dist/lib/agent-assets/native-fs.js +2 -2
- package/dist/lib/community-cockpit-lifecycle.js +95 -1
- package/dist/lib/community-migration-snapshot.js +43 -2
- package/dist/lib/community-native-backup.js +386 -0
- package/dist/lib/community-native-lifecycle.js +147 -5
- package/dist/lib/community-native-migration.js +20 -0
- package/dist/main.js +2 -2
- package/dist/seeds/agents-md.js +1 -1
- package/dist/utils/playbook-workspace.js +5 -33
- package/dist/utils/session-guidance.js +0 -7
- package/native/bin/darwin-arm64/aops-agent-assets-fs +0 -0
- package/native/bin/darwin-x64/aops-agent-assets-fs +0 -0
- package/native/bin/linux-arm64/aops-agent-assets-fs +0 -0
- package/native/bin/linux-x64/aops-agent-assets-fs +0 -0
- package/native/bin/win32-x64/aops-agent-assets-fs.exe +0 -0
- package/native/manifest.json +6 -6
- package/native/qualifications/win32-x64.json +2 -2
- package/native/tui/darwin-arm64/aops-tui +0 -0
- package/native/tui/linux-x64/aops-tui +0 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +21 -9
- package/agent-assets-release/agent-assets/core/647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09/files/references/mission/SKILL.md +0 -54
- package/assets/agent-assets/core/references/mission/SKILL.md +0 -54
- package/dist/commands/mission.js +0 -562
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/aops-cli-core/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/chatv3/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/discuss/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/docman/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/projectman/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/sys/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/view/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/agent-assets.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/aops-system.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/chatv3.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/docman.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/projectman.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/sys.md +0 -0
|
@@ -29,11 +29,13 @@ const GLOBAL_UPDATE_EXECUTOR_MAX_BYTES = 512 * 1024 * 1024;
|
|
|
29
29
|
class ChildCommandError extends Error {
|
|
30
30
|
status;
|
|
31
31
|
diagnostic;
|
|
32
|
-
|
|
32
|
+
stdout;
|
|
33
|
+
constructor(command, status, diagnostic, stdout = '') {
|
|
33
34
|
super(`${path.basename(command)} failed with status ${status ?? 'unknown'}`);
|
|
34
35
|
this.name = 'ChildCommandError';
|
|
35
36
|
this.status = status;
|
|
36
37
|
this.diagnostic = diagnostic;
|
|
38
|
+
this.stdout = stdout;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
function cleanEnvironment() {
|
|
@@ -69,7 +71,7 @@ function defaultRunner(command, args, cwd = process.cwd()) {
|
|
|
69
71
|
}
|
|
70
72
|
catch {
|
|
71
73
|
}
|
|
72
|
-
throw new ChildCommandError(command, result.status, diagnostic);
|
|
74
|
+
throw new ChildCommandError(command, result.status, diagnostic, result.stdout ?? '');
|
|
73
75
|
}
|
|
74
76
|
return result.stdout;
|
|
75
77
|
}
|
|
@@ -280,10 +282,33 @@ export function inspectGlobalUpdate(run = defaultRunner) {
|
|
|
280
282
|
function runInstalledJson(run, cliPath, args) {
|
|
281
283
|
return parseJson(run(process.execPath, [cliPath, ...args]).trim(), args.join('_'));
|
|
282
284
|
}
|
|
285
|
+
function childCommandStdout(error) {
|
|
286
|
+
const stdout = error?.stdout;
|
|
287
|
+
return typeof stdout === 'string' && stdout.trim() !== '' ? stdout : undefined;
|
|
288
|
+
}
|
|
289
|
+
function readServiceStatus(run, cliPath, service) {
|
|
290
|
+
try {
|
|
291
|
+
return runInstalledJson(run, cliPath, [service, 'status', '--json']);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
const stdout = childCommandStdout(error);
|
|
295
|
+
if (stdout === undefined)
|
|
296
|
+
throw error;
|
|
297
|
+
try {
|
|
298
|
+
const parsed = JSON.parse(stdout.trim());
|
|
299
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
300
|
+
throw error;
|
|
301
|
+
return parsed;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
283
308
|
function serviceSnapshot(run, cliPath) {
|
|
284
309
|
return {
|
|
285
|
-
server:
|
|
286
|
-
cockpit:
|
|
310
|
+
server: readServiceStatus(run, cliPath, 'server'),
|
|
311
|
+
cockpit: readServiceStatus(run, cliPath, 'cockpit'),
|
|
287
312
|
};
|
|
288
313
|
}
|
|
289
314
|
function assertHealthyServices(services) {
|
|
@@ -565,6 +590,7 @@ export function dispatchGlobalUpdate(options, run = defaultRunner) {
|
|
|
565
590
|
rollbackPerformed: false,
|
|
566
591
|
error: safeUpdateError(error),
|
|
567
592
|
events: [],
|
|
593
|
+
eventTimes: [],
|
|
568
594
|
});
|
|
569
595
|
}
|
|
570
596
|
throw new Error(`global update candidate executor failed; diagnostic record: ${resultPath}`, { cause: error });
|
|
@@ -652,24 +678,161 @@ function rollbackServerApplication(run, cliPath, updateId, serverPath, expectedR
|
|
|
652
678
|
const defaultExecutionDependencies = {
|
|
653
679
|
fetch: globalThis.fetch.bind(globalThis),
|
|
654
680
|
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
681
|
+
now: () => new Date(),
|
|
655
682
|
};
|
|
683
|
+
class GlobalUpdateEventLog {
|
|
684
|
+
now;
|
|
685
|
+
names = [];
|
|
686
|
+
times = [];
|
|
687
|
+
constructor(now) {
|
|
688
|
+
this.now = now;
|
|
689
|
+
}
|
|
690
|
+
push(event) {
|
|
691
|
+
this.names.push(event);
|
|
692
|
+
this.times.push(this.now().toISOString());
|
|
693
|
+
}
|
|
694
|
+
includes(event) {
|
|
695
|
+
return this.names.includes(event);
|
|
696
|
+
}
|
|
697
|
+
get events() {
|
|
698
|
+
return [...this.names];
|
|
699
|
+
}
|
|
700
|
+
get eventTimes() {
|
|
701
|
+
return [...this.times];
|
|
702
|
+
}
|
|
703
|
+
}
|
|
656
704
|
class StartupHealthPending extends Error {
|
|
657
|
-
|
|
705
|
+
detail;
|
|
706
|
+
constructor(cause, detail) {
|
|
658
707
|
super('global_update_health_startup_pending', { cause });
|
|
708
|
+
this.detail = detail;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function serviceHealthFacts(services) {
|
|
712
|
+
const liveness = services.server.liveness;
|
|
713
|
+
return {
|
|
714
|
+
serverState: services.server.runtimeState,
|
|
715
|
+
serverHealth: liveness?.health,
|
|
716
|
+
serverReason: services.server.error,
|
|
717
|
+
cockpitState: services.cockpit.runtimeState,
|
|
718
|
+
cockpitHealth: services.cockpit.health,
|
|
719
|
+
cockpitReason: services.cockpit.reason,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
const HEALTH_SUBJECTS = new Set([
|
|
723
|
+
'install-readback', 'server-status', 'cockpit-status', 'service-health',
|
|
724
|
+
'server', 'cockpit', 'cockpit-proxy',
|
|
725
|
+
]);
|
|
726
|
+
const HEALTH_PHASES = new Set(['startup', 'steady']);
|
|
727
|
+
const SAFE_STATE = /^[a-z][a-z0-9-]{0,40}$/u;
|
|
728
|
+
const SAFE_REASON_CODE = /\b(?:aops|community|global_update)_[a-z0-9_]+\b/iu;
|
|
729
|
+
function sanitizeHealthDetail(detail) {
|
|
730
|
+
const safe = {
|
|
731
|
+
subject: typeof detail.subject === 'string' && HEALTH_SUBJECTS.has(detail.subject)
|
|
732
|
+
? detail.subject
|
|
733
|
+
: 'unrecognized',
|
|
734
|
+
};
|
|
735
|
+
if (typeof detail.phase === 'string' && HEALTH_PHASES.has(detail.phase))
|
|
736
|
+
safe.phase = detail.phase;
|
|
737
|
+
if (typeof detail.reachable === 'boolean')
|
|
738
|
+
safe.reachable = detail.reachable;
|
|
739
|
+
for (const key of ['sample', 'attempts', 'childStatus', 'status']) {
|
|
740
|
+
const value = detail[key];
|
|
741
|
+
if (typeof value === 'number' && Number.isSafeInteger(value))
|
|
742
|
+
safe[key] = value;
|
|
743
|
+
}
|
|
744
|
+
for (const key of ['serverState', 'serverHealth', 'cockpitState', 'cockpitHealth']) {
|
|
745
|
+
const value = detail[key];
|
|
746
|
+
if (value === undefined || value === null)
|
|
747
|
+
continue;
|
|
748
|
+
safe[key] = typeof value === 'string' && SAFE_STATE.test(value) ? value : 'unrecognized';
|
|
659
749
|
}
|
|
750
|
+
for (const key of ['serverReason', 'cockpitReason']) {
|
|
751
|
+
const value = detail[key];
|
|
752
|
+
if (value === undefined || value === null)
|
|
753
|
+
continue;
|
|
754
|
+
safe[key] = typeof value === 'string'
|
|
755
|
+
? SAFE_REASON_CODE.exec(value)?.[0]?.toLowerCase() ?? 'unrecognized'
|
|
756
|
+
: 'unrecognized';
|
|
757
|
+
}
|
|
758
|
+
return safe;
|
|
759
|
+
}
|
|
760
|
+
function childCommandStatus(error) {
|
|
761
|
+
const status = error?.status;
|
|
762
|
+
return typeof status === 'number' ? status : undefined;
|
|
763
|
+
}
|
|
764
|
+
class ForwardSchemaRetainsTarget extends Error {
|
|
765
|
+
applicationRollbackError;
|
|
766
|
+
constructor(applicationRollbackError) {
|
|
767
|
+
super('global_update_forward_schema_retains_target');
|
|
768
|
+
this.applicationRollbackError = applicationRollbackError;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
export class StableHealthFailure extends Error {
|
|
772
|
+
healthFailure;
|
|
773
|
+
constructor(detail) {
|
|
774
|
+
const safe = sanitizeHealthDetail(detail);
|
|
775
|
+
super(`global_update_stable_health_failed:${String(safe.subject)}`);
|
|
776
|
+
this.healthFailure = Object.freeze(safe);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
export function resolveRecoveryClosure(state) {
|
|
780
|
+
if (!state.applicationSideEffectPossible)
|
|
781
|
+
return 'prior';
|
|
782
|
+
return state.applicationRolledBack ? 'prior' : 'target';
|
|
783
|
+
}
|
|
784
|
+
const APPLICATION_PRE_EFFECT_CODES = new Set([
|
|
785
|
+
'community_native_application_update_content_unchanged',
|
|
786
|
+
]);
|
|
787
|
+
export function applicationSideEffectPossible(attempted, updateError) {
|
|
788
|
+
if (!attempted)
|
|
789
|
+
return false;
|
|
790
|
+
const diagnostic = nestedCommunityDiagnostic(updateError);
|
|
791
|
+
if (!diagnostic)
|
|
792
|
+
return true;
|
|
793
|
+
const cause = diagnostic.cause;
|
|
794
|
+
const codes = [diagnostic.code, cause?.code];
|
|
795
|
+
return !codes.some((code) => typeof code === 'string' && APPLICATION_PRE_EFFECT_CODES.has(code));
|
|
660
796
|
}
|
|
661
797
|
async function assertStableHealth(run, plan, execution) {
|
|
662
798
|
const evidence = [];
|
|
799
|
+
const fail = (error, startup, detail) => {
|
|
800
|
+
if (startup)
|
|
801
|
+
throw new StartupHealthPending(error, detail);
|
|
802
|
+
throw new StableHealthFailure({ ...detail, phase: 'steady' });
|
|
803
|
+
};
|
|
663
804
|
const collectSample = async (sample, startup) => {
|
|
664
|
-
|
|
805
|
+
let installed;
|
|
806
|
+
try {
|
|
807
|
+
installed = inspectGlobalInstall(run);
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
fail(error, startup, { subject: 'install-readback', sample, childStatus: childCommandStatus(error) });
|
|
811
|
+
throw error;
|
|
812
|
+
}
|
|
665
813
|
let services;
|
|
666
814
|
try {
|
|
667
|
-
services =
|
|
815
|
+
services = {
|
|
816
|
+
server: readServiceStatus(run, installed.cliPath, 'server'),
|
|
817
|
+
cockpit: { runtimeState: 'not-read' },
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
fail(error, startup, { subject: 'server-status', sample, childStatus: childCommandStatus(error) });
|
|
822
|
+
throw error;
|
|
823
|
+
}
|
|
824
|
+
try {
|
|
825
|
+
services = { ...services, cockpit: readServiceStatus(run, installed.cliPath, 'cockpit') };
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
fail(error, startup, { subject: 'cockpit-status', sample, childStatus: childCommandStatus(error) });
|
|
829
|
+
throw error;
|
|
830
|
+
}
|
|
831
|
+
try {
|
|
668
832
|
assertHealthyServices(services);
|
|
669
833
|
}
|
|
670
834
|
catch (error) {
|
|
671
|
-
|
|
672
|
-
throw new StartupHealthPending(error);
|
|
835
|
+
fail(error, startup, { subject: 'service-health', sample, ...serviceHealthFacts(services) });
|
|
673
836
|
throw error;
|
|
674
837
|
}
|
|
675
838
|
const serverOrigin = validateLoopbackOrigin(services.server.origin, 'AOPS Server origin');
|
|
@@ -685,14 +848,12 @@ async function assertStableHealth(run, plan, execution) {
|
|
|
685
848
|
response = await execution.fetch(url, { cache: 'no-store', signal: AbortSignal.timeout(15_000) });
|
|
686
849
|
}
|
|
687
850
|
catch (error) {
|
|
688
|
-
|
|
689
|
-
throw new StartupHealthPending(error);
|
|
851
|
+
fail(error, startup, { subject: name, sample, reachable: false });
|
|
690
852
|
throw error;
|
|
691
853
|
}
|
|
692
|
-
if (response.status !== 200
|
|
693
|
-
|
|
854
|
+
if (response.status !== 200) {
|
|
855
|
+
fail(new Error(`${name} health returned ${response.status}`), startup, { subject: name, sample, reachable: true, status: response.status });
|
|
694
856
|
}
|
|
695
|
-
assert.equal(response.status, 200, `${name} health returned ${response.status}`);
|
|
696
857
|
const body = await response.arrayBuffer();
|
|
697
858
|
endpoints.push({ name, url, status: response.status, bytes: body.byteLength });
|
|
698
859
|
}
|
|
@@ -708,7 +869,11 @@ async function assertStableHealth(run, plan, execution) {
|
|
|
708
869
|
catch (error) {
|
|
709
870
|
if (!(error instanceof StartupHealthPending))
|
|
710
871
|
throw error;
|
|
711
|
-
startupError =
|
|
872
|
+
startupError = new StableHealthFailure({
|
|
873
|
+
...error.detail,
|
|
874
|
+
phase: 'startup',
|
|
875
|
+
attempts: attempt,
|
|
876
|
+
});
|
|
712
877
|
if (attempt < plan.safety.healthStartupAttempts) {
|
|
713
878
|
await execution.sleep(plan.safety.healthStartupIntervalMs);
|
|
714
879
|
}
|
|
@@ -755,6 +920,9 @@ function nestedCommunityDiagnostic(error) {
|
|
|
755
920
|
return undefined;
|
|
756
921
|
}
|
|
757
922
|
function safeUpdateError(error) {
|
|
923
|
+
if (error instanceof StableHealthFailure) {
|
|
924
|
+
return { code: 'global_update_stable_health_failed', healthFailure: error.healthFailure };
|
|
925
|
+
}
|
|
758
926
|
const diagnostic = nestedCommunityDiagnostic(error);
|
|
759
927
|
if (diagnostic) {
|
|
760
928
|
const details = diagnostic.details;
|
|
@@ -785,6 +953,160 @@ function writeTerminalUpdateResult(plan, result) {
|
|
|
785
953
|
const resultPath = path.join(plan.operationRoot, RESULT_FILE);
|
|
786
954
|
writeFileSync(resultPath, `${JSON.stringify(result, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
787
955
|
}
|
|
956
|
+
const RECOVERY_CONTRACT = 'aops.global-update-recovery/v1';
|
|
957
|
+
const RECOVERY_RESULT_PATTERN = /^update-recovery-(0|[1-9]\d*)\.json$/u;
|
|
958
|
+
const RECOVERABLE_TERMINAL_STATUSES = new Set(['recovery-required']);
|
|
959
|
+
function recoveryAttemptFiles(plan) {
|
|
960
|
+
const operationRoot = plainDirectory(plan.operationRoot, 'global update operation root');
|
|
961
|
+
const attempts = [];
|
|
962
|
+
for (const entry of readdirSync(operationRoot)) {
|
|
963
|
+
const match = RECOVERY_RESULT_PATTERN.exec(entry);
|
|
964
|
+
if (match)
|
|
965
|
+
attempts.push({ attempt: Number(match[1]), path: path.join(operationRoot, entry) });
|
|
966
|
+
}
|
|
967
|
+
return attempts.sort((left, right) => left.attempt - right.attempt);
|
|
968
|
+
}
|
|
969
|
+
function nextRecoveryResultPath(plan) {
|
|
970
|
+
const attempts = recoveryAttemptFiles(plan);
|
|
971
|
+
const next = attempts.length === 0 ? 1 : attempts[attempts.length - 1].attempt + 1;
|
|
972
|
+
return path.join(plan.operationRoot, `update-recovery-${next}.json`);
|
|
973
|
+
}
|
|
974
|
+
function sameClosure(installed, expected) {
|
|
975
|
+
try {
|
|
976
|
+
assert.deepEqual(installed, expected);
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
catch {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function writeRecoveryResult(resultPath, result) {
|
|
984
|
+
writeFileSync(resultPath, `${JSON.stringify(result, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
985
|
+
}
|
|
986
|
+
function preflightGlobalUpdateRecovery(options, run) {
|
|
987
|
+
const plan = readGlobalUpdatePlan(options.plan);
|
|
988
|
+
assert.equal(options.confirm, plan.planId, 'global update recovery confirmation does not match the exact plan');
|
|
989
|
+
const terminalPath = path.join(plan.operationRoot, RESULT_FILE);
|
|
990
|
+
assert.equal(existsSync(terminalPath), true, 'global_update_recovery_requires_terminal_result');
|
|
991
|
+
const terminal = parseJson(readFileSync(terminalPath, 'utf8'), 'global_update_result');
|
|
992
|
+
assert.equal(terminal.planId, plan.planId, 'terminal result belongs to a different plan');
|
|
993
|
+
const terminalStatus = String(terminal.status);
|
|
994
|
+
assert.equal(RECOVERABLE_TERMINAL_STATUSES.has(terminalStatus), true, `global_update_recovery_status_not_recoverable:${terminalStatus}`);
|
|
995
|
+
const installed = inspectGlobalInstall(run);
|
|
996
|
+
assert.equal(installed.globalPrefix, plan.globalPrefix, 'global npm prefix changed after update planning');
|
|
997
|
+
const installedClosure = [installed.packages, installed.topology];
|
|
998
|
+
const atTarget = sameClosure(installedClosure, [plan.target.packages, plan.target.topology]);
|
|
999
|
+
const atPrior = sameClosure(installedClosure, [plan.prior.packages, plan.prior.topology]);
|
|
1000
|
+
assert.equal(atTarget || atPrior, true, 'global_update_recovery_closure_drifted');
|
|
1001
|
+
const succeeded = recoveryAttemptFiles(plan)
|
|
1002
|
+
.map((file) => ({
|
|
1003
|
+
...file,
|
|
1004
|
+
record: parseJson(readFileSync(file.path, 'utf8'), 'global_update_recovery_result'),
|
|
1005
|
+
}))
|
|
1006
|
+
.find((entry) => entry.record.status === 'recovered');
|
|
1007
|
+
if (succeeded) {
|
|
1008
|
+
assert.equal(atTarget, true, 'global_update_recovery_recorded_but_closure_moved');
|
|
1009
|
+
}
|
|
1010
|
+
return { plan, terminalStatus, installed, atTarget, atPrior, succeeded };
|
|
1011
|
+
}
|
|
1012
|
+
export function dispatchGlobalUpdateRecovery(options, run = defaultRunner) {
|
|
1013
|
+
const preflight = preflightGlobalUpdateRecovery(options, run);
|
|
1014
|
+
const { plan } = preflight;
|
|
1015
|
+
if (preflight.succeeded)
|
|
1016
|
+
return preflight.succeeded.record;
|
|
1017
|
+
const resultPath = nextRecoveryResultPath(plan);
|
|
1018
|
+
let executorStarted = false;
|
|
1019
|
+
try {
|
|
1020
|
+
const { receipt, receiptPath } = stageCandidateGlobalUpdateExecutor(plan, run);
|
|
1021
|
+
executorStarted = true;
|
|
1022
|
+
return parseJson(run(process.execPath, [
|
|
1023
|
+
receipt.entrypoint,
|
|
1024
|
+
'update',
|
|
1025
|
+
'recover',
|
|
1026
|
+
'--plan',
|
|
1027
|
+
options.plan,
|
|
1028
|
+
'--confirm',
|
|
1029
|
+
options.confirm,
|
|
1030
|
+
'--executor-receipt',
|
|
1031
|
+
receiptPath,
|
|
1032
|
+
'--json',
|
|
1033
|
+
]).trim(), 'global_update_recovery_result');
|
|
1034
|
+
}
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
if (!existsSync(resultPath)) {
|
|
1037
|
+
writeRecoveryResult(resultPath, {
|
|
1038
|
+
schemaVersion: 1,
|
|
1039
|
+
contract: RECOVERY_CONTRACT,
|
|
1040
|
+
status: executorStarted ? 'executor-result-missing' : 'executor-staging-failed',
|
|
1041
|
+
planId: plan.planId,
|
|
1042
|
+
failedStage: executorStarted ? 'candidate-executor' : 'stage-candidate-executor',
|
|
1043
|
+
globalState: executorStarted ? 'unknown-requires-inspection' : 'unchanged',
|
|
1044
|
+
error: safeUpdateError(error),
|
|
1045
|
+
events: [],
|
|
1046
|
+
eventTimes: [],
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
throw new Error(`global update recovery executor failed; diagnostic record: ${resultPath}`, { cause: error });
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
export async function recoverGlobalUpdate(options, run = defaultRunner, execution = defaultExecutionDependencies) {
|
|
1053
|
+
const preflight = preflightGlobalUpdateRecovery(options, run);
|
|
1054
|
+
const { plan, installed: current, atTarget, terminalStatus } = preflight;
|
|
1055
|
+
const installedCliRoot = plainDirectory(path.dirname(path.dirname(current.cliPath)), 'installed global CLI root');
|
|
1056
|
+
const invokedEntrypoint = plainFile(process.argv[1], 'invoked AOPS CLI entrypoint');
|
|
1057
|
+
const relativeToInstalled = path.relative(installedCliRoot, invokedEntrypoint);
|
|
1058
|
+
assert.equal(relativeToInstalled.startsWith('..') || path.isAbsolute(relativeToInstalled), true, 'global_update_recovery_must_not_run_from_the_replaced_cli');
|
|
1059
|
+
if (preflight.succeeded)
|
|
1060
|
+
return preflight.succeeded.record;
|
|
1061
|
+
const resultPath = nextRecoveryResultPath(plan);
|
|
1062
|
+
const events = new GlobalUpdateEventLog(execution.now ?? (() => new Date()));
|
|
1063
|
+
try {
|
|
1064
|
+
stopService(run, current.cliPath, 'cockpit', true);
|
|
1065
|
+
stopService(run, current.cliPath, 'server', true);
|
|
1066
|
+
if (atTarget) {
|
|
1067
|
+
events.push('retained:target-closure');
|
|
1068
|
+
}
|
|
1069
|
+
else {
|
|
1070
|
+
installGlobalUpdateClosure(run, plan, plan.candidates, plan.target.topology);
|
|
1071
|
+
events.push('installed:target-closure');
|
|
1072
|
+
}
|
|
1073
|
+
const restored = inspectGlobalInstall(run);
|
|
1074
|
+
assert.deepEqual(restored.packages, plan.target.packages, 'recovered closure is not the exact target');
|
|
1075
|
+
assert.deepEqual(restored.topology, plan.target.topology, 'recovered topology is not canonical');
|
|
1076
|
+
startService(run, restored.cliPath, 'server');
|
|
1077
|
+
events.push('started:server');
|
|
1078
|
+
startService(run, restored.cliPath, 'cockpit');
|
|
1079
|
+
events.push('started:cockpit');
|
|
1080
|
+
const healthEvidence = await assertStableHealth(run, plan, execution);
|
|
1081
|
+
const result = {
|
|
1082
|
+
schemaVersion: 1,
|
|
1083
|
+
contract: RECOVERY_CONTRACT,
|
|
1084
|
+
status: 'recovered',
|
|
1085
|
+
planId: plan.planId,
|
|
1086
|
+
installed: plan.target.packages,
|
|
1087
|
+
recoveredFrom: terminalStatus,
|
|
1088
|
+
events: events.events,
|
|
1089
|
+
eventTimes: events.eventTimes,
|
|
1090
|
+
healthEvidence,
|
|
1091
|
+
};
|
|
1092
|
+
writeRecoveryResult(resultPath, result);
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
1095
|
+
catch (recoveryError) {
|
|
1096
|
+
writeRecoveryResult(resultPath, {
|
|
1097
|
+
schemaVersion: 1,
|
|
1098
|
+
contract: RECOVERY_CONTRACT,
|
|
1099
|
+
status: 'recovery-failed',
|
|
1100
|
+
planId: plan.planId,
|
|
1101
|
+
recoveredFrom: terminalStatus,
|
|
1102
|
+
globalState: 'unknown-requires-inspection',
|
|
1103
|
+
error: safeUpdateError(recoveryError),
|
|
1104
|
+
events: events.events,
|
|
1105
|
+
eventTimes: events.eventTimes,
|
|
1106
|
+
});
|
|
1107
|
+
throw recoveryError;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
788
1110
|
export async function applyGlobalUpdate(options, run = defaultRunner, execution = defaultExecutionDependencies) {
|
|
789
1111
|
const plan = readGlobalUpdatePlan(options.plan);
|
|
790
1112
|
assert.equal(options.confirm, plan.planId, 'global update confirmation does not match the exact plan');
|
|
@@ -794,8 +1116,9 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
794
1116
|
assert.deepEqual(current.topology, plan.prior.topology, 'global package topology changed after update planning');
|
|
795
1117
|
assert.equal(current.globalPrefix, plan.globalPrefix, 'global npm prefix changed after update planning');
|
|
796
1118
|
assertHealthyServices(serviceSnapshot(run, current.cliPath));
|
|
797
|
-
const events =
|
|
1119
|
+
const events = new GlobalUpdateEventLog(execution.now ?? (() => new Date()));
|
|
798
1120
|
let applicationUpdateId = null;
|
|
1121
|
+
let applicationUpdateAttempted = false;
|
|
799
1122
|
let failedStage = 'stop-cockpit';
|
|
800
1123
|
try {
|
|
801
1124
|
stopService(run, current.cliPath, 'cockpit');
|
|
@@ -817,6 +1140,7 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
817
1140
|
}
|
|
818
1141
|
else {
|
|
819
1142
|
failedStage = 'update-server-application';
|
|
1143
|
+
applicationUpdateAttempted = true;
|
|
820
1144
|
applicationUpdateId = updateServerApplication(run, updated.cliPath, {
|
|
821
1145
|
prior: plan.prior.packages.server,
|
|
822
1146
|
target: plan.target.packages.server,
|
|
@@ -835,7 +1159,8 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
835
1159
|
planId: plan.planId,
|
|
836
1160
|
installed: updated.packages,
|
|
837
1161
|
rollbackPerformed: false,
|
|
838
|
-
events,
|
|
1162
|
+
events: events.events,
|
|
1163
|
+
eventTimes: events.eventTimes,
|
|
839
1164
|
healthEvidence,
|
|
840
1165
|
next: 'Exit and reopen the AOPS TUI to run the newly installed binary.',
|
|
841
1166
|
};
|
|
@@ -844,6 +1169,7 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
844
1169
|
}
|
|
845
1170
|
catch (updateError) {
|
|
846
1171
|
const updateStage = failedStage;
|
|
1172
|
+
const sideEffectPossible = applicationSideEffectPossible(applicationUpdateAttempted, updateError);
|
|
847
1173
|
events.push('rollback:started');
|
|
848
1174
|
let rollbackError;
|
|
849
1175
|
try {
|
|
@@ -854,19 +1180,89 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
854
1180
|
const restored = inspectGlobalInstall(run);
|
|
855
1181
|
assert.deepEqual(restored.packages, plan.prior.packages, 'rollback package closure is not exact');
|
|
856
1182
|
assert.deepEqual(restored.topology, plan.prior.topology, 'rollback package topology is not exact');
|
|
1183
|
+
let applicationRollbackError;
|
|
857
1184
|
if (applicationUpdateId) {
|
|
858
|
-
|
|
859
|
-
|
|
1185
|
+
try {
|
|
1186
|
+
rollbackServerApplication(run, restored.cliPath, applicationUpdateId, restored.serverPath, plan.prior.packages.server);
|
|
1187
|
+
events.push('rolled-back:server-application');
|
|
1188
|
+
}
|
|
1189
|
+
catch (error) {
|
|
1190
|
+
applicationRollbackError = error;
|
|
1191
|
+
events.push('failed:server-application-rollback');
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
else if (sideEffectPossible) {
|
|
1195
|
+
applicationRollbackError = new Error('global_update_application_outcome_unrecorded');
|
|
1196
|
+
events.push('failed:server-application-outcome-unrecorded');
|
|
860
1197
|
}
|
|
861
1198
|
else {
|
|
862
1199
|
startService(run, restored.cliPath, 'server');
|
|
863
1200
|
events.push('started:server-restore');
|
|
864
1201
|
}
|
|
1202
|
+
if (resolveRecoveryClosure({
|
|
1203
|
+
applicationSideEffectPossible: sideEffectPossible,
|
|
1204
|
+
applicationRolledBack: applicationUpdateId !== null && applicationRollbackError === undefined,
|
|
1205
|
+
}) === 'target') {
|
|
1206
|
+
throw new ForwardSchemaRetainsTarget(applicationRollbackError);
|
|
1207
|
+
}
|
|
865
1208
|
startService(run, restored.cliPath, 'cockpit');
|
|
866
1209
|
await assertStableHealth(run, plan, execution);
|
|
867
1210
|
events.push('rollback:healthy');
|
|
868
1211
|
}
|
|
869
1212
|
catch (rollbackError) {
|
|
1213
|
+
if (rollbackError instanceof ForwardSchemaRetainsTarget) {
|
|
1214
|
+
try {
|
|
1215
|
+
installGlobalUpdateClosure(run, plan, plan.candidates, plan.target.topology);
|
|
1216
|
+
events.push('reinstalled:target-closure');
|
|
1217
|
+
const retained = inspectGlobalInstall(run);
|
|
1218
|
+
assert.deepEqual(retained.packages, plan.target.packages, 'retained target closure is not exact');
|
|
1219
|
+
assert.deepEqual(retained.topology, plan.target.topology, 'retained target topology is not canonical');
|
|
1220
|
+
startService(run, retained.cliPath, 'server');
|
|
1221
|
+
events.push('started:server-target-retained');
|
|
1222
|
+
startService(run, retained.cliPath, 'cockpit');
|
|
1223
|
+
events.push('started:cockpit-target-retained');
|
|
1224
|
+
await assertStableHealth(run, plan, execution);
|
|
1225
|
+
events.push('target-retained:healthy');
|
|
1226
|
+
}
|
|
1227
|
+
catch (retainError) {
|
|
1228
|
+
writeTerminalUpdateResult(plan, {
|
|
1229
|
+
schemaVersion: 1,
|
|
1230
|
+
contract: RESULT_CONTRACT,
|
|
1231
|
+
status: 'recovery-required',
|
|
1232
|
+
planId: plan.planId,
|
|
1233
|
+
failedStage: updateStage,
|
|
1234
|
+
rollbackPerformed: false,
|
|
1235
|
+
rollback: {
|
|
1236
|
+
status: 'failed',
|
|
1237
|
+
reason: 'application_rollback_failed_forward_schema',
|
|
1238
|
+
error: safeUpdateError(rollbackError.applicationRollbackError),
|
|
1239
|
+
},
|
|
1240
|
+
targetRetention: { status: 'failed', error: safeUpdateError(retainError) },
|
|
1241
|
+
error: safeUpdateError(updateError),
|
|
1242
|
+
events: events.events,
|
|
1243
|
+
eventTimes: events.eventTimes,
|
|
1244
|
+
});
|
|
1245
|
+
throw new AggregateError([updateError, rollbackError.applicationRollbackError, retainError], `global update failed, application rollback failed, and the schema-compatible target closure could not be restarted; services may remain stopped; recovery artifacts remain at ${plan.operationRoot}`);
|
|
1246
|
+
}
|
|
1247
|
+
writeTerminalUpdateResult(plan, {
|
|
1248
|
+
schemaVersion: 1,
|
|
1249
|
+
contract: RESULT_CONTRACT,
|
|
1250
|
+
status: 'failed-target-retained',
|
|
1251
|
+
planId: plan.planId,
|
|
1252
|
+
failedStage: updateStage,
|
|
1253
|
+
installed: plan.target.packages,
|
|
1254
|
+
rollbackPerformed: false,
|
|
1255
|
+
rollback: {
|
|
1256
|
+
status: 'not-applicable',
|
|
1257
|
+
reason: 'application_rollback_failed_forward_schema',
|
|
1258
|
+
applicationRollbackError: safeUpdateError(rollbackError.applicationRollbackError),
|
|
1259
|
+
},
|
|
1260
|
+
error: safeUpdateError(updateError),
|
|
1261
|
+
events: events.events,
|
|
1262
|
+
eventTimes: events.eventTimes,
|
|
1263
|
+
});
|
|
1264
|
+
throw new AggregateError([updateError, rollbackError.applicationRollbackError], 'global update failed and the application could not be rolled back; the schema-compatible target closure was reinstated and healthy services were restarted');
|
|
1265
|
+
}
|
|
870
1266
|
try {
|
|
871
1267
|
const restoredForRecovery = inspectGlobalInstall(run);
|
|
872
1268
|
assert.deepEqual(restoredForRecovery.packages, plan.prior.packages, 'service recovery package closure is not exact');
|
|
@@ -891,7 +1287,8 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
891
1287
|
rollback: { status: 'failed', error: safeUpdateError(rollbackError) },
|
|
892
1288
|
serviceRecovery: { status: 'failed', error: safeUpdateError(serviceRecoveryError) },
|
|
893
1289
|
error: safeUpdateError(updateError),
|
|
894
|
-
events,
|
|
1290
|
+
events: events.events,
|
|
1291
|
+
eventTimes: events.eventTimes,
|
|
895
1292
|
});
|
|
896
1293
|
throw new AggregateError([updateError, rollbackError, serviceRecoveryError], `global update, automatic rollback, and service recovery failed; services may remain stopped; recovery artifacts remain at ${plan.operationRoot}`);
|
|
897
1294
|
}
|
|
@@ -909,7 +1306,8 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
909
1306
|
applicationRollbackError: safeUpdateError(rollbackError),
|
|
910
1307
|
},
|
|
911
1308
|
error: safeUpdateError(updateError),
|
|
912
|
-
events,
|
|
1309
|
+
events: events.events,
|
|
1310
|
+
eventTimes: events.eventTimes,
|
|
913
1311
|
});
|
|
914
1312
|
throw new AggregateError([updateError, rollbackError], `global update and application rollback failed; prior exact packages and healthy services were restored; recovery artifacts remain at ${plan.operationRoot}`);
|
|
915
1313
|
}
|
|
@@ -923,7 +1321,8 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
923
1321
|
rollbackPerformed: true,
|
|
924
1322
|
rollback: { status: 'healthy' },
|
|
925
1323
|
error: safeUpdateError(updateError),
|
|
926
|
-
events,
|
|
1324
|
+
events: events.events,
|
|
1325
|
+
eventTimes: events.eventTimes,
|
|
927
1326
|
});
|
|
928
1327
|
throw new Error('global update failed; the prior exact packages were restored and are healthy', { cause: updateError });
|
|
929
1328
|
}
|
|
@@ -931,6 +1330,22 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
931
1330
|
function output(value, json) {
|
|
932
1331
|
process.stdout.write(`${JSON.stringify(value, null, json ? 0 : 2)}\n`);
|
|
933
1332
|
}
|
|
1333
|
+
const defaultRecoveryCommandDependencies = {
|
|
1334
|
+
dispatch: dispatchGlobalUpdateRecovery,
|
|
1335
|
+
recover: recoverGlobalUpdate,
|
|
1336
|
+
readPlan: readGlobalUpdatePlan,
|
|
1337
|
+
validateExecutor: validateCandidateExecutorInvocation,
|
|
1338
|
+
emit: output,
|
|
1339
|
+
};
|
|
1340
|
+
export async function runGlobalUpdateRecoveryCommand(options, dependencies = defaultRecoveryCommandDependencies) {
|
|
1341
|
+
if (typeof options.executorReceipt === 'string') {
|
|
1342
|
+
const plan = dependencies.readPlan(options.plan);
|
|
1343
|
+
dependencies.validateExecutor(plan, options.executorReceipt);
|
|
1344
|
+
dependencies.emit(await dependencies.recover(options), options.json === true);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
dependencies.emit(dependencies.dispatch(options), options.json === true);
|
|
1348
|
+
}
|
|
934
1349
|
export function makeGlobalUpdateCommand() {
|
|
935
1350
|
const command = new Command('update').description('Check, prepare, and apply a guarded global AOPS npm update');
|
|
936
1351
|
command
|
|
@@ -960,6 +1375,16 @@ export function makeGlobalUpdateCommand() {
|
|
|
960
1375
|
}
|
|
961
1376
|
output(dispatchGlobalUpdate(options), options.json === true);
|
|
962
1377
|
});
|
|
1378
|
+
command
|
|
1379
|
+
.command('recover')
|
|
1380
|
+
.description('Finish one stranded exact plan at its target closure after a failed apply')
|
|
1381
|
+
.requiredOption('--plan <path>', 'Exact update-plan.json path returned by prepare')
|
|
1382
|
+
.requiredOption('--confirm <plan-id>', 'Exact sha256 plan id returned by prepare')
|
|
1383
|
+
.addOption(new Option('--executor-receipt <path>').hideHelp())
|
|
1384
|
+
.option('--json', 'Output JSON')
|
|
1385
|
+
.action(async (options) => {
|
|
1386
|
+
await runGlobalUpdateRecoveryCommand(options);
|
|
1387
|
+
});
|
|
963
1388
|
command
|
|
964
1389
|
.command('bridge')
|
|
965
1390
|
.description('Apply an old-CLI plan from an externally launched exact public candidate')
|
package/dist/commands/memory.js
CHANGED
|
@@ -486,13 +486,19 @@ function matchesClassificationFilters(record, options) {
|
|
|
486
486
|
}
|
|
487
487
|
return true;
|
|
488
488
|
}
|
|
489
|
-
function filterHostedMemoryResult(result, options) {
|
|
489
|
+
export function filterHostedMemoryResult(result, options) {
|
|
490
490
|
if (buildClassificationTags(options).length === 0)
|
|
491
491
|
return result;
|
|
492
492
|
if (Array.isArray(result)) {
|
|
493
493
|
return result.filter((entry) => matchesClassificationFilters(entry, options));
|
|
494
494
|
}
|
|
495
|
-
if (!isRecord(result)
|
|
495
|
+
if (!isRecord(result))
|
|
496
|
+
return result;
|
|
497
|
+
if (Array.isArray(result.items)) {
|
|
498
|
+
const items = result.items.filter((entry) => matchesClassificationFilters(entry, options));
|
|
499
|
+
return { ...result, items, returned: items.length };
|
|
500
|
+
}
|
|
501
|
+
if (!Array.isArray(result.data))
|
|
496
502
|
return result;
|
|
497
503
|
return {
|
|
498
504
|
...result,
|
|
@@ -115,7 +115,7 @@ export function makeOfficialCatalogSetupCommand(dependencies = {}) {
|
|
|
115
115
|
The reserved scope is fixed to ${OFFICIAL_CATALOG_SCOPE_V1.slug}. It is routing
|
|
116
116
|
identity only: official trust comes from the verified Community release and the
|
|
117
117
|
package/release digests persisted in SkillVersion meta. Import is inert and does
|
|
118
|
-
not select a discipline, mutate a
|
|
118
|
+
not select a discipline, mutate a policy, or touch user/project scopes.
|
|
119
119
|
|
|
120
120
|
Agentspace owns the atomic composite operations and durable receipts. Inspect
|
|
121
121
|
their exact server contracts with:
|
package/dist/commands/plan.js
CHANGED
|
@@ -37,8 +37,8 @@ export function makePlanCommand() {
|
|
|
37
37
|
' For one-item checklist edits, use `aops-cli pm utask ...` until top-level plan microtask sugar is added.\n');
|
|
38
38
|
applyPlanContextOptions(applyMemoryCadenceOptions(applyWriteOptions(cmd
|
|
39
39
|
.command('create')
|
|
40
|
-
.description('Create a sprint-backed implementation plan
|
|
41
|
-
.
|
|
40
|
+
.description('Create a sprint-backed implementation plan, optionally bound to a kanban task')
|
|
41
|
+
.option('--task <id>', 'Optional kanban task id to bind the plan to')
|
|
42
42
|
.requiredOption('--name <name>', 'Plan name')
|
|
43
43
|
.requiredOption('--goal <text>', 'Plan goal')
|
|
44
44
|
.option('--reference <value>', 'Repeatable reference item', collectRepeatedOption, [])
|