create-principles-disciple 1.145.26 → 1.145.28

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 (115) hide show
  1. package/_release/product-identity.json +1 -1
  2. package/console/dist/server/models/RuleCandidateBindingConsoleModel.d.ts +26 -0
  3. package/console/dist/server/models/RuleCandidateBindingConsoleModel.js +309 -19
  4. package/console/dist/server/routes/rule-candidate-bindings.js +26 -1
  5. package/console/dist/shared/rule-candidate-binding-contract.d.ts +93 -8
  6. package/console/dist/shared/rule-candidate-binding-contract.js +4 -0
  7. package/console/dist/ui/api.d.ts +28 -0
  8. package/console/dist/ui/api.js +13 -1
  9. package/console/dist/ui/i18n/en.json +92 -20
  10. package/console/dist/ui/i18n/zh-CN.json +78 -6
  11. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +29 -0
  12. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +229 -3
  13. package/console/dist/ui/utils/rule-binding-validators.d.ts +5 -0
  14. package/console/dist/ui/utils/rule-binding-validators.js +149 -7
  15. package/console/dist/web/assets/app.css +6 -0
  16. package/console/dist/web/assets/app.js +32 -32
  17. package/core/dist/runtime-v2/evidence-triage/triage-policy.d.ts +0 -1
  18. package/core/dist/runtime-v2/evidence-triage/triage-policy.d.ts.map +1 -1
  19. package/core/dist/runtime-v2/evidence-triage/triage-policy.js +0 -1
  20. package/core/dist/runtime-v2/evidence-triage/triage-policy.js.map +1 -1
  21. package/core/dist/runtime-v2/golden-trace.d.ts.map +1 -1
  22. package/core/dist/runtime-v2/golden-trace.js +8 -1
  23. package/core/dist/runtime-v2/golden-trace.js.map +1 -1
  24. package/core/dist/runtime-v2/index.d.ts +3 -3
  25. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  26. package/core/dist/runtime-v2/index.js +3 -3
  27. package/core/dist/runtime-v2/index.js.map +1 -1
  28. package/core/dist/runtime-v2/internalization/artificer-intent-preflight.d.ts.map +1 -1
  29. package/core/dist/runtime-v2/internalization/artificer-intent-preflight.js +26 -95
  30. package/core/dist/runtime-v2/internalization/artificer-intent-preflight.js.map +1 -1
  31. package/core/dist/runtime-v2/internalization/bash-risk.d.ts +32 -0
  32. package/core/dist/runtime-v2/internalization/bash-risk.d.ts.map +1 -0
  33. package/core/dist/runtime-v2/internalization/bash-risk.js +785 -0
  34. package/core/dist/runtime-v2/internalization/bash-risk.js.map +1 -0
  35. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +25 -2
  36. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
  37. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js +177 -14
  38. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
  39. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  40. package/core/dist/runtime-v2/internalization/evaluator-runner.js +14 -2
  41. package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  42. package/core/dist/runtime-v2/internalization/internalization-orchestrator.d.ts +47 -0
  43. package/core/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
  44. package/core/dist/runtime-v2/internalization/internalization-orchestrator.js +164 -3
  45. package/core/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
  46. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +9 -1
  47. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  48. package/core/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  49. package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts +39 -0
  50. package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
  51. package/core/dist/runtime-v2/internalization/pitask-metadata.js +59 -0
  52. package/core/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
  53. package/core/dist/runtime-v2/internalization/rule-source-correction.d.ts +60 -0
  54. package/core/dist/runtime-v2/internalization/rule-source-correction.d.ts.map +1 -0
  55. package/core/dist/runtime-v2/internalization/rule-source-correction.js +214 -0
  56. package/core/dist/runtime-v2/internalization/rule-source-correction.js.map +1 -0
  57. package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.d.ts.map +1 -1
  58. package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.js +65 -0
  59. package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.js.map +1 -1
  60. package/core/dist/runtime-v2/internalization/scribe-output.d.ts +14 -2
  61. package/core/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
  62. package/core/dist/runtime-v2/internalization/scribe-output.js +57 -2
  63. package/core/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
  64. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts +13 -1
  65. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts.map +1 -1
  66. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js +15 -3
  67. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js.map +1 -1
  68. package/core/dist/runtime-v2/internalization/scribe-runner.d.ts +34 -0
  69. package/core/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
  70. package/core/dist/runtime-v2/internalization/scribe-runner.js +128 -4
  71. package/core/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
  72. package/core/dist/runtime-v2/internalization/textual-basis-anchor.d.ts +72 -0
  73. package/core/dist/runtime-v2/internalization/textual-basis-anchor.d.ts.map +1 -0
  74. package/core/dist/runtime-v2/internalization/textual-basis-anchor.js +100 -0
  75. package/core/dist/runtime-v2/internalization/textual-basis-anchor.js.map +1 -0
  76. package/core/dist/runtime-v2/store/lifecycle/lease-manager.d.ts.map +1 -1
  77. package/core/dist/runtime-v2/store/lifecycle/lease-manager.js +11 -2
  78. package/core/dist/runtime-v2/store/lifecycle/lease-manager.js.map +1 -1
  79. package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.d.ts.map +1 -1
  80. package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.js +26 -0
  81. package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.js.map +1 -1
  82. package/core/dist/telemetry-event.d.ts +2 -2
  83. package/core/dist/telemetry-event.d.ts.map +1 -1
  84. package/core/dist/telemetry-event.js +5 -0
  85. package/core/dist/telemetry-event.js.map +1 -1
  86. package/core/package.json +5 -1
  87. package/host-runtime/dist/index.d.ts +0 -1
  88. package/host-runtime/dist/index.js +0 -3
  89. package/host-runtime/dist/production-rulehost-gate.js +11 -1
  90. package/host-runtime/package.json +1 -1
  91. package/package.json +2 -2
  92. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
  93. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js +32 -0
  94. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
  95. package/pd-cli/package.json +1 -1
  96. package/plugin/dist/bundle.js +503 -490
  97. package/plugin/dist/hooks/pain-ingress-adapter.d.ts +1 -2
  98. package/plugin/dist/hooks/triage-adapter.d.ts +3 -1
  99. package/plugin/dist/openclaw.plugin.json +1 -1
  100. package/plugin/openclaw.plugin.json +1 -1
  101. package/plugin/package.json +1 -1
  102. package/release-manager/package.json +2 -2
  103. package/core/dist/runtime-v2/pain-gate/index.d.ts +0 -10
  104. package/core/dist/runtime-v2/pain-gate/index.d.ts.map +0 -1
  105. package/core/dist/runtime-v2/pain-gate/index.js +0 -9
  106. package/core/dist/runtime-v2/pain-gate/index.js.map +0 -1
  107. package/core/dist/runtime-v2/pain-gate/pain-diagnostic-gate-policy.d.ts +0 -123
  108. package/core/dist/runtime-v2/pain-gate/pain-diagnostic-gate-policy.d.ts.map +0 -1
  109. package/core/dist/runtime-v2/pain-gate/pain-diagnostic-gate-policy.js +0 -159
  110. package/core/dist/runtime-v2/pain-gate/pain-diagnostic-gate-policy.js.map +0 -1
  111. package/host-runtime/dist/pain-evidence-ingress.d.ts +0 -16
  112. package/host-runtime/dist/pain-evidence-ingress.js +0 -15
  113. package/plugin/dist/core/pain-diagnostic-gate.d.ts +0 -47
  114. package/plugin/dist/core/thinking-os-parser.d.ts +0 -33
  115. package/plugin/dist/core/workflow-funnel-loader.d.ts +0 -72
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "productVersion": "2.3.13",
4
- "sourceCommit": "7fa8fd8361962f41676275ba26ac81d51e6ee84b"
4
+ "sourceCommit": "7195795954fd2ed208714ada70ccbc10b1efdfe6"
5
5
  }
@@ -7,6 +7,19 @@ export declare class RuleCandidateBindingConsoleModel {
7
7
  private readonly workspaceDir;
8
8
  constructor(workspaceDir: string);
9
9
  private ledger;
10
+ /** One bounded readonly batch read of the pain events behind the queue.
11
+ * `degraded` names an unreadable trajectory store so lookups that found
12
+ * nothing are labeled 不可读 rather than 未记录 (rc-9 — never misattribute). */
13
+ private painSources;
14
+ /** Identity stays with the ledger adapter (the identity authority); the
15
+ * principle entry / decidedAt come from the caller's single file snapshot.
16
+ * bindingRevision rides along so the source-correction preview can anchor
17
+ * its eligibility to the CURRENT binding (PRI-1048 R6 #4). */
18
+ private queueBinding;
19
+ /** PRI-1048 R6 #4: read-only eligibility preview for source-correct-once.
20
+ * Only the bound+blocked shape can offer it; the click-time authority
21
+ * (reviseRuleSourceOnce) re-checks everything server-side. */
22
+ private sourceCorrectionPreview;
10
23
  list(): Promise<RuleBindingQueueView>;
11
24
  review(candidateId: string, search: string): Promise<{
12
25
  ok: true;
@@ -37,4 +50,17 @@ export declare class RuleCandidateBindingConsoleModel {
37
50
  scribeTaskId: string;
38
51
  taskRevisionEpoch: number;
39
52
  }>;
53
+ /**
54
+ * SPEC-PRI-1047 修复 B: 认证入口的一次 Owner source-correction。区别于
55
+ * revise(binding 修订预算):这是「修正上游材料」的独立一次预算,回执
56
+ * 绑定真实 Owner actor/request。调用侧(路由)负责 ownerIdentity 门。
57
+ */
58
+ correctSource(candidateId: string, input: {
59
+ expectedBindingRevision: number;
60
+ expectedScribeTaskId: string;
61
+ expectedArtificerTaskId: string;
62
+ expectedScribeArtifactDigest: string;
63
+ reason: string;
64
+ requestId: string;
65
+ }, actorId: string): Promise<import("@principles/core/runtime-v2").ReviseRuleSourceOnceResult>;
40
66
  }
@@ -1,6 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { existsSync } from 'node:fs';
3
- import { createRuntimeStateHandle, PrincipleTreeLedgerAdapter, reviseRuleBindingOnce, } from '@principles/core/runtime-v2';
3
+ import Database from 'better-sqlite3';
4
+ import { createRuntimeStateHandle, PrincipleTreeLedgerAdapter, reviseRuleBindingOnce, resolveRuleBindingRevisionTarget, reviseRuleSourceOnce, previewRuleSourceCorrection, hydratePITaskRecord, candidateIdFromDreamerSeed, } from '@principles/core/runtime-v2';
5
+ import { readLedgerFileState } from '@principles/core/principle-tree-ledger';
4
6
  import { ActivationsConsoleModel } from './ActivationsConsoleModel.js';
5
7
  function record(value) {
6
8
  return value !== null && typeof value === 'object' && !Array.isArray(value);
@@ -8,6 +10,108 @@ function record(value) {
8
10
  function stringField(value, key) {
9
11
  return record(value) && typeof value[key] === 'string' ? value[key] : '';
10
12
  }
13
+ /** Internalization chain order; the queue projection walks it read-only. */
14
+ const CHAIN_KINDS = ['dreamer', 'philosopher', 'scribe', 'artificer', 'evaluator', 'rollout_reviewer'];
15
+ /** PRI-1048: bounded queue — a governance workspace carries tens of candidates;
16
+ * the cap keeps the derived scan bounded and the response honest via `truncated`. */
17
+ const MAX_QUEUE_ITEMS = 200;
18
+ function buildChainContext(allTasks) {
19
+ const byId = new Map(allTasks.map(task => [task.taskId, task]));
20
+ const dreamersByCandidate = new Map();
21
+ const dependentsByTaskId = new Map();
22
+ for (const task of allTasks) {
23
+ if (task.taskKind === 'dreamer') {
24
+ const seed = candidateIdFromDreamerSeed(task.diagnosticJson ?? null);
25
+ if (seed) {
26
+ const list = dreamersByCandidate.get(seed) ?? [];
27
+ list.push(task);
28
+ dreamersByCandidate.set(seed, list);
29
+ }
30
+ }
31
+ const pi = hydratePITaskRecord(task);
32
+ if (!pi)
33
+ continue;
34
+ for (const dependencyId of pi.dependencyTaskIds) {
35
+ const list = dependentsByTaskId.get(dependencyId) ?? [];
36
+ list.push(task);
37
+ dependentsByTaskId.set(dependencyId, list);
38
+ }
39
+ }
40
+ return { byId, dreamersByCandidate, dependentsByTaskId };
41
+ }
42
+ function walkChain(dreamer, context) {
43
+ let current = dreamer;
44
+ let kind = 'dreamer';
45
+ for (;;) {
46
+ if (!current)
47
+ return { stage: 'advanced', stageKind: kind, reasonCode: null, detail: null };
48
+ if (current.status === 'needs_human_review' || current.status === 'failed') {
49
+ const pi = hydratePITaskRecord(current);
50
+ const review = pi?.humanReviewContext;
51
+ return {
52
+ stage: 'blocked', stageKind: kind,
53
+ reasonCode: review && typeof review.reasonCode === 'string' ? review.reasonCode : null,
54
+ detail: review && typeof review.detail === 'string' ? review.detail : (current.lastError ?? null),
55
+ };
56
+ }
57
+ if (current.status !== 'succeeded')
58
+ return { stage: 'in_progress', stageKind: kind, reasonCode: null, detail: null };
59
+ const nextIndex = CHAIN_KINDS.indexOf(kind) + 1;
60
+ const nextKind = nextIndex < CHAIN_KINDS.length ? CHAIN_KINDS[nextIndex] : undefined;
61
+ if (!nextKind)
62
+ return { stage: 'advanced', stageKind: kind, reasonCode: null, detail: null };
63
+ const next = (context.dependentsByTaskId.get(current.taskId) ?? []).find(task => task.taskKind === nextKind);
64
+ if (!next)
65
+ return { stage: 'advanced', stageKind: kind, reasonCode: null, detail: null };
66
+ current = next;
67
+ kind = nextKind;
68
+ }
69
+ }
70
+ function deriveGeneration(candidateId, context) {
71
+ const dreamers = context.dreamersByCandidate.get(candidateId) ?? [];
72
+ if (dreamers.length === 0)
73
+ return { stage: 'not_built', stageKind: null, reasonCode: null, detail: null };
74
+ const walks = dreamers.map(dreamer => walkChain(dreamer, context));
75
+ const [first] = walks;
76
+ if (!first)
77
+ return { stage: 'ambiguous', stageKind: null, reasonCode: null, detail: null };
78
+ const sameWalk = (a, b) => a.stage === b.stage && a.stageKind === b.stageKind && a.reasonCode === b.reasonCode;
79
+ if (walks.every(walk => sameWalk(walk, first)))
80
+ return first;
81
+ return { stage: 'ambiguous', stageKind: null, reasonCode: null, detail: null };
82
+ }
83
+ /**
84
+ * Real backend eligibility only: the production resolver plus the exact
85
+ * budget rule reviseRuleBindingOnce enforces on the same scribe task. The
86
+ * revise-once route still authoritatively re-checks at click time.
87
+ */
88
+ async function deriveResume(stateManager, candidateId) {
89
+ const target = await resolveRuleBindingRevisionTarget(stateManager, candidateId);
90
+ if (target.status !== 'ready')
91
+ return { status: 'refused', reason: target.reason, scribeTaskId: null, taskRevisionEpoch: null };
92
+ const scribe = await stateManager.getTask(target.scribeTaskId);
93
+ const pi = scribe ? hydratePITaskRecord(scribe) : null;
94
+ if (!scribe || !pi)
95
+ return { status: 'refused', reason: 'scribe_missing', scribeTaskId: target.scribeTaskId, taskRevisionEpoch: target.taskRevisionEpoch };
96
+ if (pi.ruleBindingRevisionCauseId || pi.revisionCauseId?.startsWith('rule-binding:')) {
97
+ return { status: 'refused', reason: 'revision_budget_exhausted', scribeTaskId: target.scribeTaskId, taskRevisionEpoch: target.taskRevisionEpoch };
98
+ }
99
+ return { status: 'ready', reason: null, scribeTaskId: target.scribeTaskId, taskRevisionEpoch: target.taskRevisionEpoch };
100
+ }
101
+ /** Uses the caller's ONE ledger snapshot: decidedAt + principle entry, no extra file read. */
102
+ function boundPrincipleView(state, lookup) {
103
+ if (state.availability.status === 'unreadable')
104
+ return null;
105
+ const principle = Object.hasOwn(state.ledger.tree.principles, lookup.principleId) ? state.ledger.tree.principles[lookup.principleId] : undefined;
106
+ if (!principle)
107
+ return null;
108
+ const binding = Object.hasOwn(state.ledger.tree.ruleCandidateBindings ?? {}, lookup.candidateId)
109
+ ? state.ledger.tree.ruleCandidateBindings?.[lookup.candidateId]?.revisions.at(-1) : undefined;
110
+ return {
111
+ principleId: lookup.principleId, text: principle.text, status: principle.status,
112
+ version: principle.version, revision: lookup.revision, decidedAt: binding?.decidedAt ?? '',
113
+ };
114
+ }
11
115
  /** Derived review only. Binding authority stays inside the locked core Ledger writer. */
12
116
  export class RuleCandidateBindingConsoleModel {
13
117
  workspaceDir;
@@ -32,31 +136,172 @@ export class RuleCandidateBindingConsoleModel {
32
136
  },
33
137
  });
34
138
  }
139
+ /** One bounded readonly batch read of the pain events behind the queue.
140
+ * `degraded` names an unreadable trajectory store so lookups that found
141
+ * nothing are labeled 不可读 rather than 未记录 (rc-9 — never misattribute). */
142
+ painSources(painIds) {
143
+ const result = new Map();
144
+ const distinct = [...new Set(painIds.filter(id => id !== ''))];
145
+ if (distinct.length === 0)
146
+ return { sources: result, degraded: null };
147
+ const dbPath = path.join(this.workspaceDir, '.state', 'trajectory.db');
148
+ if (!existsSync(dbPath))
149
+ return { sources: result, degraded: null };
150
+ let db = null;
151
+ try {
152
+ db = new Database(dbPath, { readonly: true });
153
+ const placeholders = distinct.map(() => '?').join(',');
154
+ const rows = db.prepare(`SELECT canonical_pain_id AS painId, source, text, created_at AS createdAt
155
+ FROM pain_events WHERE canonical_pain_id IN (${placeholders})`).all(...distinct);
156
+ // Multiple events may share a canonical id; keep the newest (same policy
157
+ // as the pain list ordering — newest first).
158
+ for (const row of rows) {
159
+ if (!record(row))
160
+ continue;
161
+ const painId = typeof row.painId === 'string' ? row.painId : '';
162
+ if (painId === '')
163
+ continue;
164
+ const existing = result.get(painId);
165
+ const createdAt = typeof row.createdAt === 'string' ? row.createdAt : null;
166
+ if (existing && existing.createdAt && createdAt && existing.createdAt >= createdAt)
167
+ continue;
168
+ result.set(painId, {
169
+ painId,
170
+ text: typeof row.text === 'string' && row.text.trim() !== '' ? row.text : null,
171
+ source: typeof row.source === 'string' ? row.source : 'unknown',
172
+ createdAt,
173
+ });
174
+ }
175
+ }
176
+ catch {
177
+ // Read failure must not masquerade as never-recorded: the degraded flag
178
+ // turns missing lookups into 记录暂不可读 (rc-9 observable degradation).
179
+ return { sources: new Map(), degraded: 'trajectory_unreadable' };
180
+ }
181
+ finally {
182
+ db?.close();
183
+ }
184
+ return { sources: result, degraded: null };
185
+ }
186
+ /** Identity stays with the ledger adapter (the identity authority); the
187
+ * principle entry / decidedAt come from the caller's single file snapshot.
188
+ * bindingRevision rides along so the source-correction preview can anchor
189
+ * its eligibility to the CURRENT binding (PRI-1048 R6 #4). */
190
+ queueBinding(stateManager, ledgerState, candidateId) {
191
+ const identity = this.ledger(stateManager).resolveCandidatePrincipleIdentity(candidateId, 'rule');
192
+ if (identity.status === 'resolved') {
193
+ const view = boundPrincipleView(ledgerState, { candidateId, principleId: identity.principleId, revision: identity.bindingRevision ?? 0 });
194
+ if (view)
195
+ return { view: { status: 'bound', principle: view }, bindingRevision: identity.bindingRevision ?? null };
196
+ // Bound in the ledger but the principle entry itself is unreadable or
197
+ // gone — surface as attention, never silently unlinked.
198
+ return { view: { status: 'attention', reason: 'bound_principle_unreadable' }, bindingRevision: identity.bindingRevision ?? null };
199
+ }
200
+ if (identity.reason === 'missing')
201
+ return { view: { status: 'unlinked' }, bindingRevision: null };
202
+ if (identity.reason === 'binding_revoked')
203
+ return { view: { status: 'binding_revoked' }, bindingRevision: null };
204
+ return { view: { status: 'attention', reason: identity.reason }, bindingRevision: null };
205
+ }
206
+ /** PRI-1048 R6 #4: read-only eligibility preview for source-correct-once.
207
+ * Only the bound+blocked shape can offer it; the click-time authority
208
+ * (reviseRuleSourceOnce) re-checks everything server-side. */
209
+ async sourceCorrectionPreview(stateManager, candidateId, bindingRevision) {
210
+ const ledger = this.ledger(stateManager);
211
+ const preview = await previewRuleSourceCorrection({
212
+ stateManager,
213
+ ledger: { resolveForCandidate: async (id) => ledger.resolveCandidatePrincipleIdentity(id, 'rule') },
214
+ candidateId,
215
+ expectedBindingRevision: bindingRevision,
216
+ });
217
+ return preview.status === 'ready'
218
+ ? {
219
+ status: 'ready', reason: null,
220
+ scribeTaskId: preview.scribeTaskId, artificerTaskId: preview.artificerTaskId,
221
+ scribeArtifactDigest: preview.scribeArtifactDigest, expectedBindingRevision: preview.expectedBindingRevision,
222
+ }
223
+ : { status: 'unavailable', reason: preview.reason, scribeTaskId: null, artificerTaskId: null, scribeArtifactDigest: null, expectedBindingRevision: null };
224
+ }
35
225
  async list() {
36
226
  // A fresh workspace with no state.db simply has no binding queue yet;
37
227
  // any other storage problem still surfaces as an error (503 at the route).
38
- if (!existsSync(path.join(this.workspaceDir, '.pd', 'state.db')))
39
- return { items: [] };
228
+ if (!existsSync(path.join(this.workspaceDir, '.pd', 'state.db'))) {
229
+ return { items: [], summary: { painGroups: 0, candidates: 0, bound: 0, unlinked: 0, blocked: 0 }, truncated: false };
230
+ }
40
231
  const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
41
232
  try {
42
- const ledger = this.ledger(handle.stateManager);
233
+ const { stateManager } = handle;
234
+ const allTasks = await stateManager.listTasks();
235
+ const taskById = new Map(allTasks.map(task => [task.taskId, task]));
43
236
  // Split diagnoses commit their rule candidates on diag_router sub-tasks;
44
- // the queue must surface both provenance shapes, not only direct
45
- // diagnostician commits.
46
- const tasks = [
47
- ...await handle.stateManager.listTasks({ taskKind: 'diagnostician' }),
48
- ...await handle.stateManager.listTasks({ taskKind: 'diag_router' }),
49
- ];
50
- const items = [];
51
- for (const task of tasks) {
52
- for (const candidate of await handle.stateManager.getCandidatesByTaskId(task.taskId)) {
237
+ // getCandidatesByTaskId already spans "own + child" tasks, so traversing
238
+ // BOTH task lists surfaces router-committed candidates twice. The queue
239
+ // aggregation owner (this model) dedupes by canonical candidateId; the
240
+ // shared store query keeps its semantics for every other consumer.
241
+ const diagnosisTasks = allTasks.filter(task => task.taskKind === 'diagnostician' || task.taskKind === 'diag_router');
242
+ const collected = new Map();
243
+ for (const task of diagnosisTasks) {
244
+ for (const candidate of await stateManager.getCandidatesByTaskId(task.taskId)) {
53
245
  if (candidate.rawRecommendationKind !== 'rule')
54
246
  continue;
55
- const identity = ledger.resolveCandidatePrincipleIdentity(candidate.candidateId, 'rule');
56
- items.push({ candidateId: candidate.candidateId, text: candidate.description || candidate.title, reason: identity.status === 'unresolved' ? identity.reason : 'bound' });
247
+ if (collected.has(candidate.candidateId))
248
+ continue;
249
+ // Source pain: a router candidate resolves through its parent
250
+ // diagnostician; a direct candidate reads its own inputRef.
251
+ const candidateTask = taskById.get(candidate.taskId) ?? task;
252
+ const parentTask = candidateTask.taskKind === 'diag_router' && candidateTask.inputRef
253
+ ? taskById.get(candidateTask.inputRef) : undefined;
254
+ const diagnosisTaskId = parentTask?.taskId ?? candidateTask.taskId;
255
+ const painId = (parentTask?.inputRef ?? candidateTask.inputRef) ?? '';
256
+ collected.set(candidate.candidateId, {
257
+ candidateId: candidate.candidateId,
258
+ text: candidate.description || candidate.title,
259
+ painId, diagnosisTaskId,
260
+ });
57
261
  }
58
262
  }
59
- return { items };
263
+ const truncated = collected.size > MAX_QUEUE_ITEMS;
264
+ const entries = [...collected.values()].slice(0, MAX_QUEUE_ITEMS);
265
+ const painLookup = this.painSources(entries.map(entry => entry.painId));
266
+ const chainContext = buildChainContext(allTasks);
267
+ const ledgerState = readLedgerFileState(path.join(this.workspaceDir, '.state'));
268
+ const items = [];
269
+ for (const entry of entries) {
270
+ const diagnosisTask = chainContext.byId.get(entry.diagnosisTaskId) ?? null;
271
+ const { view: binding, bindingRevision } = this.queueBinding(stateManager, ledgerState, entry.candidateId);
272
+ const generation = binding.status === 'unlinked' ? null : deriveGeneration(entry.candidateId, chainContext);
273
+ const resume = binding.status === 'bound' ? await deriveResume(stateManager, entry.candidateId) : null;
274
+ // PRI-1048 R6 #4: the source-correction entry applies to the bound+blocked
275
+ // shape only (the binding revision budget is typically already spent there).
276
+ const sourceCorrection = binding.status === 'bound' && generation?.stage === 'blocked' && bindingRevision !== null
277
+ ? await this.sourceCorrectionPreview(stateManager, entry.candidateId, bindingRevision)
278
+ : null;
279
+ items.push({
280
+ candidateId: entry.candidateId,
281
+ text: entry.text,
282
+ pain: painLookup.sources.get(entry.painId) ?? {
283
+ painId: entry.painId, text: null,
284
+ source: painLookup.degraded === 'trajectory_unreadable' && entry.painId !== '' ? 'unreadable' : 'unknown',
285
+ createdAt: null,
286
+ },
287
+ diagnosisStatus: diagnosisTask?.status ?? 'unknown',
288
+ binding,
289
+ generation,
290
+ resume,
291
+ sourceCorrection,
292
+ });
293
+ }
294
+ return {
295
+ items,
296
+ summary: {
297
+ painGroups: new Set(items.map(item => item.pain.painId === '' ? `candidate:${item.candidateId}` : item.pain.painId)).size,
298
+ candidates: items.length,
299
+ bound: items.filter(item => item.binding.status === 'bound').length,
300
+ unlinked: items.filter(item => item.binding.status === 'unlinked').length,
301
+ blocked: items.filter(item => item.generation?.stage === 'blocked').length,
302
+ },
303
+ truncated,
304
+ };
60
305
  }
61
306
  finally {
62
307
  await handle.close();
@@ -65,20 +310,44 @@ export class RuleCandidateBindingConsoleModel {
65
310
  async review(candidateId, search) {
66
311
  const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
67
312
  try {
68
- const result = await this.ledger(handle.stateManager).createRuleCandidateBindingReview(candidateId, { search, limit: 20 });
313
+ const { stateManager } = handle;
314
+ const result = await this.ledger(stateManager).createRuleCandidateBindingReview(candidateId, { search, limit: 20 });
69
315
  if (!result.ok)
70
316
  return result;
71
317
  const { candidateMaterial: material } = result;
318
+ const parentTaskInputRef = stringField(Reflect.get(material, 'parentTask'), 'inputRef');
319
+ const painId = parentTaskInputRef || stringField(material.task, 'inputRef');
320
+ const painLookup = this.painSources([painId]);
321
+ const pain = painLookup.sources.get(painId) ?? {
322
+ painId, text: null,
323
+ source: painLookup.degraded === 'trajectory_unreadable' && painId !== '' ? 'unreadable' : 'unknown',
324
+ createdAt: null,
325
+ };
326
+ const { currentBinding } = result;
327
+ const isBound = currentBinding?.action === 'bind';
328
+ const boundPrinciple = isBound && currentBinding
329
+ ? boundPrincipleView(readLedgerFileState(path.join(this.workspaceDir, '.state')), { candidateId, principleId: currentBinding.principleId, revision: currentBinding.revision })
330
+ : null;
331
+ const chainContext = buildChainContext(await stateManager.listTasks());
332
+ const generation = isBound ? deriveGeneration(candidateId, chainContext) : null;
333
+ const resume = isBound ? await deriveResume(stateManager, candidateId) : null;
334
+ const sourceCorrection = isBound && generation?.stage === 'blocked' && currentBinding
335
+ ? await this.sourceCorrectionPreview(stateManager, candidateId, currentBinding.revision)
336
+ : null;
72
337
  return { ok: true, review: {
73
338
  candidateId, candidateText: stringField(material.candidate, 'description') || stringField(material.candidate, 'title'),
74
- sourcePainId: stringField(Reflect.get(material, 'parentTask'), 'inputRef') || stringField(material.task, 'inputRef'),
339
+ sourcePain: pain,
75
340
  evidence: stringField(material.artifact, 'contentJson') || stringField(material.candidate, 'sourceRecommendationJson'),
76
341
  candidateDigest: result.reviewedCandidateDigest,
77
342
  currentRevision: result.currentRevision,
78
- currentBinding: result.currentBinding,
343
+ currentBinding,
344
+ boundPrinciple,
79
345
  eligibleTargets: result.eligibleTargets.map(({ principle, reviewedPrincipleDigest }) => ({
80
346
  principleId: principle.id, text: principle.text, status: principle.status, version: principle.version, digest: reviewedPrincipleDigest,
81
347
  })),
348
+ generation,
349
+ resume,
350
+ sourceCorrection,
82
351
  resumeNotice: '关联后自动启动一次现有配置的正文修订与代码生成、验证;沿用现有调用预算和超时。失败会保留关联与旧工件,不会自动循环,也不会批准或启用规则。',
83
352
  } };
84
353
  }
@@ -127,4 +396,25 @@ export class RuleCandidateBindingConsoleModel {
127
396
  await handle.close();
128
397
  }
129
398
  }
399
+ /**
400
+ * SPEC-PRI-1047 修复 B: 认证入口的一次 Owner source-correction。区别于
401
+ * revise(binding 修订预算):这是「修正上游材料」的独立一次预算,回执
402
+ * 绑定真实 Owner actor/request。调用侧(路由)负责 ownerIdentity 门。
403
+ */
404
+ async correctSource(candidateId, input, actorId) {
405
+ const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir });
406
+ try {
407
+ const ledger = this.ledger(handle.stateManager);
408
+ return await reviseRuleSourceOnce({
409
+ stateManager: handle.stateManager,
410
+ ledger: { resolveForCandidate: async (id) => ledger.resolveCandidatePrincipleIdentity(id, 'rule') },
411
+ candidateId,
412
+ ...input,
413
+ actor: actorId,
414
+ });
415
+ }
416
+ finally {
417
+ await handle.close();
418
+ }
419
+ }
130
420
  }
@@ -43,7 +43,7 @@ export async function handleRuleCandidateBindingsRoute(req, res, ctx) {
43
43
  return;
44
44
  }
45
45
  const model = new RuleCandidateBindingConsoleModel(ctx.workspaceDir);
46
- const match = /^\/([^/]+)(?:\/(review|revise-once))?$/.exec(ctx.subPath);
46
+ const match = /^\/([^/]+)(?:\/(review|revise-once|source-correct-once))?$/.exec(ctx.subPath);
47
47
  const url = new URL(req.url ?? '/', 'http://localhost');
48
48
  try {
49
49
  if ((ctx.subPath === '' || ctx.subPath === '/') && req.method === 'GET') {
@@ -84,6 +84,31 @@ export async function handleRuleCandidateBindingsRoute(req, res, ctx) {
84
84
  sendBadRequest(res, '请求必须是有效的 JSON。');
85
85
  return;
86
86
  }
87
+ if (match[2] === 'source-correct-once') {
88
+ // SPEC-PRI-1047: one bounded Owner source-correction per candidate. Closed
89
+ // field set: chain identity + digest precondition + reason/requestId —
90
+ // never identity-fabrication or approval fields.
91
+ if (!closed(raw, ['expectedBindingRevision', 'expectedScribeTaskId', 'expectedArtificerTaskId', 'expectedScribeArtifactDigest', 'reason', 'requestId'])
92
+ || !revision(raw.expectedBindingRevision) || raw.expectedBindingRevision < 1
93
+ || !text(raw.expectedScribeTaskId, 512) || !text(raw.expectedArtificerTaskId, 512)
94
+ || !digest(raw.expectedScribeArtifactDigest) || !text(raw.reason, 2000) || !text(raw.requestId, 200)) {
95
+ sendBadRequest(res, '请提交当前关联、原候选链任务与工件摘要;不接受身份或批准字段。');
96
+ return;
97
+ }
98
+ const result = await model.correctSource(candidateId, {
99
+ expectedBindingRevision: raw.expectedBindingRevision,
100
+ expectedScribeTaskId: raw.expectedScribeTaskId,
101
+ expectedArtificerTaskId: raw.expectedArtificerTaskId,
102
+ expectedScribeArtifactDigest: raw.expectedScribeArtifactDigest,
103
+ reason: raw.reason,
104
+ requestId: raw.requestId,
105
+ }, ctx.ownerIdentity.ownerId);
106
+ if (result.status === 'refused')
107
+ failure(res, result);
108
+ else
109
+ sendSuccess(res, result);
110
+ return;
111
+ }
87
112
  if (match[2] === 'revise-once') {
88
113
  if (!closed(raw, ['expectedBindingRevision', 'expectedScribeTaskId', 'expectedTaskRevisionEpoch', 'requestId'])
89
114
  || !revision(raw.expectedBindingRevision) || raw.expectedBindingRevision < 1
@@ -1,3 +1,91 @@
1
+ /** Owner-facing projection DTOs for the rule-candidate binding queue (PRI-1048).
2
+ * Derived read-only views over existing authorities (state.db tasks/candidates,
3
+ * trajectory.db pain_events, the principle ledger, the internalization chain).
4
+ * No new persisted state; the Ledger remains the only binding authority. */
5
+ /** Which owner feedback produced the candidate. Text is null when the pain
6
+ * record is gone — the UI must show 未记录, never fabricate a quote. */
7
+ export interface RuleBindingPainSourceView {
8
+ painId: string;
9
+ text: string | null;
10
+ source: string;
11
+ createdAt: string | null;
12
+ }
13
+ /** The formal principle a candidate is currently bound to (ledger authority). */
14
+ export interface RuleBindingBoundPrincipleView {
15
+ principleId: string;
16
+ text: string;
17
+ status: string;
18
+ version: number;
19
+ revision: number;
20
+ decidedAt: string;
21
+ }
22
+ /** Where the bound candidate's regeneration chain stands. `stageKind` names the
23
+ * deepest internalization task reached ('scribe', 'artificer', …). Blocked
24
+ * carries the server's reasonCode (e.g. textual_basis_unanchored) verbatim. */
25
+ export interface RuleBindingGenerationView {
26
+ stage: 'not_built' | 'in_progress' | 'blocked' | 'advanced' | 'ambiguous';
27
+ stageKind: string | null;
28
+ reasonCode: string | null;
29
+ detail: string | null;
30
+ }
31
+ /** Real backend revision eligibility, resolved by the production resolver
32
+ * (resolveRuleBindingRevisionTarget + the same budget rule reviseRuleBindingOnce
33
+ * enforces). The UI may only offer 修订并继续 when status === 'ready'. */
34
+ export interface RuleBindingResumeView {
35
+ status: 'ready' | 'refused';
36
+ reason: string | null;
37
+ scribeTaskId: string | null;
38
+ /** CAS input for the revise-once request (ready only). */
39
+ taskRevisionEpoch: number | null;
40
+ }
41
+ /** PRI-1048 (Owner review R6 #4): read-only eligibility preview for the
42
+ * source-correct-once capability (PRI-1047) — the bounded one-shot source
43
+ * revision that applies when the Artificer stopped for a SOURCE-material
44
+ * defect and the binding revision budget is spent. Click-time authority
45
+ * stays with reviseRuleSourceOnce on the server. */
46
+ export interface RuleBindingSourceCorrectionView {
47
+ status: 'ready' | 'unavailable';
48
+ reason: string | null;
49
+ /** CAS inputs for the source-correct-once request (ready only). */
50
+ scribeTaskId: string | null;
51
+ artificerTaskId: string | null;
52
+ scribeArtifactDigest: string | null;
53
+ expectedBindingRevision: number | null;
54
+ }
55
+ export type RuleBindingQueueBindingView = {
56
+ status: 'unlinked';
57
+ } | {
58
+ status: 'bound';
59
+ principle: RuleBindingBoundPrincipleView;
60
+ } | {
61
+ status: 'binding_revoked';
62
+ } | {
63
+ status: 'attention';
64
+ reason: string;
65
+ };
66
+ export interface RuleBindingQueueItem {
67
+ candidateId: string;
68
+ text: string;
69
+ pain: RuleBindingPainSourceView;
70
+ diagnosisStatus: string;
71
+ binding: RuleBindingQueueBindingView;
72
+ generation: RuleBindingGenerationView | null;
73
+ resume: RuleBindingResumeView | null;
74
+ /** PRI-1048 R6 #4: non-null only when bound (null = the capability does not apply). */
75
+ sourceCorrection: RuleBindingSourceCorrectionView | null;
76
+ }
77
+ export interface RuleBindingQueueSummary {
78
+ painGroups: number;
79
+ candidates: number;
80
+ bound: number;
81
+ unlinked: number;
82
+ blocked: number;
83
+ }
84
+ export interface RuleBindingQueueView {
85
+ items: RuleBindingQueueItem[];
86
+ summary: RuleBindingQueueSummary;
87
+ truncated: boolean;
88
+ }
1
89
  export interface RuleBindingTargetView {
2
90
  principleId: string;
3
91
  text: string;
@@ -8,7 +96,7 @@ export interface RuleBindingTargetView {
8
96
  export interface RuleBindingReviewView {
9
97
  candidateId: string;
10
98
  candidateText: string;
11
- sourcePainId: string;
99
+ sourcePain: RuleBindingPainSourceView;
12
100
  evidence: string;
13
101
  candidateDigest: string;
14
102
  currentRevision: number;
@@ -18,13 +106,10 @@ export interface RuleBindingReviewView {
18
106
  reason: string;
19
107
  revision: number;
20
108
  } | null;
109
+ boundPrinciple: RuleBindingBoundPrincipleView | null;
21
110
  eligibleTargets: RuleBindingTargetView[];
111
+ generation: RuleBindingGenerationView | null;
112
+ resume: RuleBindingResumeView | null;
113
+ sourceCorrection: RuleBindingSourceCorrectionView | null;
22
114
  resumeNotice: string;
23
115
  }
24
- export interface RuleBindingQueueView {
25
- items: {
26
- candidateId: string;
27
- text: string;
28
- reason: string;
29
- }[];
30
- }
@@ -1 +1,5 @@
1
+ /** Owner-facing projection DTOs for the rule-candidate binding queue (PRI-1048).
2
+ * Derived read-only views over existing authorities (state.db tasks/candidates,
3
+ * trajectory.db pain_events, the principle ledger, the internalization chain).
4
+ * No new persisted state; the Ledger remains the only binding authority. */
1
5
  export {};
@@ -50,6 +50,34 @@ export declare function mutateRuleBinding(candidateId: string, body: RuleCandida
50
50
  revisionStatus?: string;
51
51
  nextAction?: string;
52
52
  }>>;
53
+ /** PRI-1048: consume the REAL backend revision capability (revise-once route);
54
+ * eligibility was resolved server-side from the production resolver. */
55
+ export declare function reviseRuleBindingOnce(candidateId: string, body: {
56
+ expectedBindingRevision: number;
57
+ expectedScribeTaskId: string;
58
+ expectedTaskRevisionEpoch: number;
59
+ requestId: string;
60
+ }): Promise<ApiResponse<{
61
+ status: string;
62
+ reason?: string;
63
+ nextAction?: string;
64
+ }>>;
65
+ /** PRI-1048 R6 #4: the bounded one-shot SOURCE correction (PRI-1047) — the
66
+ * capability that applies when the binding revision budget is spent and the
67
+ * Artificer stopped for a source-material defect. CAS inputs come from the
68
+ * server-computed sourceCorrection preview; the server re-validates all of it. */
69
+ export declare function correctRuleSource(candidateId: string, body: {
70
+ expectedBindingRevision: number;
71
+ expectedScribeTaskId: string;
72
+ expectedArtificerTaskId: string;
73
+ expectedScribeArtifactDigest: string;
74
+ reason: string;
75
+ requestId: string;
76
+ }): Promise<ApiResponse<{
77
+ status: string;
78
+ reason?: string;
79
+ nextAction?: string;
80
+ }>>;
53
81
  declare function fetchWorkspaces(): Promise<ApiResponse<WorkspaceEntryData[]>>;
54
82
  declare function addWorkspace(name: string, path: string): Promise<ApiResponse<WorkspaceEntryData>>;
55
83
  declare function removeWorkspace(name: string): Promise<ApiResponse<RemovedEnvelopeData>>;
@@ -1,5 +1,5 @@
1
1
  import { clearStoredToken, loadStoredToken, storeToken } from "./utils/token-storage.js";
2
- import { validateBindingReview, validateBindingQueue, validateBindingMutation } from './utils/rule-binding-validators.js';
2
+ import { validateBindingReview, validateBindingQueue, validateBindingMutation, validateBindingRevise } from './utils/rule-binding-validators.js';
3
3
  import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateRecoveryResult, validateFailedTaskDetail, validateOwnerDecisionsData, validateOwnerResolutionResult, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateUpdateRecovery, validateUpdateTransaction, validateApprovalRecordDirect, validatePrinciplesList, validateCorePrinciples, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, validateOwnerDecisionViewCore, validateOwnerDecisionInbox, validateOwnerDecisionLibrary, validateGovernanceExperienceSnapshot, validateRuleCodeOwnerReview, validateRuleCodeMutation, validateOwnerIdentityView, validateOwnerIdentityRegister, validateOwnerIdentityUnregister, } from "./utils/validators.js";
4
4
  // ── Auth ──────────────────────────────────────────────────────────────────────
5
5
  function getToken() {
@@ -136,6 +136,18 @@ export function fetchRuleBindingReview(candidateId, search = '') {
136
136
  export function mutateRuleBinding(candidateId, body) {
137
137
  return request(`/api/v1/governance/rule-candidate-bindings/${encodeURIComponent(candidateId)}`, { method: 'POST', body: JSON.stringify(body) }, validateBindingMutation);
138
138
  }
139
+ /** PRI-1048: consume the REAL backend revision capability (revise-once route);
140
+ * eligibility was resolved server-side from the production resolver. */
141
+ export function reviseRuleBindingOnce(candidateId, body) {
142
+ return request(`/api/v1/governance/rule-candidate-bindings/${encodeURIComponent(candidateId)}/revise-once`, { method: 'POST', body: JSON.stringify(body) }, validateBindingRevise);
143
+ }
144
+ /** PRI-1048 R6 #4: the bounded one-shot SOURCE correction (PRI-1047) — the
145
+ * capability that applies when the binding revision budget is spent and the
146
+ * Artificer stopped for a source-material defect. CAS inputs come from the
147
+ * server-computed sourceCorrection preview; the server re-validates all of it. */
148
+ export function correctRuleSource(candidateId, body) {
149
+ return request(`/api/v1/governance/rule-candidate-bindings/${encodeURIComponent(candidateId)}/source-correct-once`, { method: 'POST', body: JSON.stringify(body) }, validateBindingRevise);
150
+ }
139
151
  async function fetchWorkspaces() {
140
152
  return request("/api/workspaces", undefined, validateWorkspaceList);
141
153
  }