create-principles-disciple 1.121.9 → 1.122.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 (133) hide show
  1. package/console/dist/server/index.js +17 -0
  2. package/console/dist/server/models/OwnerDecisionConsoleModel.d.ts +39 -0
  3. package/console/dist/server/models/OwnerDecisionConsoleModel.js +233 -0
  4. package/console/dist/server/models/PrincipleClassifier.d.ts +3 -3
  5. package/console/dist/server/models/PrincipleClassifier.js +12 -5
  6. package/console/dist/server/models/PrinciplesConsoleModel.d.ts +1 -1
  7. package/console/dist/server/models/PrinciplesConsoleModel.js +2 -2
  8. package/console/dist/server/routes/failed-tasks.js +40 -2
  9. package/console/dist/server/routes/owner-decisions.d.ts +25 -0
  10. package/console/dist/server/routes/owner-decisions.js +186 -0
  11. package/console/dist/server/routes/principles.js +15 -7
  12. package/console/dist/server/routes/update.js +82 -14
  13. package/console/dist/server/utils/update-links.d.ts +38 -0
  14. package/console/dist/server/utils/update-links.js +71 -0
  15. package/console/dist/ui/api.d.ts +12 -1
  16. package/console/dist/ui/api.js +9 -2
  17. package/console/dist/ui/components/notifications/NotificationProvider.js +8 -2
  18. package/console/dist/ui/i18n/en.json +33 -1
  19. package/console/dist/ui/i18n/zh-CN.json +33 -1
  20. package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +5 -1
  21. package/console/dist/ui/pages/focus/FocusPage.js +19 -2
  22. package/console/dist/ui/pages/focus/OwnerDecisionCard.d.ts +6 -0
  23. package/console/dist/ui/pages/focus/OwnerDecisionCard.js +86 -0
  24. package/console/dist/ui/pages/principles/PrinciplesPage.js +11 -5
  25. package/console/dist/ui/utils/validators.d.ts +37 -0
  26. package/console/dist/ui/utils/validators.js +123 -0
  27. package/console/dist/web/assets/app.css +11 -0
  28. package/console/dist/web/assets/app.js +2740 -2350
  29. package/core/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js +20 -0
  30. package/core/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js.map +1 -1
  31. package/core/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js +26 -5
  32. package/core/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js.map +1 -1
  33. package/core/dist/runtime-v2/index.d.ts +7 -1
  34. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  35. package/core/dist/runtime-v2/index.js +4 -0
  36. package/core/dist/runtime-v2/index.js.map +1 -1
  37. package/core/dist/runtime-v2/internalization/__tests__/evaluator-prompt-builder.test.js +1 -1
  38. package/core/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js +15 -14
  39. package/core/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js.map +1 -1
  40. package/core/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.d.ts +2 -0
  41. package/core/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.d.ts.map +1 -0
  42. package/core/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.js +56 -0
  43. package/core/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.js.map +1 -0
  44. package/core/dist/runtime-v2/internalization/__tests__/owner-decision.test.d.ts +2 -0
  45. package/core/dist/runtime-v2/internalization/__tests__/owner-decision.test.d.ts.map +1 -0
  46. package/core/dist/runtime-v2/internalization/__tests__/owner-decision.test.js +883 -0
  47. package/core/dist/runtime-v2/internalization/__tests__/owner-decision.test.js.map +1 -0
  48. package/core/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.d.ts +2 -0
  49. package/core/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.d.ts.map +1 -0
  50. package/core/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.js +328 -0
  51. package/core/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.js.map +1 -0
  52. package/core/dist/runtime-v2/internalization/evaluator-output.d.ts +63 -2
  53. package/core/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
  54. package/core/dist/runtime-v2/internalization/evaluator-output.js +139 -2
  55. package/core/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
  56. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +54 -2
  57. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
  58. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js +49 -2
  59. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
  60. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts +34 -0
  61. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  62. package/core/dist/runtime-v2/internalization/evaluator-runner.js +295 -12
  63. package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  64. package/core/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
  65. package/core/dist/runtime-v2/internalization/internalization-orchestrator.js +10 -54
  66. package/core/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
  67. package/core/dist/runtime-v2/internalization/internalization-transition-decision.d.ts +7 -1
  68. package/core/dist/runtime-v2/internalization/internalization-transition-decision.d.ts.map +1 -1
  69. package/core/dist/runtime-v2/internalization/internalization-transition-decision.js +9 -2
  70. package/core/dist/runtime-v2/internalization/internalization-transition-decision.js.map +1 -1
  71. package/core/dist/runtime-v2/internalization/owner-resolution-service.d.ts +89 -0
  72. package/core/dist/runtime-v2/internalization/owner-resolution-service.d.ts.map +1 -0
  73. package/core/dist/runtime-v2/internalization/owner-resolution-service.js +272 -0
  74. package/core/dist/runtime-v2/internalization/owner-resolution-service.js.map +1 -0
  75. package/core/dist/runtime-v2/internalization/owner-retry.d.ts +12 -0
  76. package/core/dist/runtime-v2/internalization/owner-retry.d.ts.map +1 -1
  77. package/core/dist/runtime-v2/internalization/owner-retry.js +23 -0
  78. package/core/dist/runtime-v2/internalization/owner-retry.js.map +1 -1
  79. package/core/dist/runtime-v2/internalization/owner-review.d.ts +195 -0
  80. package/core/dist/runtime-v2/internalization/owner-review.d.ts.map +1 -0
  81. package/core/dist/runtime-v2/internalization/owner-review.js +470 -0
  82. package/core/dist/runtime-v2/internalization/owner-review.js.map +1 -0
  83. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +5 -1
  84. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  85. package/core/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  86. package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts +68 -0
  87. package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
  88. package/core/dist/runtime-v2/internalization/pitask-metadata.js +104 -0
  89. package/core/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
  90. package/core/dist/runtime-v2/internalization/revision-reopen.d.ts +56 -0
  91. package/core/dist/runtime-v2/internalization/revision-reopen.d.ts.map +1 -0
  92. package/core/dist/runtime-v2/internalization/revision-reopen.js +144 -0
  93. package/core/dist/runtime-v2/internalization/revision-reopen.js.map +1 -0
  94. package/core/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts +12 -0
  95. package/core/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts.map +1 -1
  96. package/core/dist/runtime-v2/internalization/rollout-reviewer-runner.js +145 -14
  97. package/core/dist/runtime-v2/internalization/rollout-reviewer-runner.js.map +1 -1
  98. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.test.js +1 -0
  99. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.test.js.map +1 -1
  100. package/core/dist/runtime-v2/store/runtime-state-manager.d.ts +6 -0
  101. package/core/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
  102. package/core/dist/runtime-v2/store/runtime-state-manager.js +9 -0
  103. package/core/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
  104. package/core/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.d.ts +2 -0
  105. package/core/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.d.ts.map +1 -0
  106. package/core/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.js +78 -0
  107. package/core/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.js.map +1 -0
  108. package/core/dist/runtime-v2/store/task/memory-task-store.d.ts +1 -0
  109. package/core/dist/runtime-v2/store/task/memory-task-store.d.ts.map +1 -1
  110. package/core/dist/runtime-v2/store/task/memory-task-store.js +8 -0
  111. package/core/dist/runtime-v2/store/task/memory-task-store.js.map +1 -1
  112. package/core/dist/runtime-v2/store/task/sqlite-task-store.d.ts +1 -0
  113. package/core/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
  114. package/core/dist/runtime-v2/store/task/sqlite-task-store.js +52 -0
  115. package/core/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
  116. package/core/dist/runtime-v2/store/task/task-store.d.ts +9 -0
  117. package/core/dist/runtime-v2/store/task/task-store.d.ts.map +1 -1
  118. package/core/dist/runtime-v2/store/workspace-isolation.test.js +1 -0
  119. package/core/dist/runtime-v2/store/workspace-isolation.test.js.map +1 -1
  120. package/core/package.json +1 -1
  121. package/host-runtime/package.json +1 -1
  122. package/install-layout/package.json +3 -7
  123. package/package.json +2 -2
  124. package/pd-cli/dist/commands/runtime-internalization-retry.d.ts.map +1 -1
  125. package/pd-cli/dist/commands/runtime-internalization-retry.js +14 -0
  126. package/pd-cli/dist/commands/runtime-internalization-retry.js.map +1 -1
  127. package/pd-cli/package.json +1 -1
  128. package/plugin/dist/bundle.js +580 -569
  129. package/plugin/dist/governance-audit.js +153 -153
  130. package/plugin/dist/openclaw.plugin.json +1 -1
  131. package/plugin/dist/rulehost-evidence.js +180 -180
  132. package/plugin/openclaw.plugin.json +1 -1
  133. package/plugin/package.json +1 -1
@@ -0,0 +1,186 @@
1
+ import { createRuntimeStateHandle, applyOwnerResolution, } from '@principles/core/runtime-v2';
2
+ import { OwnerDecisionConsoleModel } from '../models/OwnerDecisionConsoleModel.js';
3
+ import { sendSuccess, sendError, sendMethodNotAllowed, sendBadRequest } from '../utils/response.js';
4
+ import { readBody } from '../utils/request.js';
5
+ const models = new Map();
6
+ function getModel(workspaceDir) {
7
+ let model = models.get(workspaceDir);
8
+ if (!model) {
9
+ model = new OwnerDecisionConsoleModel(workspaceDir);
10
+ models.set(workspaceDir, model);
11
+ }
12
+ return model;
13
+ }
14
+ const RESOLVE_ACTIONS = new Set(['accept_current', 'revise_once', 'reject_current']);
15
+ /** rc-1/rc-2/rc-3: body 按未知值逐字段校验,非法即 400 fail-loud。 */
16
+ function parseResolveBody(raw) {
17
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
18
+ return { ok: false, error: 'Request body must be a JSON object.' };
19
+ }
20
+ const b = raw;
21
+ if (typeof b.action !== 'string' || !RESOLVE_ACTIONS.has(b.action)) {
22
+ return { ok: false, error: `action must be one of accept_current/revise_once/reject_current, got ${String(b.action)}` };
23
+ }
24
+ for (const field of ['reviewKey', 'expectedSourceRunId', 'expectedSourceArtifactId', 'expectedSourceArtifactHash']) {
25
+ const v = b[field];
26
+ if (typeof v !== 'string' || v.trim() === '') {
27
+ return { ok: false, error: `${field} must be a non-empty string.` };
28
+ }
29
+ }
30
+ if (typeof b.expectedRevisionEpoch !== 'number' || !Number.isInteger(b.expectedRevisionEpoch) || b.expectedRevisionEpoch < 0) {
31
+ return { ok: false, error: 'expectedRevisionEpoch must be a non-negative integer.' };
32
+ }
33
+ if (b.ownerInstruction !== undefined && b.ownerInstruction !== null
34
+ && (typeof b.ownerInstruction !== 'string' || b.ownerInstruction.length > 600)) {
35
+ return { ok: false, error: 'ownerInstruction must be a string of at most 600 characters (or null).' };
36
+ }
37
+ const { reviewKey } = b;
38
+ const { expectedSourceRunId } = b;
39
+ const { expectedSourceArtifactId } = b;
40
+ const { expectedSourceArtifactHash } = b;
41
+ if (typeof reviewKey !== 'string' || typeof expectedSourceRunId !== 'string'
42
+ || typeof expectedSourceArtifactId !== 'string' || typeof expectedSourceArtifactHash !== 'string') {
43
+ return { ok: false, error: 'unreachable: string fields validated above' };
44
+ }
45
+ return {
46
+ ok: true,
47
+ body: {
48
+ action: b.action,
49
+ reviewKey,
50
+ expectedRevisionEpoch: b.expectedRevisionEpoch,
51
+ expectedSourceRunId,
52
+ expectedSourceArtifactId,
53
+ expectedSourceArtifactHash,
54
+ ...(typeof b.ownerInstruction === 'string' ? { ownerInstruction: b.ownerInstruction } : {}),
55
+ },
56
+ };
57
+ }
58
+ /** OwnerResolutionOutcome → HTTP 响应映射(cli-6: 结构化 reason + nextAction)。 */
59
+ function sendOutcome(res, outcome) {
60
+ switch (outcome.status) {
61
+ case 'resolved':
62
+ sendSuccess(res, {
63
+ status: 'resolved',
64
+ resolutionId: outcome.resolutionId,
65
+ reviewKey: outcome.reviewKey,
66
+ action: outcome.action,
67
+ applied: outcome.applied,
68
+ ...(outcome.effectiveDecision !== undefined ? { effectiveDecision: outcome.effectiveDecision } : {}),
69
+ ...(outcome.targetTaskId !== undefined ? { targetTaskId: outcome.targetTaskId } : {}),
70
+ runnerWillApply: outcome.runnerWillApply,
71
+ nextAction: outcome.applied
72
+ ? 'Resolution applied. Refresh the governance focus.'
73
+ : 'Resolution recorded durably; the pipeline runner will apply it without re-invoking the LLM. Refresh the governance focus.',
74
+ });
75
+ return;
76
+ case 'not_found':
77
+ sendError(res, 404, 'task_not_found', 'Owner decision task not found.');
78
+ return;
79
+ case 'metadata_invalid':
80
+ sendError(res, 409, 'metadata_invalid', 'Task metadata failed PI hydration; refusing to resolve.', {
81
+ nextAction: 'Inspect the task metadata: pd runtime internalization integrity --json',
82
+ });
83
+ return;
84
+ case 'not_decision_capable':
85
+ sendError(res, 409, 'not_decision_capable', 'This task is not currently decision-capable.', {
86
+ nextAction: 'Refresh the governance focus — the decision facts may have changed.',
87
+ blockers: outcome.blockers,
88
+ });
89
+ return;
90
+ case 'stale_owner_decision':
91
+ sendError(res, 409, 'stale_owner_decision', 'The durable decision facts changed since this item was rendered. The decision was NOT applied.', {
92
+ nextAction: 'Refresh the governance focus and decide again on the current version.',
93
+ });
94
+ return;
95
+ case 'already_resolved':
96
+ sendError(res, 409, 'already_resolved', `This decision was already resolved with action '${outcome.existingAction}'.`, {
97
+ nextAction: 'Refresh the governance focus.',
98
+ existingAction: outcome.existingAction,
99
+ });
100
+ return;
101
+ case 'revise_target_unresolved':
102
+ sendError(res, 409, 'revise_target_unresolved', 'The revision target task could not be resolved from the lineage.', {
103
+ nextAction: 'Inspect the task lineage; this is a recovery-class failure.',
104
+ });
105
+ return;
106
+ case 'revise_reopen_failed':
107
+ sendError(res, 409, 'revise_reopen_failed', `Reopening the revision target failed: ${outcome.reason}`, {
108
+ nextAction: 'Retry after the target task is no longer in flight, or inspect the task states.',
109
+ reason: outcome.reason,
110
+ });
111
+ return;
112
+ case 'cas_conflict':
113
+ sendError(res, 409, 'cas_conflict', 'Concurrent write detected while recording the resolution.', {
114
+ nextAction: 'Retry the request — the operation is idempotent.',
115
+ });
116
+ return;
117
+ default: {
118
+ // exhaustive guard
119
+ const exhaustive = outcome;
120
+ sendError(res, 500, 'owner_resolution_error', `Unhandled outcome: ${JSON.stringify(exhaustive)}`);
121
+ }
122
+ }
123
+ }
124
+ export async function handleOwnerDecisionsRoute(req, res, ctx) {
125
+ const { workspaceDir, subPath, ownerIdentity } = ctx;
126
+ const method = req.method ?? 'GET';
127
+ // GET /api/v1/governance/owner-decisions — list
128
+ if (subPath === '' || subPath === '/') {
129
+ if (method !== 'GET') {
130
+ sendMethodNotAllowed(res);
131
+ return;
132
+ }
133
+ try {
134
+ const result = await getModel(workspaceDir).listOwnerDecisionItems();
135
+ sendSuccess(res, result);
136
+ }
137
+ catch (err) {
138
+ const message = err instanceof Error ? err.message : String(err);
139
+ sendError(res, 500, 'owner_decisions_list_error', message, {
140
+ nextAction: 'Inspect the workspace state (.pd/state.db) and retry.',
141
+ });
142
+ }
143
+ return;
144
+ }
145
+ // POST /api/v1/governance/owner-decisions/:taskId/resolve
146
+ const resolveMatch = /^\/([^/]+)\/resolve$/.exec(subPath);
147
+ if (resolveMatch) {
148
+ if (method !== 'POST') {
149
+ sendMethodNotAllowed(res);
150
+ return;
151
+ }
152
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- match group is always present
153
+ const taskId = decodeURIComponent(resolveMatch[1]);
154
+ let body;
155
+ try {
156
+ const raw = await readBody(req);
157
+ body = raw.trim() === '' ? {} : JSON.parse(raw);
158
+ }
159
+ catch {
160
+ sendBadRequest(res, 'Request body must be valid JSON.');
161
+ return;
162
+ }
163
+ const parsed = parseResolveBody(body);
164
+ if (!parsed.ok) {
165
+ sendBadRequest(res, parsed.error);
166
+ return;
167
+ }
168
+ // 应用走 core 单一服务(writable RuntimeStateManager handle)
169
+ const handle = await createRuntimeStateHandle({ workspaceDir });
170
+ try {
171
+ const outcome = await applyOwnerResolution({ stateManager: handle.stateManager }, { taskId, request: parsed.body, identity: ownerIdentity });
172
+ sendOutcome(res, outcome);
173
+ }
174
+ catch (err) {
175
+ const message = err instanceof Error ? err.message : String(err);
176
+ sendError(res, 500, 'owner_resolution_error', message, {
177
+ nextAction: 'Inspect the workspace state (.pd/state.db) and retry.',
178
+ });
179
+ }
180
+ finally {
181
+ await handle.close();
182
+ }
183
+ return;
184
+ }
185
+ sendError(res, 404, 'not_found', `Route /api/v1/governance/owner-decisions${subPath} not found`);
186
+ }
@@ -42,7 +42,7 @@ async function getDecidedPrincipleIds(workspaceDir) {
42
42
  // Runtime V2 uses <workspace>/.pd/state.db (NOT .state/state.db)
43
43
  const dbPath = path.join(workspaceDir, '.pd', 'state.db');
44
44
  if (!fs.existsSync(dbPath)) {
45
- return { ids: new Set(), unavailableReason: 'approval_db_not_found' };
45
+ return { ids: new Set(), pendingIds: new Set(), unavailableReason: 'approval_db_not_found' };
46
46
  }
47
47
  let closeFn = null;
48
48
  try {
@@ -51,25 +51,33 @@ async function getDecidedPrincipleIds(workspaceDir) {
51
51
  closeFn = () => conn.close();
52
52
  const db = conn.getDb();
53
53
  // Join approvals with pi_artifacts to find decided principle IDs
54
- const rows = db.prepare("SELECT DISTINCT a.source_principle_id " +
54
+ // PRI-629: also resolve PENDING approvals — a pending approval is a real
55
+ // Owner decision (owner_actionable), unlike candidate lifecycle (in_pipeline).
56
+ const rows = db.prepare("SELECT DISTINCT a.source_principle_id, ap.status " +
55
57
  "FROM approvals ap " +
56
58
  "JOIN pi_artifacts a ON a.artifact_id = ap.artifact_id " +
57
- "WHERE ap.status IN ('approved', 'rejected') " +
59
+ "WHERE ap.status IN ('approved', 'rejected', 'pending') " +
58
60
  "AND a.source_principle_id IS NOT NULL").all();
59
61
  // EP-01 Rule 1: treat DB rows as unknown; validate before use
60
62
  // EP-01 Rule 2: no `as` cast — use getOwnStringField instead
61
63
  const ids = new Set();
64
+ const pendingIds = new Set();
62
65
  for (const row of rows) {
63
66
  const val = getOwnStringField(row, 'source_principle_id');
64
- if (val !== undefined) {
67
+ if (val === undefined)
68
+ continue;
69
+ if (getOwnStringField(row, 'status') === 'pending') {
70
+ pendingIds.add(val);
71
+ }
72
+ else {
65
73
  ids.add(val);
66
74
  }
67
75
  }
68
- return { ids };
76
+ return { ids, pendingIds };
69
77
  }
70
78
  catch (err) {
71
79
  const message = err instanceof Error ? err.message : String(err);
72
- return { ids: new Set(), unavailableReason: `approval_query_failed: ${message}` };
80
+ return { ids: new Set(), pendingIds: new Set(), unavailableReason: `approval_query_failed: ${message}` };
73
81
  }
74
82
  finally {
75
83
  closeFn?.();
@@ -197,7 +205,7 @@ export async function handlePrinciplesRoute({ req, res, workspaceDir, subPath, f
197
205
  const VALID_FILTERS = new Set(['all', 'actionable']);
198
206
  const filter = (filterRaw !== null && VALID_FILTERS.has(filterRaw) ? filterRaw : 'actionable');
199
207
  const decidedResult = await getDecidedPrincipleIds(workspaceDir);
200
- const result = await model.listPrinciples(filter, decidedResult.ids);
208
+ const result = await model.listPrinciples(filter, decidedResult.ids, decidedResult.pendingIds);
201
209
  // Surface approval cross-check unavailability to the UI (ERR-002)
202
210
  if (decidedResult.unavailableReason) {
203
211
  result.approvalCrossCheckUnavailable = decidedResult.unavailableReason;
@@ -9,6 +9,7 @@ import { checkOpenClawGateway, stopOpenClawGateway, restartOpenClawGateway, } fr
9
9
  import { migrateLegacyExtensionBackups, reservePdBackupDestination, resolvePdBackupsRoot, } from '../utils/pd-backups.js';
10
10
  import { ActivationCompatibilityReadModel } from '@principles/core/runtime-v2';
11
11
  import { getInstallLayoutPaths, resolveInstallLayout } from '@principles/install-layout';
12
+ import { collectFileDepLinkSpecs } from '../utils/update-links.js';
12
13
  /**
13
14
  * Legacy rule contract preflight (2026-08-19): refuse to swap the runtime
14
15
  * while an ACTIVE owner-approved rule still depends on a RuleHost contract
@@ -724,22 +725,35 @@ function depsMeaningfullyChanged(oldDeps, newDeps) {
724
725
  return aKeys.some((k, i) => bKeys[i] !== k || a[k] !== b[k]);
725
726
  }
726
727
  /**
727
- * Create the node_modules/@principles/host-runtime resolution links for the
728
- * installed console and pd-cli packages, if missing.
728
+ * Create the node_modules resolution links for the installed components, if
729
+ * missing.
729
730
  *
730
731
  * Mirrors installer.ts syncPdCli: junction on Windows (no elevation needed),
731
732
  * relative symlink elsewhere. Fresh installs get these links via npm install
732
- * (the bundled package.json rewrites the dep to file:../host-runtime); the
733
- * full update deliberately skips npm install, so it must create them itself.
734
- * Without the link, the updated console dist — which statically imports
735
- * @principles/host-runtime since 2026-08-21 (41cf97ee5) — crashes at startup
736
- * with ERR_MODULE_NOT_FOUND on installs created before the installer bundled
737
- * host-runtime (PRI-561).
733
+ * (the bundled package.json rewrites internal deps to file:../<component>);
734
+ * the full update deliberately skips npm install, so it must create them
735
+ * itself. Without a link, updated dists crash at startup with
736
+ * ERR_MODULE_NOT_FOUND (host-runtime: PRI-561, 2026-08-21).
737
+ *
738
+ * Two sources, both fail-closed on creation errors:
739
+ * 1. the explicit link list below (known-critical links: a missing one
740
+ * means the updated console cannot start);
741
+ * 2. a data-driven pass that derives links from the STAGED manifests —
742
+ * the freshly extracted release trees under tempDir. Their `file:`
743
+ * declarations are the authoritative list of links the updated tree
744
+ * needs. Reading the deployed (pre-update) manifests instead would
745
+ * miss every dependency this release newly introduced: the running
746
+ * console executes update logic from its own dist (one generation
747
+ * behind the components it installs) — observed 2026-08-29 when the
748
+ * 1.221.2 console updated hosts to 1.222.5 but could not know about
749
+ * the newly introduced install-layout component, leaving
750
+ * host-runtime/node_modules/@principles/install-layout missing and
751
+ * every pd-cli runtime command failing with ERR_MODULE_NOT_FOUND.
738
752
  *
739
753
  * Returns undefined on success, or an error message (rc-9: observable, never
740
754
  * silent — a missing link means the updated console cannot start).
741
755
  */
742
- function ensureRuntimeResolutionLinks(layout) {
756
+ function ensureRuntimeResolutionLinks(layout, tempDir) {
743
757
  const links = [
744
758
  { linkPath: path.join(layout.consoleDir, 'node_modules', '@principles', 'host-runtime'), target: layout.hostRuntimeDir },
745
759
  { linkPath: path.join(layout.pdCliDir, 'node_modules', '@principles', 'host-runtime'), target: layout.hostRuntimeDir },
@@ -750,11 +764,43 @@ function ensureRuntimeResolutionLinks(layout) {
750
764
  { linkPath: path.join(layout.hostRuntimeDir, 'node_modules', '@principles', 'install-layout'), target: layout.installLayoutDir },
751
765
  { linkPath: path.join(layout.consoleDir, 'node_modules', 'principles-disciple'), target: layout.pluginDir },
752
766
  ];
753
- for (const { linkPath, target } of links) {
754
- if (!fs.existsSync(target))
755
- continue;
767
+ // Data-driven pass: derive links from the STAGED component manifests (see
768
+ // the comment above — staged, never the deployed pre-update manifests).
769
+ const stagedComponents = [
770
+ { manifestDir: path.join(tempDir, 'console'), deployedDir: layout.consoleDir },
771
+ { manifestDir: path.join(tempDir, 'pd-cli'), deployedDir: layout.pdCliDir },
772
+ { manifestDir: path.join(tempDir, 'host-runtime'), deployedDir: layout.hostRuntimeDir },
773
+ { manifestDir: path.join(tempDir, 'install-layout'), deployedDir: layout.installLayoutDir },
774
+ { manifestDir: path.join(tempDir, 'core'), deployedDir: layout.coreDir },
775
+ { manifestDir: path.join(tempDir, 'plugin'), deployedDir: layout.pluginDir },
776
+ ];
777
+ const readStagedDependencies = (manifestDir) => {
778
+ try {
779
+ const pkgPath = path.join(manifestDir, 'package.json');
780
+ if (!fs.existsSync(pkgPath))
781
+ return {};
782
+ const parsed = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
783
+ if (!isRecord(parsed) || !isRecord(parsed.dependencies))
784
+ return {};
785
+ const out = {};
786
+ for (const [name, ref] of Object.entries(parsed.dependencies)) {
787
+ if (typeof ref === 'string')
788
+ out[name] = ref;
789
+ }
790
+ return out;
791
+ }
792
+ catch {
793
+ // rc-9: an unreadable staged manifest degrades to "no derived links" —
794
+ // the explicit list above still covers the known-critical links.
795
+ return {};
796
+ }
797
+ };
798
+ const fileDepSpecs = collectFileDepLinkSpecs(stagedComponents, readStagedDependencies);
799
+ const createResolutionLink = (linkPath, target) => {
800
+ // Idempotent: never overwrite an existing link or directory (fresh
801
+ // installs have npm-created real dirs in these slots).
756
802
  if (fs.existsSync(linkPath))
757
- continue;
803
+ return undefined;
758
804
  try {
759
805
  fs.mkdirSync(path.dirname(linkPath), { recursive: true });
760
806
  if (process.platform === 'win32') {
@@ -763,10 +809,32 @@ function ensureRuntimeResolutionLinks(layout) {
763
809
  else {
764
810
  fs.symlinkSync(path.relative(path.dirname(linkPath), target), linkPath, 'dir');
765
811
  }
812
+ return undefined;
766
813
  }
767
814
  catch (error) {
768
815
  return `Failed to create runtime resolution link at ${linkPath}: ${error instanceof Error ? error.message : String(error)}`;
769
816
  }
817
+ };
818
+ // Pass 1 — explicit known-critical links, fail-closed BEFORE any byte is
819
+ // swapped (a link-creation failure aborts with the installed packages
820
+ // untouched: the PRI-561 ordering contract).
821
+ for (const { linkPath, target } of links) {
822
+ if (!fs.existsSync(target))
823
+ continue;
824
+ const error = createResolutionLink(linkPath, target);
825
+ if (error)
826
+ return error;
827
+ }
828
+ // Pass 2 — data-driven derived links. Their deployed target dir may be
829
+ // created by the copy steps that follow (a brand-new component's dir does
830
+ // not exist yet); each staged target's existence was already proven by the
831
+ // extraction, and the copies below run unconditionally.
832
+ for (const spec of fileDepSpecs) {
833
+ if (fs.existsSync(spec.linkPath))
834
+ continue;
835
+ const error = createResolutionLink(spec.linkPath, spec.target);
836
+ if (error)
837
+ return error;
770
838
  }
771
839
  return undefined;
772
840
  }
@@ -935,7 +1003,7 @@ async function doInlineFullUpdate(workspaceDir) {
935
1003
  if (fs.existsSync(path.join(installLayoutSrc, 'package.json')) && fs.existsSync(path.join(installLayoutSrc, 'dist'))) {
936
1004
  copyDirRecursive(installLayoutSrc, layout.installLayoutDir, SKIP_DIRS);
937
1005
  }
938
- const linkError = ensureRuntimeResolutionLinks(layout);
1006
+ const linkError = ensureRuntimeResolutionLinks(layout, tempDir);
939
1007
  if (linkError) {
940
1008
  appendUpdateHistory(workspaceDir, {
941
1009
  fromVersion,
@@ -0,0 +1,38 @@
1
+ export type FileDepLinkSpec = {
2
+ linkPath: string;
3
+ /** Deployed layout dir the link points at; may be created by the copy
4
+ * steps that run after derivation (see stagedTargetExists). */
5
+ target: string;
6
+ /** The staged sibling dir proving this component ships in this release. */
7
+ stagedTarget: string;
8
+ };
9
+ export type StagedComponent = {
10
+ /** Freshly extracted component dir under the update temp dir. */
11
+ manifestDir: string;
12
+ /** Deployed layout dir this component is (or will be) installed into. */
13
+ deployedDir: string;
14
+ };
15
+ /**
16
+ * Derive the node_modules link specs implied by each staged component's
17
+ * declared `file:` dependencies.
18
+ *
19
+ * - `readDependencies(manifestDir)` returns the component's dependency map
20
+ * (name -> version-or-ref); return {} for unreadable/missing manifests.
21
+ * - A `file:` ref is resolved against its staged manifest dir to find the
22
+ * staged sibling target; the deployed link target is taken from the same
23
+ * staged->deployed pairing the caller provides — component identity is
24
+ * the staged directory itself (normalized absolute path), NOT the
25
+ * deployed directory's basename. In the legacy layout the deployed
26
+ * plugin dir is named `principles-disciple` while the staged one is
27
+ * `plugin`, so a basename lookup would silently drop the derived link
28
+ * (review P1, PR #1457).
29
+ * - Deps whose staged target is not one of the layout components (e.g.
30
+ * @principles/codex-adapter — a separate host install, not part of this
31
+ * layout) are skipped.
32
+ *
33
+ * Note: `target` (the deployed dir) may not exist yet at derivation time —
34
+ * for components whose copy step runs after this call, the caller creates
35
+ * the directory right after deriving and creating these links. Use
36
+ * `stagedTarget` to verify the component actually ships in this release.
37
+ */
38
+ export declare function collectFileDepLinkSpecs(stagedComponents: readonly StagedComponent[], readDependencies: (manifestDir: string) => Record<string, string>): FileDepLinkSpec[];
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Data-driven resolution-link derivation for the full-update pipeline.
3
+ *
4
+ * The deployed console performs full updates with the update logic baked
5
+ * into its own dist — one generation behind the components it installs. A
6
+ * hardcoded link list therefore goes stale the moment a release introduces
7
+ * a new internal `file:` dependency (observed 2026-08-29: the 1.221.2
8
+ * console could not know about the newly introduced install-layout
9
+ * component, so the 1.222.5 update left
10
+ * host-runtime/node_modules/@principles/install-layout missing and every
11
+ * pd-cli runtime command failed with ERR_MODULE_NOT_FOUND).
12
+ *
13
+ * The authoritative source for "which links does the UPDATED tree need" is
14
+ * the STAGED manifests under the update's temp dir (the freshly extracted
15
+ * release trees) — NOT the deployed pre-update manifests, which by
16
+ * definition do not declare newly added dependencies.
17
+ *
18
+ * Each staged component's `file:` refs resolve to a sibling staged
19
+ * component directory; the deployed link target is that sibling's entry in
20
+ * the same staged->deployed pairing.
21
+ */
22
+ import * as path from 'node:path';
23
+ /**
24
+ * Derive the node_modules link specs implied by each staged component's
25
+ * declared `file:` dependencies.
26
+ *
27
+ * - `readDependencies(manifestDir)` returns the component's dependency map
28
+ * (name -> version-or-ref); return {} for unreadable/missing manifests.
29
+ * - A `file:` ref is resolved against its staged manifest dir to find the
30
+ * staged sibling target; the deployed link target is taken from the same
31
+ * staged->deployed pairing the caller provides — component identity is
32
+ * the staged directory itself (normalized absolute path), NOT the
33
+ * deployed directory's basename. In the legacy layout the deployed
34
+ * plugin dir is named `principles-disciple` while the staged one is
35
+ * `plugin`, so a basename lookup would silently drop the derived link
36
+ * (review P1, PR #1457).
37
+ * - Deps whose staged target is not one of the layout components (e.g.
38
+ * @principles/codex-adapter — a separate host install, not part of this
39
+ * layout) are skipped.
40
+ *
41
+ * Note: `target` (the deployed dir) may not exist yet at derivation time —
42
+ * for components whose copy step runs after this call, the caller creates
43
+ * the directory right after deriving and creating these links. Use
44
+ * `stagedTarget` to verify the component actually ships in this release.
45
+ */
46
+ export function collectFileDepLinkSpecs(stagedComponents, readDependencies) {
47
+ const deployedDirByStagedDir = new Map();
48
+ for (const component of stagedComponents) {
49
+ deployedDirByStagedDir.set(path.resolve(component.manifestDir), component.deployedDir);
50
+ }
51
+ const specs = [];
52
+ const seen = new Set();
53
+ for (const component of stagedComponents) {
54
+ for (const [name, ref] of Object.entries(readDependencies(component.manifestDir))) {
55
+ if (typeof ref !== 'string' || !ref.startsWith('file:'))
56
+ continue;
57
+ const stagedTarget = path.resolve(component.manifestDir, ref.slice('file:'.length));
58
+ const deployedTarget = deployedDirByStagedDir.get(stagedTarget);
59
+ if (deployedTarget === undefined)
60
+ continue;
61
+ // The link lives in the DEPLOYED component dir (what the updated dist
62
+ // resolves against) — never inside the throwaway temp dir.
63
+ const linkPath = path.join(component.deployedDir, 'node_modules', name);
64
+ if (seen.has(linkPath))
65
+ continue;
66
+ seen.add(linkPath);
67
+ specs.push({ linkPath, target: deployedTarget, stagedTarget });
68
+ }
69
+ }
70
+ return specs;
71
+ }
@@ -1,4 +1,5 @@
1
1
  import type { ApiResponse } from "../types.js";
2
+ import type { OwnerDecisionsData, OwnerResolutionResultData } from "./utils/validators.js";
2
3
  import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm } from "./utils/signal-keywords-api.js";
3
4
  import type { GovernanceExperienceSnapshot, OwnerGovernanceView } from '@principles/core/runtime-v2';
4
5
  import type { OwnerIdentityViewData, OwnerIdentityRegisterData, OwnerIdentityUnregisterData } from "./utils/validators.js";
@@ -148,6 +149,16 @@ declare function updateOutputLanguage(outputLanguage: string): Promise<ApiRespon
148
149
  declare function fetchGovernanceQueue(): Promise<ApiResponse<GovernanceQueueData>>;
149
150
  declare function fetchGovernanceExperience(): Promise<ApiResponse<GovernanceExperienceSnapshot>>;
150
151
  declare function recoverFailedTask(taskId: string, reason?: string, force?: boolean): Promise<ApiResponse<RecoveryResultData>>;
152
+ declare function fetchOwnerDecisions(): Promise<ApiResponse<OwnerDecisionsData>>;
153
+ declare function resolveOwnerDecision(taskId: string, body: {
154
+ action: 'accept_current' | 'revise_once' | 'reject_current';
155
+ reviewKey: string;
156
+ expectedRevisionEpoch: number;
157
+ expectedSourceRunId: string;
158
+ expectedSourceArtifactId: string;
159
+ expectedSourceArtifactHash: string;
160
+ ownerInstruction?: string | null;
161
+ }): Promise<ApiResponse<OwnerResolutionResultData>>;
151
162
  declare function fetchApprovalsGrouped(): Promise<ApiResponse<ApprovalsGroupedData>>;
152
163
  declare function fetchAllActivations(): Promise<ApiResponse<ActivationsData>>;
153
164
  declare function disableActivation(activationId: string): Promise<ApiResponse<DisableActivationData>>;
@@ -214,7 +225,7 @@ export interface FollowUpPayload {
214
225
  candidateId?: string;
215
226
  }
216
227
  declare function dispatchFollowUp(decisionId: string, payload: FollowUpPayload): Promise<ApiResponse<FollowUpResponseData>>;
217
- export { request, listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport, fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchGovernanceExperience, recoverFailedTask, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
228
+ export { request, listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport, fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchGovernanceExperience, recoverFailedTask, fetchOwnerDecisions, resolveOwnerDecision, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
218
229
  export type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, RecoveryResultData, ActivationsData, DisableActivationData, RuleCodeOwnerReviewData, RuleCodeMutationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, EvidenceChainRecordData, EvidenceChainStateData, TrajectoryData, TrajectoryStageData, IntentSummaryData, IntentSectionsData, IntentDocWarningData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, LinkCandidateFollowUpData, GuideRulehostFollowUpData, GeneratePatchProposalFollowUpData, IntentVersionEntry, IntentVersionData, } from "./utils/validators.js";
219
230
  export type { SignalKeyword, PendingSignalTerm } from "./utils/signal-keywords-types.js";
220
231
  export type { ActivationRecordData as ActivationRecord } from "./utils/validators.js";
@@ -1,5 +1,5 @@
1
1
  import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, } from "./utils/signal-keywords-api.js";
2
- import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateRecoveryResult, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, validateGovernanceExperienceSnapshot, validateRuleCodeOwnerReview, validateRuleCodeMutation, validateOwnerIdentityView, validateOwnerIdentityRegister, validateOwnerIdentityUnregister, } from "./utils/validators.js";
2
+ import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateRecoveryResult, validateOwnerDecisionsData, validateOwnerResolutionResult, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, validateGovernanceExperienceSnapshot, validateRuleCodeOwnerReview, validateRuleCodeMutation, validateOwnerIdentityView, validateOwnerIdentityRegister, validateOwnerIdentityUnregister, } from "./utils/validators.js";
3
3
  // ── Auth ──────────────────────────────────────────────────────────────────────
4
4
  function getToken() {
5
5
  return sessionStorage.getItem("pd_token");
@@ -454,6 +454,13 @@ async function recoverFailedTask(taskId, reason, force) {
454
454
  body: JSON.stringify(body),
455
455
  }, validateRecoveryResult);
456
456
  }
457
+ // PRI-629: unified Owner Decision inbox — read projection + resolution.
458
+ async function fetchOwnerDecisions() {
459
+ return request('/api/v1/governance/owner-decisions', undefined, validateOwnerDecisionsData);
460
+ }
461
+ async function resolveOwnerDecision(taskId, body) {
462
+ return request('/api/v1/governance/owner-decisions/' + encodeURIComponent(taskId) + '/resolve', { method: 'POST', body: JSON.stringify(body) }, validateOwnerResolutionResult);
463
+ }
457
464
  async function fetchApprovalsGrouped() {
458
465
  return request('/api/v1/approvals/grouped', undefined, validateApprovalsGrouped);
459
466
  }
@@ -570,4 +577,4 @@ listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendin
570
577
  // auth
571
578
  getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport,
572
579
  // Feedback submit ladder (Slice 3, spec §8)
573
- fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchGovernanceExperience, recoverFailedTask, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
580
+ fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchGovernanceExperience, recoverFailedTask, fetchOwnerDecisions, resolveOwnerDecision, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useEffect, useRef, useState, } from 'react';
3
- import { fetchGovernanceQueue } from '../../api.js';
3
+ import { fetchGovernanceQueue, fetchOwnerDecisions } from '../../api.js';
4
4
  import { useNotificationSound } from '../../hooks/useNotificationSound.js';
5
5
  import { diffNotificationCounts } from './notification-reducer.js';
6
6
  import { loadSoundEnabled, saveSoundEnabled } from './sound-storage.js';
@@ -33,8 +33,14 @@ export function NotificationProvider({ children }) {
33
33
  console.warn('Notification poll failed:', result.error);
34
34
  return;
35
35
  }
36
- const pendingCount = result.data.pendingReviewCount;
37
36
  const degradedCount = result.data.degradedSignals?.length ?? 0;
37
+ // PRI-629 (SPEC §27): 侧边栏徽标 N = 真实可执行 Owner 决策数
38
+ // (OwnerDecisionItem.length) — 不是 approvals pending / candidate /
39
+ // needs_human_review 计数。投影失败时回退 approvals pending (保守)。
40
+ const decisionsResult = await fetchOwnerDecisions();
41
+ const pendingCount = decisionsResult.success
42
+ ? decisionsResult.data.total
43
+ : result.data.pendingReviewCount;
38
44
  hasSuccessfulPollRef.current = true;
39
45
  setState((prev) => ({
40
46
  ...prev,