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.
- package/console/dist/server/index.js +19 -0
- package/console/dist/server/models/OwnerDecisionViewModel.d.ts +65 -0
- package/console/dist/server/models/OwnerDecisionViewModel.js +686 -0
- package/console/dist/server/routes/owner-decision.d.ts +40 -0
- package/console/dist/server/routes/owner-decision.js +88 -0
- package/console/dist/server/routes/principles.d.ts +3 -0
- package/console/dist/server/routes/principles.js +3 -0
- package/console/dist/ui/api.d.ts +7 -3
- package/console/dist/ui/api.js +13 -2
- package/console/dist/ui/i18n/en.json +70 -4
- package/console/dist/ui/i18n/zh-CN.json +70 -4
- package/console/dist/ui/pages/focus/FocusPage.js +25 -2
- package/console/dist/ui/pages/principles/PrincipleDetailPage.d.ts +24 -0
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +168 -210
- package/console/dist/ui/pages/principles/PrinciplesPage.js +25 -10
- package/console/dist/ui/utils/enum-labels.d.ts +1 -1
- package/console/dist/ui/utils/enum-labels.js +8 -0
- package/console/dist/ui/utils/validators.d.ts +99 -1
- package/console/dist/ui/utils/validators.js +115 -0
- package/console/dist/web/assets/app.css +3 -0
- package/console/dist/web/assets/app.js +590 -349
- package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js +25 -0
- package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +16 -0
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +22 -0
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +7 -0
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +4 -0
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.d.ts +2 -0
- package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.js +559 -0
- package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.js.map +1 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.d.ts +7868 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.js +367 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.js.map +1 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view.d.ts +415 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view.js +856 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view.js.map +1 -0
- package/core/dist/runtime-v2/owner-decision/semantic-selector.d.ts +72 -0
- package/core/dist/runtime-v2/owner-decision/semantic-selector.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-decision/semantic-selector.js +176 -0
- package/core/dist/runtime-v2/owner-decision/semantic-selector.js.map +1 -0
- package/package.json +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts +14 -0
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js +47 -7
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +84 -0
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js +215 -38
- package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/plugin/dist/bundle.js +526 -526
- package/plugin/dist/governance-audit.js +205 -205
- package/plugin/dist/rulehost-evidence.js +246 -246
- package/release-manager/package.json +1 -1
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OwnerDecisionViewModel — the collecting boundary for the canonical
|
|
3
|
+
* Owner-facing decision projection (Owner Decision Experience v1, Phase B).
|
|
4
|
+
*
|
|
5
|
+
* Gathers the sources `deriveOwnerDecisionView` needs and stays READ-ONLY:
|
|
6
|
+
* - ledger entry (principle_training_state.json, via the collector's parser)
|
|
7
|
+
* - GovernanceFacts (GovernanceProjectionCollector — same 4-table batch read
|
|
8
|
+
* the existing governance endpoint uses; one snapshot per request)
|
|
9
|
+
* - candidate / generic diagnosis artifact / PI scribe + philosopher texts
|
|
10
|
+
* (diagnostic read-through axes, SPEC §9.2 — never conflated with
|
|
11
|
+
* governance lineage)
|
|
12
|
+
* - behavior application counts (principle_applications, existing ledger)
|
|
13
|
+
* + receipt coverage availability
|
|
14
|
+
*
|
|
15
|
+
* It writes nothing, creates nothing, and does not mutate flags. Every source
|
|
16
|
+
* read is recorded in `sourceReads`; failures degrade the specific source only.
|
|
17
|
+
*/
|
|
18
|
+
/* eslint-disable @typescript-eslint/no-use-before-define -- helpers declared after the class, matching codebase convention */
|
|
19
|
+
/* eslint-disable @typescript-eslint/class-methods-use-this -- lifecycle-style methods; kept on the model for cohesion */
|
|
20
|
+
import * as fs from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import { GovernanceProjectionCollector, isRecord, readOwnString, } from './GovernanceProjectionCollector.js';
|
|
23
|
+
import { deriveOwnerDecisionView, } from '@principles/core/runtime-v2';
|
|
24
|
+
import { SqliteConnection } from '@principles/core/runtime-v2';
|
|
25
|
+
const ISO_NOW = () => new Date().toISOString();
|
|
26
|
+
export class OwnerDecisionViewModel {
|
|
27
|
+
workspaceDir;
|
|
28
|
+
constructor(workspaceDir) {
|
|
29
|
+
this.workspaceDir = workspaceDir;
|
|
30
|
+
}
|
|
31
|
+
/** Full OwnerDecisionViewCore for one principle (Detail first layer). */
|
|
32
|
+
async getView(principleId, now = ISO_NOW()) {
|
|
33
|
+
const inputs = await this.collectInputs(principleId, now);
|
|
34
|
+
return deriveOwnerDecisionView(inputs);
|
|
35
|
+
}
|
|
36
|
+
/** Compact inbox projection over every ledger principle (§11.1). */
|
|
37
|
+
async getInbox(now = ISO_NOW()) {
|
|
38
|
+
const snapshot = this.readBatchSnapshot();
|
|
39
|
+
let ledgerParsed;
|
|
40
|
+
try {
|
|
41
|
+
ledgerParsed = this.parseLedger();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// No readable ledger: degrade observably instead of an empty success
|
|
45
|
+
// (SPEC §15 — an unavailable ledger is not "zero principles, all fine").
|
|
46
|
+
return {
|
|
47
|
+
schemaVersion: '1', generatedAt: now, totalPrinciples: 0,
|
|
48
|
+
groups: { decision: [], blocked: [], recovery: [] },
|
|
49
|
+
historicalUnknown: { count: 0, reasonText: '' },
|
|
50
|
+
degraded: { reason: 'principle ledger unavailable', nextAction: '检查原则账本文件。' },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const principlesTree = GovernanceProjectionCollector.principleTreeFromLedger(ledgerParsed);
|
|
54
|
+
if (principlesTree === null) {
|
|
55
|
+
return {
|
|
56
|
+
schemaVersion: '1', generatedAt: now, totalPrinciples: 0,
|
|
57
|
+
groups: { decision: [], blocked: [], recovery: [] },
|
|
58
|
+
historicalUnknown: { count: 0, reasonText: '' },
|
|
59
|
+
degraded: { reason: 'principle ledger unavailable', nextAction: '检查原则账本文件。' },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const decision = [];
|
|
63
|
+
const blocked = [];
|
|
64
|
+
const recovery = [];
|
|
65
|
+
let historicalUnknown = 0;
|
|
66
|
+
let total = 0;
|
|
67
|
+
for (const principleId of Object.keys(principlesTree).sort()) {
|
|
68
|
+
total += 1;
|
|
69
|
+
try {
|
|
70
|
+
const inputs = this.buildInputsFor({ principleId, ledgerParsed, snapshot, now, compact: true });
|
|
71
|
+
const view = deriveOwnerDecisionView(inputs);
|
|
72
|
+
const item = {
|
|
73
|
+
principleId,
|
|
74
|
+
decisionState: view.decisionState,
|
|
75
|
+
learnedPrinciple: view.learnedPrinciple.status === 'known'
|
|
76
|
+
? { status: 'known', text: view.learnedPrinciple.value.text, sourceTier: view.learnedPrinciple.value.sourceTier }
|
|
77
|
+
: { status: 'unknown', reasonText: view.learnedPrinciple.reason.ownerText },
|
|
78
|
+
inbox: view.inbox,
|
|
79
|
+
blockerCodes: view.blockers.map((blocker) => blocker.reason.code),
|
|
80
|
+
nextAction: view.nextAction,
|
|
81
|
+
};
|
|
82
|
+
if (view.inbox.group === 'decision')
|
|
83
|
+
decision.push(item);
|
|
84
|
+
else if (view.inbox.group === 'blocked')
|
|
85
|
+
blocked.push(item);
|
|
86
|
+
else if (view.inbox.group === 'recovery')
|
|
87
|
+
recovery.push(item);
|
|
88
|
+
if (view.inbox.group === 'blocked' && view.inbox.attention === 'aggregate')
|
|
89
|
+
historicalUnknown += 1;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// A single principle failing derivation must not take down the inbox;
|
|
93
|
+
// it is counted as blocked-judgment so it stays observable.
|
|
94
|
+
blocked.push({
|
|
95
|
+
principleId,
|
|
96
|
+
decisionState: 'blocked',
|
|
97
|
+
learnedPrinciple: { status: 'unknown', reasonText: '该条目的决策投影暂时无法生成。' },
|
|
98
|
+
inbox: {
|
|
99
|
+
group: 'blocked', attention: 'individual',
|
|
100
|
+
reason: { code: 'projection_error', ownerText: '该条目的决策投影暂时无法生成。', sourceRefs: [] },
|
|
101
|
+
},
|
|
102
|
+
blockerCodes: ['projection_error'],
|
|
103
|
+
nextAction: { code: 'inspect_data', ownerText: '查看该原则详情页的错误说明。', targetRefs: [] },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
schemaVersion: '1',
|
|
109
|
+
generatedAt: now,
|
|
110
|
+
totalPrinciples: total,
|
|
111
|
+
groups: { decision, blocked, recovery },
|
|
112
|
+
historicalUnknown: {
|
|
113
|
+
count: historicalUnknown,
|
|
114
|
+
reasonText: historicalUnknown > 0
|
|
115
|
+
? `有 ${historicalUnknown} 条历史原则的决策状态暂时无法确认(治理关联尚未建立)。这不是待办事项,也不代表需要你立即处理。`
|
|
116
|
+
: '',
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Compact per-principle projection for the Library list (§11.2). */
|
|
121
|
+
async getLibraryCompact(now = ISO_NOW()) {
|
|
122
|
+
const snapshot = this.readBatchSnapshot();
|
|
123
|
+
let ledgerParsed;
|
|
124
|
+
try {
|
|
125
|
+
ledgerParsed = this.parseLedger();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return { schemaVersion: '1', generatedAt: now, entries: {}, degraded: { reason: 'principle ledger unavailable' } };
|
|
129
|
+
}
|
|
130
|
+
const principlesTree = GovernanceProjectionCollector.principleTreeFromLedger(ledgerParsed);
|
|
131
|
+
if (principlesTree === null) {
|
|
132
|
+
return { schemaVersion: '1', generatedAt: now, entries: {}, degraded: { reason: 'principle ledger unavailable' } };
|
|
133
|
+
}
|
|
134
|
+
const entries = {};
|
|
135
|
+
for (const principleId of Object.keys(principlesTree).sort()) {
|
|
136
|
+
try {
|
|
137
|
+
const view = deriveOwnerDecisionView(this.buildInputsFor({ principleId, ledgerParsed, snapshot, now, compact: true }));
|
|
138
|
+
entries[principleId] = {
|
|
139
|
+
decisionState: view.decisionState,
|
|
140
|
+
learnedPrinciple: view.learnedPrinciple.status === 'known'
|
|
141
|
+
? { status: 'known', text: view.learnedPrinciple.value.text, sourceTier: view.learnedPrinciple.value.sourceTier }
|
|
142
|
+
: { status: 'unknown', reasonText: view.learnedPrinciple.reason.ownerText },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Skip unreadable entries — the Library falls back to neutral copy.
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { schemaVersion: '1', generatedAt: now, entries };
|
|
150
|
+
}
|
|
151
|
+
// ── input assembly ────────────────────────────────────────────────────────
|
|
152
|
+
async collectInputs(principleId, now) {
|
|
153
|
+
const snapshot = this.readBatchSnapshot();
|
|
154
|
+
const ledgerParsed = this.parseLedger();
|
|
155
|
+
return this.buildInputsFor({ principleId, ledgerParsed, snapshot, now, compact: false });
|
|
156
|
+
}
|
|
157
|
+
parseLedger() {
|
|
158
|
+
return GovernanceProjectionCollector.parsePrincipleLedgerFile(this.workspaceDir);
|
|
159
|
+
}
|
|
160
|
+
buildInputsFor(input) {
|
|
161
|
+
const { principleId, ledgerParsed, snapshot, now, compact } = input;
|
|
162
|
+
const issues = [];
|
|
163
|
+
const principle = GovernanceProjectionCollector.principleFactFromLedger(ledgerParsed, principleId, issues);
|
|
164
|
+
const entry = this.ledgerEntryFor(ledgerParsed, principleId);
|
|
165
|
+
const sourceReads = [];
|
|
166
|
+
let governance;
|
|
167
|
+
if (!snapshot.dbAvailable) {
|
|
168
|
+
sourceReads.push({ source: 'state.db', status: 'unavailable', capturedAt: now, scope: 'governance tables', reason: snapshot.ledgerUnavailableReason });
|
|
169
|
+
governance = GovernanceProjectionCollector.factsForUnavailableSource({
|
|
170
|
+
principleId, asOf: now, principle, collectionIssues: issues,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
sourceReads.push({ source: 'state.db', status: 'available', capturedAt: now, scope: 'pi_artifacts/tasks/approvals/activations' });
|
|
175
|
+
governance = GovernanceProjectionCollector.buildFacts({
|
|
176
|
+
principleId, asOf: now, principle,
|
|
177
|
+
tables: {
|
|
178
|
+
artifactRows: snapshot.artifactRows,
|
|
179
|
+
taskRows: snapshot.taskRows,
|
|
180
|
+
approvalRows: snapshot.approvalRows,
|
|
181
|
+
activationRows: snapshot.activationRows,
|
|
182
|
+
},
|
|
183
|
+
collectionIssues: issues,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// F5-compatible reference classification: derivedFromPainIds may hold
|
|
187
|
+
// candidate UUIDs (current producer) or real Pain IDs (legacy writers).
|
|
188
|
+
// Only ids that resolve to principle_candidates become candidate refs —
|
|
189
|
+
// presence of an id never implies "it is a candidate".
|
|
190
|
+
const resolvedCandidates = [];
|
|
191
|
+
let ambiguousReferences = false;
|
|
192
|
+
for (const refId of entry.derivedFromPainIds) {
|
|
193
|
+
const hit = snapshot.candidates.get(refId);
|
|
194
|
+
if (hit !== undefined)
|
|
195
|
+
resolvedCandidates.push(hit);
|
|
196
|
+
}
|
|
197
|
+
if (resolvedCandidates.length > 1)
|
|
198
|
+
ambiguousReferences = true;
|
|
199
|
+
const candidate = resolvedCandidates.length === 1 ? resolvedCandidates[0] : undefined;
|
|
200
|
+
if (candidate === undefined && !compact) {
|
|
201
|
+
sourceReads.push({
|
|
202
|
+
source: 'principle_candidates',
|
|
203
|
+
status: ambiguousReferences ? 'unavailable' : 'not_requested',
|
|
204
|
+
capturedAt: now,
|
|
205
|
+
scope: 'candidate by derivedFromPainIds',
|
|
206
|
+
reason: ambiguousReferences ? 'multiple candidates resolve — refusing to guess (F5)' : 'no candidate resolved for the stored references',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
else if (candidate !== undefined && !compact) {
|
|
210
|
+
sourceReads.push({ source: 'principle_candidates', status: 'available', capturedAt: now, scope: `candidate ${candidate.candidateId}` });
|
|
211
|
+
}
|
|
212
|
+
// Diagnosis read-through (diagnostic axis; NOT governance lineage).
|
|
213
|
+
let diagnosis = null;
|
|
214
|
+
if (!compact && candidate?.artifactId !== undefined) {
|
|
215
|
+
const artifact = snapshot.genericArtifacts.get(candidate.artifactId);
|
|
216
|
+
if (artifact?.contentJson !== undefined) {
|
|
217
|
+
diagnosis = this.parseDiagnosis(artifact.artifactId, artifact.contentJson);
|
|
218
|
+
sourceReads.push({ source: 'artifacts.diagnostician_output', status: diagnosis !== null ? 'available' : 'unavailable', capturedAt: now, scope: `artifact ${artifact.artifactId}`, reason: diagnosis === null ? 'contentJson not a DiagnosticianOutput shape' : undefined });
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
sourceReads.push({ source: 'artifacts.diagnostician_output', status: 'unavailable', capturedAt: now, scope: `artifact ${candidate.artifactId}`, reason: 'generic artifact missing or unreadable' });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Scribe + philosopher materials from PI artifacts (governance axis).
|
|
225
|
+
const semantic = this.readSemanticArtifacts(principleId, snapshot, compact);
|
|
226
|
+
const { scribe, philosopher } = semantic;
|
|
227
|
+
// Behavior applications (existing receipt ledger; read-only aggregate).
|
|
228
|
+
// Codex review P2 fix: a missing/unreadable principle_applications table
|
|
229
|
+
// must surface as an UNAVAILABLE source — an empty map with dbAvailable
|
|
230
|
+
// true would present "available, zero retained" on a damaged workspace.
|
|
231
|
+
let applications = null;
|
|
232
|
+
if (!compact && snapshot.dbAvailable && !snapshot.applicationsAvailable) {
|
|
233
|
+
sourceReads.push({ source: 'principle_applications', status: 'unavailable', capturedAt: now, scope: 'level+kind aggregates', reason: 'principle_applications table missing or unreadable' });
|
|
234
|
+
}
|
|
235
|
+
else if (!compact && snapshot.dbAvailable) {
|
|
236
|
+
const aggregated = snapshot.applicationsByPrinciple.get(principleId);
|
|
237
|
+
if (aggregated !== undefined) {
|
|
238
|
+
applications = {
|
|
239
|
+
sourceStatus: 'available',
|
|
240
|
+
deterministicCount: aggregated.deterministic,
|
|
241
|
+
selfReportedCount: aggregated.selfReported,
|
|
242
|
+
contextPresenceCount: aggregated.presence,
|
|
243
|
+
windowDays: 90,
|
|
244
|
+
recent: aggregated.recent,
|
|
245
|
+
};
|
|
246
|
+
sourceReads.push({ source: 'principle_applications', status: 'available', capturedAt: now, scope: 'level+kind aggregates + recent 5' });
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
applications = { sourceStatus: 'available', deterministicCount: 0, selfReportedCount: 0, contextPresenceCount: 0, windowDays: 90, recent: [] };
|
|
250
|
+
sourceReads.push({ source: 'principle_applications', status: 'available', capturedAt: now, scope: 'level+kind aggregates (none retained)' });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Semantic sources (lineage-constrained, tier-ordered inputs for the
|
|
254
|
+
// core selector; the collector only FILTERS by binding, never by text).
|
|
255
|
+
const semanticSources = [];
|
|
256
|
+
if (scribe?.statement !== undefined) {
|
|
257
|
+
semanticSources.push({ tier: 'scribe', text: scribe.statement, sourceVersion: scribe.artifactId });
|
|
258
|
+
}
|
|
259
|
+
if (candidate?.abstractedPrinciple !== undefined && candidate.abstractedPrinciple.trim() !== '') {
|
|
260
|
+
semanticSources.push({ tier: 'distiller', text: candidate.abstractedPrinciple, sourceVersion: candidate.candidateId });
|
|
261
|
+
}
|
|
262
|
+
if (philosopher?.title !== undefined) {
|
|
263
|
+
semanticSources.push({ tier: 'philosopher', text: philosopher.title, sourceVersion: philosopher.artifactId });
|
|
264
|
+
}
|
|
265
|
+
if (candidate !== undefined && candidate.recommendationKind === 'principle') {
|
|
266
|
+
semanticSources.push({ tier: 'candidate_principle', text: candidate.description, sourceVersion: candidate.candidateId });
|
|
267
|
+
}
|
|
268
|
+
const piRootBound = snapshot.dbAvailable
|
|
269
|
+
&& snapshot.artifactRows.some((row) => isRecord(row) && readOwnString(row, 'source_principle_id') === principleId);
|
|
270
|
+
sourceReads.push({
|
|
271
|
+
source: 'ledger',
|
|
272
|
+
status: 'available',
|
|
273
|
+
capturedAt: now,
|
|
274
|
+
scope: 'principle entry',
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
schemaVersion: '1',
|
|
278
|
+
principleId,
|
|
279
|
+
asOf: now,
|
|
280
|
+
ledger: {
|
|
281
|
+
status: entry.status,
|
|
282
|
+
text: entry.text,
|
|
283
|
+
evaluability: entry.evaluability,
|
|
284
|
+
triggerPattern: entry.triggerPattern,
|
|
285
|
+
action: entry.action,
|
|
286
|
+
derivedFromPainIds: entry.derivedFromPainIds,
|
|
287
|
+
ruleIds: entry.ruleIds,
|
|
288
|
+
createdAt: entry.createdAt,
|
|
289
|
+
updatedAt: entry.updatedAt,
|
|
290
|
+
},
|
|
291
|
+
governance,
|
|
292
|
+
candidate: candidate === undefined ? null : {
|
|
293
|
+
candidateId: candidate.candidateId,
|
|
294
|
+
status: candidate.status,
|
|
295
|
+
recommendationKind: candidate.recommendationKind,
|
|
296
|
+
description: candidate.description,
|
|
297
|
+
...(candidate.abstractedPrinciple === undefined ? {} : { abstractedPrinciple: candidate.abstractedPrinciple }),
|
|
298
|
+
...(candidate.trigger === undefined ? {} : { trigger: candidate.trigger }),
|
|
299
|
+
...(candidate.action === undefined ? {} : { action: candidate.action }),
|
|
300
|
+
...(candidate.title === undefined ? {} : { title: candidate.title }),
|
|
301
|
+
...(candidate.confidence === undefined ? {} : { confidence: candidate.confidence }),
|
|
302
|
+
...(candidate.artifactId === undefined ? {} : { sourceRef: candidate.artifactId }),
|
|
303
|
+
},
|
|
304
|
+
diagnosis: diagnosis === null ? null : {
|
|
305
|
+
artifactId: diagnosis.artifactId,
|
|
306
|
+
...(diagnosis.summary === undefined ? {} : { summary: diagnosis.summary }),
|
|
307
|
+
...(diagnosis.rootCause === undefined ? {} : { rootCause: diagnosis.rootCause }),
|
|
308
|
+
evidenceItems: [],
|
|
309
|
+
truncated: diagnosis.truncated,
|
|
310
|
+
},
|
|
311
|
+
scribe: scribe === null ? null : {
|
|
312
|
+
artifactId: scribe.artifactId,
|
|
313
|
+
...(scribe.statement === undefined ? {} : { statement: scribe.statement }),
|
|
314
|
+
...(scribe.rationale === undefined ? {} : { rationale: scribe.rationale }),
|
|
315
|
+
applicability: scribe.applicability,
|
|
316
|
+
antiPatterns: scribe.antiPatterns,
|
|
317
|
+
risks: scribe.risks,
|
|
318
|
+
...(scribe.targetBehavior === undefined ? {} : { targetBehavior: scribe.targetBehavior }),
|
|
319
|
+
updatedAt: scribe.updatedAt,
|
|
320
|
+
},
|
|
321
|
+
philosopher: philosopher === null ? null : {
|
|
322
|
+
artifactId: philosopher.artifactId,
|
|
323
|
+
...(philosopher.title === undefined ? {} : { title: philosopher.title }),
|
|
324
|
+
...(philosopher.rationale === undefined ? {} : { rationale: philosopher.rationale }),
|
|
325
|
+
...(philosopher.scope === undefined ? {} : { scope: philosopher.scope }),
|
|
326
|
+
},
|
|
327
|
+
applications,
|
|
328
|
+
coverageSourceStatus: !snapshot.dbAvailable ? 'unavailable' : 'available',
|
|
329
|
+
semanticSources,
|
|
330
|
+
technicalRecommendationAvailable: candidate !== undefined
|
|
331
|
+
&& candidate.recommendationKind !== 'principle'
|
|
332
|
+
&& candidate.description.trim() !== '',
|
|
333
|
+
// Fix 1 (review P1): per-artifact scribe materials — subject material
|
|
334
|
+
// resolves against this list by the subject's own artifactId; unrelated
|
|
335
|
+
// revisions never borrow each other's text.
|
|
336
|
+
scribeMaterials: semantic.scribeMaterials.map((material) => ({
|
|
337
|
+
artifactId: material.artifactId,
|
|
338
|
+
statement: material.statement ?? '',
|
|
339
|
+
...(material.rationale === undefined ? {} : { rationale: material.rationale }),
|
|
340
|
+
...(material.targetBehavior === undefined ? {} : { targetBehavior: material.targetBehavior }),
|
|
341
|
+
sourceVersion: material.artifactId,
|
|
342
|
+
})),
|
|
343
|
+
piRootBound,
|
|
344
|
+
sourceReads,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
ledgerEntryFor(ledgerParsed, principleId) {
|
|
348
|
+
const tree = GovernanceProjectionCollector.principleTreeFromLedger(ledgerParsed);
|
|
349
|
+
const raw = tree?.[principleId];
|
|
350
|
+
if (!isRecord(raw))
|
|
351
|
+
throw new Error('principle_not_found');
|
|
352
|
+
const stringArray = (value) => (Array.isArray(value) ? value.filter((item) => typeof item === 'string') : []);
|
|
353
|
+
return {
|
|
354
|
+
status: readOwnString(raw, 'status') ?? 'candidate',
|
|
355
|
+
text: readOwnString(raw, 'text') ?? '',
|
|
356
|
+
evaluability: readOwnString(raw, 'evaluability') ?? 'weak_heuristic',
|
|
357
|
+
triggerPattern: readOwnString(raw, 'triggerPattern') ?? '',
|
|
358
|
+
action: readOwnString(raw, 'action') ?? '',
|
|
359
|
+
derivedFromPainIds: stringArray(raw.derivedFromPainIds),
|
|
360
|
+
ruleIds: stringArray(raw.ruleIds),
|
|
361
|
+
createdAt: readOwnString(raw, 'createdAt') ?? '',
|
|
362
|
+
updatedAt: readOwnString(raw, 'updatedAt') ?? '',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
parseDiagnosis(artifactId, contentJson) {
|
|
366
|
+
let parsed;
|
|
367
|
+
try {
|
|
368
|
+
parsed = JSON.parse(contentJson);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
if (!isRecord(parsed))
|
|
374
|
+
return null;
|
|
375
|
+
const summary = readOwnString(parsed, 'summary');
|
|
376
|
+
const rootCause = readOwnString(parsed, 'rootCause');
|
|
377
|
+
if (summary === undefined && rootCause === undefined)
|
|
378
|
+
return null;
|
|
379
|
+
const rawText = JSON.stringify(parsed);
|
|
380
|
+
return {
|
|
381
|
+
artifactId,
|
|
382
|
+
...(summary === undefined ? {} : { summary }),
|
|
383
|
+
...(rootCause === undefined ? {} : { rootCause }),
|
|
384
|
+
truncated: rawText.includes('___TRUNCATED___'),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
readSemanticArtifacts(principleId, snapshot, compact) {
|
|
388
|
+
let scribe = null;
|
|
389
|
+
const scribeMaterials = [];
|
|
390
|
+
let philosopher = null;
|
|
391
|
+
if (!snapshot.dbAvailable)
|
|
392
|
+
return { scribe, philosopher, scribeMaterials };
|
|
393
|
+
// Content rows are a separate targeted read: the shared governance batch
|
|
394
|
+
// SELECT intentionally omits content_json (heavy) — the semantic read-through
|
|
395
|
+
// needs it only for artifacts bound to THIS principle.
|
|
396
|
+
const boundRows = snapshot.piArtifactContentRows.filter((row) => isRecord(row) && readOwnString(row, 'source_principle_id') === principleId);
|
|
397
|
+
for (const row of boundRows) {
|
|
398
|
+
if (!isRecord(row))
|
|
399
|
+
continue;
|
|
400
|
+
const artifactId = readOwnString(row, 'artifact_id');
|
|
401
|
+
const sourceTaskId = readOwnString(row, 'source_task_id');
|
|
402
|
+
const updatedAt = readOwnString(row, 'updated_at') ?? '';
|
|
403
|
+
const contentJson = readOwnString(row, 'content_json');
|
|
404
|
+
if (artifactId === undefined || sourceTaskId === undefined || contentJson === undefined)
|
|
405
|
+
continue;
|
|
406
|
+
const taskKind = snapshot.piArtifactTaskKinds.get(sourceTaskId);
|
|
407
|
+
if (taskKind === 'scribe') {
|
|
408
|
+
const material = parseScribeContent(artifactId, contentJson, updatedAt);
|
|
409
|
+
if (material === null)
|
|
410
|
+
continue;
|
|
411
|
+
// Fix 1 (review P1): EVERY bound scribe artifact with a statement is
|
|
412
|
+
// per-artifact decision material — subject material resolves against
|
|
413
|
+
// this list by the subject's own artifactId, never borrowed across
|
|
414
|
+
// revisions.
|
|
415
|
+
scribeMaterials.push(material);
|
|
416
|
+
if (scribe === null || material.updatedAt >= scribe.updatedAt)
|
|
417
|
+
scribe = material;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (!compact) {
|
|
421
|
+
// Philosopher tier needs an exact lineage relation: the artifact must be
|
|
422
|
+
// inside the bound closure (lineage_artifact_ids from a bound artifact).
|
|
423
|
+
const philosopherRows = this.philosopherRowsInBoundClosure(snapshot, boundRows);
|
|
424
|
+
for (const row of philosopherRows) {
|
|
425
|
+
if (!isRecord(row))
|
|
426
|
+
continue;
|
|
427
|
+
const artifactId = readOwnString(row, 'artifact_id');
|
|
428
|
+
const contentJson = readOwnString(row, 'content_json');
|
|
429
|
+
if (artifactId === undefined || contentJson === undefined)
|
|
430
|
+
continue;
|
|
431
|
+
const material = parsePhilosopherContent(artifactId, contentJson);
|
|
432
|
+
if (material !== null)
|
|
433
|
+
philosopher = material;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return { scribe, philosopher, scribeMaterials };
|
|
437
|
+
}
|
|
438
|
+
piContentRowsById(snapshot) {
|
|
439
|
+
const rowsById = new Map();
|
|
440
|
+
for (const row of snapshot.piArtifactContentRows) {
|
|
441
|
+
if (isRecord(row)) {
|
|
442
|
+
const id = readOwnString(row, 'artifact_id');
|
|
443
|
+
if (id !== undefined)
|
|
444
|
+
rowsById.set(id, row);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return rowsById;
|
|
448
|
+
}
|
|
449
|
+
philosopherRowsInBoundClosure(snapshot, boundRows) {
|
|
450
|
+
const closure = new Set();
|
|
451
|
+
const queue = boundRows
|
|
452
|
+
.filter(isRecord)
|
|
453
|
+
.map((row) => readOwnString(row, 'artifact_id'))
|
|
454
|
+
.filter((id) => id !== undefined);
|
|
455
|
+
const rowsById = this.piContentRowsById(snapshot);
|
|
456
|
+
while (queue.length > 0) {
|
|
457
|
+
const current = queue.shift();
|
|
458
|
+
if (current === undefined || closure.has(current))
|
|
459
|
+
continue;
|
|
460
|
+
closure.add(current);
|
|
461
|
+
const row = rowsById.get(current);
|
|
462
|
+
if (!isRecord(row))
|
|
463
|
+
continue;
|
|
464
|
+
const lineageJson = readOwnString(row, 'lineage_artifact_ids');
|
|
465
|
+
if (lineageJson === undefined)
|
|
466
|
+
continue;
|
|
467
|
+
try {
|
|
468
|
+
const parsed = JSON.parse(lineageJson);
|
|
469
|
+
if (Array.isArray(parsed)) {
|
|
470
|
+
for (const id of parsed) {
|
|
471
|
+
if (typeof id === 'string' && !closure.has(id))
|
|
472
|
+
queue.push(id);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
// malformed lineage on one row — skip its expansion only
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const result = [];
|
|
481
|
+
for (const artifactId of closure) {
|
|
482
|
+
const row = rowsById.get(artifactId);
|
|
483
|
+
if (!isRecord(row))
|
|
484
|
+
continue;
|
|
485
|
+
const sourceTaskId = readOwnString(row, 'source_task_id');
|
|
486
|
+
if (sourceTaskId !== undefined && snapshot.piArtifactTaskKinds.get(sourceTaskId) === 'philosopher') {
|
|
487
|
+
result.push(row);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
// ── batch snapshot (shared reads; one DB open per request) ────────────────
|
|
493
|
+
readBatchSnapshot() {
|
|
494
|
+
const dbPath = path.join(this.workspaceDir, '.pd', 'state.db');
|
|
495
|
+
const dbAvailable = fs.existsSync(dbPath);
|
|
496
|
+
const candidates = new Map();
|
|
497
|
+
const genericArtifacts = new Map();
|
|
498
|
+
const piArtifactTaskKinds = new Map();
|
|
499
|
+
const applicationsByPrinciple = new Map();
|
|
500
|
+
let applicationsAvailable = true;
|
|
501
|
+
if (!dbAvailable) {
|
|
502
|
+
// No state.db: governance tables are unavailable; the view degrades with
|
|
503
|
+
// source_read_status=partial rather than pretending an empty world.
|
|
504
|
+
return {
|
|
505
|
+
artifactRows: [], taskRows: [], approvalRows: [], activationRows: [],
|
|
506
|
+
piArtifactContentRows: [],
|
|
507
|
+
candidates, genericArtifacts, piArtifactTaskKinds, applicationsByPrinciple,
|
|
508
|
+
applicationsAvailable: false,
|
|
509
|
+
dbAvailable: false,
|
|
510
|
+
ledgerUnavailableReason: 'state.db not found',
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const conn = new SqliteConnection({ workspaceDir: this.workspaceDir, readonly: true });
|
|
514
|
+
try {
|
|
515
|
+
const db = conn.getDb();
|
|
516
|
+
// One injected handle for the shared 4-table read + the extra sources.
|
|
517
|
+
const tables = GovernanceProjectionCollector.readTables(this.workspaceDir, conn);
|
|
518
|
+
// Targeted content read for the semantic axes (scribe/philosopher text):
|
|
519
|
+
// the shared governance SELECT omits content_json by design.
|
|
520
|
+
const piArtifactContentRows = db.prepare('SELECT artifact_id, source_task_id, source_principle_id, lineage_artifact_ids, content_json, updated_at FROM pi_artifacts ORDER BY artifact_id ASC').all();
|
|
521
|
+
for (const row of db.prepare('SELECT candidate_id, status, recommendation_kind, description, abstracted_principle, trigger_pattern, action, title, confidence, artifact_id FROM principle_candidates').all()) {
|
|
522
|
+
if (!isRecord(row))
|
|
523
|
+
continue;
|
|
524
|
+
const candidateId = readOwnString(row, 'candidate_id');
|
|
525
|
+
if (candidateId === undefined)
|
|
526
|
+
continue;
|
|
527
|
+
const confidence = Object.hasOwn(row, 'confidence') ? row.confidence : undefined;
|
|
528
|
+
candidates.set(candidateId, {
|
|
529
|
+
candidateId,
|
|
530
|
+
status: readOwnString(row, 'status') ?? 'unknown',
|
|
531
|
+
recommendationKind: readOwnString(row, 'recommendation_kind') ?? 'unknown',
|
|
532
|
+
description: readOwnString(row, 'description') ?? '',
|
|
533
|
+
...(readOwnString(row, 'abstracted_principle') === undefined ? {} : { abstractedPrinciple: readOwnString(row, 'abstracted_principle') }),
|
|
534
|
+
...(readOwnString(row, 'trigger_pattern') === undefined ? {} : { trigger: readOwnString(row, 'trigger_pattern') }),
|
|
535
|
+
...(readOwnString(row, 'action') === undefined ? {} : { action: readOwnString(row, 'action') }),
|
|
536
|
+
...(readOwnString(row, 'title') === undefined ? {} : { title: readOwnString(row, 'title') }),
|
|
537
|
+
...(typeof confidence === 'number' ? { confidence } : {}),
|
|
538
|
+
...(readOwnString(row, 'artifact_id') === undefined ? {} : { artifactId: readOwnString(row, 'artifact_id') }),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
for (const row of db.prepare('SELECT artifact_id, artifact_kind, content_json FROM artifacts').all()) {
|
|
542
|
+
if (!isRecord(row))
|
|
543
|
+
continue;
|
|
544
|
+
const artifactId = readOwnString(row, 'artifact_id');
|
|
545
|
+
if (artifactId === undefined)
|
|
546
|
+
continue;
|
|
547
|
+
genericArtifacts.set(artifactId, {
|
|
548
|
+
artifactId,
|
|
549
|
+
kind: readOwnString(row, 'artifact_kind') ?? 'unknown',
|
|
550
|
+
...(readOwnString(row, 'content_json') === undefined ? {} : { contentJson: readOwnString(row, 'content_json') }),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
for (const row of tables.taskRows) {
|
|
554
|
+
if (!isRecord(row))
|
|
555
|
+
continue;
|
|
556
|
+
const taskId = readOwnString(row, 'task_id');
|
|
557
|
+
const taskKind = readOwnString(row, 'task_kind');
|
|
558
|
+
if (taskId !== undefined && taskKind !== undefined)
|
|
559
|
+
piArtifactTaskKinds.set(taskId, taskKind);
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
const grouped = db.prepare('SELECT principle_id, level, kind, COUNT(*) AS n FROM principle_applications GROUP BY principle_id, level, kind').all();
|
|
563
|
+
for (const row of grouped) {
|
|
564
|
+
if (!isRecord(row))
|
|
565
|
+
continue;
|
|
566
|
+
const principleId = readOwnString(row, 'principle_id');
|
|
567
|
+
const level = readOwnString(row, 'level');
|
|
568
|
+
const kind = readOwnString(row, 'kind');
|
|
569
|
+
const n = Object.hasOwn(row, 'n') && typeof row.n === 'number' ? row.n : 0;
|
|
570
|
+
if (principleId === undefined || level === undefined || kind === undefined)
|
|
571
|
+
continue;
|
|
572
|
+
const bucket = applicationsByPrinciple.get(principleId) ?? { deterministic: 0, selfReported: 0, presence: 0, recent: [] };
|
|
573
|
+
if (level === 'effect' && (kind === 'rule_blocked' || kind === 'auto_correct_applied'))
|
|
574
|
+
bucket.deterministic += n;
|
|
575
|
+
else if (level === 'effect' && kind === 'self_reported')
|
|
576
|
+
bucket.selfReported += n;
|
|
577
|
+
else if (level === 'presence')
|
|
578
|
+
bucket.presence += n;
|
|
579
|
+
applicationsByPrinciple.set(principleId, bucket);
|
|
580
|
+
}
|
|
581
|
+
const recent = db.prepare('SELECT principle_id, level, kind, digest, created_at FROM principle_applications ORDER BY created_at DESC LIMIT 250').all();
|
|
582
|
+
for (const row of recent) {
|
|
583
|
+
if (!isRecord(row))
|
|
584
|
+
continue;
|
|
585
|
+
const principleId = readOwnString(row, 'principle_id');
|
|
586
|
+
if (principleId === undefined)
|
|
587
|
+
continue;
|
|
588
|
+
const bucket = applicationsByPrinciple.get(principleId) ?? { deterministic: 0, selfReported: 0, presence: 0, recent: [] };
|
|
589
|
+
if (bucket.recent.length < 5) {
|
|
590
|
+
bucket.recent.push({
|
|
591
|
+
text: `${readOwnString(row, 'kind') ?? 'observation'}${readOwnString(row, 'digest') !== undefined ? `:${(readOwnString(row, 'digest') ?? '').slice(0, 120)}` : ''}`,
|
|
592
|
+
...(readOwnString(row, 'created_at') === undefined ? {} : { createdAt: readOwnString(row, 'created_at') }),
|
|
593
|
+
level: readOwnString(row, 'level') ?? 'unknown',
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
// Codex review P2: a missing/unreadable ledger table is an UNAVAILABLE
|
|
600
|
+
// source, not an available-with-zero source (Codex P2 fix).
|
|
601
|
+
applicationsAvailable = false;
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
...tables,
|
|
605
|
+
piArtifactContentRows,
|
|
606
|
+
candidates,
|
|
607
|
+
genericArtifacts,
|
|
608
|
+
piArtifactTaskKinds,
|
|
609
|
+
applicationsByPrinciple,
|
|
610
|
+
applicationsAvailable,
|
|
611
|
+
dbAvailable: true,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
finally {
|
|
615
|
+
conn.close();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
// ── PI artifact content parsers (rc-1/rc-2: untrusted JSON, validated) ─────
|
|
620
|
+
function parseScribeContent(artifactId, contentJson, updatedAt) {
|
|
621
|
+
let parsed;
|
|
622
|
+
try {
|
|
623
|
+
parsed = JSON.parse(contentJson);
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
if (!isRecord(parsed))
|
|
629
|
+
return null;
|
|
630
|
+
const draft = parsed.principleDraft;
|
|
631
|
+
const draftRecord = isRecord(draft) ? draft : undefined;
|
|
632
|
+
// Codex P1 follow-up: a scribe artifact WITHOUT a statement carries no
|
|
633
|
+
// owner-facing principle material — it must not be selected as "the" scribe
|
|
634
|
+
// material for the principle (that would shadow a real one and would let
|
|
635
|
+
// per-subject gating treat its revision as scribe-backed).
|
|
636
|
+
if (draftRecord === undefined || readOwnString(draftRecord, 'statement') === undefined)
|
|
637
|
+
return null;
|
|
638
|
+
const risksValue = parsed.risks;
|
|
639
|
+
const risks = Array.isArray(risksValue) ? risksValue.filter((item) => typeof item === 'string' && item.trim() !== '') : [];
|
|
640
|
+
const applicability = draftRecord !== undefined && Array.isArray(draftRecord.applicability)
|
|
641
|
+
? draftRecord.applicability.filter((item) => typeof item === 'string')
|
|
642
|
+
: [];
|
|
643
|
+
const antiPatterns = draftRecord !== undefined && Array.isArray(draftRecord.antiPatterns)
|
|
644
|
+
? draftRecord.antiPatterns.filter((item) => typeof item === 'string')
|
|
645
|
+
: [];
|
|
646
|
+
const intentContract = isRecord(parsed.intentContract) ? parsed.intentContract : undefined;
|
|
647
|
+
const targetBehavior = intentContract !== undefined ? readOwnString(intentContract, 'targetBehavior') : undefined;
|
|
648
|
+
return {
|
|
649
|
+
artifactId,
|
|
650
|
+
statement: readOwnString(draftRecord, 'statement'),
|
|
651
|
+
...(draftRecord !== undefined && readOwnString(draftRecord, 'rationale') !== undefined ? { rationale: readOwnString(draftRecord, 'rationale') } : {}),
|
|
652
|
+
applicability,
|
|
653
|
+
antiPatterns,
|
|
654
|
+
risks,
|
|
655
|
+
...(targetBehavior === undefined ? {} : { targetBehavior }),
|
|
656
|
+
updatedAt,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function parsePhilosopherContent(artifactId, contentJson) {
|
|
660
|
+
let parsed;
|
|
661
|
+
try {
|
|
662
|
+
parsed = JSON.parse(contentJson);
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
if (!isRecord(parsed))
|
|
668
|
+
return null;
|
|
669
|
+
const candidateRecord = isRecord(parsed.principleCandidate) ? parsed.principleCandidate : undefined;
|
|
670
|
+
if (candidateRecord === undefined)
|
|
671
|
+
return null;
|
|
672
|
+
return {
|
|
673
|
+
artifactId,
|
|
674
|
+
...(readOwnString(candidateRecord, 'title') === undefined ? {} : { title: readOwnString(candidateRecord, 'title') }),
|
|
675
|
+
...(readOwnString(candidateRecord, 'rationale') === undefined ? {} : { rationale: readOwnString(candidateRecord, 'rationale') }),
|
|
676
|
+
...(readOwnString(candidateRecord, 'scope') === undefined ? {} : { scope: readOwnString(candidateRecord, 'scope') }),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
// Compact semantic title helper for the Library list (same selector, no
|
|
680
|
+
// second eligibility algorithm).
|
|
681
|
+
export function compactSemanticTitle(view) {
|
|
682
|
+
if (view.learnedPrinciple.status === 'known') {
|
|
683
|
+
return { status: 'known', text: view.learnedPrinciple.value.text, sourceTier: view.learnedPrinciple.value.sourceTier };
|
|
684
|
+
}
|
|
685
|
+
return { status: 'unknown', reasonText: view.learnedPrinciple.reason.ownerText };
|
|
686
|
+
}
|