create-principles-disciple 1.123.0 → 1.124.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.
Files changed (58) hide show
  1. package/core/dist/runtime-v2/__tests__/pain-signal-bridge-execute-pending.test.d.ts +2 -0
  2. package/core/dist/runtime-v2/__tests__/pain-signal-bridge-execute-pending.test.d.ts.map +1 -0
  3. package/core/dist/runtime-v2/__tests__/pain-signal-bridge-execute-pending.test.js +106 -0
  4. package/core/dist/runtime-v2/__tests__/pain-signal-bridge-execute-pending.test.js.map +1 -0
  5. package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js +2 -2
  6. package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js.map +1 -1
  7. package/core/dist/runtime-v2/index.d.ts +2 -2
  8. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  9. package/core/dist/runtime-v2/index.js +2 -2
  10. package/core/dist/runtime-v2/index.js.map +1 -1
  11. package/core/dist/runtime-v2/internalization/internalization-task-guards.d.ts +5 -8
  12. package/core/dist/runtime-v2/internalization/internalization-task-guards.d.ts.map +1 -1
  13. package/core/dist/runtime-v2/internalization/internalization-task-guards.js +11 -12
  14. package/core/dist/runtime-v2/internalization/internalization-task-guards.js.map +1 -1
  15. package/core/dist/runtime-v2/pain-signal-bridge.d.ts +40 -0
  16. package/core/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
  17. package/core/dist/runtime-v2/pain-signal-bridge.js +95 -0
  18. package/core/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
  19. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +10 -4
  20. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  21. package/core/dist/runtime-v2/pain-signal-runtime-factory.js +86 -51
  22. package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  23. package/dist/installer.d.ts.map +1 -1
  24. package/dist/installer.js +65 -4
  25. package/dist/installer.js.map +1 -1
  26. package/host-runtime/dist/governance-observation-store.d.ts +17 -0
  27. package/host-runtime/dist/governance-observation-store.js +38 -0
  28. package/host-runtime/dist/governance-signal-admission.js +55 -43
  29. package/host-runtime/dist/index.d.ts +3 -0
  30. package/host-runtime/dist/index.js +7 -0
  31. package/host-runtime/dist/internalization-consumer-cycle.d.ts +43 -0
  32. package/host-runtime/dist/internalization-consumer-cycle.js +468 -0
  33. package/host-runtime/dist/internalization-consumer-governance.d.ts +39 -0
  34. package/host-runtime/dist/internalization-consumer-governance.js +152 -0
  35. package/{plugin/dist/service/workspace-telemetry-sink.d.ts → host-runtime/dist/workspace-telemetry-emitter.d.ts} +5 -4
  36. package/host-runtime/dist/workspace-telemetry-emitter.js +74 -0
  37. package/install-layout/dist/index.d.ts +10 -0
  38. package/install-layout/dist/index.js +34 -1
  39. package/install-layout/dist/index.test.js +36 -1
  40. package/package.json +2 -2
  41. package/pd-cli/dist/commands/codex-ingest-catchup.d.ts +20 -0
  42. package/pd-cli/dist/commands/codex-ingest-catchup.d.ts.map +1 -0
  43. package/pd-cli/dist/commands/codex-ingest-catchup.js +95 -0
  44. package/pd-cli/dist/commands/codex-ingest-catchup.js.map +1 -0
  45. package/pd-cli/dist/commands/codex-worker.d.ts +20 -0
  46. package/pd-cli/dist/commands/codex-worker.d.ts.map +1 -0
  47. package/pd-cli/dist/commands/codex-worker.js +156 -0
  48. package/pd-cli/dist/commands/codex-worker.js.map +1 -0
  49. package/pd-cli/dist/index.js +47 -0
  50. package/pd-cli/dist/index.js.map +1 -1
  51. package/pd-cli/package.json +1 -1
  52. package/plugin/dist/bundle.js +432 -432
  53. package/plugin/dist/governance-audit.js +106 -106
  54. package/plugin/dist/rulehost-evidence.js +111 -111
  55. package/plugin/dist/service/internalization-auto-consumer-service.d.ts +11 -0
  56. package/plugin/openclaw.plugin.json +1 -1
  57. package/plugin/package.json +1 -1
  58. package/plugin/dist/service/auto-consumer-governance-wiring.d.ts +0 -34
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Internalization consumer governance wiring (host-neutral, PRI-624).
3
+ *
4
+ * Ported verbatim from openclaw-plugin `auto-consumer-governance-wiring.ts`
5
+ * (P0-D/E/F 生产接线) so the OpenClaw auto-consumer and the Companion
6
+ * workspace worker share ONE wiring implementation instead of copying it:
7
+ * - evaluator: isRepairLoopEnabled + seedArtificerRepairTask (bounded repair)
8
+ * - rollout_reviewer: dispatchActivation (approve_rollout → ActivationDispatcher,
9
+ * 低风险 auto_activate / 高风险 approvals.pending) + reopenRevisionTarget
10
+ * (needs_revision → reopen scribe/artificer, 禁止入 approval)
11
+ *
12
+ * Everything is assembled from @principles/core stores/dispatchers; the only
13
+ * host knowledge is a structural logger. Idempotency (INV-08): dispatcher
14
+ * dedupes by `${artifactId}::${channel}`; reopen idempotency is owned by
15
+ * orchestrator.reopenTaskForRevision.
16
+ */
17
+ import { type RolloutAutoDispatchInput, type RolloutAutoDispatchOutcome, type RolloutRevisionRoutingInput, type SeedArtificerRepairParams, type RuntimeStateManager, type InternalizationOrchestrator } from '@principles/core/runtime-v2';
18
+ /** Structural logger port — PluginLogger satisfies this without adaptation. */
19
+ export interface ConsumerGovernanceLogger {
20
+ info?: (msg: string) => void;
21
+ warn?: (msg: string) => void;
22
+ }
23
+ /** rollout → ActivationDispatcher 的生产接线 (per dispatch 打开短连接, 与 Console 模式一致) */
24
+ export declare function dispatchRolloutActivation(workspaceDir: string, input: RolloutAutoDispatchInput, logger?: ConsumerGovernanceLogger): Promise<RolloutAutoDispatchOutcome>;
25
+ /** flag 探针: config 异常 → 全 false (fail-closed, 不 throw) */
26
+ /** evaluator repair deps (PRI-509 机制的生产接线; flag 语义保留为运行时开关) */
27
+ export declare function createEvaluatorRepairDeps(workspaceDir: string, stateManager: RuntimeStateManager, logger?: ConsumerGovernanceLogger): {
28
+ isRepairLoopEnabled: () => boolean;
29
+ seedArtificerRepairTask: (params: SeedArtificerRepairParams) => Promise<string>;
30
+ };
31
+ /** rollout reviewer 治理 deps: dispatch + revision reopen */
32
+ export declare function createRolloutGovernanceDeps(workspaceDir: string, orchestrator: InternalizationOrchestrator, logger?: ConsumerGovernanceLogger): {
33
+ dispatchActivation: (input: RolloutAutoDispatchInput) => Promise<RolloutAutoDispatchOutcome>;
34
+ reopenRevisionTarget: (input: RolloutRevisionRoutingInput) => Promise<{
35
+ ok: boolean;
36
+ reason: string;
37
+ reopenedTaskId?: string;
38
+ }>;
39
+ };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Internalization consumer governance wiring (host-neutral, PRI-624).
3
+ *
4
+ * Ported verbatim from openclaw-plugin `auto-consumer-governance-wiring.ts`
5
+ * (P0-D/E/F 生产接线) so the OpenClaw auto-consumer and the Companion
6
+ * workspace worker share ONE wiring implementation instead of copying it:
7
+ * - evaluator: isRepairLoopEnabled + seedArtificerRepairTask (bounded repair)
8
+ * - rollout_reviewer: dispatchActivation (approve_rollout → ActivationDispatcher,
9
+ * 低风险 auto_activate / 高风险 approvals.pending) + reopenRevisionTarget
10
+ * (needs_revision → reopen scribe/artificer, 禁止入 approval)
11
+ *
12
+ * Everything is assembled from @principles/core stores/dispatchers; the only
13
+ * host knowledge is a structural logger. Idempotency (INV-08): dispatcher
14
+ * dedupes by `${artifactId}::${channel}`; reopen idempotency is owned by
15
+ * orchestrator.reopenTaskForRevision.
16
+ */
17
+ import { ActivationDispatcher, PromptWriter, DeferArchiveWriter, RuleHostWriter, SqliteConnection, SqliteActivationStateStore, SqliteApprovalQueueStore, SqlitePIArtifactStore, createProductionGateDeps, createPITaskDiagnosticJson, computeFeatureFlagsFromConfig, isFeatureEnabled, } from '@principles/core/runtime-v2';
18
+ import { loadPdConfigForPlugin } from './pd-config.js';
19
+ function normalizeDecision(decision) {
20
+ if (decision.decision === 'activated') {
21
+ return { decision: decision.decision, activationId: decision.activationId };
22
+ }
23
+ if (decision.decision === 'already_activated') {
24
+ return { decision: decision.decision, reason: 'idempotent_redispatch' };
25
+ }
26
+ if (decision.decision === 'queued_for_approval') {
27
+ return { decision: decision.decision, reason: decision.approvalId };
28
+ }
29
+ // refused / would_* 家族
30
+ const reason = 'reason' in decision && typeof decision.reason === 'string' ? decision.reason : decision.decision;
31
+ return { decision: decision.decision, reason };
32
+ }
33
+ function makeFlagProbe(workspaceDir) {
34
+ const configResult = loadPdConfigForPlugin(workspaceDir);
35
+ if (!configResult.ok)
36
+ return () => false;
37
+ const flags = computeFeatureFlagsFromConfig(configResult.effective);
38
+ return (flagId) => isFeatureEnabled(flags, flagId);
39
+ }
40
+ /** rollout → ActivationDispatcher 的生产接线 (per dispatch 打开短连接, 与 Console 模式一致) */
41
+ export async function dispatchRolloutActivation(workspaceDir, input, logger) {
42
+ const connection = new SqliteConnection(workspaceDir);
43
+ try {
44
+ const piArtifactStore = new SqlitePIArtifactStore(connection);
45
+ const artifactReadModel = {
46
+ getArtifactById: async (id) => {
47
+ const rec = await piArtifactStore.getArtifactById(id);
48
+ if (!rec)
49
+ return null;
50
+ return {
51
+ artifactId: rec.artifactId,
52
+ artifactKind: rec.artifactKind,
53
+ sourceTaskId: rec.sourceTaskId,
54
+ sourcePrincipleId: rec.sourcePrincipleId,
55
+ sourceRuleId: rec.sourceRuleId,
56
+ lineageArtifactIds: rec.lineageArtifactIds,
57
+ validationStatus: rec.validationStatus,
58
+ contentJson: rec.contentJson,
59
+ createdAt: rec.createdAt,
60
+ updatedAt: rec.updatedAt,
61
+ };
62
+ },
63
+ };
64
+ const activationStateStore = new SqliteActivationStateStore(connection);
65
+ const approvalQueueStore = new SqliteApprovalQueueStore(connection);
66
+ const flagProbe = makeFlagProbe(workspaceDir);
67
+ const dispatcher = new ActivationDispatcher(artifactReadModel, activationStateStore, {
68
+ writers: [
69
+ new PromptWriter(),
70
+ new RuleHostWriter({ gateDeps: createProductionGateDeps(), featureFlagProbe: flagProbe }),
71
+ new DeferArchiveWriter(),
72
+ ],
73
+ approvalQueueStore,
74
+ });
75
+ const decision = await dispatcher.dispatch({
76
+ artifactId: input.artifactId,
77
+ channel: input.channel, // InternalizationChannel union; rollout 链的 channel 已由任务元数据校验
78
+ rolloutDecision: 'auto_activate',
79
+ actor: { kind: 'system', source: 'rollout_reviewer' },
80
+ now: new Date().toISOString(),
81
+ confirm: true,
82
+ confidence: input.confidence,
83
+ });
84
+ const outcome = normalizeDecision(decision);
85
+ logger?.info?.(`[PD:Consumer] rollout dispatch: artifact=${input.artifactId} channel=${input.channel} → ${outcome.decision}${outcome.activationId ? ` (${outcome.activationId})` : ''}${outcome.reason ? ` reason=${outcome.reason}` : ''}`);
86
+ return outcome;
87
+ }
88
+ finally {
89
+ try {
90
+ connection.close();
91
+ }
92
+ catch { /* best-effort */ }
93
+ }
94
+ }
95
+ /** flag 探针: config 异常 → 全 false (fail-closed, 不 throw) */
96
+ /** evaluator repair deps (PRI-509 机制的生产接线; flag 语义保留为运行时开关) */
97
+ export function createEvaluatorRepairDeps(workspaceDir, stateManager, logger) {
98
+ return {
99
+ isRepairLoopEnabled: () => {
100
+ // flag evaluator_artificer_repair_loop (registry 默认已改为 ON — 见
101
+ // feature-flag-contract.ts;此处读取 config 以保留运行时可关闭能力)
102
+ const configResult = loadPdConfigForPlugin(workspaceDir);
103
+ if (!configResult.ok)
104
+ return false;
105
+ const flags = computeFeatureFlagsFromConfig(configResult.effective);
106
+ return isFeatureEnabled(flags, 'evaluator_artificer_repair_loop');
107
+ },
108
+ seedArtificerRepairTask: async (params) => {
109
+ // P0-4: 确定性 revision identity — evaluatorTaskId + iteration 唯一定位
110
+ // 一个逻辑 repair 任务; 重放 (consumer 重复周期 / crash 恢复) reuse 而非再建。
111
+ const repairTaskId = `artificer-repair-${params.repairPayload.sourceEvaluatorTaskId}-r${params.repairPayload.repairIteration}`;
112
+ const existing = await stateManager.getTask(repairTaskId);
113
+ if (existing) {
114
+ logger?.info?.(`[PD:Consumer] repair task ${repairTaskId} already exists; reusing (idempotent seed)`);
115
+ return repairTaskId;
116
+ }
117
+ await stateManager.createTask({
118
+ taskId: repairTaskId,
119
+ taskKind: 'artificer',
120
+ status: 'pending',
121
+ attemptCount: 0,
122
+ maxAttempts: 3,
123
+ diagnosticJson: createPITaskDiagnosticJson({
124
+ dependencyTaskIds: [...params.inheritedDependencyTaskIds],
125
+ channel: params.inheritedChannel,
126
+ timeoutMs: params.inheritedTimeoutMs,
127
+ inputArtifactRefs: [...params.inheritedInputArtifactRefs],
128
+ outputArtifactRefs: [],
129
+ repairPayload: params.repairPayload,
130
+ }),
131
+ });
132
+ logger?.info?.(`[PD:Consumer] seeded artificer repair task ${repairTaskId} (iteration ${params.repairPayload.repairIteration})`);
133
+ return repairTaskId;
134
+ },
135
+ };
136
+ }
137
+ /** rollout reviewer 治理 deps: dispatch + revision reopen */
138
+ export function createRolloutGovernanceDeps(workspaceDir, orchestrator, logger) {
139
+ return {
140
+ dispatchActivation: (input) => dispatchRolloutActivation(workspaceDir, input, logger),
141
+ reopenRevisionTarget: async (input) => {
142
+ const result = await orchestrator.reopenTaskForRevision(input.targetTaskId, {
143
+ revisionFeedback: input.revisionFeedback,
144
+ reason: `rollout_revision_iteration_${input.revisionIteration}`,
145
+ revisionCauseId: `rollout-${input.sourceRolloutTaskId}-r${input.revisionIteration}`,
146
+ });
147
+ return result.ok
148
+ ? { ok: true, reason: result.reason, reopenedTaskId: input.targetTaskId }
149
+ : { ok: false, reason: result.reason };
150
+ },
151
+ };
152
+ }
@@ -1,13 +1,14 @@
1
1
  import { StoreEventEmitter, type TelemetryEvent } from '@principles/core/runtime-v2';
2
+ export type WorkspaceTelemetryPersistFailureSink = (detailJson: string) => void;
2
3
  export declare class WorkspaceTelemetryEmitter extends StoreEventEmitter {
3
4
  private readonly upstream;
4
5
  private readonly sinkFilePath;
5
- private readonly workspaceDir;
6
- constructor(upstream: StoreEventEmitter, workspaceDir: string);
6
+ private readonly onPersistFailure;
7
+ constructor(upstream: StoreEventEmitter, workspaceDir: string, onPersistFailure: WorkspaceTelemetryPersistFailureSink);
7
8
  /**
8
9
  * Validate + persist (allowlist) + forward upstream. Never throws —
9
- * persistence errors degrade to SystemLogger (the upstream emit semantics
10
- * are preserved unchanged).
10
+ * persistence errors degrade to the injected failure sink (the upstream
11
+ * emit semantics are preserved unchanged).
11
12
  */
12
13
  emitTelemetry(event: TelemetryEvent): true;
13
14
  private persist;
@@ -0,0 +1,74 @@
1
+ import { appendFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { StoreEventEmitter } from '@principles/core/runtime-v2';
4
+ /**
5
+ * PRI-634 A3 — workspace-scoped durable observability for critical runner
6
+ * telemetry (host-neutral; PRI-624 Slice C final integration).
7
+ *
8
+ * 背景:`StoreEventEmitter`(进程内单例)生产装配零订阅者、零持久化 ——
9
+ * `evaluator_adversarial_replay_skipped` 等关键事件发射即丢,事故(如
10
+ * 48371236 链)无法从日志取证。
11
+ *
12
+ * 为什么不是「给全局 storeEmitter 挂落盘订阅者」:singleton 的 telemetry
13
+ * payload 不含 workspaceDir,multi-workspace 进程下全局订阅者无法判断事件
14
+ * 归属,会重演 SystemLogger PRI-504 修过的 cross-workspace log leakage
15
+ * (ERR-092)。正确粒度是 **workspace-scoped**:本 emitter 在 consumer
16
+ * cycle 的 per-wake 装配处构造,只经手本 workspace runner 发出的事件。
17
+ *
18
+ * 持久化策略:**allowlist**,只落盘 4 类 critical events(与 telemetry-event
19
+ * schema 枚举同源),不做全量 telemetry 无差别写盘(日志量 + 隐私审计面
20
+ * 无谓扩大):
21
+ * - evaluator_adversarial_replay_skipped
22
+ * - evaluator_adversarial_replay
23
+ * - evaluator_rule_assembled
24
+ * - evaluator_rule_assembly_failed
25
+ *
26
+ * 落点 `<workspaceDir>/.pd/telemetry/critical-events.jsonl`(JSONL,一行一
27
+ * 事件,含完整 TelemetryEvent)。同步 append:事件量小(allowlist 限流),
28
+ * 换取 crash 前落盘的 durable 语义。写失败绝不影响 runner —— 降级为
29
+ * `onPersistFailure` 回调(宿主注入的结构化告警口;OpenClaw 是
30
+ * SystemLogger,Companion worker 是自己的事件口)。
31
+ */
32
+ const CRITICAL_EVENT_ALLOWLIST = new Set([
33
+ 'evaluator_adversarial_replay_skipped',
34
+ 'evaluator_adversarial_replay',
35
+ 'evaluator_rule_assembled',
36
+ 'evaluator_rule_assembly_failed',
37
+ ]);
38
+ export class WorkspaceTelemetryEmitter extends StoreEventEmitter {
39
+ upstream;
40
+ sinkFilePath;
41
+ onPersistFailure;
42
+ constructor(upstream, workspaceDir, onPersistFailure) {
43
+ super();
44
+ this.upstream = upstream;
45
+ this.onPersistFailure = onPersistFailure;
46
+ this.sinkFilePath = join(workspaceDir, '.pd', 'telemetry', 'critical-events.jsonl');
47
+ }
48
+ /**
49
+ * Validate + persist (allowlist) + forward upstream. Never throws —
50
+ * persistence errors degrade to the injected failure sink (the upstream
51
+ * emit semantics are preserved unchanged).
52
+ */
53
+ emitTelemetry(event) {
54
+ if (CRITICAL_EVENT_ALLOWLIST.has(event.eventType)) {
55
+ this.persist(event);
56
+ }
57
+ return this.upstream.emitTelemetry(event);
58
+ }
59
+ persist(event) {
60
+ try {
61
+ mkdirSync(dirname(this.sinkFilePath), { recursive: true });
62
+ appendFileSync(this.sinkFilePath, `${JSON.stringify(event)}\n`, 'utf8');
63
+ }
64
+ catch (err) {
65
+ // Non-fatal by contract: losing a telemetry line must never break the
66
+ // runner. Surface once per occurrence through the injected host sink.
67
+ this.onPersistFailure(JSON.stringify({
68
+ eventType: event.eventType,
69
+ sinkFilePath: this.sinkFilePath,
70
+ error: err instanceof Error ? err.message : String(err),
71
+ }));
72
+ }
73
+ }
74
+ }
@@ -7,6 +7,14 @@ export interface InstallManifest {
7
7
  layoutVersion: typeof INSTALL_LAYOUT_VERSION;
8
8
  mode: InstallLayoutMode;
9
9
  hosts: InstallHost[];
10
+ /**
11
+ * Canonical absolute paths of Workspaces the install serves (PRI-624 Slice C).
12
+ * Optional so pre-PRI-624 manifests stay valid; always normalized to
13
+ * resolved absolute paths and deduped. The installer merges the install
14
+ * Workspace in; the Companion worker discovers its per-Workspace workers
15
+ * from this list (SPEC §13 "canonical install manifest").
16
+ */
17
+ workspaces?: readonly string[];
10
18
  }
11
19
  export interface InstallLayoutPaths {
12
20
  homeDir: string;
@@ -37,6 +45,8 @@ export interface ResolveInstallLayoutOptions {
37
45
  canonicalRuntimeExists: boolean;
38
46
  legacyExtensionExists: boolean;
39
47
  }
48
+ /** Merge one Workspace into a manifest's workspace list (installer write path). Idempotent by canonical path. */
49
+ export declare function mergeInstallManifestWorkspaces(current: InstallManifest | undefined, workspaceDir: string): string[];
40
50
  export declare function parseInstallManifest(value: unknown): {
41
51
  manifest?: InstallManifest;
42
52
  error?: string;
@@ -8,6 +8,36 @@ function isRecord(value) {
8
8
  function isHost(value) {
9
9
  return typeof value === 'string' && INSTALL_HOSTS.includes(value);
10
10
  }
11
+ /** Workspace entries must be non-empty absolute paths — the Companion spawns per-Workspace workers keyed by canonical path. */
12
+ function parseWorkspaceList(value) {
13
+ if (value === undefined)
14
+ return { workspaces: [] };
15
+ if (!Array.isArray(value))
16
+ return { error: 'install_manifest_malformed: workspaces must be non-empty absolute paths' };
17
+ const resolved = [];
18
+ for (const entry of value) {
19
+ if (typeof entry !== 'string' || entry.length === 0 || !path.isAbsolute(entry)) {
20
+ return { error: 'install_manifest_malformed: workspaces must be non-empty absolute paths' };
21
+ }
22
+ const canonical = path.resolve(entry);
23
+ if (!resolved.includes(canonical))
24
+ resolved.push(canonical);
25
+ }
26
+ return { workspaces: resolved };
27
+ }
28
+ /** Merge one Workspace into a manifest's workspace list (installer write path). Idempotent by canonical path. */
29
+ export function mergeInstallManifestWorkspaces(current, workspaceDir) {
30
+ const canonical = path.resolve(workspaceDir);
31
+ const resolved = [];
32
+ for (const entry of current?.workspaces ?? []) {
33
+ const normalized = path.resolve(entry);
34
+ if (!resolved.includes(normalized))
35
+ resolved.push(normalized);
36
+ }
37
+ if (!resolved.includes(canonical))
38
+ resolved.push(canonical);
39
+ return resolved;
40
+ }
11
41
  export function parseInstallManifest(value) {
12
42
  if (!isRecord(value))
13
43
  return { error: 'install_manifest_malformed: expected an object' };
@@ -21,7 +51,10 @@ export function parseInstallManifest(value) {
21
51
  return { error: 'install_manifest_malformed: hosts must contain codex and/or openclaw' };
22
52
  }
23
53
  const hosts = [...new Set(value.hosts)];
24
- return { manifest: { layoutVersion: INSTALL_LAYOUT_VERSION, mode: value.mode, hosts } };
54
+ const workspaces = parseWorkspaceList(value.workspaces);
55
+ if (workspaces.error)
56
+ return { error: workspaces.error };
57
+ return { manifest: { layoutVersion: INSTALL_LAYOUT_VERSION, mode: value.mode, hosts, workspaces: workspaces.workspaces } };
25
58
  }
26
59
  export function getInstallLayoutPaths(homeDir) {
27
60
  const home = path.resolve(homeDir);
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { getInstallLayoutPaths, getConsoleServerEntry, parseInstallManifest, resolveInstallLayout } from './index.js';
2
+ import * as path from 'node:path';
3
+ import { getInstallLayoutPaths, getConsoleServerEntry, parseInstallManifest, resolveInstallLayout, mergeInstallManifestWorkspaces, } from './index.js';
3
4
  describe('install layout', () => {
4
5
  it('resolves a canonical Codex-only install without OpenClaw', () => {
5
6
  const paths = getInstallLayoutPaths('C:/Users/alice');
@@ -31,4 +32,38 @@ describe('install layout', () => {
31
32
  error: 'install_manifest_malformed: hosts must contain codex and/or openclaw',
32
33
  });
33
34
  });
35
+ it('accepts a manifest without workspaces (pre-PRI-624 manifests stay valid)', () => {
36
+ const { manifest, error } = parseInstallManifest({ layoutVersion: 1, mode: 'canonical', hosts: ['codex'] });
37
+ expect(error).toBeUndefined();
38
+ expect(manifest?.workspaces).toEqual([]);
39
+ });
40
+ it('parses workspaces as canonical absolute paths, deduped in insertion order', () => {
41
+ const { manifest, error } = parseInstallManifest({
42
+ layoutVersion: 1,
43
+ mode: 'canonical',
44
+ hosts: ['codex'],
45
+ workspaces: ['D:/Code/ws-a', 'D:\\Code\\ws-a', 'C:/work/ws b'],
46
+ });
47
+ expect(error).toBeUndefined();
48
+ expect(manifest?.workspaces?.length).toBe(2);
49
+ expect(manifest?.workspaces?.[0]).toBe(path.resolve('D:/Code/ws-a'));
50
+ expect(manifest?.workspaces).toContain(path.resolve('C:/work/ws b'));
51
+ });
52
+ it('rejects malformed workspace entries loudly', () => {
53
+ expect(parseInstallManifest({ layoutVersion: 1, mode: 'canonical', hosts: ['codex'], workspaces: [''] })).toEqual({
54
+ error: 'install_manifest_malformed: workspaces must be non-empty absolute paths',
55
+ });
56
+ expect(parseInstallManifest({ layoutVersion: 1, mode: 'canonical', hosts: ['codex'], workspaces: ['ws-a'] })).toEqual({
57
+ error: 'install_manifest_malformed: workspaces must be non-empty absolute paths',
58
+ });
59
+ });
60
+ it('merges a workspace into an existing manifest without duplicates', () => {
61
+ const merged = mergeInstallManifestWorkspaces({ layoutVersion: 1, mode: 'canonical', hosts: ['codex'], workspaces: ['D:/Code/ws-a'] }, 'D:\\Code\\ws-a');
62
+ expect(merged.length).toBe(1);
63
+ expect(merged[0]).toBe(path.resolve('D:/Code/ws-a'));
64
+ });
65
+ it('merge accepts unknown current manifests and starts a fresh list', () => {
66
+ const merged = mergeInstallManifestWorkspaces(undefined, 'D:/Code/ws-b');
67
+ expect(merged).toEqual([path.resolve('D:/Code/ws-b')]);
68
+ });
34
69
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.123.0",
3
+ "version": "1.124.0",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -72,6 +72,6 @@
72
72
  "directory": "packages/create-principles-disciple"
73
73
  },
74
74
  "pd": {
75
- "bundledPluginVersion": "1.224.0"
75
+ "bundledPluginVersion": "1.225.0"
76
76
  }
77
77
  }
@@ -0,0 +1,20 @@
1
+ interface CodexIngestCatchUpOptions {
2
+ workspace?: string;
3
+ json?: boolean;
4
+ maxRollouts?: number;
5
+ }
6
+ export interface CodexIngestCatchUpReport {
7
+ generatedAt: string;
8
+ host: 'codex';
9
+ workspace: string;
10
+ status: 'ok' | 'degraded' | 'skipped';
11
+ reason?: string;
12
+ nextAction?: string;
13
+ rolloutsProcessed: number;
14
+ remainingLagRollouts: string[];
15
+ unexaminedRollouts: string[];
16
+ degradations: string[];
17
+ }
18
+ export declare function handleCodexIngestCatchUp(options: CodexIngestCatchUpOptions): Promise<void>;
19
+ export {};
20
+ //# sourceMappingURL=codex-ingest-catchup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-ingest-catchup.d.ts","sourceRoot":"","sources":["../../src/commands/codex-ingest-catchup.ts"],"names":[],"mappings":"AAwBA,UAAU,yBAAyB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,IAAI,GAAG,UAAU,GAAG,SAAS,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAyCD,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BhG"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * pd codex ingest catch-up command implementation (Codex Governance Closure
3
+ * Slice C, PRI-624; SPEC §13/§15 recovery command).
4
+ *
5
+ * Performs ONE bounded non-destructive catch-up pass over the workspace's
6
+ * durable Codex checkpoints: resolves each previously-authenticated rollout
7
+ * by exact uuid, re-authorizes the transcript path, and resumes the bounded
8
+ * incremental ingestion from the checkpoint — then feeds any admission
9
+ * candidates through the same Slice B admission/continuation pass the hook
10
+ * uses. Flag-off (`codex_conversation_ingestion=false`) performs ZERO
11
+ * transcript filesystem I/O and reports a structured skip.
12
+ *
13
+ * This is the manual-mode counterpart of the Companion worker's catch-up
14
+ * step (worker cycle step 2); it creates no LLM execution.
15
+ *
16
+ * CLI gate compliance:
17
+ * - cli-1: --json outputs exactly one parseable JSON object on stdout.
18
+ * - cli-2: exit paths stop execution.
19
+ * - cli-5/6: skipped/degraded results carry reason + nextAction; nothing is
20
+ * mutated on failure (the ingestion seam is transactional per rollout).
21
+ */
22
+ import { resolveWorkspaceDir } from '../resolve-workspace.js';
23
+ import { catchUpCodexIngestion } from '@principles/codex-adapter';
24
+ function buildReport(generatedAt, workspace, result) {
25
+ if (result.status === 'skipped') {
26
+ return {
27
+ generatedAt,
28
+ host: 'codex',
29
+ workspace,
30
+ status: 'skipped',
31
+ reason: result.reason,
32
+ nextAction: result.nextAction,
33
+ rolloutsProcessed: 0,
34
+ remainingLagRollouts: [],
35
+ unexaminedRollouts: [],
36
+ degradations: [],
37
+ };
38
+ }
39
+ const degradations = [];
40
+ for (const rollout of result.rollouts) {
41
+ for (const degradation of rollout.admissionDegradations) {
42
+ degradations.push(`${rollout.rolloutIdentity}: ${degradation.reason}`);
43
+ }
44
+ if (rollout.outcome.status === 'degraded') {
45
+ degradations.push(`${rollout.rolloutIdentity}: ${rollout.outcome.reason}`);
46
+ }
47
+ }
48
+ return {
49
+ generatedAt,
50
+ host: 'codex',
51
+ workspace,
52
+ status: result.status,
53
+ rolloutsProcessed: result.rollouts.length,
54
+ remainingLagRollouts: [...result.remainingLagRollouts],
55
+ unexaminedRollouts: [...result.unexaminedRollouts],
56
+ degradations: degradations.slice(0, 10),
57
+ ...(degradations.length > 0 && result.status === 'degraded'
58
+ ? { reason: 'one or more rollouts degraded; committed observations remain durable', nextAction: 'Inspect the per-rollout degradations; re-run catch-up after fixing the underlying condition (Slice D adds the audited quarantine command).' }
59
+ : {}),
60
+ };
61
+ }
62
+ export async function handleCodexIngestCatchUp(options) {
63
+ const generatedAt = new Date().toISOString();
64
+ const workspace = resolveWorkspaceDir(options.workspace);
65
+ const result = await catchUpCodexIngestion({
66
+ workspaceDir: workspace,
67
+ env: { CODEX_HOME: process.env.CODEX_HOME },
68
+ ...(typeof options.maxRollouts === 'number' ? { maxRollouts: options.maxRollouts } : {}),
69
+ });
70
+ const report = buildReport(generatedAt, workspace, result);
71
+ if (options.json) {
72
+ console.log(JSON.stringify(report));
73
+ if (report.status === 'degraded')
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+ const lines = [
78
+ `Codex ingestion catch-up (${workspace})`,
79
+ ` status: ${report.status}`,
80
+ ` rollouts processed: ${report.rolloutsProcessed}`,
81
+ ` remaining lag: ${report.remainingLagRollouts.length}`,
82
+ ` unexamined (bound): ${report.unexaminedRollouts.length}`,
83
+ ];
84
+ for (const degradation of report.degradations) {
85
+ lines.push(` degradation: ${degradation}`);
86
+ }
87
+ if (report.reason !== undefined)
88
+ lines.push(` reason: ${report.reason}`);
89
+ if (report.nextAction !== undefined)
90
+ lines.push(` next action: ${report.nextAction}`);
91
+ console.log(lines.join('\n'));
92
+ if (report.status === 'degraded')
93
+ process.exitCode = 1;
94
+ }
95
+ //# sourceMappingURL=codex-ingest-catchup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-ingest-catchup.js","sourceRoot":"","sources":["../../src/commands/codex-ingest-catchup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ interface CodexWorkerOptions {
2
+ workspace?: string;
3
+ json?: boolean;
4
+ once?: boolean;
5
+ intervalMs?: number;
6
+ status?: boolean;
7
+ }
8
+ export interface CodexWorkerStatusReport {
9
+ generatedAt: string;
10
+ host: 'codex';
11
+ workspace: string;
12
+ mode: 'ready' | 'manual_action_required' | 'paused' | 'degraded';
13
+ registeredInInstallManifest: boolean;
14
+ reason?: string;
15
+ nextAction?: string;
16
+ }
17
+ export declare function handleCodexWorker(options: CodexWorkerOptions): Promise<void>;
18
+ export {};
19
+ /** SPEC §15 worker mode, evaluated without executing anything. */
20
+ //# sourceMappingURL=codex-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-worker.d.ts","sourceRoot":"","sources":["../../src/commands/codex-worker.ts"],"names":[],"mappings":"AA6BA,UAAU,kBAAkB;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,GAAG,wBAAwB,GAAG,QAAQ,GAAG,UAAU,CAAC;IACjE,2BAA2B,EAAE,OAAO,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA6ED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDlF;;AAED,kEAAkE"}