create-principles-disciple 1.132.5 → 1.133.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/console/dist/server/routes/update.js +105 -1
- package/core/dist/runtime-v2/__tests__/evidence-sanitizer.test.js +33 -2
- package/core/dist/runtime-v2/__tests__/evidence-sanitizer.test.js.map +1 -1
- package/core/dist/runtime-v2/evidence-sanitizer.d.ts.map +1 -1
- package/core/dist/runtime-v2/evidence-sanitizer.js +73 -4
- package/core/dist/runtime-v2/evidence-sanitizer.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +7 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +17 -6
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.d.ts.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js +14 -6
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js.map +1 -1
- package/core/package.json +1 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +4 -0
- package/dist/i18n.js.map +1 -1
- package/dist/installer.d.ts +18 -3
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +99 -29
- package/dist/installer.js.map +1 -1
- package/dist/mvp-config.d.ts +7 -0
- package/dist/mvp-config.d.ts.map +1 -1
- package/dist/mvp-config.js +21 -7
- package/dist/mvp-config.js.map +1 -1
- package/dist/uninstaller.d.ts +15 -0
- package/dist/uninstaller.d.ts.map +1 -1
- package/dist/uninstaller.js +85 -0
- package/dist/uninstaller.js.map +1 -1
- package/dist/update/apply-payload.d.ts +88 -0
- package/dist/update/apply-payload.d.ts.map +1 -0
- package/dist/update/apply-payload.js +166 -0
- package/dist/update/apply-payload.js.map +1 -0
- package/dist/update/bootstrap-protocol.d.ts.map +1 -1
- package/dist/update/bootstrap-protocol.js +14 -2
- package/dist/update/bootstrap-protocol.js.map +1 -1
- package/dist/update/release-manager-authority.d.ts +15 -0
- package/dist/update/release-manager-authority.d.ts.map +1 -1
- package/dist/update/release-manager-authority.js +35 -7
- package/dist/update/release-manager-authority.js.map +1 -1
- package/dist/update/release-manager.d.ts +74 -6
- package/dist/update/release-manager.d.ts.map +1 -1
- package/dist/update/release-manager.js +272 -36
- package/dist/update/release-manager.js.map +1 -1
- package/dist/update/transaction-journal.d.ts +3 -1
- package/dist/update/transaction-journal.d.ts.map +1 -1
- package/dist/update/transaction-journal.js +3 -1
- package/dist/update/transaction-journal.js.map +1 -1
- package/package.json +1 -1
- package/pd-cli/dist/commands/console.d.ts.map +1 -1
- package/pd-cli/dist/commands/console.js +12 -2
- package/pd-cli/dist/commands/console.js.map +1 -1
- package/pd-cli/dist/services/console-launcher.d.ts.map +1 -1
- package/pd-cli/dist/services/console-launcher.js +8 -1
- package/pd-cli/dist/services/console-launcher.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +327 -327
- package/plugin/dist/governance-audit.js +1 -1
- package/plugin/dist/rulehost-evidence.js +1 -1
- package/release-manager/dist/i18n.d.ts.map +1 -1
- package/release-manager/dist/i18n.js +4 -0
- package/release-manager/dist/i18n.js.map +1 -1
- package/release-manager/dist/installer.d.ts +18 -3
- package/release-manager/dist/installer.d.ts.map +1 -1
- package/release-manager/dist/installer.js +99 -29
- package/release-manager/dist/installer.js.map +1 -1
- package/release-manager/dist/mvp-config.d.ts +7 -0
- package/release-manager/dist/mvp-config.d.ts.map +1 -1
- package/release-manager/dist/mvp-config.js +21 -7
- package/release-manager/dist/mvp-config.js.map +1 -1
- package/release-manager/dist/uninstaller.d.ts +15 -0
- package/release-manager/dist/uninstaller.d.ts.map +1 -1
- package/release-manager/dist/uninstaller.js +85 -0
- package/release-manager/dist/uninstaller.js.map +1 -1
- package/release-manager/dist/update/apply-payload.d.ts +88 -0
- package/release-manager/dist/update/apply-payload.d.ts.map +1 -0
- package/release-manager/dist/update/apply-payload.js +166 -0
- package/release-manager/dist/update/apply-payload.js.map +1 -0
- package/release-manager/dist/update/bootstrap-protocol.d.ts.map +1 -1
- package/release-manager/dist/update/bootstrap-protocol.js +14 -2
- package/release-manager/dist/update/bootstrap-protocol.js.map +1 -1
- package/release-manager/dist/update/release-manager-authority.d.ts +15 -0
- package/release-manager/dist/update/release-manager-authority.d.ts.map +1 -1
- package/release-manager/dist/update/release-manager-authority.js +35 -7
- package/release-manager/dist/update/release-manager-authority.js.map +1 -1
- package/release-manager/dist/update/release-manager.d.ts +74 -6
- package/release-manager/dist/update/release-manager.d.ts.map +1 -1
- package/release-manager/dist/update/release-manager.js +272 -36
- package/release-manager/dist/update/release-manager.js.map +1 -1
- package/release-manager/dist/update/transaction-journal.d.ts +3 -1
- package/release-manager/dist/update/transaction-journal.d.ts.map +1 -1
- package/release-manager/dist/update/transaction-journal.js +3 -1
- package/release-manager/dist/update/transaction-journal.js.map +1 -1
- package/release-manager/package.json +1 -1
|
@@ -31,14 +31,28 @@
|
|
|
31
31
|
* activation rollout opens the `shadow_mode_read_only` gate (its own go/no-go);
|
|
32
32
|
* until then these kinds explicitly fall back to the legacy console updater —
|
|
33
33
|
* TEMPORARY migration debt per ADR-0024 D-1, not a supported long-term state.
|
|
34
|
+
*
|
|
35
|
+
* PRI-698 Phase 1: `apply-full` (full-runtime update) is served by the real
|
|
36
|
+
* ReleaseManager.apply() orchestration (installer + journal) and reports the
|
|
37
|
+
* same base readiness as `check`. The CONSOLE additionally gates the routing
|
|
38
|
+
* behind the `release_manager_write_authority` flag (default off) — flag-off
|
|
39
|
+
* dispatches fall back explicitly with `release_manager_write_disabled`.
|
|
40
|
+
* `apply` (the plugin-diff mechanism, which the ReleaseManager does not
|
|
41
|
+
* implement) and `rollback` (Phase 2 — must prove same-version restore first)
|
|
42
|
+
* stay structurally not-ready with explicit reasons.
|
|
34
43
|
*/
|
|
35
44
|
import * as fs from 'node:fs';
|
|
36
45
|
import { ReleaseManager, ReleaseManagerError, } from './release-manager.js';
|
|
37
46
|
import { InstallLayoutError, resolvePdHomePaths } from './install-layout.js';
|
|
38
47
|
/** Mutation kinds the console MutationController routes (PRI-659 contract). */
|
|
39
48
|
export const RELEASE_MANAGER_AUTHORITY_KINDS = ['check', 'apply', 'apply-full', 'rollback'];
|
|
40
|
-
/**
|
|
41
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Structural gate for the kinds whose write path does not exist yet:
|
|
51
|
+
* `rollback` arrives with the Phase 2 restore migration; `apply` (plugin
|
|
52
|
+
* diff) is not a ReleaseManager mechanism at all. `apply-full` left this
|
|
53
|
+
* gate in PRI-698 Phase 1.
|
|
54
|
+
*/
|
|
55
|
+
const ROLLBACK_AVAILABLE = false;
|
|
42
56
|
/**
|
|
43
57
|
* Probe the transactions directory WITHOUT writing: an existing directory must
|
|
44
58
|
* be writable (the journal is a mandatory mutation contract, ADR-0024 D-2), a
|
|
@@ -95,15 +109,18 @@ export function createReleaseManagerAuthority(options) {
|
|
|
95
109
|
},
|
|
96
110
|
apply: {
|
|
97
111
|
ready: false,
|
|
98
|
-
reasons:
|
|
112
|
+
reasons: ROLLBACK_AVAILABLE ? baseReasons : [...baseReasons, 'rollback_not_available'],
|
|
99
113
|
},
|
|
114
|
+
// PRI-698 Phase 1: the full-runtime write path exists (ReleaseManager.apply
|
|
115
|
+
// → installer → journal). Console routing is additionally flag-gated
|
|
116
|
+
// (`release_manager_write_authority`, default off).
|
|
100
117
|
'apply-full': {
|
|
101
|
-
ready:
|
|
102
|
-
reasons:
|
|
118
|
+
ready: baseReady,
|
|
119
|
+
reasons: baseReasons,
|
|
103
120
|
},
|
|
104
121
|
rollback: {
|
|
105
122
|
ready: false,
|
|
106
|
-
reasons:
|
|
123
|
+
reasons: ROLLBACK_AVAILABLE ? baseReasons : [...baseReasons, 'rollback_not_available'],
|
|
107
124
|
},
|
|
108
125
|
};
|
|
109
126
|
return { manager, installStatus, kinds };
|
|
@@ -111,15 +128,26 @@ export function createReleaseManagerAuthority(options) {
|
|
|
111
128
|
/**
|
|
112
129
|
* Map a ReleaseManager refusal onto the console's explicit-fallback reason
|
|
113
130
|
* vocabulary (rc-9: degradation must be observable with a stable reason).
|
|
131
|
+
* `transactionOpened` passes through the PRI-698 default-on safety net
|
|
132
|
+
* semantics: false ⇒ the refusal happened before any side effect, so the
|
|
133
|
+
* console may fall back to the legacy updater with an explicit reason; true
|
|
134
|
+
* ⇒ runtime-side effects may exist and the refusal must surface as a
|
|
135
|
+
* failure, never as a fallback.
|
|
114
136
|
*/
|
|
115
137
|
export function mapReleaseManagerErrorToFallback(error) {
|
|
116
138
|
if (error instanceof ReleaseManagerError) {
|
|
117
|
-
return {
|
|
139
|
+
return {
|
|
140
|
+
reason: error.reason,
|
|
141
|
+
message: error.message,
|
|
142
|
+
nextAction: error.nextAction,
|
|
143
|
+
transactionOpened: error.transactionOpened,
|
|
144
|
+
};
|
|
118
145
|
}
|
|
119
146
|
return {
|
|
120
147
|
reason: 'release_manager_check_failed',
|
|
121
148
|
message: error instanceof Error ? error.message : String(error),
|
|
122
149
|
nextAction: null,
|
|
150
|
+
transactionOpened: false,
|
|
123
151
|
};
|
|
124
152
|
}
|
|
125
153
|
//# sourceMappingURL=release-manager-authority.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"release-manager-authority.js","sourceRoot":"","sources":["../../src/update/release-manager-authority.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"release-manager-authority.js","sourceRoot":"","sources":["../../src/update/release-manager-authority.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,9 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* External surface is intentionally small: inspect / check / apply /
|
|
5
5
|
* rollback. The module hides metadata validation, download, extraction,
|
|
6
|
-
* staging, probes, journaling, host control, rollback, and cleanup.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* staging, probes, journaling, host control, rollback, and cleanup.
|
|
7
|
+
*
|
|
8
|
+
* PRI-698 Phase 1: `apply()` is the update ORCHESTRATOR. It acquires the
|
|
9
|
+
* signed release payload into the staging area (apply-payload.ts) and hands
|
|
10
|
+
* deployment to the installer — the only direct artifact deployment authority
|
|
11
|
+
* (ADR-0024 §2.1) — as ONE journaled transaction (planned → downloaded →
|
|
12
|
+
* verified by this module; staged → probed → activated → confirmed by the
|
|
13
|
+
* installer's existing cycle, same journal file). This module performs ZERO
|
|
14
|
+
* deployment-side filesystem mutation: no writes under `~/.pd/runtime` or the
|
|
15
|
+
* host extension directories ever originate here. `rollback()` still refuses:
|
|
16
|
+
* rollback migration is Phase 2 and must prove same-version restore before it
|
|
17
|
+
* replaces the legacy rollback.
|
|
9
18
|
*
|
|
10
19
|
* Every refusal carries a stable reason and an Owner-visible next action
|
|
11
20
|
* (rc-9) and is computed BEFORE any installation state is mutated.
|
|
@@ -13,12 +22,23 @@
|
|
|
13
22
|
import { resolveTrustedReleaseTarget, type TrustedReleaseTarget } from './trust-metadata.js';
|
|
14
23
|
import { buildReleaseMetadata } from './release-metadata.js';
|
|
15
24
|
import { type ReleasePolicyDecision } from './release-policy.js';
|
|
25
|
+
import type { Language } from '../i18n.js';
|
|
26
|
+
import type { HostTarget } from '../installers/index.js';
|
|
16
27
|
import type { ReleaseChannelName } from './product-identity.js';
|
|
17
|
-
export type ReleaseManagerReason = 'shadow_mode_read_only' | 'bootstrap_not_installed' | 'metadata_refresh_failed' | 'release_metadata_unavailable' | 'release_metadata_invalid' | 'active_record_corrupt';
|
|
28
|
+
export type ReleaseManagerReason = 'shadow_mode_read_only' | 'bootstrap_not_installed' | 'metadata_refresh_failed' | 'release_metadata_unavailable' | 'release_metadata_invalid' | 'active_record_corrupt' | 'legacy_layout_not_supported' | 'journal_unavailable' | 'apply_failed';
|
|
18
29
|
export declare class ReleaseManagerError extends Error {
|
|
19
30
|
readonly reason: ReleaseManagerReason;
|
|
20
31
|
readonly nextAction: string;
|
|
21
|
-
|
|
32
|
+
/**
|
|
33
|
+
* PRI-698 Phase 1 default-on safety net: true when the refusal happened
|
|
34
|
+
* AFTER the apply transaction was opened (planned journaled) — runtime
|
|
35
|
+
* state may include staging writes and a terminal journal tail. False for
|
|
36
|
+
* pre-transaction refusals (layout, metadata, journal-unavailable): the
|
|
37
|
+
* caller can safely fall back to the legacy updater with an explicit
|
|
38
|
+
* reason, because zero side effects exist.
|
|
39
|
+
*/
|
|
40
|
+
readonly transactionOpened: boolean;
|
|
41
|
+
constructor(reason: ReleaseManagerReason, message: string, nextAction: string, transactionOpened?: boolean);
|
|
22
42
|
}
|
|
23
43
|
export type InstallationLayout = 'dual-slot' | 'legacy-overlay' | 'none';
|
|
24
44
|
export interface InstallStatus {
|
|
@@ -55,6 +75,25 @@ export interface UpdateCheck {
|
|
|
55
75
|
readonly note: string | null;
|
|
56
76
|
};
|
|
57
77
|
}
|
|
78
|
+
/** Caller-supplied deployment context for apply() (PRI-698 Phase 1). */
|
|
79
|
+
export interface ApplyOptions {
|
|
80
|
+
/** Workspace the installer re-run operates on (console passes its own). */
|
|
81
|
+
readonly workspaceDir: string;
|
|
82
|
+
/** Installer language; defaults to 'zh' (the installer's default locale). */
|
|
83
|
+
readonly language?: Language;
|
|
84
|
+
/** Host installers to run; defaults to 'openclaw' (matches the legacy full-update sync). */
|
|
85
|
+
readonly host?: HostTarget;
|
|
86
|
+
}
|
|
87
|
+
export type ApplyOutcome = {
|
|
88
|
+
readonly kind: 'applied';
|
|
89
|
+
readonly productVersion: string;
|
|
90
|
+
readonly transactionId: string;
|
|
91
|
+
readonly journalPath: string;
|
|
92
|
+
} | {
|
|
93
|
+
readonly kind: 'no_update';
|
|
94
|
+
/** Why nothing was applied (policy refusal reason or already-current note). */
|
|
95
|
+
readonly note: string;
|
|
96
|
+
};
|
|
58
97
|
export interface ReleaseManagerOptions {
|
|
59
98
|
readonly pdHome: string;
|
|
60
99
|
readonly metadataBaseUrl: string;
|
|
@@ -83,8 +122,37 @@ export declare class ReleaseManager {
|
|
|
83
122
|
constructor(options: ReleaseManagerOptions);
|
|
84
123
|
inspect(): InstallStatus;
|
|
85
124
|
check(channel: ReleaseChannelName): Promise<UpdateCheck>;
|
|
86
|
-
|
|
125
|
+
/**
|
|
126
|
+
* PRI-698 Phase 1: the update orchestrator. Readiness → signed candidate
|
|
127
|
+
* resolution → payload acquisition into staging (journaled) → deployment
|
|
128
|
+
* through the installer (the only direct artifact deployment authority,
|
|
129
|
+
* ADR-0024 §2.1) as ONE transaction. Every failure leaves the runtime on
|
|
130
|
+
* the previous release and the journal at a terminal state.
|
|
131
|
+
*/
|
|
132
|
+
apply(options: ApplyOptions): Promise<ApplyOutcome>;
|
|
87
133
|
rollback(): Promise<never>;
|
|
134
|
+
/**
|
|
135
|
+
* Resolve + digest-verify + cache the signed channel document (shared by
|
|
136
|
+
* check() and apply(); writes stay inside the RM-owned channels cache).
|
|
137
|
+
*/
|
|
138
|
+
private refreshSignedChannel;
|
|
139
|
+
/**
|
|
140
|
+
* evaluateReleaseAdvancement with the ACTIVE release's own metadata as the
|
|
141
|
+
* current state when a dual-slot installation records one (shared by
|
|
142
|
+
* check() and apply()).
|
|
143
|
+
*/
|
|
144
|
+
private evaluateCandidateDecision;
|
|
145
|
+
/**
|
|
146
|
+
* The release metadata document names the bytes to deploy. check() only
|
|
147
|
+
* evaluates already-cached documents; apply() is the "transactional updater"
|
|
148
|
+
* that finally downloads it: TUF target `releases/<releaseId>/metadata.json`
|
|
149
|
+
* (Phase 1 convention, custom identity {releaseId, channel, platform:
|
|
150
|
+
* 'metadata'}) fetched digest-verified into the releases cache, then read
|
|
151
|
+
* back through the SAME strict validation path (identity + expiry + channel
|
|
152
|
+
* digest binding). A pre-seeded local document is still accepted unchanged,
|
|
153
|
+
* so check()-time behavior is untouched.
|
|
154
|
+
*/
|
|
155
|
+
private ensureReleaseMetadataDocument;
|
|
88
156
|
private legacyOverlayMarker;
|
|
89
157
|
private readChannelMetadataPayload;
|
|
90
158
|
private readActiveReleaseMetadata;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"release-manager.d.ts","sourceRoot":"","sources":["../../src/update/release-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"release-manager.d.ts","sourceRoot":"","sources":["../../src/update/release-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,13 +3,23 @@
|
|
|
3
3
|
*
|
|
4
4
|
* External surface is intentionally small: inspect / check / apply /
|
|
5
5
|
* rollback. The module hides metadata validation, download, extraction,
|
|
6
|
-
* staging, probes, journaling, host control, rollback, and cleanup.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* staging, probes, journaling, host control, rollback, and cleanup.
|
|
7
|
+
*
|
|
8
|
+
* PRI-698 Phase 1: `apply()` is the update ORCHESTRATOR. It acquires the
|
|
9
|
+
* signed release payload into the staging area (apply-payload.ts) and hands
|
|
10
|
+
* deployment to the installer — the only direct artifact deployment authority
|
|
11
|
+
* (ADR-0024 §2.1) — as ONE journaled transaction (planned → downloaded →
|
|
12
|
+
* verified by this module; staged → probed → activated → confirmed by the
|
|
13
|
+
* installer's existing cycle, same journal file). This module performs ZERO
|
|
14
|
+
* deployment-side filesystem mutation: no writes under `~/.pd/runtime` or the
|
|
15
|
+
* host extension directories ever originate here. `rollback()` still refuses:
|
|
16
|
+
* rollback migration is Phase 2 and must prove same-version restore before it
|
|
17
|
+
* replaces the legacy rollback.
|
|
9
18
|
*
|
|
10
19
|
* Every refusal carries a stable reason and an Owner-visible next action
|
|
11
20
|
* (rc-9) and is computed BEFORE any installation state is mutated.
|
|
12
21
|
*/
|
|
22
|
+
import { randomUUID } from 'node:crypto';
|
|
13
23
|
import * as fs from 'node:fs';
|
|
14
24
|
import * as os from 'node:os';
|
|
15
25
|
import * as path from 'node:path';
|
|
@@ -18,16 +28,89 @@ import { parseChannelMetadata } from './channel-metadata.js';
|
|
|
18
28
|
import { buildReleaseMetadata, isReleaseMetadataExpired, parseReleaseMetadata, verifyReleaseMetadataIdentity, } from './release-metadata.js';
|
|
19
29
|
import { evaluateReleaseAdvancement, } from './release-policy.js';
|
|
20
30
|
import { readBootstrapManifest, readInstallConfig, resolvePdHomePaths, } from './install-layout.js';
|
|
21
|
-
import { readActiveRecord, TransactionJournalError } from './transaction-journal.js';
|
|
31
|
+
import { appendJournalTransition, readActiveRecord, TransactionJournalError, } from './transaction-journal.js';
|
|
32
|
+
import { downloadReleaseAsset, extractAndVerifyReleaseAsset, ApplyPayloadError } from './apply-payload.js';
|
|
22
33
|
export class ReleaseManagerError extends Error {
|
|
23
34
|
reason;
|
|
24
35
|
nextAction;
|
|
25
|
-
|
|
36
|
+
/**
|
|
37
|
+
* PRI-698 Phase 1 default-on safety net: true when the refusal happened
|
|
38
|
+
* AFTER the apply transaction was opened (planned journaled) — runtime
|
|
39
|
+
* state may include staging writes and a terminal journal tail. False for
|
|
40
|
+
* pre-transaction refusals (layout, metadata, journal-unavailable): the
|
|
41
|
+
* caller can safely fall back to the legacy updater with an explicit
|
|
42
|
+
* reason, because zero side effects exist.
|
|
43
|
+
*/
|
|
44
|
+
transactionOpened;
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- (reason, message, nextAction, transactionOpened) mirrors the four fields of the refusal contract; the 4th is an optional PRI-698 default-on safety-net marker
|
|
46
|
+
constructor(reason, message, nextAction, transactionOpened = false) {
|
|
26
47
|
super(message);
|
|
27
48
|
this.name = 'ReleaseManagerError';
|
|
28
49
|
this.reason = reason;
|
|
29
50
|
this.nextAction = nextAction;
|
|
51
|
+
this.transactionOpened = transactionOpened;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Terminal journal states (mirrors transaction-journal.ts TERMINAL_STATES semantics). */
|
|
55
|
+
const APPLY_TERMINAL_STATES = new Set([
|
|
56
|
+
'confirmed', 'rolled_back', 'refused', 'failed',
|
|
57
|
+
]);
|
|
58
|
+
/**
|
|
59
|
+
* Append one transition to the apply transaction's journal file (journal-first
|
|
60
|
+
* append+fsync, same discipline and format as the installer's writer — one
|
|
61
|
+
* JSONL file per transaction under ~/.pd/transactions/, ADR-0024 D-2/D-6).
|
|
62
|
+
* The installer continues THIS file when it takes over deployment.
|
|
63
|
+
*/
|
|
64
|
+
function appendApplyTransition(journal, to, detail) {
|
|
65
|
+
appendJournalTransition(journal.journalPath, {
|
|
66
|
+
at: new Date().toISOString(),
|
|
67
|
+
from: journal.lastState,
|
|
68
|
+
to,
|
|
69
|
+
transactionId: journal.transactionId,
|
|
70
|
+
releaseId: journal.releaseId,
|
|
71
|
+
productVersion: journal.productVersion,
|
|
72
|
+
releaseMetadataDigest: journal.releaseMetadataDigest,
|
|
73
|
+
releaseMetadataDigestSource: journal.releaseMetadataDigestSource,
|
|
74
|
+
generation: journal.generation,
|
|
75
|
+
detail,
|
|
76
|
+
});
|
|
77
|
+
journal.lastState = to;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Close the apply transaction with a terminal `failed` state when the flow
|
|
81
|
+
* dies before the installer reached one. The installer's own catch path
|
|
82
|
+
* appends rolled_back/failed for failures inside deployment; this covers
|
|
83
|
+
* acquisition-phase failures and unexpected throws so a journal never ends
|
|
84
|
+
* mid-chain (the strict reader rejects transitions after a terminal state,
|
|
85
|
+
* and a non-terminal tail means "unfinished" for Phase 3 recovery).
|
|
86
|
+
* The append itself is best-effort: if even the failure record cannot be
|
|
87
|
+
* written, the on-disk journal stays where it stopped and the thrown error
|
|
88
|
+
* still reports the failure (observable degradation, rc-9).
|
|
89
|
+
*/
|
|
90
|
+
function ensureTerminalFailed(journal, detail) {
|
|
91
|
+
if (journal.lastState !== null && APPLY_TERMINAL_STATES.has(journal.lastState))
|
|
92
|
+
return;
|
|
93
|
+
try {
|
|
94
|
+
appendApplyTransition(journal, 'failed', detail);
|
|
95
|
+
}
|
|
96
|
+
catch (appendError) {
|
|
97
|
+
// Tier-2 degradation (same policy as installer.ts): the transaction stays
|
|
98
|
+
// partially journaled; the backup/restore safety net inside the installer
|
|
99
|
+
// does not depend on the journal.
|
|
100
|
+
void appendError;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function toReleaseManagerError(error) {
|
|
104
|
+
if (error instanceof ReleaseManagerError)
|
|
105
|
+
return error;
|
|
106
|
+
if (error instanceof ApplyPayloadError) {
|
|
107
|
+
return new ReleaseManagerError(error.reason, error.message, error.nextAction);
|
|
30
108
|
}
|
|
109
|
+
if (error instanceof ReleaseTrustError) {
|
|
110
|
+
return new ReleaseManagerError('metadata_refresh_failed', error.message, error.nextAction);
|
|
111
|
+
}
|
|
112
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
+
return new ReleaseManagerError('apply_failed', `Release application failed: ${message}`, 'The runtime was left on the previous release (installer backup/restore). Retry the update; if it fails again, run the official installer to repair.');
|
|
31
114
|
}
|
|
32
115
|
/**
|
|
33
116
|
* Single authority for active.json: the transaction journal's strict reader
|
|
@@ -97,6 +180,150 @@ export class ReleaseManager {
|
|
|
97
180
|
async check(channel) {
|
|
98
181
|
const now = this.options.now ?? (() => new Date());
|
|
99
182
|
const status = this.inspect();
|
|
183
|
+
const { channelMetadata, trustedTarget } = await this.refreshSignedChannel(channel);
|
|
184
|
+
const releaseMetadata = this.readReleaseMetadataDocument(channelMetadata, now);
|
|
185
|
+
const decision = this.evaluateCandidateDecision({ channelMetadata, releaseMetadata, status, now: now() });
|
|
186
|
+
const candidate = {
|
|
187
|
+
productVersion: releaseMetadata.productVersion,
|
|
188
|
+
releaseId: releaseMetadata.releaseId,
|
|
189
|
+
publicationSequence: releaseMetadata.publicationSequence,
|
|
190
|
+
assets: releaseMetadata.assets.map((asset) => ({
|
|
191
|
+
platform: asset.platform,
|
|
192
|
+
arch: asset.arch,
|
|
193
|
+
nodeAbi: asset.nodeAbi,
|
|
194
|
+
})),
|
|
195
|
+
};
|
|
196
|
+
const shadowComparison = await this.compareWithLegacyUpdater(status, decision, candidate.productVersion);
|
|
197
|
+
return {
|
|
198
|
+
channel,
|
|
199
|
+
candidate,
|
|
200
|
+
decision,
|
|
201
|
+
trustedTarget,
|
|
202
|
+
shadowComparison,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* PRI-698 Phase 1: the update orchestrator. Readiness → signed candidate
|
|
207
|
+
* resolution → payload acquisition into staging (journaled) → deployment
|
|
208
|
+
* through the installer (the only direct artifact deployment authority,
|
|
209
|
+
* ADR-0024 §2.1) as ONE transaction. Every failure leaves the runtime on
|
|
210
|
+
* the previous release and the journal at a terminal state.
|
|
211
|
+
*/
|
|
212
|
+
async apply(options) {
|
|
213
|
+
const status = this.inspect();
|
|
214
|
+
if (status.layout === 'none') {
|
|
215
|
+
throw new ReleaseManagerError('bootstrap_not_installed', 'No PD installation was found under this PD home; there is nothing to update.', 'Run the official installer (npx create-principles-disciple) first.');
|
|
216
|
+
}
|
|
217
|
+
if (status.layout === 'legacy-overlay') {
|
|
218
|
+
throw new ReleaseManagerError('legacy_layout_not_supported', 'This installation uses the legacy overlay layout, which the transactional updater does not serve.', 'Run the official installer once to migrate into the dual-slot layout; the current updater continues to serve this installation until then.');
|
|
219
|
+
}
|
|
220
|
+
// Pre-transaction resolution phase: no side effects have happened, so any
|
|
221
|
+
// refusal here is a clean, unjournaled refusal — still mapped onto the
|
|
222
|
+
// typed ReleaseManagerError contract (rc-9), never a raw transport error.
|
|
223
|
+
let channelMetadata;
|
|
224
|
+
let releaseMetadata;
|
|
225
|
+
try {
|
|
226
|
+
const { channelMetadata: refreshedChannel } = await this.refreshSignedChannel(status.channel);
|
|
227
|
+
channelMetadata = refreshedChannel;
|
|
228
|
+
releaseMetadata = await this.ensureReleaseMetadataDocument(channelMetadata);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw toReleaseManagerError(error);
|
|
232
|
+
}
|
|
233
|
+
const now = this.options.now ?? (() => new Date());
|
|
234
|
+
const decision = this.evaluateCandidateDecision({ channelMetadata, releaseMetadata, status, now: now() });
|
|
235
|
+
if (!decision.allowed) {
|
|
236
|
+
return { kind: 'no_update', note: `${decision.reason}: ${decision.message}` };
|
|
237
|
+
}
|
|
238
|
+
const transactionId = `update-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
239
|
+
const journalPath = path.join(this.paths.transactionsDir, `${transactionId}.jsonl`);
|
|
240
|
+
const journal = {
|
|
241
|
+
transactionId,
|
|
242
|
+
journalPath,
|
|
243
|
+
releaseId: releaseMetadata.releaseId,
|
|
244
|
+
productVersion: releaseMetadata.productVersion,
|
|
245
|
+
releaseMetadataDigest: releaseMetadata.metadataDigest,
|
|
246
|
+
releaseMetadataDigestSource: 'signed_channel',
|
|
247
|
+
generation: (status.generation ?? 0) + 1,
|
|
248
|
+
degraded: false,
|
|
249
|
+
lastState: null,
|
|
250
|
+
};
|
|
251
|
+
// Journal-first (ADR-0024 D-2, installer Tier-1 policy): record the
|
|
252
|
+
// transaction BEFORE the first side effect. If even 'planned' cannot be
|
|
253
|
+
// written, refuse with zero side effects.
|
|
254
|
+
try {
|
|
255
|
+
appendApplyTransition(journal, 'planned', `host=${options.host ?? 'openclaw'} mode=smart source=release-manager`);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
throw new ReleaseManagerError('journal_unavailable', `The transaction journal could not be written — refusing to update unjournaled (ADR-0024 D-2): ${error instanceof Error ? error.message : String(error)}`, 'Resolve write access to ~/.pd/transactions (disk space / permissions), then retry. No changes were made.');
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const { install } = await import('../installer.js');
|
|
262
|
+
const downloaded = await downloadReleaseAsset({
|
|
263
|
+
paths: this.paths,
|
|
264
|
+
metadataBaseUrl: this.options.metadataBaseUrl,
|
|
265
|
+
fetcher: this.options.fetcher,
|
|
266
|
+
releaseMetadata,
|
|
267
|
+
channel: status.channel,
|
|
268
|
+
transactionId,
|
|
269
|
+
});
|
|
270
|
+
appendApplyTransition(journal, 'downloaded', `artifact sha256=${downloaded.trustedTarget.artifactSha256} target=${downloaded.trustedTarget.targetPath}`);
|
|
271
|
+
const { payloadDir } = await extractAndVerifyReleaseAsset({
|
|
272
|
+
transactionDir: downloaded.transactionDir,
|
|
273
|
+
archivePath: downloaded.archivePath,
|
|
274
|
+
});
|
|
275
|
+
appendApplyTransition(journal, 'verified', 'release-asset preflight passed (identity + whole-payload digest)');
|
|
276
|
+
// Deployment is the installer's job, byte for byte the same cycle a
|
|
277
|
+
// manual run performs: digest preflight → backup rename-swap → component
|
|
278
|
+
// deploy → console probe → host installers → commit (backup cleanup).
|
|
279
|
+
// The transaction handle keeps ONE journal file for the whole update.
|
|
280
|
+
const installResult = await install({
|
|
281
|
+
language: options.language ?? 'zh',
|
|
282
|
+
mode: 'smart',
|
|
283
|
+
workspaceDir: options.workspaceDir,
|
|
284
|
+
channels: [],
|
|
285
|
+
overwriteConfig: false,
|
|
286
|
+
host: options.host ?? 'openclaw',
|
|
287
|
+
stopGateway: true,
|
|
288
|
+
}, payloadDir, { quiet: true, nonInteractive: true }, journal);
|
|
289
|
+
if (!installResult.success) {
|
|
290
|
+
ensureTerminalFailed(journal, `installer reported failure before deploying (no runtime mutation): ${installResult.error ?? installResult.reason ?? 'unknown'}`);
|
|
291
|
+
throw new ReleaseManagerError('apply_failed', `The installer refused or failed the update: ${installResult.error ?? installResult.reason ?? 'unknown'}`, installResult.nextAction ?? 'The runtime is unchanged. Resolve the reported cause and retry.');
|
|
292
|
+
}
|
|
293
|
+
if (journal.lastState !== 'confirmed') {
|
|
294
|
+
// rc-7: never claim success from a non-terminal journal — the state
|
|
295
|
+
// the installer left behind is what recovery will reason about.
|
|
296
|
+
ensureTerminalFailed(journal, `installer reported success but the transaction ended at '${journal.lastState ?? 'nothing'}'`);
|
|
297
|
+
throw new ReleaseManagerError('apply_failed', `The installer reported success but the transaction journal ended at '${journal.lastState ?? 'nothing'}' instead of 'confirmed'.`, 'Treat the update as NOT applied. Inspect the transaction journal and re-run the installer to reach a consistent state.');
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
kind: 'applied',
|
|
301
|
+
productVersion: releaseMetadata.productVersion,
|
|
302
|
+
transactionId,
|
|
303
|
+
journalPath,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
ensureTerminalFailed(journal, `update failed at '${journal.lastState ?? 'planned'}': ${error instanceof Error ? error.message : String(error)}`);
|
|
308
|
+
// The transaction was opened (planned journaled): mark the refusal so
|
|
309
|
+
// the caller knows runtime-side effects may exist (staging writes +
|
|
310
|
+
// terminal journal tail) and must NOT auto-fallback to the legacy
|
|
311
|
+
// updater over it.
|
|
312
|
+
const mapped = toReleaseManagerError(error);
|
|
313
|
+
throw mapped.transactionOpened
|
|
314
|
+
? mapped
|
|
315
|
+
: new ReleaseManagerError(mapped.reason, mapped.message, mapped.nextAction, true);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async rollback() {
|
|
319
|
+
void this.paths; // reserved: Phase 2 rollback drives this.paths / this.options
|
|
320
|
+
throw new ReleaseManagerError('shadow_mode_read_only', 'Rollback is not enabled yet: the ReleaseManager runs in read-only shadow mode while the transactional activation system is brought up.', 'Continue using the current update path. Rollback arrives with the dual-slot transaction rollout.');
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Resolve + digest-verify + cache the signed channel document (shared by
|
|
324
|
+
* check() and apply(); writes stay inside the RM-owned channels cache).
|
|
325
|
+
*/
|
|
326
|
+
async refreshSignedChannel(channel) {
|
|
100
327
|
const channelTargetPath = `channels/${channel}.json`;
|
|
101
328
|
let trustedTarget;
|
|
102
329
|
try {
|
|
@@ -124,16 +351,21 @@ export class ReleaseManager {
|
|
|
124
351
|
destinationPath: path.join(this.paths.channelsDir, `${channel}.json`),
|
|
125
352
|
fetcher: this.options.fetcher,
|
|
126
353
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
354
|
+
return { channelMetadata: this.readChannelMetadataPayload(channel), trustedTarget };
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* evaluateReleaseAdvancement with the ACTIVE release's own metadata as the
|
|
358
|
+
* current state when a dual-slot installation records one (shared by
|
|
359
|
+
* check() and apply()).
|
|
360
|
+
*/
|
|
361
|
+
evaluateCandidateDecision(input) {
|
|
362
|
+
const { channelMetadata, releaseMetadata, status, now } = input;
|
|
131
363
|
let decision = evaluateReleaseAdvancement({
|
|
132
364
|
channel: channelMetadata,
|
|
133
365
|
candidate: releaseMetadata,
|
|
134
366
|
current: null,
|
|
135
367
|
bootstrapVersion: status.bootstrapVersion ?? '0.0.0',
|
|
136
|
-
now
|
|
368
|
+
now,
|
|
137
369
|
});
|
|
138
370
|
if (status.releaseId !== null && status.productVersion !== null) {
|
|
139
371
|
const activeMetadata = this.readActiveReleaseMetadata(status.releaseId);
|
|
@@ -149,36 +381,40 @@ export class ReleaseManager {
|
|
|
149
381
|
previouslyConfirmedReleaseIds: [status.releaseId],
|
|
150
382
|
},
|
|
151
383
|
bootstrapVersion: status.bootstrapVersion ?? '0.0.0',
|
|
152
|
-
now
|
|
384
|
+
now,
|
|
153
385
|
});
|
|
154
386
|
}
|
|
155
387
|
}
|
|
156
|
-
|
|
157
|
-
productVersion: releaseMetadata.productVersion,
|
|
158
|
-
releaseId: releaseMetadata.releaseId,
|
|
159
|
-
publicationSequence: releaseMetadata.publicationSequence,
|
|
160
|
-
assets: releaseMetadata.assets.map((asset) => ({
|
|
161
|
-
platform: asset.platform,
|
|
162
|
-
arch: asset.arch,
|
|
163
|
-
nodeAbi: asset.nodeAbi,
|
|
164
|
-
})),
|
|
165
|
-
};
|
|
166
|
-
const shadowComparison = await this.compareWithLegacyUpdater(status, decision, candidate.productVersion);
|
|
167
|
-
return {
|
|
168
|
-
channel,
|
|
169
|
-
candidate,
|
|
170
|
-
decision,
|
|
171
|
-
trustedTarget,
|
|
172
|
-
shadowComparison,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
async apply() {
|
|
176
|
-
void this.paths; // reserved: Phase 4 activation drives this.paths / this.options
|
|
177
|
-
throw new ReleaseManagerError('shadow_mode_read_only', 'Release application is not enabled yet: the ReleaseManager runs in read-only shadow mode while the transactional activation system is brought up.', 'Continue using the current update path. Activation arrives with the dual-slot transaction rollout; this refusal protects against hybrid installations.');
|
|
388
|
+
return decision;
|
|
178
389
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
390
|
+
/**
|
|
391
|
+
* The release metadata document names the bytes to deploy. check() only
|
|
392
|
+
* evaluates already-cached documents; apply() is the "transactional updater"
|
|
393
|
+
* that finally downloads it: TUF target `releases/<releaseId>/metadata.json`
|
|
394
|
+
* (Phase 1 convention, custom identity {releaseId, channel, platform:
|
|
395
|
+
* 'metadata'}) fetched digest-verified into the releases cache, then read
|
|
396
|
+
* back through the SAME strict validation path (identity + expiry + channel
|
|
397
|
+
* digest binding). A pre-seeded local document is still accepted unchanged,
|
|
398
|
+
* so check()-time behavior is untouched.
|
|
399
|
+
*/
|
|
400
|
+
async ensureReleaseMetadataDocument(channel) {
|
|
401
|
+
const now = this.options.now ?? (() => new Date());
|
|
402
|
+
try {
|
|
403
|
+
return this.readReleaseMetadataDocument(channel, now);
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
if (!(error instanceof ReleaseManagerError) || error.reason !== 'release_metadata_unavailable') {
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
await downloadTrustedReleasePayload({
|
|
410
|
+
metadataDir: this.paths.trustDir,
|
|
411
|
+
metadataBaseUrl: this.options.metadataBaseUrl,
|
|
412
|
+
targetPath: `releases/${channel.releaseId}/metadata.json`,
|
|
413
|
+
destinationPath: path.join(this.paths.releasesDir, channel.releaseId, 'metadata.json'),
|
|
414
|
+
fetcher: this.options.fetcher,
|
|
415
|
+
});
|
|
416
|
+
return this.readReleaseMetadataDocument(channel, now);
|
|
417
|
+
}
|
|
182
418
|
}
|
|
183
419
|
legacyOverlayMarker() {
|
|
184
420
|
const openclawRoot = this.options.openclawHome
|