create-principles-disciple 1.133.13 → 1.134.1

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.
Files changed (71) hide show
  1. package/codex-adapter/package.json +3 -2
  2. package/console/dist/server/routes/update.js +181 -34
  3. package/console/dist/server/update/legacy-mutation-journal.d.ts +118 -0
  4. package/console/dist/server/update/legacy-mutation-journal.js +220 -0
  5. package/console/dist/server/utils/cli-smoke.d.ts +19 -0
  6. package/console/dist/server/utils/cli-smoke.js +60 -0
  7. package/console/dist/server/utils/installed-layout.d.ts +8 -0
  8. package/console/dist/server/utils/installed-layout.js +4 -0
  9. package/console/dist/ui/i18n/en.json +2 -2
  10. package/console/dist/ui/i18n/zh-CN.json +2 -2
  11. package/console/dist/web/assets/app.js +85 -15
  12. package/console/package.json +2 -2
  13. package/core/dist/runtime-v2/__tests__/recovery-sweep-service.test.js +130 -0
  14. package/core/dist/runtime-v2/__tests__/recovery-sweep-service.test.js.map +1 -1
  15. package/core/dist/runtime-v2/recovery-sweep-service.d.ts +9 -0
  16. package/core/dist/runtime-v2/recovery-sweep-service.d.ts.map +1 -1
  17. package/core/dist/runtime-v2/recovery-sweep-service.js +7 -2
  18. package/core/dist/runtime-v2/recovery-sweep-service.js.map +1 -1
  19. package/core/package.json +1 -1
  20. package/dist/installer.d.ts +69 -0
  21. package/dist/installer.d.ts.map +1 -1
  22. package/dist/installer.js +200 -14
  23. package/dist/installer.js.map +1 -1
  24. package/dist/uninstaller.d.ts.map +1 -1
  25. package/dist/uninstaller.js +7 -3
  26. package/dist/uninstaller.js.map +1 -1
  27. package/dist/update/install-layout.d.ts +39 -2
  28. package/dist/update/install-layout.d.ts.map +1 -1
  29. package/dist/update/install-layout.js +69 -3
  30. package/dist/update/install-layout.js.map +1 -1
  31. package/dist/update/release-manager-authority.d.ts +8 -0
  32. package/dist/update/release-manager-authority.d.ts.map +1 -1
  33. package/dist/update/release-manager-authority.js +26 -4
  34. package/dist/update/release-manager-authority.js.map +1 -1
  35. package/dist/update/release-metadata-source.d.ts +85 -0
  36. package/dist/update/release-metadata-source.d.ts.map +1 -0
  37. package/dist/update/release-metadata-source.js +102 -0
  38. package/dist/update/release-metadata-source.js.map +1 -0
  39. package/install-layout/dist/index.d.ts +8 -0
  40. package/install-layout/dist/index.js +1 -0
  41. package/install-layout/package.json +1 -1
  42. package/package.json +2 -2
  43. package/pd-cli/dist/commands/runtime-recovery-failed-tasks.d.ts.map +1 -1
  44. package/pd-cli/dist/commands/runtime-recovery-failed-tasks.js +82 -6
  45. package/pd-cli/dist/commands/runtime-recovery-failed-tasks.js.map +1 -1
  46. package/pd-cli/dist/index.js +6 -3
  47. package/pd-cli/dist/index.js.map +1 -1
  48. package/pd-cli/package.json +1 -1
  49. package/plugin/dist/bundle.js +44 -44
  50. package/plugin/dist/governance-audit.js +139 -139
  51. package/plugin/dist/rulehost-evidence.js +144 -144
  52. package/release-manager/dist/installer.d.ts +69 -0
  53. package/release-manager/dist/installer.d.ts.map +1 -1
  54. package/release-manager/dist/installer.js +200 -14
  55. package/release-manager/dist/installer.js.map +1 -1
  56. package/release-manager/dist/uninstaller.d.ts.map +1 -1
  57. package/release-manager/dist/uninstaller.js +7 -3
  58. package/release-manager/dist/uninstaller.js.map +1 -1
  59. package/release-manager/dist/update/install-layout.d.ts +39 -2
  60. package/release-manager/dist/update/install-layout.d.ts.map +1 -1
  61. package/release-manager/dist/update/install-layout.js +69 -3
  62. package/release-manager/dist/update/install-layout.js.map +1 -1
  63. package/release-manager/dist/update/release-manager-authority.d.ts +8 -0
  64. package/release-manager/dist/update/release-manager-authority.d.ts.map +1 -1
  65. package/release-manager/dist/update/release-manager-authority.js +26 -4
  66. package/release-manager/dist/update/release-manager-authority.js.map +1 -1
  67. package/release-manager/dist/update/release-metadata-source.d.ts +85 -0
  68. package/release-manager/dist/update/release-metadata-source.d.ts.map +1 -0
  69. package/release-manager/dist/update/release-metadata-source.js +102 -0
  70. package/release-manager/dist/update/release-metadata-source.js.map +1 -0
  71. package/release-manager/package.json +2 -2
@@ -0,0 +1,220 @@
1
+ /**
2
+ * PRI-709 P0-3 — journal coverage for the legacy console updater (ADR-0024 D-2).
3
+ *
4
+ * PRI-698 Phase 0 Audit finding F-3: the console updater performed runtime
5
+ * mutations with **zero** journal writes. Under ADR-0024 D-2 an unjournaled
6
+ * runtime mutation is the one thing that must never happen, so the legacy
7
+ * updater had to be brought under the SAME journal as the installer and the
8
+ * ReleaseManager before the legacy path can be retired.
9
+ *
10
+ * Design rules (all load-bearing):
11
+ *
12
+ * - **No new journal implementation.** This module reuses
13
+ * `create-principles-disciple`'s `transaction-journal` — one JSONL file per
14
+ * transaction under `~/.pd/transactions/`, append + fsync, strict reader.
15
+ * Nothing here parses, formats or rotates journals.
16
+ * - **The ReleaseManager path is never double-journaled.** ReleaseManager
17
+ * `apply-full` orchestrates the installer, which journals the whole
18
+ * lifecycle; the console dispatch for that path never reaches the legacy
19
+ * handlers this module wraps. When the ReleaseManager explicitly falls back
20
+ * to legacy, the legacy handler is the ONLY writer — one transaction per
21
+ * mutation, whichever authority served it.
22
+ * - **Explicit degradation, never a blocked mutation.** The journal module is
23
+ * loaded dynamically: the console runs in installations where the
24
+ * create-principles-disciple dist may be absent (the same delivery-surface
25
+ * gap the ReleaseManager authority loader already handles). A missing module
26
+ * is reported and the mutation proceeds unjournaled — refusing the Owner's
27
+ * update would be worse than an unaudited one, and the gap is observable.
28
+ * - **No fabricated digests.** The legacy updater does not verify signed
29
+ * release metadata, so its digest provenance is `fallback`: a synthetic
30
+ * sha256 over a literal marker, readable but explicitly NOT verifiable. It
31
+ * never claims `manifest` / `signed_channel` it cannot back.
32
+ *
33
+ * Transition strategy for legacy kinds:
34
+ *
35
+ * apply / apply-full / rollback: `planned` → `confirmed` | `failed`
36
+ *
37
+ * `planned` is appended immediately before the mutation (after all request
38
+ * validation, so rejected requests leave no transaction); `confirmed` or
39
+ * `failed` after it. `rolled_back` is NOT used for the rollback kind — a
40
+ * rollback restores a previous deployment and is itself a forward transition
41
+ * to a known-good state, whereas `rolled_back` means "this transaction was
42
+ * undone".
43
+ *
44
+ * Two documented trade-offs (PRI-709 review, deliberate):
45
+ *
46
+ * - **`generation` stays at the standalone default `1`.** Legacy console
47
+ * transactions do not participate in the dual-slot generation lineage — they
48
+ * never move the active record — so recording a real `active.json.generation
49
+ * + 1` would CLAIM a lineage the console does not actually advance. Recovery
50
+ * is unaffected either way: `recoverUnfinishedTransaction` keys on
51
+ * `activeRecord.transactionId`, which a console transaction never matches, so
52
+ * it resolves to "the previously confirmed release stands".
53
+ * - **The legacy path leaves `active.json` untouched.** The legacy updater
54
+ * replaces the runtime without the installer's dual-slot swap, so after a
55
+ * legacy `apply-full` the active record describes the PREVIOUS deployment.
56
+ * Making the console a second writer of the deployment identity would be a
57
+ * worse violation (one source of truth); the honest fix is retiring the
58
+ * legacy path (ADR-0024 D-1), which is exactly what this journal coverage
59
+ * unblocks.
60
+ */
61
+ import { createHash, randomUUID } from 'node:crypto';
62
+ import * as fs from 'node:fs';
63
+ import * as path from 'node:path';
64
+ /** Load the shared journal; `null` when the delivery surface lacks the module. */
65
+ async function loadJournalPort() {
66
+ try {
67
+ const module = await import('create-principles-disciple/dist/update/transaction-journal.js');
68
+ return { appendJournalTransition: module.appendJournalTransition };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Product version of the currently installed runtime.
76
+ *
77
+ * Same rule as the installer (PRI-709 P0-2): the PRODUCT manifest is
78
+ * `pluginDir/package.json`; `pd-cli` versions independently and is only a
79
+ * fallback.
80
+ */
81
+ function resolveInstalledProductVersion(pluginDir) {
82
+ for (const candidate of [path.join(pluginDir, 'package.json'), path.join(pluginDir, 'pd-cli', 'package.json')]) {
83
+ try {
84
+ const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
85
+ if (typeof parsed.version === 'string' && parsed.version.length > 0)
86
+ return parsed.version;
87
+ }
88
+ catch {
89
+ // Identity falls back; an unjournaled-able version must not block a mutation.
90
+ }
91
+ }
92
+ return 'unknown';
93
+ }
94
+ /**
95
+ * Open one legacy transaction and append `planned` before the mutation runs.
96
+ */
97
+ export async function openLegacyMutationJournal(options) {
98
+ const port = options.journal ?? await loadJournalPort();
99
+ if (port === null) {
100
+ return { journaled: false, reason: 'journal_module_unavailable' };
101
+ }
102
+ const productVersion = resolveInstalledProductVersion(options.pluginDir);
103
+ // Unverifiable by construction: the legacy updater never sees signed release
104
+ // metadata, so the digest is a marker hash labelled `fallback`.
105
+ const releaseMetadataDigest = createHash('sha256')
106
+ .update(`legacy-console-updater-unverified:${options.kind}`)
107
+ .digest('hex');
108
+ const transactionId = `console-${options.kind}-${Date.now()}-${randomUUID().slice(0, 8)}`;
109
+ const releaseId = `console-${options.kind}-${productVersion}-${releaseMetadataDigest.slice(0, 12)}`;
110
+ const journal = {
111
+ transactionId,
112
+ journalPath: path.join(options.pdHome, 'transactions', `${transactionId}.jsonl`),
113
+ releaseId,
114
+ productVersion,
115
+ releaseMetadataDigest,
116
+ };
117
+ try {
118
+ port.appendJournalTransition(journal.journalPath, {
119
+ at: (options.now ?? (() => new Date))().toISOString(),
120
+ from: null,
121
+ to: 'planned',
122
+ transactionId,
123
+ releaseId,
124
+ productVersion,
125
+ releaseMetadataDigest,
126
+ releaseMetadataDigestSource: 'fallback',
127
+ generation: 1,
128
+ detail: `actor=console-updater kind=${options.kind}`,
129
+ });
130
+ }
131
+ catch (error) {
132
+ return {
133
+ journaled: false,
134
+ reason: `journal_write_failed: ${error instanceof Error ? error.message : String(error)}`,
135
+ };
136
+ }
137
+ return { journaled: true, journal };
138
+ }
139
+ /**
140
+ * Maps a mutation's RESULT to its journal terminal state.
141
+ *
142
+ * PRI-709 review: the legacy updater reports failure as a VALUE —
143
+ * `doApplyUpdate` / `doRollbackUpdate` / `doInlineFullUpdate` return
144
+ * `{ success: false, message }` for ~30 distinct failure modes and only throw
145
+ * for infrastructure errors. Terminal state decided on "did it throw" alone
146
+ * would journal confirmed for most real failures. So: `success === true` is
147
+ * `confirmed`, `success === false` is `failed` (with the reported message as
148
+ * detail), and a non-object result (no contract) is treated as completed.
149
+ * The HTTP response contract is untouched either way — this only decides what
150
+ * the journal records.
151
+ */
152
+ function toLegacyMutationOutcome(result) {
153
+ if (typeof result !== 'object' || result === null) {
154
+ return { to: 'confirmed', detail: 'completed' };
155
+ }
156
+ const record = result;
157
+ if (!Object.hasOwn(record, 'success')) {
158
+ return { to: 'confirmed', detail: 'completed' };
159
+ }
160
+ const detailParts = [record.message, record.reason]
161
+ .filter((part) => typeof part === 'string' && part.length > 0);
162
+ const detail = detailParts.length > 0 ? detailParts.join('; ') : 'no detail reported';
163
+ return { to: record.success === true ? 'confirmed' : 'failed', detail };
164
+ }
165
+ /**
166
+ * Writes the terminal transition for an opened legacy transaction and reports
167
+ * loud when it cannot land: the journal would then hold an unfinished
168
+ * transaction, which is a governance gap (rc-9) even though the mutation's
169
+ * real outcome must not be masked.
170
+ */
171
+ async function appendLegacyOutcome(options, status, outcome) {
172
+ if (!status.journaled)
173
+ return;
174
+ const port = options.journal ?? await loadJournalPort();
175
+ if (port === null)
176
+ return;
177
+ try {
178
+ port.appendJournalTransition(status.journal.journalPath, {
179
+ at: (options.now ?? (() => new Date))().toISOString(),
180
+ from: 'planned',
181
+ to: outcome.to,
182
+ transactionId: status.journal.transactionId,
183
+ releaseId: status.journal.releaseId,
184
+ productVersion: status.journal.productVersion,
185
+ releaseMetadataDigest: status.journal.releaseMetadataDigest,
186
+ releaseMetadataDigestSource: 'fallback',
187
+ generation: 1,
188
+ detail: `actor=console-updater kind=${options.kind} ${outcome.detail}`,
189
+ });
190
+ }
191
+ catch (error) {
192
+ console.warn(`[update] Legacy mutation "${options.kind}" finished as "${outcome.to}" but its terminal journal transition `
193
+ + `could not be written (${error instanceof Error ? error.message : String(error)}). `
194
+ + `Transaction ${status.journal.transactionId} stays unfinished and must be reconciled.`);
195
+ }
196
+ }
197
+ /**
198
+ * Runs a legacy mutation under one journal transaction.
199
+ *
200
+ * `planned` is written before `run()`; `confirmed` / `failed` after — decided
201
+ * by BOTH the thrown/not-thrown boundary and the updater's `success` result
202
+ * shape (see `toLegacyMutationOutcome`). A journal failure never changes the
203
+ * mutation's outcome — it is reported alongside it.
204
+ */
205
+ export async function runLegacyJournaledMutation(options, run) {
206
+ const opened = await openLegacyMutationJournal(options);
207
+ try {
208
+ const result = await run();
209
+ const outcome = toLegacyMutationOutcome(result);
210
+ await appendLegacyOutcome(options, opened, { to: outcome.to, detail: `${options.kind}: ${outcome.detail}` });
211
+ return { result, journal: opened };
212
+ }
213
+ catch (error) {
214
+ await appendLegacyOutcome(options, opened, {
215
+ to: 'failed',
216
+ detail: `${options.kind}: ${error instanceof Error ? error.message : String(error)}`,
217
+ });
218
+ throw error;
219
+ }
220
+ }
@@ -0,0 +1,19 @@
1
+ export type CliSmokeResult = {
2
+ ok: true;
3
+ version: string;
4
+ } | {
5
+ ok: false;
6
+ error: string;
7
+ };
8
+ /**
9
+ * Spawn the pd CLI entry and require exit 0.
10
+ *
11
+ * Resolves `node` from PATH — the same resolution the installed bin/pd.cmd
12
+ * shim already depends on, so any environment that can run `pd` at all can
13
+ * run this probe. The entry path is boundary-checked to stay inside the
14
+ * pd-cli component dir (same guard shape as the legacy-rule preflight's
15
+ * subprocess target), and the subprocess is invoked with an argv array —
16
+ * never a shell string (ERR-045). Bounded stderr extract on failure (rc-8);
17
+ * never throws.
18
+ */
19
+ export declare function runPostUpdateCliSmoke(pdCliDir: string): CliSmokeResult;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * PRI-711: post-apply CLI smoke for the full-update pipeline.
3
+ *
4
+ * The full update swaps component dists without running npm install, and
5
+ * resolution gaps of one class have now shipped three times — each discovered
6
+ * only as a bricked CLI at the next console launch (PRI-561 host-runtime
7
+ * links, the 2026-08-29 install-layout component gap, PRI-711 codex-adapter).
8
+ * Spawning the pd CLI once with `--version` loads its entire eager import
9
+ * graph (health-codex → @principles/codex-adapter →
10
+ * @principles/host-runtime → @principles/core) and exits without touching
11
+ * workspace state, so a gap anywhere in that graph surfaces before the update
12
+ * is recorded as a success.
13
+ */
14
+ import { execFileSync } from 'child_process';
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ /**
18
+ * Spawn the pd CLI entry and require exit 0.
19
+ *
20
+ * Resolves `node` from PATH — the same resolution the installed bin/pd.cmd
21
+ * shim already depends on, so any environment that can run `pd` at all can
22
+ * run this probe. The entry path is boundary-checked to stay inside the
23
+ * pd-cli component dir (same guard shape as the legacy-rule preflight's
24
+ * subprocess target), and the subprocess is invoked with an argv array —
25
+ * never a shell string (ERR-045). Bounded stderr extract on failure (rc-8);
26
+ * never throws.
27
+ */
28
+ export function runPostUpdateCliSmoke(pdCliDir) {
29
+ const resolvedRoot = path.resolve(pdCliDir);
30
+ const pdCliEntry = path.join(resolvedRoot, 'dist', 'index.js');
31
+ if (!pdCliEntry.startsWith(resolvedRoot + path.sep)) {
32
+ return { ok: false, error: `pd CLI entry ${pdCliEntry} is outside the pd-cli component dir ${resolvedRoot}` };
33
+ }
34
+ if (!fs.existsSync(pdCliEntry)) {
35
+ return { ok: false, error: `pd CLI entry not found at ${pdCliEntry}` };
36
+ }
37
+ try {
38
+ const stdout = execFileSync('node', [pdCliEntry, '--version'], {
39
+ timeout: 60_000,
40
+ encoding: 'utf8',
41
+ windowsHide: true,
42
+ stdio: ['ignore', 'pipe', 'pipe'],
43
+ });
44
+ const version = (stdout.trim().split(/\r?\n/, 1)[0] ?? '').trim();
45
+ return { ok: true, version };
46
+ }
47
+ catch (error) {
48
+ // rc-1/rc-2: the child failure shape is unknown — guard every read.
49
+ // Node prints the error MESSAGE first and stack frames after, so the
50
+ // operator-facing extract is the bounded HEAD of stderr (a tail would
51
+ // carry only stack noise — observed in the PRI-711 test fixture).
52
+ let reason = error instanceof Error ? error.message : String(error);
53
+ if (typeof error === 'object' && error !== null && Object.hasOwn(error, 'stderr')) {
54
+ const { stderr } = error;
55
+ if (typeof stderr === 'string' && stderr.trim().length > 0)
56
+ reason = stderr;
57
+ }
58
+ return { ok: false, error: reason.trim().slice(0, 400) };
59
+ }
60
+ }
@@ -8,6 +8,14 @@ export interface UpdateLayout {
8
8
  pdCliDir: string;
9
9
  installLayoutDir: string;
10
10
  releaseManagerDir: string;
11
+ /**
12
+ * PRI-711: codex-adapter is a runtime-layout component pd-cli resolves
13
+ * eagerly. Undefined when the DEPLOYED @principles/install-layout is one
14
+ * generation old (this update runs inside the currently-running console,
15
+ * which resolves the layout helper installed by the PREVIOUS update) —
16
+ * consumers must degrade to skipping the adapter, never assume it.
17
+ */
18
+ codexAdapterDir: string | undefined;
11
19
  hosts: InstallHost[];
12
20
  }
13
21
  export declare function resolveUpdateLayout(): UpdateLayout | undefined;
@@ -46,6 +46,9 @@ export function resolveUpdateLayout() {
46
46
  pdCliDir: paths.pdCliDir,
47
47
  installLayoutDir: paths.installLayoutDir,
48
48
  releaseManagerDir: paths.releaseManagerDir,
49
+ // rc-1: the deployed install-layout may predate the codexAdapterDir
50
+ // field — guard instead of trusting the shape.
51
+ codexAdapterDir: typeof paths.codexAdapterDir === 'string' ? paths.codexAdapterDir : undefined,
49
52
  hosts: resolution.manifest?.hosts ?? [],
50
53
  };
51
54
  }
@@ -57,6 +60,7 @@ export function resolveUpdateLayout() {
57
60
  pdCliDir: path.join(legacyPluginDir, 'pd-cli'),
58
61
  installLayoutDir: path.join(legacyPluginDir, 'install-layout'),
59
62
  releaseManagerDir: path.join(legacyPluginDir, 'release-manager'),
63
+ codexAdapterDir: path.join(legacyPluginDir, 'codex-adapter'),
60
64
  hosts: ['openclaw'],
61
65
  };
62
66
  }
@@ -1355,8 +1355,8 @@
1355
1355
  "filesUpdated": "Updated {{count}} files",
1356
1356
  "networkErrorHint": "Please check your network connection and try again.",
1357
1357
  "retry": "Retry",
1358
- "codexWarning": "Codex Host detected. This update only covers the OpenClaw plugin code and does not update the Codex adapter (@principles/codex-adapter). For a full update, run the installer.",
1359
- "partialUpdateHint": "Plugin code updated. To update the full runtime (including the Codex adapter and dependencies), run: npx create-principles-disciple",
1358
+ "codexWarning": "Codex Host detected. The full update also refreshes the Codex adapter (@principles/codex-adapter) in the runtime layout. To re-register Codex hooks or repair the Codex host install, run the installer.",
1359
+ "partialUpdateHint": "Plugin code updated. If components ever look out of sync, refresh the full runtime with: npx create-principles-disciple",
1360
1360
  "applyFullUpdate": "Full Update",
1361
1361
  "fullUpdating": "Full update in progress…",
1362
1362
  "fullUpdateSuccess": "Full update completed.",
@@ -1355,8 +1355,8 @@
1355
1355
  "filesUpdated": "更新了 {{count}} 个文件",
1356
1356
  "networkErrorHint": "请检查网络连接后重试。",
1357
1357
  "retry": "重试",
1358
- "codexWarning": "检测到 Codex Host 已安装。此处的更新仅覆盖 OpenClaw 插件代码,不会更新 Codex 适配器(@principles/codex-adapter)。如需完整更新,请运行安装器。",
1359
- "partialUpdateHint": "插件代码已更新。如需更新完整运行时(含 Codex 适配器和依赖),请运行:npx create-principles-disciple",
1358
+ "codexWarning": "检测到 Codex Host 已安装。全量更新现在会同步刷新运行时布局中的 Codex 适配器(@principles/codex-adapter)。如需重新注册 Codex hooks 或修复 Codex 宿主安装,请运行安装器。",
1359
+ "partialUpdateHint": "插件代码已更新。如发现组件版本不同步,请运行以下命令刷新完整运行时:npx create-principles-disciple",
1360
1360
  "applyFullUpdate": "全量更新",
1361
1361
  "fullUpdating": "全量更新中…",
1362
1362
  "fullUpdateSuccess": "全量更新完成。",