create-principles-disciple 1.143.0 → 1.143.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/console/dist/server/index.js +19 -0
  2. package/console/dist/server/models/OwnerDecisionViewModel.d.ts +65 -0
  3. package/console/dist/server/models/OwnerDecisionViewModel.js +686 -0
  4. package/console/dist/server/routes/owner-decision.d.ts +40 -0
  5. package/console/dist/server/routes/owner-decision.js +88 -0
  6. package/console/dist/server/routes/principles.d.ts +3 -0
  7. package/console/dist/server/routes/principles.js +3 -0
  8. package/console/dist/ui/api.d.ts +7 -3
  9. package/console/dist/ui/api.js +13 -2
  10. package/console/dist/ui/i18n/en.json +70 -4
  11. package/console/dist/ui/i18n/zh-CN.json +70 -4
  12. package/console/dist/ui/pages/focus/FocusPage.js +25 -2
  13. package/console/dist/ui/pages/principles/PrincipleDetailPage.d.ts +24 -0
  14. package/console/dist/ui/pages/principles/PrincipleDetailPage.js +168 -210
  15. package/console/dist/ui/pages/principles/PrinciplesPage.js +25 -10
  16. package/console/dist/ui/utils/enum-labels.d.ts +1 -1
  17. package/console/dist/ui/utils/enum-labels.js +8 -0
  18. package/console/dist/ui/utils/validators.d.ts +99 -1
  19. package/console/dist/ui/utils/validators.js +115 -0
  20. package/console/dist/web/assets/app.css +3 -0
  21. package/console/dist/web/assets/app.js +590 -349
  22. package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js +25 -0
  23. package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js.map +1 -1
  24. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +16 -0
  25. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
  26. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +22 -0
  27. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
  28. package/core/dist/runtime-v2/index.d.ts +7 -0
  29. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  30. package/core/dist/runtime-v2/index.js +4 -0
  31. package/core/dist/runtime-v2/index.js.map +1 -1
  32. package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.d.ts +2 -0
  33. package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.d.ts.map +1 -0
  34. package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.js +559 -0
  35. package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.js.map +1 -0
  36. package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.d.ts +7868 -0
  37. package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.d.ts.map +1 -0
  38. package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.js +367 -0
  39. package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.js.map +1 -0
  40. package/core/dist/runtime-v2/owner-decision/owner-decision-view.d.ts +415 -0
  41. package/core/dist/runtime-v2/owner-decision/owner-decision-view.d.ts.map +1 -0
  42. package/core/dist/runtime-v2/owner-decision/owner-decision-view.js +856 -0
  43. package/core/dist/runtime-v2/owner-decision/owner-decision-view.js.map +1 -0
  44. package/core/dist/runtime-v2/owner-decision/semantic-selector.d.ts +72 -0
  45. package/core/dist/runtime-v2/owner-decision/semantic-selector.d.ts.map +1 -0
  46. package/core/dist/runtime-v2/owner-decision/semantic-selector.js +176 -0
  47. package/core/dist/runtime-v2/owner-decision/semantic-selector.js.map +1 -0
  48. package/package.json +1 -1
  49. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts +14 -0
  50. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  51. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js +47 -7
  52. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  53. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +84 -0
  54. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  55. package/pd-cli/dist/services/rulehost-pipeline-runner.js +215 -38
  56. package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
  57. package/plugin/dist/bundle.js +526 -526
  58. package/plugin/dist/governance-audit.js +205 -205
  59. package/plugin/dist/rulehost-evidence.js +246 -246
  60. package/release-manager/package.json +1 -1
@@ -0,0 +1,856 @@
1
+ /**
2
+ * deriveOwnerDecisionView — Owner Decision Experience v1 (SPEC §§7–8, §13).
3
+ *
4
+ * Pure derivation from collected facts to the canonical Owner-facing
5
+ * OwnerDecisionViewCore. The Console collector gathers facts (ledger,
6
+ * GovernanceFacts, candidate/diagnosis/semantic sources, receipts coverage,
7
+ * behavior applications); this module is the SINGLE Owner-facing derivation
8
+ * boundary — the Console renders mechanically and mutation services remain
9
+ * the final write authority.
10
+ *
11
+ * Non-negotiables encoded here:
12
+ * - F1: candidate ≠ waiting-for-owner-approval. Only concrete pending
13
+ * approval subjects (artifact+channel) create decision actions.
14
+ * - §8.1: unknown target/readable-subject/consequence blocks ordinary
15
+ * approve via the Decision Material Gate; reject is assessed separately.
16
+ * - §9.2: a readable diagnosis does NOT establish governance identity —
17
+ * lineage-unavailable principles are `blocked`, never `no_action`.
18
+ * - §13: old runtime frontier items never become a generic "do not
19
+ * approve"; they surface as relevance-unconfirmed historical issues.
20
+ * - §12: evidence dimensions stay separate (source availability ≠ record
21
+ * count ≠ lineage completeness ≠ promotion sufficiency).
22
+ */
23
+ import { Value } from '@sinclair/typebox/value';
24
+ import { Type } from '@sinclair/typebox';
25
+ import { GovernanceFactsSchema } from '../governance-projection-contract.js';
26
+ import { deriveOwnerGovernanceView } from '../governance-projection.js';
27
+ import { selectLearnedPrincipleV1, isWholeFieldHumanReadable } from './semantic-selector.js';
28
+ import { OwnerDecisionViewCoreSchema } from './owner-decision-view-contract.js';
29
+ const NonEmptyString = Type.String({ minLength: 1 });
30
+ const Timestamp = GovernanceFactsSchema.properties.asOf;
31
+ // ── Input contract (gathered by the Console collector) ──────────────────────
32
+ export const OwnerDecisionCandidateSchema = Type.Object({
33
+ candidateId: NonEmptyString,
34
+ status: Type.String(),
35
+ recommendationKind: Type.String(),
36
+ description: Type.String(),
37
+ abstractedPrinciple: Type.Optional(Type.String()),
38
+ trigger: Type.Optional(Type.String()),
39
+ action: Type.Optional(Type.String()),
40
+ title: Type.Optional(Type.String()),
41
+ confidence: Type.Optional(Type.Number()),
42
+ sourceRef: Type.Optional(NonEmptyString),
43
+ });
44
+ export const OwnerDecisionDiagnosisSchema = Type.Object({
45
+ artifactId: NonEmptyString,
46
+ summary: Type.Optional(Type.String()),
47
+ rootCause: Type.Optional(Type.String()),
48
+ evidenceItems: Type.Array(Type.String()),
49
+ diagnosisId: Type.Optional(NonEmptyString),
50
+ sourceRunId: Type.Optional(NonEmptyString),
51
+ truncated: Type.Boolean(),
52
+ });
53
+ export const OwnerDecisionScribeSchema = Type.Object({
54
+ artifactId: NonEmptyString,
55
+ statement: Type.Optional(Type.String()),
56
+ rationale: Type.Optional(Type.String()),
57
+ applicability: Type.Array(Type.String()),
58
+ antiPatterns: Type.Array(Type.String()),
59
+ risks: Type.Array(Type.String()),
60
+ targetBehavior: Type.Optional(Type.String()),
61
+ updatedAt: Type.String(),
62
+ });
63
+ export const OwnerDecisionPhilosopherSchema = Type.Object({
64
+ artifactId: NonEmptyString,
65
+ title: Type.Optional(Type.String()),
66
+ rationale: Type.Optional(Type.String()),
67
+ scope: Type.Optional(Type.String()),
68
+ });
69
+ export const OwnerDecisionApplicationsSchema = Type.Object({
70
+ sourceStatus: Type.Union([Type.Literal('available'), Type.Literal('disabled'), Type.Literal('unavailable')]),
71
+ deterministicCount: Type.Integer({ minimum: 0 }),
72
+ selfReportedCount: Type.Integer({ minimum: 0 }),
73
+ contextPresenceCount: Type.Integer({ minimum: 0 }),
74
+ windowDays: Type.Integer({ minimum: 0 }),
75
+ recent: Type.Array(Type.Object({
76
+ text: Type.String(),
77
+ createdAt: Type.Optional(Type.String()),
78
+ level: Type.String(),
79
+ })),
80
+ });
81
+ export const OwnerDecisionInputsSchema = Type.Object({
82
+ schemaVersion: Type.Literal('1'),
83
+ principleId: NonEmptyString,
84
+ asOf: Timestamp,
85
+ ledger: Type.Object({
86
+ status: NonEmptyString,
87
+ text: Type.String(),
88
+ evaluability: Type.String(),
89
+ triggerPattern: Type.String(),
90
+ action: Type.String(),
91
+ derivedFromPainIds: Type.Array(NonEmptyString),
92
+ ruleIds: Type.Array(NonEmptyString),
93
+ createdAt: Type.String(),
94
+ updatedAt: Type.String(),
95
+ }),
96
+ governance: GovernanceFactsSchema,
97
+ candidate: Type.Union([OwnerDecisionCandidateSchema, Type.Null()]),
98
+ diagnosis: Type.Union([OwnerDecisionDiagnosisSchema, Type.Null()]),
99
+ scribe: Type.Union([OwnerDecisionScribeSchema, Type.Null()]),
100
+ philosopher: Type.Union([OwnerDecisionPhilosopherSchema, Type.Null()]),
101
+ applications: Type.Union([OwnerDecisionApplicationsSchema, Type.Null()]),
102
+ coverageSourceStatus: Type.Union([
103
+ Type.Literal('available'), Type.Literal('disabled'), Type.Literal('unavailable'),
104
+ ]),
105
+ /** Lineage-constrained, pre-ordered semantic sources for this principle. */
106
+ semanticSources: Type.Array(Type.Object({
107
+ tier: Type.Union([
108
+ Type.Literal('scribe'), Type.Literal('distiller'),
109
+ Type.Literal('philosopher'), Type.Literal('candidate_principle'),
110
+ ]),
111
+ text: Type.String(),
112
+ sourceVersion: NonEmptyString,
113
+ })),
114
+ /**
115
+ * True when a diagnostic recommendation exists but was withheld from
116
+ * semanticSources by the kind policy (rule/implementation/prompt originals
117
+ * never surface as human principles, §10.3). Drives the honest UNKNOWN
118
+ * reason ("technical recommendation exists, readable principle does not")
119
+ * instead of a misleading "no sources".
120
+ */
121
+ technicalRecommendationAvailable: Type.Boolean(),
122
+ /**
123
+ * Fix 1 (review P1): every bound scribe artifact of THIS principle that
124
+ * carries a statement — per-artifact decision material. Subject-level
125
+ * material resolves against this list by the subject's own artifactId;
126
+ * an unrelated revision's pending subject never borrows another
127
+ * revision's text (SPEC §10.2 per-branch material rule).
128
+ */
129
+ scribeMaterials: Type.Array(Type.Object({
130
+ artifactId: NonEmptyString,
131
+ statement: NonEmptyString,
132
+ rationale: Type.Optional(Type.String()),
133
+ targetBehavior: Type.Optional(Type.String()),
134
+ sourceVersion: NonEmptyString,
135
+ })),
136
+ /** True iff ≥1 pi_artifacts row carries source_principle_id = this principle. */
137
+ piRootBound: Type.Boolean(),
138
+ sourceReads: Type.Array(Type.Object({
139
+ source: NonEmptyString,
140
+ status: Type.Union([
141
+ Type.Literal('available'), Type.Literal('unavailable'), Type.Literal('not_requested'),
142
+ ]),
143
+ capturedAt: Timestamp,
144
+ scope: NonEmptyString,
145
+ reason: Type.Optional(NonEmptyString),
146
+ })),
147
+ });
148
+ // ── small builders ──────────────────────────────────────────────────────────
149
+ function ref(input) {
150
+ return {
151
+ kind: input.kind, id: input.id, fieldPath: input.fieldPath,
152
+ relation: input.relation, claimClass: input.claimClass, producer: input.producer,
153
+ capturedAt: input.capturedAt,
154
+ ...(input.recordedAt === undefined ? {} : { recordedAt: input.recordedAt }),
155
+ truncated: input.truncated ?? false,
156
+ };
157
+ }
158
+ function reason(code, ownerText, sourceRefs = []) {
159
+ return { code, ownerText, sourceRefs };
160
+ }
161
+ function knownNarrative(items, provenance, warnings = []) {
162
+ return { status: 'known', value: items, provenance, warnings };
163
+ }
164
+ function unknownField(code, ownerText, provenance = []) {
165
+ return { status: 'unknown', reason: reason(code, ownerText, provenance), provenance };
166
+ }
167
+ function knownString(value, provenance) {
168
+ return { status: 'known', value, provenance, warnings: [] };
169
+ }
170
+ function knownNumber(value, provenance) {
171
+ return { status: 'known', value, provenance, warnings: [] };
172
+ }
173
+ function unknownNumber(code, ownerText, provenance = []) {
174
+ return { status: 'unknown', reason: reason(code, ownerText, provenance), provenance };
175
+ }
176
+ function narrativeItem(text, claimClass, sourceRefs) {
177
+ return { text, claimClass, sourceRefs };
178
+ }
179
+ // ── enforcement folding (mirrors governance-projection foldActivations) ─────
180
+ function foldGovernanceActivations(facts) {
181
+ const groups = new Map();
182
+ for (const row of facts.activations.filter((item) => item.lineageConfidence === 'strong')) {
183
+ const key = `${row.artifactId}:${row.channel}`;
184
+ groups.set(key, [...(groups.get(key) ?? []), row]);
185
+ }
186
+ return [...groups.values()].map((group) => [...group]
187
+ .sort((a, b) => a.activatedAt.localeCompare(b.activatedAt) || a.activationId.localeCompare(b.activationId))
188
+ .at(-1))
189
+ .filter((row) => row !== undefined);
190
+ }
191
+ function foldApprovalSubjects(facts) {
192
+ const groups = new Map();
193
+ for (const approval of facts.approvals) {
194
+ if (approval.lineageConfidence !== 'strong')
195
+ continue;
196
+ const key = `${approval.artifactId}:${approval.channel}`;
197
+ groups.set(key, [...(groups.get(key) ?? []), approval]);
198
+ }
199
+ const subjects = [];
200
+ for (const group of groups.values()) {
201
+ const sorted = [...group].sort((a, b) => a.recordedAt.localeCompare(b.recordedAt) || a.approvalId.localeCompare(b.approvalId));
202
+ const latest = sorted.at(-1);
203
+ if (latest === undefined)
204
+ continue;
205
+ subjects.push({
206
+ artifactId: latest.artifactId,
207
+ channel: latest.channel,
208
+ approvalId: latest.approvalId,
209
+ outcome: latest.outcome,
210
+ recordedAt: latest.recordedAt,
211
+ sourceRefId: latest.sourceRef.id,
212
+ });
213
+ }
214
+ return subjects;
215
+ }
216
+ // ── main derivation ─────────────────────────────────────────────────────────
217
+ export function deriveOwnerDecisionView(rawInput) {
218
+ if (!Value.Check(OwnerDecisionInputsSchema, rawInput)) {
219
+ throw new Error('invalid_owner_decision_inputs');
220
+ }
221
+ const input = rawInput;
222
+ const { principleId, asOf, governance } = input;
223
+ const capturedAt = asOf;
224
+ // The existing projection remains the attention/automation authority — this
225
+ // derivation consumes it (one algorithm, no second truth for recovery items).
226
+ const ownerGovernanceView = deriveOwnerGovernanceView(governance);
227
+ const ledgerRef = ref({
228
+ kind: 'ledger', id: principleId, fieldPath: '_tree.principles.<id>',
229
+ relation: 'exact_id', claimClass: 'system_state',
230
+ producer: { status: 'known', value: 'candidate-intake-ledger' }, capturedAt,
231
+ recordedAt: input.ledger.updatedAt,
232
+ });
233
+ const lineageAvailable = governance.lineage.confidence !== 'unknown';
234
+ const lineageNotAvailableIssue = governance.collectionIssues.find((issue) => issue.reasonCode === 'lineage_not_available');
235
+ const sourceReadStatus = input.sourceReads.some((read) => read.status === 'unavailable')
236
+ ? 'partial'
237
+ : 'complete';
238
+ // ── incident_summary (§7.5: diagnosis summary is PD's interpretation) ────
239
+ let incidentSummary;
240
+ if (input.diagnosis !== null && input.diagnosis.summary !== undefined && input.diagnosis.summary.trim() !== '') {
241
+ const diagRef = ref({
242
+ kind: 'generic_artifact', id: input.diagnosis.artifactId, fieldPath: 'contentJson.summary',
243
+ relation: input.candidate?.sourceRef === input.diagnosis.artifactId ? 'exact_id' : 'derived_relation',
244
+ claimClass: 'pd_interpretation',
245
+ producer: { status: 'known', value: 'diagnostician' }, capturedAt,
246
+ });
247
+ incidentSummary = knownNarrative([narrativeItem(`【PD 的事件概述】${input.diagnosis.summary.trim()}`, 'pd_interpretation', [diagRef])], [diagRef], input.diagnosis.truncated ? [reason('diagnosis_truncated', '诊断原文包含截断标记,事件经过未完整核验。', [diagRef])] : []);
248
+ }
249
+ else {
250
+ incidentSummary = unknownField('incident_summary_unavailable', input.diagnosis === null
251
+ ? '暂无法读取这条原则对应的事件经过(诊断来源缺失或不可读)。'
252
+ : '诊断来源存在,但没有可用的事件摘要。', [ledgerRef]);
253
+ }
254
+ // ── learned_principle (Semantic Selector v0) ─────────────────────────────
255
+ // Semantic Selector v1 (Phase D): whole-field first, bounded verbatim
256
+ // sentence extraction as the only fallback (SPEC §10.3 — no rewriting).
257
+ const selection = selectLearnedPrincipleV1(input.semanticSources);
258
+ const learnedPrinciple = selection.status === 'known'
259
+ ? {
260
+ status: 'known',
261
+ value: {
262
+ text: selection.text,
263
+ sourceTier: selection.sourceTier,
264
+ selectionMode: selection.selectionMode,
265
+ selectionReason: selection.selectionReason,
266
+ sourceVersion: selection.sourceVersion,
267
+ },
268
+ provenance: [ref({
269
+ kind: selection.sourceTier === 'scribe' || selection.sourceTier === 'philosopher' ? 'pi_artifact' : 'candidate',
270
+ id: selection.sourceVersion, fieldPath: selection.sourceTier === 'scribe' ? 'principleDraft.statement' : 'abstracted_principle',
271
+ relation: 'exact_id', claimClass: 'proposed_behavior',
272
+ producer: { status: 'known', value: selection.sourceTier }, capturedAt,
273
+ })],
274
+ warnings: [],
275
+ }
276
+ : {
277
+ status: 'unknown',
278
+ reason: reason(selection.reasonCode === 'no_sources' && input.technicalRecommendationAvailable
279
+ ? 'only_technical_recommendation_available'
280
+ : selection.reasonCode, selection.reasonCode === 'no_sources' && input.technicalRecommendationAvailable
281
+ ? '这项提案已有技术建议,但可读的行为准则暂缺。'
282
+ : selection.reasonText, [ledgerRef]),
283
+ provenance: [ledgerRef],
284
+ };
285
+ // ── rationale (scribe rationale → diagnosis rootCause, both if present) ──
286
+ const rationaleItems = [];
287
+ const rationaleRefs = [];
288
+ if (input.scribe?.rationale !== undefined && input.scribe.rationale.trim() !== '') {
289
+ const scribeRef = ref({
290
+ kind: 'pi_artifact', id: input.scribe.artifactId, fieldPath: 'principleDraft.rationale',
291
+ relation: 'exact_id', claimClass: 'pd_interpretation',
292
+ producer: { status: 'known', value: 'scribe' }, capturedAt,
293
+ recordedAt: input.scribe.updatedAt,
294
+ });
295
+ rationaleItems.push(narrativeItem(`【正式草案理由】${input.scribe.rationale.trim()}`, 'pd_interpretation', [scribeRef]));
296
+ rationaleRefs.push(scribeRef);
297
+ }
298
+ if (input.diagnosis?.rootCause !== undefined && input.diagnosis.rootCause.trim() !== '') {
299
+ const diagRef = ref({
300
+ kind: 'generic_artifact', id: input.diagnosis.artifactId, fieldPath: 'contentJson.rootCause',
301
+ relation: 'derived_relation', claimClass: 'pd_interpretation',
302
+ producer: { status: 'known', value: 'diagnostician' }, capturedAt,
303
+ });
304
+ rationaleItems.push(narrativeItem(`【诊断归因】${input.diagnosis.rootCause.trim()}`, 'pd_interpretation', [diagRef]));
305
+ rationaleRefs.push(diagRef);
306
+ }
307
+ const rationale = rationaleItems.length > 0
308
+ ? knownNarrative(rationaleItems, rationaleRefs)
309
+ : unknownField('rationale_unavailable', '暂无法说明 PD 形成这条判断的理由。', [ledgerRef]);
310
+ // ── applicability / non-applicability ────────────────────────────────────
311
+ const applicabilityQualified = (input.scribe?.applicability ?? [])
312
+ .filter((item) => isWholeFieldHumanReadable(item));
313
+ const scribeRefFor = (fieldPath) => ref({
314
+ kind: 'pi_artifact', id: input.scribe?.artifactId ?? 'unknown', fieldPath,
315
+ relation: input.scribe === null ? 'unknown' : 'exact_id', claimClass: 'pd_interpretation',
316
+ producer: input.scribe === null ? { status: 'unknown', reasonCode: 'scribe_artifact_absent' } : { status: 'known', value: 'scribe' },
317
+ capturedAt,
318
+ });
319
+ const applicability = input.scribe !== null && applicabilityQualified.length > 0
320
+ ? knownNarrative(applicabilityQualified.map((item) => narrativeItem(item, 'pd_interpretation', [scribeRefFor('principleDraft.applicability')])), [scribeRefFor('principleDraft.applicability')])
321
+ : unknownField('applicability_unavailable', '尚未记录这条准则的明确适用条件(触发条件多为技术配置,保留在技术详情中)。', [ledgerRef]);
322
+ // antiPatterns are NOT exceptions (SPEC §10.3); explicit exceptions must be
323
+ // explicitly written — v0 has no reliable source, so UNKNOWN.
324
+ const nonApplicability = unknownField('non_applicability_unavailable', '尚未记录明确的例外或不适用范围。', input.scribe === null ? [ledgerRef] : [scribeRefFor('risks')]);
325
+ // ── expected_behavior (proposal ≠ current execution) ─────────────────────
326
+ const behaviorRefs = [];
327
+ const behaviorItems = [];
328
+ if (input.scribe?.targetBehavior !== undefined && input.scribe.targetBehavior.trim() !== '') {
329
+ const scribeBehaviorRef = scribeRefFor('intentContract.targetBehavior');
330
+ behaviorItems.push(narrativeItem(`【拟议行为】${input.scribe.targetBehavior.trim()}`, 'proposed_behavior', [scribeBehaviorRef]));
331
+ behaviorRefs.push(scribeBehaviorRef);
332
+ }
333
+ else if (input.ledger.action !== undefined && input.ledger.action.trim() !== '' && isWholeFieldHumanReadable(input.ledger.action)) {
334
+ const actionRef = ref({
335
+ kind: 'ledger', id: principleId, fieldPath: 'action', relation: 'exact_id',
336
+ claimClass: 'proposed_behavior', producer: { status: 'unknown', reasonCode: 'ledger_action_producer_unverified' }, capturedAt,
337
+ });
338
+ behaviorItems.push(narrativeItem(`【未部署建议】${input.ledger.action.trim()}`, 'proposed_behavior', [actionRef]));
339
+ behaviorRefs.push(actionRef);
340
+ }
341
+ const expectedBehavior = behaviorItems.length > 0
342
+ ? knownNarrative(behaviorItems, behaviorRefs)
343
+ : unknownField('expected_behavior_unavailable', '尚未记录批准后 Agent 应当如何行动的可读说明。', [ledgerRef]);
344
+ // ── current_enforcement (§7.5: unknown unless binding/query complete) ────
345
+ const foldedActivations = foldGovernanceActivations(governance);
346
+ const currentEnforcement = lineageAvailable
347
+ ? (() => {
348
+ const items = foldedActivations.map((row) => {
349
+ const enforcementRef = ref({
350
+ kind: 'activation', id: row.activationId, fieldPath: 'activations.row', relation: 'exact_id',
351
+ claimClass: 'system_state', producer: { status: 'known', value: 'activation-store' }, capturedAt,
352
+ recordedAt: row.activatedAt,
353
+ });
354
+ return {
355
+ activationRef: enforcementRef,
356
+ artifactRef: ref({
357
+ kind: 'pi_artifact', id: row.artifactId, fieldPath: 'source_principle_id', relation: 'exact_id',
358
+ claimClass: 'system_state', producer: { status: 'known', value: 'pi-artifact-store' }, capturedAt,
359
+ }),
360
+ channel: row.channel,
361
+ effectKind: row.channel === 'prompt' ? 'context_guidance' : row.channel === 'code_tool_hook' ? 'runtime_rule' : 'non_enforcing_outcome',
362
+ mode: knownString(row.channel === 'code_tool_hook' ? 'RuleCode 执行' : row.channel === 'prompt' ? '上下文提示' : '归档', [enforcementRef]),
363
+ active: row.deactivatedAt === undefined,
364
+ since: knownString(row.activatedAt, [enforcementRef]),
365
+ };
366
+ });
367
+ const activeCount = items.filter((item) => item.active).length;
368
+ const state = items.length === 0
369
+ ? 'none'
370
+ : activeCount === items.length ? 'active' : activeCount > 0 ? 'partially_active' : 'deactivated';
371
+ return { status: 'known', value: { state, items }, provenance: items.map((item) => item.activationRef), warnings: [] };
372
+ })()
373
+ : {
374
+ status: 'unknown',
375
+ reason: reason('enforcement_binding_unknown', '治理身份尚未建立,无法确认这条原则当前是否有正在执行的生效方式。', [ledgerRef]),
376
+ provenance: [ledgerRef],
377
+ warnings: [],
378
+ };
379
+ // ── evidence_summary (five separate dimensions, §12) ─────────────────────
380
+ const applicationRef = ref({
381
+ kind: 'application', id: principleId, fieldPath: 'principle_applications', relation: 'exact_id',
382
+ claimClass: 'observed_fact', producer: { status: 'known', value: 'principle-application-ledger' }, capturedAt,
383
+ });
384
+ const coverageRef = ref({
385
+ kind: 'evidence_snapshot', id: principleId, fieldPath: 'receipt-coverage', relation: 'derived_relation',
386
+ claimClass: 'system_state', producer: { status: 'known', value: 'receipt-coverage-reader' }, capturedAt,
387
+ });
388
+ const availabilityText = input.coverageSourceStatus === 'available'
389
+ ? '行为记录来源可读取。'
390
+ : input.coverageSourceStatus === 'disabled'
391
+ ? '行为记录采集当前处于关闭状态。'
392
+ : '行为记录来源当前不可读取。';
393
+ const evidenceValue = (() => {
394
+ const apps = input.applications;
395
+ const retained = apps === null ? null : apps.deterministicCount + apps.selfReportedCount + apps.contextPresenceCount;
396
+ const explanationParts = [];
397
+ if (apps !== null && retained === 0) {
398
+ explanationParts.push('行为记录来源可读取;当前保留窗口内没有行为记录。不能据此判断原则已经产生影响。');
399
+ }
400
+ else if (apps !== null && retained !== null) {
401
+ explanationParts.push(`当前保留窗口内有 ${retained} 条行为相关记录(确定性干预 ${apps.deterministicCount}、自述遵循 ${apps.selfReportedCount}、上下文出现 ${apps.contextPresenceCount})。数字代表当前保留的证据,不是完整历史。`);
402
+ }
403
+ explanationParts.push(`来源引用 ${input.ledger.derivedFromPainIds.length} 条(当前指向诊断候选记录,非行为证据行数)。`);
404
+ explanationParts.push(input.piRootBound
405
+ ? '治理关联已建立(原则工件与审批/执行链路相连)。'
406
+ : '诊断来源存在,但用于判断审批状态的治理关联尚未建立。');
407
+ return {
408
+ ownerExplanation: explanationParts.join(''),
409
+ sourceAvailability: knownString(availabilityText, [coverageRef]),
410
+ retainedBehaviorCount: apps === null || retained === null
411
+ ? unknownNumber('applications_unavailable', '行为记录数据暂不可读取。', [applicationRef])
412
+ : knownNumber(retained, [applicationRef]),
413
+ sourceReferenceCount: knownNumber(input.ledger.derivedFromPainIds.length, [ledgerRef]),
414
+ governanceLineage: knownString(input.piRootBound ? '已建立' : '未建立', [ledgerRef]),
415
+ promotionSufficiency: knownString('未评估(仅在实际启用正式执行的决策中读取)', [ledgerRef]),
416
+ observation: {
417
+ deterministicEffects: apps === null ? unknownNumber('applications_unavailable', '暂不可读。', [applicationRef]) : knownNumber(apps.deterministicCount, [applicationRef]),
418
+ selfReportedEffects: apps === null ? unknownNumber('applications_unavailable', '暂不可读。', [applicationRef]) : knownNumber(apps.selfReportedCount, [applicationRef]),
419
+ contextPresence: apps === null ? unknownNumber('applications_unavailable', '暂不可读。', [applicationRef]) : knownNumber(apps.contextPresenceCount, [applicationRef]),
420
+ window: knownString(`${input.applications?.windowDays ?? 0} 天保留窗口`, [applicationRef]),
421
+ recentExamples: (apps?.recent ?? []).slice(0, 5).map((row) => narrativeItem(row.text, 'observed_fact', [ref({
422
+ kind: 'application', id: `${principleId}:${row.createdAt ?? ''}`, fieldPath: 'recent', relation: 'exact_id',
423
+ claimClass: 'observed_fact', producer: { status: 'known', value: 'principle-application-ledger' }, capturedAt,
424
+ recordedAt: row.createdAt,
425
+ })])),
426
+ },
427
+ };
428
+ })();
429
+ const evidenceSummary = input.applications === null && input.coverageSourceStatus === 'unavailable'
430
+ ? { status: 'unknown', reason: reason('evidence_sources_unavailable', '证据相关来源当前不可读取。', [coverageRef]), provenance: [coverageRef], warnings: [] }
431
+ : { status: 'known', value: evidenceValue, provenance: [coverageRef, applicationRef, ledgerRef], warnings: [] };
432
+ // ── uncertainty ──────────────────────────────────────────────────────────
433
+ const uncertaintyItems = [];
434
+ if (lineageNotAvailableIssue !== undefined) {
435
+ uncertaintyItems.push(narrativeItem('治理身份尚未建立:这条原则与正式治理链路(审批/执行)的关联暂时无法确认。', 'system_state', [ledgerRef]));
436
+ }
437
+ if (input.diagnosis?.truncated === true) {
438
+ uncertaintyItems.push(narrativeItem('原始事件文本包含截断标记,经过未完整核验。', 'observed_fact', [ledgerRef]));
439
+ }
440
+ if (input.ledger.evaluability === 'weak_heuristic') {
441
+ uncertaintyItems.push(narrativeItem('这条准则目前主要依赖行为观察来评估(可评价性:弱启发式)。', 'system_state', [ledgerRef]));
442
+ }
443
+ const recoveryAttention = ownerGovernanceView.attention.items.filter((item) => item.kind === 'recovery');
444
+ for (const item of recoveryAttention) {
445
+ // §13: relevance to current actions is NOT confirmed — honest copy only.
446
+ uncertaintyItems.push(narrativeItem('存在历史处理问题(如失败或等待重试的任务),其与当前操作的关系尚未确认。', 'system_state', [
447
+ ref({ kind: 'task', id: item.sourceRef.id, fieldPath: 'tasks.status', relation: 'exact_id', claimClass: 'system_state', producer: { status: 'known', value: 'runtime-task-store' }, capturedAt }),
448
+ ]));
449
+ }
450
+ const uncertainty = knownNarrative(uncertaintyItems, [ledgerRef]);
451
+ // ── risk ─────────────────────────────────────────────────────────────────
452
+ const risk = input.scribe !== null
453
+ ? {
454
+ status: 'known',
455
+ value: {
456
+ items: input.scribe.risks
457
+ .filter((item) => item.trim() !== '')
458
+ .map((item) => narrativeItem(item, 'pd_interpretation', [scribeRefFor('risks')])),
459
+ completeness: 'partial',
460
+ },
461
+ provenance: [scribeRefFor('risks')],
462
+ warnings: input.scribe.risks.length === 0
463
+ ? [reason('risks_not_assessed', '尚未对这条准则做过风险评估;这不代表没有风险。', [scribeRefFor('risks')])]
464
+ : [],
465
+ }
466
+ : { status: 'unknown', reason: reason('risk_unavailable', '暂无这条准则的风险记录。这不代表没有风险。', [ledgerRef]), provenance: [ledgerRef], warnings: [] };
467
+ // ── rollback (§14: capability-proven, per activation) ────────────────────
468
+ const rollback = (() => {
469
+ if (!lineageAvailable) {
470
+ return {
471
+ status: 'unknown',
472
+ reason: reason('rollback_binding_unknown', '治理关联尚未建立,无法确认是否存在可以停止的执行对象。', [ledgerRef]),
473
+ provenance: [ledgerRef], warnings: [],
474
+ };
475
+ }
476
+ const activeRows = foldedActivations.filter((row) => row.deactivatedAt === undefined);
477
+ const deactivatedRows = foldedActivations.filter((row) => row.deactivatedAt !== undefined);
478
+ const stopFuture = activeRows.length > 0
479
+ ? knownNarrative(activeRows.map((row) => narrativeItem(`可通过「停止此执行方式」停止 ${row.channel === 'code_tool_hook' ? 'RuleCode 运行拦截' : row.channel === 'prompt' ? '上下文提示' : '归档通道'} 今后的影响(执行对象 ${row.activationId})。该操作只停止后续影响,不撤回已发生的外部效果。`, 'system_state', [ref({ kind: 'activation', id: row.activationId, fieldPath: 'deactivated_at', relation: 'exact_id', claimClass: 'system_state', producer: { status: 'known', value: 'activation-store' }, capturedAt })])), [ledgerRef])
480
+ : {
481
+ status: 'not_applicable',
482
+ reason: reason('no_active_enforcement', deactivatedRows.length > 0 ? '当前没有正在执行的生效方式(此前的执行方式已停用)。' : '当前没有可停止的执行对象(这条原则尚未进入任何执行通道)。', [ledgerRef]),
483
+ provenance: [ledgerRef],
484
+ };
485
+ const ownerText = activeRows.length > 0
486
+ ? '可以停止未来的影响;无法撤回已经发生的外部效果。'
487
+ : deactivatedRows.length > 0
488
+ ? '此前的执行方式已停用;当前没有需要停止的执行对象。'
489
+ : '这条原则当前没有生效中的执行对象,无需撤销。';
490
+ return {
491
+ status: 'known',
492
+ value: {
493
+ stopFuture,
494
+ restoreConfiguration: unknownField('restore_capability_unverified', '没有已验证的配置恢复服务;不承诺恢复到先前版本。', [ledgerRef]),
495
+ undoPastExternalEffects: unknownField('compensation_capability_unverified', '未验证可撤回已发生外部效果的能力;不提供通用承诺。', [ledgerRef]),
496
+ ownerText,
497
+ },
498
+ provenance: [ledgerRef], warnings: [],
499
+ };
500
+ })();
501
+ // ── Decision subjects + Material Gates + Actions (§7.4, §8) ─────────────
502
+ const foldedSubjects = foldApprovalSubjects(governance);
503
+ const pendingSubjects = foldedSubjects.filter((row) => row.outcome === 'pending');
504
+ function approveConsequenceText(channel) {
505
+ if (channel === 'prompt')
506
+ return '批准后,这条行为准则将进入 Agent 的上下文提示(prompt 通道)。它影响后续对话中的行为倾向,不构成运行时强制拦截。';
507
+ if (channel === 'code_tool_hook')
508
+ return '批准后,将进入 RuleCode 影子观察流程;是否正式启用运行拦截属于另一个独立决定(需要通过准备度检查)。';
509
+ return '批准后,该提案将通过归档通道处理,不会主动执行任何行为变更。';
510
+ }
511
+ const decisionSubjects = foldedSubjects.map((row) => {
512
+ const approvalRef = ref({
513
+ kind: 'approval', id: row.approvalId, fieldPath: 'approvals.status', relation: 'exact_id',
514
+ claimClass: 'system_state', producer: { status: 'known', value: 'approval-queue-store' }, capturedAt,
515
+ recordedAt: row.recordedAt,
516
+ });
517
+ const artifactRef = ref({
518
+ kind: 'pi_artifact', id: row.artifactId, fieldPath: 'artifact_id', relation: 'exact_id',
519
+ claimClass: 'system_state', producer: { status: 'known', value: 'pi-artifact-store' }, capturedAt,
520
+ });
521
+ // Fix 1 (review P1): subject-SCOPED decision material. When the selected
522
+ // material tier is scribe (revision-scoped), a subject's material comes
523
+ // ONLY from its own artifact's scribe entry — pending approval A can
524
+ // never borrow pending approval B's text (SPEC §10.2). Non-scribe tiers
525
+ // (distiller/philosopher/candidate) are candidate-wide and apply to every
526
+ // subject. Principle-level learnedPrinciple stays a Library/overview
527
+ // summary; it does not leak into another revision's approval context.
528
+ const tier = learnedPrinciple.status === 'known' ? learnedPrinciple.value.sourceTier : null;
529
+ const subjectScribe = tier === 'scribe'
530
+ ? input.scribeMaterials.find((entry) => entry.artifactId === row.artifactId) ?? null
531
+ : null;
532
+ const subjectReadableSatisfied = tier === null
533
+ ? false
534
+ : tier === 'scribe'
535
+ ? subjectScribe !== null
536
+ : true;
537
+ const subjectBlockerText = learnedPrinciple.status !== 'known'
538
+ ? '缺少可读的行为准则:目前只有技术建议,无法呈现为可理解的批准对象。'
539
+ : '该待批对象所属修订没有自己的可读行为准则材料(不能借用其他修订的文本作为批准依据)。';
540
+ const subjectLearnedMaterial = subjectReadableSatisfied
541
+ ? knownNarrative([narrativeItem(tier === 'scribe' && subjectScribe !== null ? subjectScribe.statement : learnedPrinciple.status === 'known' ? learnedPrinciple.value.text : '', 'proposed_behavior', [artifactRef])], [artifactRef])
542
+ : unknownField('subject_material_unavailable', subjectBlockerText, [artifactRef]);
543
+ const subjectRationaleMaterial = tier === 'scribe'
544
+ ? (subjectScribe?.rationale !== undefined && subjectScribe.rationale.trim() !== ''
545
+ ? knownNarrative([narrativeItem(`【正式草案理由】${subjectScribe.rationale.trim()}`, 'pd_interpretation', [artifactRef])], [artifactRef])
546
+ : unknownField('subject_rationale_unavailable', '该修订没有自己的理由说明。', [artifactRef]))
547
+ : rationale;
548
+ // §7.4 item 3: the approve consequence is CHANNEL-level ("批准以后这个
549
+ // subject 会发生什么") — derivable from the subject's real channel
550
+ // semantics, never "批准=已 live". The principle-level proposed behavior
551
+ // is a separate field (expectedBehavior) and does not gate this item.
552
+ const subjectConsequenceMaterial = knownNarrative([narrativeItem(approveConsequenceText(row.channel), 'system_state', [approvalRef, artifactRef])], [approvalRef, artifactRef]);
553
+ const approveGate = {
554
+ actionSemantic: 'approve',
555
+ status: subjectReadableSatisfied ? 'ready' : 'blocked',
556
+ requiredItems: [
557
+ {
558
+ key: 'target_identity',
559
+ status: 'satisfied',
560
+ ownerText: `将要批准的对象:审批记录 ${row.approvalId}(工件 ${row.artifactId},通道 ${row.channel})。`,
561
+ sourceRefs: [approvalRef, artifactRef],
562
+ },
563
+ {
564
+ key: 'human_readable_subject',
565
+ status: subjectReadableSatisfied ? 'satisfied' : 'missing',
566
+ ownerText: subjectReadableSatisfied
567
+ ? '已具备可读的行为准则说明。'
568
+ : subjectBlockerText,
569
+ sourceRefs: [ledgerRef],
570
+ },
571
+ {
572
+ key: 'expected_consequence',
573
+ status: 'satisfied',
574
+ ownerText: approveConsequenceText(row.channel),
575
+ sourceRefs: [approvalRef, artifactRef],
576
+ },
577
+ {
578
+ key: 'current_revision',
579
+ status: 'satisfied',
580
+ ownerText: `当前目标版本:${row.artifactId}`,
581
+ sourceRefs: [artifactRef],
582
+ },
583
+ ],
584
+ };
585
+ const rejectGate = {
586
+ actionSemantic: 'reject',
587
+ status: 'ready',
588
+ requiredItems: [
589
+ {
590
+ key: 'target_identity',
591
+ status: 'satisfied',
592
+ ownerText: `将要拒绝的对象:审批记录 ${row.approvalId}(工件 ${row.artifactId},通道 ${row.channel})。`,
593
+ sourceRefs: [approvalRef, artifactRef],
594
+ },
595
+ {
596
+ key: 'expected_consequence',
597
+ status: 'satisfied',
598
+ ownerText: '拒绝后,这个提案不会进入执行;已产生的诊断和工件记录会保留。',
599
+ sourceRefs: [approvalRef],
600
+ },
601
+ ],
602
+ };
603
+ return {
604
+ key: row.approvalId,
605
+ targetRefs: [approvalRef, artifactRef],
606
+ channel: row.channel,
607
+ revisionArtifactId: knownString(row.artifactId, [artifactRef]),
608
+ state: row.outcome,
609
+ reason: reason('approval_subject_state', row.outcome === 'pending' ? '该对象存在待处理审批。' : `该对象的最近审批结果为 ${row.outcome}。`, [approvalRef]),
610
+ materialGates: [approveGate, rejectGate],
611
+ decisionMaterial: {
612
+ learnedPrinciple: subjectLearnedMaterial,
613
+ rationale: subjectRationaleMaterial,
614
+ consequence: subjectConsequenceMaterial,
615
+ },
616
+ };
617
+ });
618
+ const availableActions = [];
619
+ for (const subject of pendingSubjects) {
620
+ const subjectFull = decisionSubjects.find((row) => row.key === subject.approvalId);
621
+ if (subjectFull === undefined)
622
+ continue;
623
+ const approveGate = subjectFull.materialGates.find((gate) => gate.actionSemantic === 'approve');
624
+ if (approveGate?.status === 'ready') {
625
+ const consequenceItems = [narrativeItem(approveConsequenceText(subject.channel), 'system_state', subjectFull.targetRefs)];
626
+ if (expectedBehavior.status === 'known') {
627
+ consequenceItems.push(...expectedBehavior.value);
628
+ }
629
+ availableActions.push({
630
+ key: `approve:${subject.approvalId}`,
631
+ semantic: 'approve',
632
+ label: '批准',
633
+ targetRefs: subjectFull.targetRefs,
634
+ serviceOperation: 'POST /api/v1/approvals/:id/approve',
635
+ channel: subject.channel,
636
+ assessedAt: capturedAt,
637
+ requirements: [{ name: 'note', ownerText: '可以附加备注(可选)。', required: false }],
638
+ expectedConsequence: knownNarrative(consequenceItems, subjectFull.targetRefs),
639
+ confirmation: { required: true, text: `确认批准该对象?${approveConsequenceText(subject.channel)}` },
640
+ sourceRefs: subjectFull.targetRefs,
641
+ });
642
+ }
643
+ // reject is assessed independently (SPEC §7.4): a safe rejection needs only
644
+ // the concrete target, not the readable-subject material approve needs.
645
+ availableActions.push({
646
+ key: `reject:${subject.approvalId}`,
647
+ semantic: 'reject',
648
+ label: '拒绝',
649
+ targetRefs: subjectFull.targetRefs,
650
+ serviceOperation: 'POST /api/v1/approvals/:id/reject',
651
+ channel: subject.channel,
652
+ assessedAt: capturedAt,
653
+ requirements: [{ name: 'reason', ownerText: '需要填写拒绝原因。', required: true }],
654
+ expectedConsequence: knownNarrative([narrativeItem('拒绝后,这个提案不会进入执行;已产生的诊断和工件记录会保留。', 'system_state', subjectFull.targetRefs)], subjectFull.targetRefs),
655
+ confirmation: { required: true, text: '确认拒绝该对象?此操作不会删除已产生的记录。' },
656
+ sourceRefs: subjectFull.targetRefs,
657
+ });
658
+ // edit_approval (SPEC §8.2): a REAL existing capability with a concrete
659
+ // pending target — kept on genuinely supported subjects, with its real
660
+ // operator-grade requirements surfaced (validated artifact id + reason).
661
+ availableActions.push({
662
+ key: `edit:${subject.approvalId}`,
663
+ semantic: 'edit_approval',
664
+ label: '替换审批工件',
665
+ targetRefs: subjectFull.targetRefs,
666
+ serviceOperation: 'POST /api/v1/approvals/:id/edit',
667
+ channel: subject.channel,
668
+ assessedAt: capturedAt,
669
+ requirements: [
670
+ { name: 'newArtifactId', ownerText: '需要提供新的已验证工件 ID(操作员级输入)。', required: true },
671
+ { name: 'reason', ownerText: '需要填写编辑原因。', required: true },
672
+ ],
673
+ expectedConsequence: knownNarrative([narrativeItem('将该审批的目标工件替换为你指定的新工件(需要已验证的工件 ID)。这是替换审批对象,不是修改原则措辞。', 'system_state', subjectFull.targetRefs)], subjectFull.targetRefs),
674
+ confirmation: { required: true, text: '确认替换该审批的目标工件?需要新的已验证工件 ID 与编辑原因。' },
675
+ sourceRefs: subjectFull.targetRefs,
676
+ });
677
+ }
678
+ for (const row of foldedActivations.filter((item) => item.deactivatedAt === undefined)) {
679
+ const activationRef = ref({
680
+ kind: 'activation', id: row.activationId, fieldPath: 'deactivated_at', relation: 'exact_id',
681
+ claimClass: 'system_state', producer: { status: 'known', value: 'activation-store' }, capturedAt,
682
+ recordedAt: row.activatedAt,
683
+ });
684
+ availableActions.push({
685
+ key: `disable:${row.activationId}`,
686
+ semantic: 'disable',
687
+ label: '停止此执行方式今后的影响',
688
+ targetRefs: [activationRef],
689
+ serviceOperation: 'POST /api/v1/activations/:id/disable',
690
+ channel: row.channel,
691
+ assessedAt: capturedAt,
692
+ requirements: [{ name: 'confirmed', ownerText: '需要明确确认(confirmed=true)。', required: true }],
693
+ expectedConsequence: knownNarrative([narrativeItem('停止后,该执行方式不再影响后续行为;已经发生的外部效果不会被撤回。', 'system_state', [activationRef])], [activationRef]),
694
+ confirmation: { required: true, text: '确认停止该执行方式今后的影响?已发生的外部效果不会被撤回。' },
695
+ sourceRefs: [activationRef],
696
+ });
697
+ }
698
+ // ── blockers ─────────────────────────────────────────────────────────────
699
+ const blockers = [];
700
+ if (lineageNotAvailableIssue !== undefined) {
701
+ blockers.push({
702
+ subjectKey: principleId,
703
+ actionSemantic: 'judgment',
704
+ kind: 'data',
705
+ reason: reason('lineage_not_available', '治理身份尚未建立,系统暂时无法判断是否需要你决定。', [ledgerRef]),
706
+ requiredAudience: 'owner',
707
+ });
708
+ }
709
+ for (const subject of pendingSubjects) {
710
+ const subjectFull = decisionSubjects.find((row) => row.key === subject.approvalId);
711
+ const approveGate = subjectFull?.materialGates.find((gate) => gate.actionSemantic === 'approve');
712
+ if (approveGate?.status === 'blocked') {
713
+ blockers.push({
714
+ subjectKey: subject.approvalId,
715
+ actionSemantic: 'approve',
716
+ kind: 'data',
717
+ reason: reason('decision_material_missing', '这个对象虽然有待处理审批,但缺少足够的信息让你安全地批准(可读的行为准则或后果说明缺失)。拒绝仍可单独评估。', subjectFull?.targetRefs ?? [ledgerRef]),
718
+ requiredAudience: 'owner',
719
+ });
720
+ }
721
+ }
722
+ for (const read of input.sourceReads) {
723
+ if (read.status === 'unavailable') {
724
+ blockers.push({
725
+ subjectKey: principleId,
726
+ actionSemantic: 'judgment',
727
+ kind: 'data',
728
+ reason: reason(`source_unavailable:${read.source}`, `数据源 ${read.source} 读取失败:${read.reason ?? '原因未知'}。`, [ledgerRef]),
729
+ requiredAudience: 'operator',
730
+ });
731
+ }
732
+ }
733
+ for (const item of recoveryAttention) {
734
+ blockers.push({
735
+ subjectKey: principleId,
736
+ actionSemantic: 'judgment',
737
+ kind: 'runtime',
738
+ reason: reason('historical_processing_issue', '存在历史处理问题,其与当前操作的关系尚未确认。', [
739
+ ref({ kind: 'task', id: item.sourceRef.id, fieldPath: 'tasks.status', relation: 'exact_id', claimClass: 'system_state', producer: { status: 'known', value: 'runtime-task-store' }, capturedAt }),
740
+ ]),
741
+ requiredAudience: 'system',
742
+ });
743
+ }
744
+ // ── decision_state (§8.1 priority) ───────────────────────────────────────
745
+ const runningWork = ownerGovernanceView.automation.state === 'running'
746
+ || ownerGovernanceView.automation.state === 'queued'
747
+ || ownerGovernanceView.automation.state === 'retry_scheduled';
748
+ const hasApproveAction = availableActions.some((action) => action.semantic === 'approve');
749
+ const decisionState = (() => {
750
+ if (pendingSubjects.length > 0 && hasApproveAction)
751
+ return 'needs_owner_decision';
752
+ if (pendingSubjects.length > 0) {
753
+ // Pending subject exists but no approve passed its material gate.
754
+ return 'blocked';
755
+ }
756
+ if (lineageNotAvailableIssue !== undefined) {
757
+ // §9.2: readable diagnostics do NOT establish governance identity. We
758
+ // cannot prove "nothing needs deciding" without the lineage, so this is
759
+ // blocked (system cannot establish the governance judgment), never
760
+ // no_action.
761
+ return 'blocked';
762
+ }
763
+ if (recoveryAttention.length > 0)
764
+ return 'recovery_needed';
765
+ if (runningWork)
766
+ return 'processing';
767
+ if (foldedSubjects.some((row) => row.outcome === 'approved' || row.outcome === 'rejected'))
768
+ return 'decided';
769
+ if (foldedActivations.length > 0)
770
+ return 'decided';
771
+ return 'no_action';
772
+ })();
773
+ // ── next_action ──────────────────────────────────────────────────────────
774
+ const nextAction = (() => {
775
+ if (decisionState === 'needs_owner_decision') {
776
+ const first = availableActions.find((action) => action.semantic === 'approve');
777
+ return { code: 'review', ownerText: '有一项决定需要你审阅:请阅读上述说明后选择批准或拒绝。', targetRefs: first?.sourceRefs ?? [ledgerRef], actionKey: first?.key };
778
+ }
779
+ if (decisionState === 'blocked') {
780
+ const dataBlocker = blockers.find((blocker) => blocker.kind === 'data');
781
+ return {
782
+ code: 'inspect_data',
783
+ ownerText: dataBlocker?.reason.ownerText ?? '系统暂时无法建立完整的治理判断。',
784
+ targetRefs: [ledgerRef],
785
+ };
786
+ }
787
+ if (decisionState === 'recovery_needed') {
788
+ return { code: 'inspect_recovery', ownerText: '这项处理存在历史遗留问题;可查看恢复信息,由系统或操作员处理。', targetRefs: [ledgerRef] };
789
+ }
790
+ if (decisionState === 'processing') {
791
+ return { code: 'wait', ownerText: '系统正在处理这项提案,请等待处理完成。', targetRefs: [ledgerRef] };
792
+ }
793
+ if (decisionState === 'decided') {
794
+ return { code: 'monitor', ownerText: '此提案已有完成的决定。可查看上方的执行状态与行为记录。', targetRefs: [ledgerRef] };
795
+ }
796
+ return { code: 'none', ownerText: '目前没有需要你决定的事项。', targetRefs: [ledgerRef] };
797
+ })();
798
+ // ── inbox placement (§11.1) ──────────────────────────────────────────────
799
+ const inbox = (() => {
800
+ if (decisionState === 'needs_owner_decision') {
801
+ return { group: 'decision', attention: 'individual', reason: reason('pending_decision_subject', '存在待决定的审批对象。', [ledgerRef]) };
802
+ }
803
+ if (decisionState === 'blocked') {
804
+ const hasRealSubject = pendingSubjects.length > 0;
805
+ return {
806
+ group: 'blocked',
807
+ attention: hasRealSubject ? 'individual' : 'aggregate',
808
+ reason: reason(hasRealSubject ? 'blocked_real_subject' : 'judgment_unavailable', hasRealSubject
809
+ ? '当前待批对象缺少安全批准所需的决策材料。'
810
+ : '系统暂时无法判断是否需要你决定(治理关联尚未建立)。', [ledgerRef]),
811
+ };
812
+ }
813
+ if (decisionState === 'recovery_needed') {
814
+ return { group: 'recovery', attention: 'individual', reason: reason('recovery_attention', '存在需要恢复处理的事项。', [ledgerRef]) };
815
+ }
816
+ return { group: 'none', attention: 'none', reason: reason('no_inbox_attention', '当前无需特别关注。', [ledgerRef]) };
817
+ })();
818
+ const view = {
819
+ schemaVersion: '1',
820
+ principleId,
821
+ asOf,
822
+ sourceReadStatus,
823
+ sourceReads: input.sourceReads,
824
+ incidentSummary,
825
+ learnedPrinciple,
826
+ rationale,
827
+ applicability,
828
+ nonApplicabilityOrUnknown: nonApplicability,
829
+ expectedBehavior,
830
+ currentEnforcement,
831
+ evidenceSummary,
832
+ uncertainty,
833
+ risk,
834
+ rollback,
835
+ decisionState,
836
+ decisionSubjects,
837
+ availableActions,
838
+ blockers,
839
+ nextAction,
840
+ inbox,
841
+ technicalDetails: {
842
+ available: true,
843
+ links: [
844
+ { kind: 'governance', target: `/api/v1/principles/${principleId}/governance`, reason: '完整治理投影(任务图、时间线、数据质量)。' },
845
+ { kind: 'trajectory', target: `/api/v1/principles/${principleId}/trajectory`, reason: '六阶段轨迹(证据→诊断→提案→评审→部署→行为)。' },
846
+ { kind: 'receipts', target: `/api/v1/principles/${principleId}/receipts`, reason: '行为回执与证据覆盖。' },
847
+ ],
848
+ warnings: [],
849
+ },
850
+ };
851
+ if (!Value.Check(OwnerDecisionViewCoreSchema, view)) {
852
+ throw new Error('invalid_owner_decision_view');
853
+ }
854
+ return view;
855
+ }
856
+ //# sourceMappingURL=owner-decision-view.js.map