create-principles-disciple 1.134.7 → 1.134.9

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.
@@ -1,4 +1,19 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ /**
3
+ * PRI-702 (ADR-0024 D-7): which authority actually performed the mutation.
4
+ *
5
+ * The vocabulary is REUSED from the MutationController — the one place that
6
+ * already names mutation authorities — so the history stream can never drift
7
+ * into a second authority vocabulary. `legacy-migration` is deliberately not
8
+ * listed: it writes the separate SPEC §12 stream (`<pdHome>/logs/history.jsonl`)
9
+ * and must not be conflated with this Owner-facing one.
10
+ *
11
+ * The field is additive and optional. Records written before PRI-702 have no
12
+ * `authority`; they are kept verbatim (no migration) and the reader never
13
+ * fabricates a value for them.
14
+ */
15
+ export declare const UPDATE_HISTORY_AUTHORITIES: readonly ["release-manager", "legacy-console-updater"];
16
+ export type UpdateHistoryAuthority = (typeof UPDATE_HISTORY_AUTHORITIES)[number];
2
17
  export declare const UPDATE_HISTORY_KINDS: readonly ['update', 'reinstall', 'legacy_migration', 'rollback', 'refusal', 'failure', 'recovery', 'unknown'];
3
18
  export type UpdateHistoryKind = (typeof UPDATE_HISTORY_KINDS)[number];
4
19
  interface UpdateHistoryEntry {
@@ -11,7 +26,29 @@ interface UpdateHistoryEntry {
11
26
  backupPath?: string;
12
27
  reason?: string;
13
28
  nextAction?: string;
29
+ /** PRI-702: authority that performed the mutation. Absent on pre-PRI-702 records. */
30
+ authority?: UpdateHistoryAuthority;
31
+ /**
32
+ * PRI-702: the transaction journal id of the update, when one exists.
33
+ * Correlates the Owner-facing event with the machine-recovery stream
34
+ * (`~/.pd/transactions/<transactionId>.jsonl`) — the two stay separate
35
+ * structures (ADR-0024 §2.5-2); this is a pointer, not a copy.
36
+ */
37
+ transactionId?: string;
14
38
  }
39
+ /**
40
+ * PRI-702 (ADR-0024 D-7): the ONE Owner-facing update-history writer.
41
+ *
42
+ * Both mutation authorities append through this function — the legacy console
43
+ * updater (every existing call site in `routes/update.ts`) and the
44
+ * ReleaseManager-served apply-full dispatch. There is no second writer and no
45
+ * authority-specific schema: callers differ only in the `authority` they pass.
46
+ *
47
+ * `authority` defaults to `legacy-console-updater` because that is the writer
48
+ * that has owned this stream since before PRI-702; a caller that omits it is
49
+ * by definition the legacy path. Every entry written from now on carries the
50
+ * field explicitly, so PRI-701's legacy-usage census can read it directly.
51
+ */
15
52
  export declare function appendUpdateHistory(workspaceDir: string, entry: Omit<UpdateHistoryEntry, 'id' | 'timestamp'>): void;
16
53
  export declare function handleUpdateHistoryRoute(req: IncomingMessage, res: ServerResponse, workspaceDir: string, _subPath: string): Promise<void>;
17
54
  export {};
@@ -1,6 +1,24 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { sendSuccess, sendMethodNotAllowed } from '../utils/response.js';
4
+ import { LEGACY_MUTATION_AUTHORITY, RELEASE_MANAGER_AUTHORITY, } from '../update/mutation-controller.js';
5
+ /**
6
+ * PRI-702 (ADR-0024 D-7): which authority actually performed the mutation.
7
+ *
8
+ * The vocabulary is REUSED from the MutationController — the one place that
9
+ * already names mutation authorities — so the history stream can never drift
10
+ * into a second authority vocabulary. `legacy-migration` is deliberately not
11
+ * listed: it writes the separate SPEC §12 stream (`<pdHome>/logs/history.jsonl`)
12
+ * and must not be conflated with this Owner-facing one.
13
+ *
14
+ * The field is additive and optional. Records written before PRI-702 have no
15
+ * `authority`; they are kept verbatim (no migration) and the reader never
16
+ * fabricates a value for them.
17
+ */
18
+ export const UPDATE_HISTORY_AUTHORITIES = [
19
+ RELEASE_MANAGER_AUTHORITY,
20
+ LEGACY_MUTATION_AUTHORITY,
21
+ ];
4
22
  export const UPDATE_HISTORY_KINDS = [
5
23
  'update',
6
24
  'reinstall',
@@ -14,6 +32,9 @@ export const UPDATE_HISTORY_KINDS = [
14
32
  function isHistoryKind(value) {
15
33
  return typeof value === 'string' && UPDATE_HISTORY_KINDS.includes(value);
16
34
  }
35
+ function isHistoryAuthority(value) {
36
+ return typeof value === 'string' && UPDATE_HISTORY_AUTHORITIES.includes(value);
37
+ }
17
38
  function parseHistoryEntry(value) {
18
39
  if (typeof value !== 'object' || value === null || Array.isArray(value))
19
40
  return undefined;
@@ -32,6 +53,8 @@ function parseHistoryEntry(value) {
32
53
  return undefined;
33
54
  if (raw.backupPath !== undefined && typeof raw.backupPath !== 'string')
34
55
  return undefined;
56
+ if (raw.transactionId !== undefined && typeof raw.transactionId !== 'string')
57
+ return undefined;
35
58
  return {
36
59
  id: raw.id,
37
60
  timestamp: raw.timestamp,
@@ -45,6 +68,11 @@ function parseHistoryEntry(value) {
45
68
  ...(typeof raw.backupPath === 'string' ? { backupPath: raw.backupPath } : {}),
46
69
  ...(typeof raw.reason === 'string' ? { reason: raw.reason } : {}),
47
70
  ...(typeof raw.nextAction === 'string' ? { nextAction: raw.nextAction } : {}),
71
+ // An authority outside the closed vocabulary is dropped rather than
72
+ // failing the whole entry: this stream is an Owner read model, and one
73
+ // unknown writer must not hide every other record (rc-3).
74
+ ...(isHistoryAuthority(raw.authority) ? { authority: raw.authority } : {}),
75
+ ...(typeof raw.transactionId === 'string' ? { transactionId: raw.transactionId } : {}),
48
76
  };
49
77
  }
50
78
  function getHistoryPath(workspaceDir) {
@@ -68,11 +96,25 @@ function loadHistory(historyPath) {
68
96
  }
69
97
  return [];
70
98
  }
99
+ /**
100
+ * PRI-702 (ADR-0024 D-7): the ONE Owner-facing update-history writer.
101
+ *
102
+ * Both mutation authorities append through this function — the legacy console
103
+ * updater (every existing call site in `routes/update.ts`) and the
104
+ * ReleaseManager-served apply-full dispatch. There is no second writer and no
105
+ * authority-specific schema: callers differ only in the `authority` they pass.
106
+ *
107
+ * `authority` defaults to `legacy-console-updater` because that is the writer
108
+ * that has owned this stream since before PRI-702; a caller that omits it is
109
+ * by definition the legacy path. Every entry written from now on carries the
110
+ * field explicitly, so PRI-701's legacy-usage census can read it directly.
111
+ */
71
112
  export function appendUpdateHistory(workspaceDir, entry) {
72
113
  const historyPath = getHistoryPath(workspaceDir);
73
114
  const history = loadHistory(historyPath);
74
115
  history.push({
75
116
  ...entry,
117
+ authority: entry.authority ?? LEGACY_MUTATION_AUTHORITY,
76
118
  id: `update-${Date.now()}`,
77
119
  timestamp: new Date().toISOString(),
78
120
  });
@@ -1921,7 +1921,31 @@ async function runReleaseManagerCheckDispatch(mod, req, res, ctx) {
1921
1921
  * layer maps the outcome into the legacy doInlineFullUpdate response contract
1922
1922
  * ({success, message, reason?, nextAction?, newVersion?, requiresRestart}) —
1923
1923
  * wire contract unchanged. It performs NO runtime mutation itself.
1924
+ *
1925
+ * PRI-702 (ADR-0024 D-7): this boundary is also where the Owner-facing update
1926
+ * history is appended for an RM-served update, through the SAME
1927
+ * `appendUpdateHistory` writer the legacy updater uses. It is the only layer
1928
+ * that knows all three of the workspace (where the history lives), the
1929
+ * authority that served the mutation, and the final outcome — the installer
1930
+ * writes the transaction journal only, and a pre-transaction refusal writes
1931
+ * NO history event (the fallback's legacy event is the single record, see
1932
+ * `X-PD-Mutation-Fallback-Reason`).
1933
+ */
1934
+ /**
1935
+ * A history-write failure must never misreport a mutation that already
1936
+ * happened: the response still carries the true outcome (same policy as
1937
+ * `logLegacyJournalGap` above — blocking the Owner's update would be worse
1938
+ * than an unaudited one) but the audit gap is logged loud (rc-9).
1924
1939
  */
1940
+ function appendGovernedUpdateHistory(workspaceDir, entry) {
1941
+ try {
1942
+ appendUpdateHistory(workspaceDir, entry);
1943
+ }
1944
+ catch (error) {
1945
+ console.error(`[update] ReleaseManager-served mutation completed WITHOUT an update-history record (${error instanceof Error ? error.message : String(error)}). `
1946
+ + 'ADR-0024 D-7 requires every update to be auditable; the response still reports the real outcome.');
1947
+ }
1948
+ }
1925
1949
  async function runReleaseManagerApplyFullDispatch(mod, req, res, ctx) {
1926
1950
  if (req.method !== 'POST') {
1927
1951
  sendMethodNotAllowed(res);
@@ -1951,9 +1975,26 @@ async function runReleaseManagerApplyFullDispatch(mod, req, res, ctx) {
1951
1975
  await legacyApplyFullMutation(req, res, ctx);
1952
1976
  return;
1953
1977
  }
1978
+ // PRI-702 (ADR-0024 D-7): the pre-apply version, read from the SAME install
1979
+ // state the ReleaseManager used to decide the update (active.json) — never
1980
+ // from a second version source.
1981
+ const fromVersion = authority.installStatus?.productVersion ?? 'unknown';
1954
1982
  try {
1955
1983
  const outcome = await authority.manager.apply({ workspaceDir: ctx.workspaceDir });
1956
1984
  if (outcome.kind === 'applied') {
1985
+ // One update → one Owner-visible history event. The Console boundary is
1986
+ // the only layer that owns workspaceDir + authority + outcome together,
1987
+ // so the canonical writer is called here; the installer and the
1988
+ // ReleaseManager keep writing the transaction journal only (journal =
1989
+ // machine recovery, history = Owner audit, ADR-0024 §2.5-2).
1990
+ appendGovernedUpdateHistory(ctx.workspaceDir, {
1991
+ fromVersion,
1992
+ toVersion: outcome.productVersion,
1993
+ success: true,
1994
+ kind: 'update',
1995
+ authority: RELEASE_MANAGER_AUTHORITY,
1996
+ transactionId: outcome.transactionId,
1997
+ });
1957
1998
  sendSuccess(res, {
1958
1999
  success: true,
1959
2000
  message: `Updated to ${outcome.productVersion}. Transaction ${outcome.transactionId} confirmed in the journal.`,
@@ -1963,6 +2004,19 @@ async function runReleaseManagerApplyFullDispatch(mod, req, res, ctx) {
1963
2004
  });
1964
2005
  }
1965
2006
  else {
2007
+ // Legacy parity: the legacy updater records a `refusal` event when the
2008
+ // source does not advance the installation, so an RM-served "no update"
2009
+ // leaves the same trace instead of silence. No runtime byte changed —
2010
+ // toVersion stays at the installed version.
2011
+ appendGovernedUpdateHistory(ctx.workspaceDir, {
2012
+ fromVersion,
2013
+ toVersion: fromVersion,
2014
+ success: false,
2015
+ kind: 'refusal',
2016
+ reason: outcome.note,
2017
+ nextAction: 'No runtime change was made. Retry when a newer signed release is published.',
2018
+ authority: RELEASE_MANAGER_AUTHORITY,
2019
+ });
1966
2020
  sendSuccess(res, {
1967
2021
  success: true,
1968
2022
  message: `No update applied: ${outcome.note}`,
@@ -1986,6 +2040,19 @@ async function runReleaseManagerApplyFullDispatch(mod, req, res, ctx) {
1986
2040
  await legacyApplyFullMutation(req, res, ctx);
1987
2041
  return;
1988
2042
  }
2043
+ // PRI-702: a post-transaction failure is a real, terminal update attempt,
2044
+ // so it gets exactly one failure event under the ReleaseManager authority.
2045
+ // There is no legacy fallback on this path, hence no second event — the
2046
+ // runtime is left on the previous release (installer backup/restore).
2047
+ appendGovernedUpdateHistory(ctx.workspaceDir, {
2048
+ fromVersion,
2049
+ toVersion: 'failed',
2050
+ success: false,
2051
+ kind: 'failure',
2052
+ reason: mapped.reason,
2053
+ nextAction: mapped.nextAction ?? 'The runtime is unchanged. Retry the update after resolving the cause.',
2054
+ authority: RELEASE_MANAGER_AUTHORITY,
2055
+ });
1989
2056
  sendSuccess(res, {
1990
2057
  success: false,
1991
2058
  message: mapped.message,
@@ -10,10 +10,12 @@ export interface UpdateLayout {
10
10
  releaseManagerDir: string;
11
11
  /**
12
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.
13
+ * eagerly. Undefined only when the canonical runtime root itself cannot be
14
+ * resolved: a one-generation-old deployed install-layout (this update runs
15
+ * inside the currently-running console, which resolves the layout helper
16
+ * installed by the PREVIOUS update) lacks the codexAdapterDir field, but
17
+ * PRI-724 derives the destination from the stable runtimeDir instead of
18
+ * degrading to a silent skip.
17
19
  */
18
20
  codexAdapterDir: string | undefined;
19
21
  hosts: InstallHost[];
@@ -18,6 +18,29 @@ import { resolveOpenClawHome } from './pd-backups.js';
18
18
  export function resolveExtensionsDir() {
19
19
  return path.join(resolveOpenClawHome(), 'extensions');
20
20
  }
21
+ /**
22
+ * Resolve where the codex-adapter component lives in a CANONICAL layout.
23
+ *
24
+ * PRI-724: a one-generation-old deployed install-layout does not export the
25
+ * codexAdapterDir field (introduced 2026-09-09, PRI-711). The full update
26
+ * runs inside the currently-running console, which resolves the layout
27
+ * helper installed by the PREVIOUS update — so during exactly one update
28
+ * generation the field is always missing. Derive the destination from the
29
+ * stable runtimeDir (the adapter has always been installed at
30
+ * <runtimeDir>/codex-adapter) instead of degrading the update to skipping
31
+ * the adapter copy, which left the deployed adapter one release behind
32
+ * while the update reported success (observed 2026-09-10).
33
+ */
34
+ function resolveCanonicalCodexAdapterDir(paths) {
35
+ if (typeof paths.codexAdapterDir === 'string' && paths.codexAdapterDir.length > 0) {
36
+ return paths.codexAdapterDir;
37
+ }
38
+ // rc-1/rc-2: the deployed module's shape is not trusted — only derive from
39
+ // a runtimeDir that is actually a non-empty string.
40
+ return typeof paths.runtimeDir === 'string' && paths.runtimeDir.length > 0
41
+ ? path.join(paths.runtimeDir, 'codex-adapter')
42
+ : undefined;
43
+ }
21
44
  export function resolveUpdateLayout() {
22
45
  const homeDir = os.homedir();
23
46
  const paths = getInstallLayoutPaths(homeDir);
@@ -46,9 +69,11 @@ export function resolveUpdateLayout() {
46
69
  pdCliDir: paths.pdCliDir,
47
70
  installLayoutDir: paths.installLayoutDir,
48
71
  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,
72
+ // PRI-711: the deployed install-layout may predate the codexAdapterDir
73
+ // field — guard instead of trusting the shape. PRI-724: when the field
74
+ // is missing, derive the destination from runtimeDir so the update
75
+ // still swaps the adapter (see resolveCanonicalCodexAdapterDir).
76
+ codexAdapterDir: resolveCanonicalCodexAdapterDir(paths),
52
77
  hosts: resolution.manifest?.hosts ?? [],
53
78
  };
54
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.134.7",
3
+ "version": "1.134.9",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.134.7",
3
+ "version": "1.134.9",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {