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
package/dist/installer.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, statSync, readFileSync, writeFileSync, mkdirSync, rmSync, copyFileSync, cpSync, renameSync, chmodSync, symlinkSync } from 'fs';
1
+ import { existsSync, lstatSync, readdirSync, realpathSync, statSync, readFileSync, writeFileSync, mkdirSync, rmSync, copyFileSync, cpSync, renameSync, chmodSync, symlinkSync } from 'fs';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import fse from 'fs-extra';
@@ -16,7 +16,9 @@ import { mergeInstallManifestWorkspaces, parseInstallManifest } from '@principle
16
16
  import { detectOpenClawMainWorkspaceDivergence } from './utils/workspace-divergence.js';
17
17
  import { applySkillLanguageSelection } from './skill-language.js';
18
18
  import { parseReleaseAssetIdentity, parseReleaseAssetManifest, ReleaseAssetManifestError, verifyReleaseAssetManifestAsync, verifyReleaseAssetTarget, } from './update/release-asset-manifest.js';
19
- import { appendJournalTransition } from './update/transaction-journal.js';
19
+ import { mergeIntoInstallJson, readInstallJsonRecord, resolvePdHomePaths, } from './update/install-layout.js';
20
+ import { RELEASE_METADATA_URL_ENV, normalizeReleaseMetadataUrl } from './update/release-metadata-source.js';
21
+ import { appendJournalTransition, readActiveRecord, writeActiveRecord, } from './update/transaction-journal.js';
20
22
  /** PRI-343: Keep in sync with @principles/core CONVERSATION_ACCESS_CONFIG_KEY */
21
23
  export const CONVERSATION_ACCESS_CONFIG_KEY = 'allowConversationAccess';
22
24
  function isRecord(value) {
@@ -155,7 +157,90 @@ function resolveInstallManifestWorkspaces(workspaceDir) {
155
157
  }
156
158
  function writeInstallManifest(hosts, workspaces) {
157
159
  mkdirSync(getPdDir(), { recursive: true });
158
- writeFileSync(getInstallManifestPath(), JSON.stringify({ layoutVersion: 1, mode: 'canonical', hosts, workspaces }, null, 2) + '\n', 'utf8');
160
+ // PRI-709 P0-1: merge into the existing record. This file has more than one
161
+ // writer (the update side owns `channel` / `autoCheck` / `releaseMetadataUrl`);
162
+ // a wholesale replace silently deleted those fields.
163
+ const existing = readInstallJsonRecord(getInstallManifestPath());
164
+ const payload = {
165
+ ...(existing ?? {}),
166
+ layoutVersion: 1,
167
+ mode: 'canonical',
168
+ hosts,
169
+ workspaces,
170
+ };
171
+ writeFileSync(getInstallManifestPath(), JSON.stringify(payload, null, 2) + '\n', 'utf8');
172
+ }
173
+ export function commitInstallerActiveRecord(journal) {
174
+ const paths = resolvePdHomePaths(getPdDir());
175
+ let previous = null;
176
+ let previousWarning = null;
177
+ try {
178
+ previous = readActiveRecord(paths.activeRecordPath);
179
+ }
180
+ catch (error) {
181
+ // A corrupt previous record must not block committing the new identity —
182
+ // overwriting it with a valid record is strictly an improvement.
183
+ previousWarning = error instanceof Error ? error.message : String(error);
184
+ logger.warn(`active.json was unreadable and is being replaced (${paths.activeRecordPath}): ${previousWarning}`);
185
+ }
186
+ // Generation continuity: never move backwards. A standalone install already
187
+ // carries generation 1, but re-installing over an existing deployment must
188
+ // advance past it — otherwise the deployment identity source would regress.
189
+ const generation = Math.max(journal.generation, previous === null ? 1 : previous.generation + 1);
190
+ try {
191
+ writeActiveRecord(paths.activeRecordPath, {
192
+ generation,
193
+ releaseId: journal.releaseId,
194
+ releaseMetadataDigest: journal.releaseMetadataDigest,
195
+ previousReleaseId: previous?.releaseId ?? null,
196
+ transactionId: journal.transactionId,
197
+ productVersion: journal.productVersion,
198
+ });
199
+ return {
200
+ written: true,
201
+ activeRecordPath: paths.activeRecordPath,
202
+ generation,
203
+ previousReleaseId: previous?.releaseId ?? null,
204
+ ...(previousWarning !== null ? { reason: `previous_record_unreadable: ${previousWarning}` } : {}),
205
+ };
206
+ }
207
+ catch (error) {
208
+ const reason = error instanceof Error ? error.message : String(error);
209
+ logger.error(`active.json could not be written after a committed install (${paths.activeRecordPath}): ${reason}. `
210
+ + `Deployment identity is out of sync with the transaction ${journal.transactionId}; re-run the installer to repair it.`);
211
+ return {
212
+ written: false,
213
+ activeRecordPath: paths.activeRecordPath,
214
+ generation,
215
+ previousReleaseId: previous?.releaseId ?? null,
216
+ reason,
217
+ };
218
+ }
219
+ }
220
+ /**
221
+ * PRI-709 P0-1 — installer supply path for the release metadata source.
222
+ *
223
+ * The installer is the only component that legitimately knows the metadata
224
+ * repository at install time, so it persists `PD_RELEASE_METADATA_URL` into
225
+ * `~/.pd/install.json` (the durable `install_config` tier). Without this the
226
+ * variable had to be exported in every shell that wanted a governed update
227
+ * check (PRI-698 audit F-4).
228
+ *
229
+ * Absent env is a no-op — an install with no metadata source stays
230
+ * unconfigured rather than persisting a guessed URL. A malformed env value is
231
+ * reported and skipped: it must never be written into install.json, where the
232
+ * strict reader would then fail loud and mark the install corrupt.
233
+ */
234
+ export function persistReleaseMetadataSource() {
235
+ const raw = process.env[RELEASE_METADATA_URL_ENV];
236
+ if (raw === undefined || raw.trim().length === 0)
237
+ return;
238
+ const normalized = normalizeReleaseMetadataUrl(raw);
239
+ if (normalized === null) {
240
+ logger.warn(`${RELEASE_METADATA_URL_ENV} is not a valid http(s) URL and was not persisted to install.json: ${JSON.stringify(raw)}. Release metadata source stays unconfigured.`);
241
+ return;
242
+ }
243
+ mergeIntoInstallJson(getInstallManifestPath(), { releaseMetadataUrl: normalized });
159
244
  }
160
245
  function installBundledLayoutPackage(pluginDir) {
161
246
  const source = path.join(pluginDir, 'install-layout');
@@ -1444,6 +1529,85 @@ function installBundledCodexAdapter(pluginDir) {
1444
1529
  rmSync(codexAdapterDest, { recursive: true, force: true });
1445
1530
  cpSync(codexAdapterSrc, codexAdapterDest, { recursive: true });
1446
1531
  }
1532
+ /**
1533
+ * PRI-711: materialize node_modules links for the INSTALLED codex-adapter so
1534
+ * its declared `file:../<component>` dependencies resolve under ESM realpath
1535
+ * rules. The adapter's dist imports @principles/host-runtime and
1536
+ * @principles/core, and pd-cli's eager import graph (health-codex) statically
1537
+ * imports the adapter — without these links every pd command dies with
1538
+ * ERR_MODULE_NOT_FOUND at startup (observed 2026-09-09: the 1.231.2 full
1539
+ * update swapped in Slice-D pd-cli while the deployed adapter, laid down by
1540
+ * older installers, had no node_modules at all).
1541
+ *
1542
+ * Data-driven from the deployed adapter manifest: every dependency whose ref
1543
+ * starts with `file:../` names a sibling runtime component directory. An
1544
+ * existing correct link is kept; a stale physical copy (self-contained
1545
+ * payloads ship materialized node_modules) or a wrong-target link is
1546
+ * replaced with the canonical junction/symlink, mirroring syncPdCli.
1547
+ * Idempotent. MUST run after installBundledCodexAdapter AND the
1548
+ * core/host-runtime/install-layout installs — the manifest declares all
1549
+ * three as file:../ siblings, and a missing sibling means a corrupted
1550
+ * payload or a call-order regression, which fails the install loudly.
1551
+ */
1552
+ export function ensureCodexAdapterResolution() {
1553
+ const codexAdapterDir = getInstalledCodexAdapterDir();
1554
+ const manifestPath = path.join(codexAdapterDir, 'package.json');
1555
+ let manifest;
1556
+ try {
1557
+ manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
1558
+ }
1559
+ catch (error) {
1560
+ throw new Error(`Installed @principles/codex-adapter package.json is missing or malformed (${manifestPath}): ${error instanceof Error ? error.message : String(error)}`, { cause: error });
1561
+ }
1562
+ if (!isRecord(manifest)) {
1563
+ throw new Error(`Installed @principles/codex-adapter package.json must contain an object (${manifestPath}).`);
1564
+ }
1565
+ if (manifest.dependencies !== undefined && !isRecord(manifest.dependencies)) {
1566
+ throw new Error(`Installed @principles/codex-adapter package.json dependencies must be an object (${manifestPath}).`);
1567
+ }
1568
+ const runtimeDir = getPdRuntimeDir();
1569
+ for (const [name, ref] of Object.entries(manifest.dependencies ?? {})) {
1570
+ if (typeof ref !== 'string' || !ref.startsWith('file:../'))
1571
+ continue;
1572
+ const siblingName = ref.slice('file:../'.length);
1573
+ // rc-1: the manifest is untrusted input — only accept single-path segment
1574
+ // siblings so a crafted ref cannot escape the runtime dir.
1575
+ if (!/^[A-Za-z0-9._-]+$/.test(siblingName) || siblingName === '.' || siblingName === '..') {
1576
+ throw new Error(`Installed @principles/codex-adapter declares an unsupported file dependency ref "${ref}" for ${name}.`);
1577
+ }
1578
+ const siblingDir = path.join(runtimeDir, siblingName);
1579
+ if (!existsSync(siblingDir)) {
1580
+ throw new Error(`@principles/codex-adapter depends on ${ref} but the sibling runtime component is missing: ${siblingDir}`);
1581
+ }
1582
+ const linkPath = path.join(codexAdapterDir, 'node_modules', name);
1583
+ let stat;
1584
+ try {
1585
+ stat = lstatSync(linkPath);
1586
+ }
1587
+ catch {
1588
+ stat = undefined; // ENOENT — create below
1589
+ }
1590
+ if (stat !== undefined) {
1591
+ if (stat.isSymbolicLink()) {
1592
+ try {
1593
+ if (realpathSync(linkPath) === realpathSync(siblingDir))
1594
+ continue;
1595
+ }
1596
+ catch {
1597
+ // Unreadable link target — fall through to replace.
1598
+ }
1599
+ }
1600
+ rmSync(linkPath, { recursive: stat.isDirectory() && !stat.isSymbolicLink(), force: true });
1601
+ }
1602
+ mkdirSync(path.dirname(linkPath), { recursive: true });
1603
+ if (isWindows()) {
1604
+ symlinkSync(siblingDir, linkPath, 'junction');
1605
+ }
1606
+ else {
1607
+ symlinkSync(path.relative(path.dirname(linkPath), siblingDir), linkPath, 'dir');
1608
+ }
1609
+ }
1610
+ }
1447
1611
  function ensureCoreDependency(_targetDir) {
1448
1612
  const coreDir = getInstalledCoreDir();
1449
1613
  if (!existsSync(coreDir)) {
@@ -1888,6 +2052,18 @@ async function runHostInstallers(host, ctx) {
1888
2052
  function sha256File(filePath) {
1889
2053
  return createHash('sha256').update(readFileSync(filePath)).digest('hex');
1890
2054
  }
2055
+ function readPackageVersion(pkgPath) {
2056
+ if (!existsSync(pkgPath))
2057
+ return null;
2058
+ try {
2059
+ const parsed = JSON.parse(readFileSync(pkgPath, 'utf8'));
2060
+ return typeof parsed.version === 'string' && parsed.version.length > 0 ? parsed.version : null;
2061
+ }
2062
+ catch {
2063
+ // Identity falls back; journaling must not brick install.
2064
+ return null;
2065
+ }
2066
+ }
1891
2067
  /**
1892
2068
  * Identity of the payload being installed. Prefers the self-contained asset
1893
2069
  * manifest (covers the whole payload); falls back to the bundled pd-cli
@@ -1896,20 +2072,19 @@ function sha256File(filePath) {
1896
2072
  * legacy-migration.ts). The last-resort fallback hashes the literal reason
1897
2073
  * string only to satisfy the journal's 64-hex format requirement; it is not
1898
2074
  * part of any release-metadata identity chain.
2075
+ *
2076
+ * PRI-709 P0-2 (PRI-698 audit F-1): `productVersion` is the PRODUCT version —
2077
+ * the plugin package manifest. It previously read `pd-cli/package.json`, but
2078
+ * the two packages version independently (on the Owner machine: plugin
2079
+ * 1.230.2 vs pd-cli 1.147.5), so every confirmed journal recorded a version
2080
+ * that no runtime state could ever match. That is why active.json could not
2081
+ * serve as the deployment identity source.
1899
2082
  */
1900
2083
  function resolveInstallerPayloadIdentity(pluginDir) {
1901
- let productVersion = 'unknown';
2084
+ const productVersion = readPackageVersion(path.join(pluginDir, 'package.json'))
2085
+ ?? readPackageVersion(path.join(pluginDir, 'pd-cli', 'package.json'))
2086
+ ?? 'unknown';
1902
2087
  const pdCliPkgPath = path.join(pluginDir, 'pd-cli', 'package.json');
1903
- if (existsSync(pdCliPkgPath)) {
1904
- try {
1905
- const parsed = JSON.parse(readFileSync(pdCliPkgPath, 'utf8'));
1906
- if (typeof parsed.version === 'string' && parsed.version.length > 0)
1907
- productVersion = parsed.version;
1908
- }
1909
- catch {
1910
- // Identity falls back to 'unknown'; journaling must not brick install.
1911
- }
1912
- }
1913
2088
  const assetManifestPath = path.join(pluginDir, '_release', 'manifest.json');
1914
2089
  let releaseMetadataDigest;
1915
2090
  let releaseMetadataDigestSource;
@@ -2213,6 +2388,11 @@ transaction) {
2213
2388
  installBundledCodexAdapter(pluginDir);
2214
2389
  stepIndex++;
2215
2390
  installBundledLayoutPackage(pluginDir);
2391
+ // PRI-711: the adapter's own file: deps must resolve or pd-cli's eager
2392
+ // import graph crashes on every command. Runs only once ALL adapter
2393
+ // siblings exist — the manifest declares file:../core, file:../host-runtime
2394
+ // AND file:../install-layout, and install-layout lands just above.
2395
+ ensureCodexAdapterResolution();
2216
2396
  if (spinner)
2217
2397
  updateProgress(spinner, stepIndex, 'Installing release-manager authority module...');
2218
2398
  installBundledReleaseManagerPackage(pluginDir);
@@ -2405,6 +2585,9 @@ transaction) {
2405
2585
  throw new Error(`Host installation failed: ${hostFailures.join(' | ')}`);
2406
2586
  }
2407
2587
  writeInstallManifest(installManifestHosts, resolveInstallManifestWorkspaces(options.workspaceDir));
2588
+ // PRI-709 P0-1: persist the metadata source after the manifest write, so
2589
+ // the durable tier cannot be clobbered by the same install.
2590
+ persistReleaseMetadataSource();
2408
2591
  // ADR-0024 D-2: host installers completed and the install manifest is
2409
2592
  // written — the new installation is fully activated (backups not yet
2410
2593
  // discarded, so a crash here still recovers via the backup).
@@ -2412,6 +2595,9 @@ transaction) {
2412
2595
  cleanupBackup(backupDir, runtimeBackupDir);
2413
2596
  // ADR-0024 D-2: backup cleanup is the commit point of the transaction.
2414
2597
  journalInstallerTransitionDegrading(journal, journal.lastState, 'confirmed', 'backup cleaned up; install complete');
2598
+ // PRI-709 P0-2: active.json is the deployment identity source, written
2599
+ // journal-first — after `confirmed`, never before it.
2600
+ commitInstallerActiveRecord(journal);
2415
2601
  if (spinner) {
2416
2602
  spinner.succeed('Install complete!');
2417
2603
  }