@clien-ai/mcp 0.13.1 → 0.13.3
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/dist/hypothesis-semantics.js +21 -4
- package/dist/hypothesis-semantics.js.map +1 -1
- package/dist/lib/excerpt-suitability.js +46 -0
- package/dist/lib/excerpt-suitability.js.map +1 -0
- package/dist/tools/collections.js +7 -3
- package/dist/tools/collections.js.map +1 -1
- package/dist/tools/market-sizing-proof.js +3 -1
- package/dist/tools/market-sizing-proof.js.map +1 -1
- package/dist/tools/receipt-children.js +4 -1
- package/dist/tools/receipt-children.js.map +1 -1
- package/dist/tools/report-digest.js +100 -38
- package/dist/tools/report-digest.js.map +1 -1
- package/dist/types/report.js +357 -13
- package/dist/types/report.js.map +1 -1
- package/package.json +1 -1
package/dist/types/report.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* — see how research.ts handles it.
|
|
13
13
|
*/
|
|
14
14
|
import { z } from 'zod';
|
|
15
|
-
import { CONFIDENCE_BASES, CONFIDENCE_LEVELS, EVIDENCE_SUFFICIENCY_LEVELS, HYPOTHESIS_PRIORITIES, HYPOTHESIS_SEMANTICS_VERSION, LEGACY_HYPOTHESIS_SEMANTICS_VERSION, ROBUSTNESS_OUTCOMES, VERDICT_STATUSES, finalClauseCoverageMatchesIntegrity, hasCoherentRobustnessWorking, hypothesisWebReceiptAuthenticates, normalizeFinalHypothesisState, readRobustnessCounts, robustnessWorkingContradictsSummary, } from '../hypothesis-semantics.js';
|
|
15
|
+
import { CONFIDENCE_BASES, CONFIDENCE_LEVELS, EVIDENCE_SUFFICIENCY_LEVELS, HYPOTHESIS_PRIORITIES, HYPOTHESIS_SEMANTICS_VERSION, LEGACY_HYPOTHESIS_SEMANTICS_VERSION, ROBUSTNESS_OUTCOMES, VERDICT_STATUSES, finalClauseCoverageMatchesIntegrity, hasCoherentRobustnessWorking, hypothesisWebReceiptAuthenticates, normalizeFinalHypothesisState, readRobustnessCounts, reportClaimNeedsIndependentMarketAuthority, robustnessWorkingContradictsSummary, sourceAuthenticatesMarketEconomics, } from '../hypothesis-semantics.js';
|
|
16
16
|
// ⚠️ A types module importing from `tools/` reads backwards, and it is deliberate.
|
|
17
17
|
// `content-type-display.ts` imports NOTHING — it is the package's content-type vocabulary, which
|
|
18
18
|
// happens to live beside the two renderers that were its first callers, and the FUL-341 coupling
|
|
@@ -21,6 +21,7 @@ import { CONFIDENCE_BASES, CONFIDENCE_LEVELS, EVIDENCE_SUFFICIENCY_LEVELS, HYPOT
|
|
|
21
21
|
import { CONTENT_TYPE_EXCLUSIONS, CONTENT_TYPE_STATES } from '../tools/content-type-display.js';
|
|
22
22
|
import { parseReceiptId } from '../tools/receipt-children.js';
|
|
23
23
|
import { readSemanticTheme } from './semantic-theme.js';
|
|
24
|
+
import { isDisplaySuitableExcerpt } from '../lib/excerpt-suitability.js';
|
|
24
25
|
export { CONFIDENCE_BASES, CONFIDENCE_LEVELS, EVIDENCE_SUFFICIENCY_LEVELS, HYPOTHESIS_SEMANTICS_VERSION, ROBUSTNESS_OUTCOMES, VERDICT_STATUSES, } from '../hypothesis-semantics.js';
|
|
25
26
|
/**
|
|
26
27
|
* A competitor discovered during the research run.
|
|
@@ -690,6 +691,7 @@ export const ReportEvidenceSchema = z
|
|
|
690
691
|
'source type is genuinely unknown, NOT checked and found benign. ' +
|
|
691
692
|
CONTENT_TYPE_EXCLUSIONS),
|
|
692
693
|
publisherSubjectRelationship: z.enum(['independent', 'self', 'unknown']).optional(),
|
|
694
|
+
marketEconomicsAuthority: z.enum(['independent', 'commercial_report_seller', 'unknown']).optional(),
|
|
693
695
|
})
|
|
694
696
|
.passthrough();
|
|
695
697
|
/**
|
|
@@ -1130,6 +1132,114 @@ export function sanitizeMcpReportTitle(value) {
|
|
|
1130
1132
|
const words = latinWords.filter((word) => !/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u.test(word)).length + cjkCharacters.length;
|
|
1131
1133
|
return words >= 6 && words <= 12 ? title : 'Report';
|
|
1132
1134
|
}
|
|
1135
|
+
export const MCP_REPORT_DECISION_SPINE_VERSION = 1;
|
|
1136
|
+
export const MCP_REPORT_DECISION_ACTION_METHODS = [
|
|
1137
|
+
'target_user_test',
|
|
1138
|
+
'independent_market_evidence',
|
|
1139
|
+
];
|
|
1140
|
+
export const MCP_REPORT_DECISION_ACTION_PREFIXES = {
|
|
1141
|
+
target_user_test: 'Test ',
|
|
1142
|
+
independent_market_evidence: 'Collect independent market evidence for ',
|
|
1143
|
+
};
|
|
1144
|
+
export const MCP_REPORT_MIN_EXECUTIVE_SUMMARY_WORDS = 150;
|
|
1145
|
+
export const MCP_REPORT_MAX_EXECUTIVE_SUMMARY_WORDS = 250;
|
|
1146
|
+
export const MCP_REPORT_MIN_RECOMMENDATIONS = 2;
|
|
1147
|
+
export const MCP_REPORT_MAX_RECOMMENDATIONS = 8;
|
|
1148
|
+
/** Mirrors the producer's CJK-aware decision-summary counter at this package boundary. */
|
|
1149
|
+
export function countMcpReportWords(value) {
|
|
1150
|
+
const cjk = /[-ヿ㐀-䶿一-鿿豈-ヲ-゚ᄀ-ᇿ-가-힣]/gu;
|
|
1151
|
+
const trimmed = value.trim();
|
|
1152
|
+
if (trimmed === '')
|
|
1153
|
+
return 0;
|
|
1154
|
+
const cjkCharacters = trimmed.match(cjk)?.length ?? 0;
|
|
1155
|
+
if (cjkCharacters === 0)
|
|
1156
|
+
return trimmed.split(/\s+/u).length;
|
|
1157
|
+
return cjkCharacters + trimmed.split(/\s+/u).filter((word) => word.replace(cjk, '') !== '').length;
|
|
1158
|
+
}
|
|
1159
|
+
export const MCP_REPORT_DECISION_SPINE_LABELS = [
|
|
1160
|
+
'Decision:',
|
|
1161
|
+
'Strongest support:',
|
|
1162
|
+
'Strongest contradiction:',
|
|
1163
|
+
'Market and community context:',
|
|
1164
|
+
'Material uncertainty:',
|
|
1165
|
+
'Next learning step:',
|
|
1166
|
+
];
|
|
1167
|
+
function authenticatesMcpDecisionSpine(record, projectedRows, projectedRecommendations, reconciledIds) {
|
|
1168
|
+
if (record.reportDecisionSpineVersion !== MCP_REPORT_DECISION_SPINE_VERSION || reconciledIds.size === 0)
|
|
1169
|
+
return false;
|
|
1170
|
+
if (typeof record.executiveSummary !== 'string')
|
|
1171
|
+
return false;
|
|
1172
|
+
const summary = record.executiveSummary.trim();
|
|
1173
|
+
if (countMcpReportWords(summary) < MCP_REPORT_MIN_EXECUTIVE_SUMMARY_WORDS ||
|
|
1174
|
+
countMcpReportWords(summary) > MCP_REPORT_MAX_EXECUTIVE_SUMMARY_WORDS)
|
|
1175
|
+
return false;
|
|
1176
|
+
let labelOffset = -1;
|
|
1177
|
+
for (const label of MCP_REPORT_DECISION_SPINE_LABELS) {
|
|
1178
|
+
labelOffset = summary.indexOf(label, labelOffset + 1);
|
|
1179
|
+
if (labelOffset < 0)
|
|
1180
|
+
return false;
|
|
1181
|
+
}
|
|
1182
|
+
const knownIds = new Set();
|
|
1183
|
+
for (const rawRow of projectedRows) {
|
|
1184
|
+
if (rawRow === null || typeof rawRow !== 'object' || Array.isArray(rawRow))
|
|
1185
|
+
return false;
|
|
1186
|
+
const id = rawRow.hypothesisId;
|
|
1187
|
+
if (typeof id !== 'string' || !id.trim() || knownIds.has(id))
|
|
1188
|
+
return false;
|
|
1189
|
+
knownIds.add(id);
|
|
1190
|
+
}
|
|
1191
|
+
if (!Array.isArray(record.recommendations) ||
|
|
1192
|
+
projectedRecommendations !== record.recommendations ||
|
|
1193
|
+
record.recommendations.length < MCP_REPORT_MIN_RECOMMENDATIONS ||
|
|
1194
|
+
record.recommendations.length > MCP_REPORT_MAX_RECOMMENDATIONS)
|
|
1195
|
+
return false;
|
|
1196
|
+
const recommendations = [];
|
|
1197
|
+
for (const rawRecommendation of record.recommendations) {
|
|
1198
|
+
if (rawRecommendation === null || typeof rawRecommendation !== 'object' || Array.isArray(rawRecommendation))
|
|
1199
|
+
return false;
|
|
1200
|
+
const recommendation = rawRecommendation;
|
|
1201
|
+
if (typeof recommendation.action !== 'string' || !recommendation.action.trim())
|
|
1202
|
+
return false;
|
|
1203
|
+
if (!Array.isArray(recommendation.relatedHypotheses) || recommendation.relatedHypotheses.length === 0)
|
|
1204
|
+
return false;
|
|
1205
|
+
const relatedHypotheses = recommendation.relatedHypotheses;
|
|
1206
|
+
if (!relatedHypotheses.every((id) => typeof id === 'string' && knownIds.has(id)))
|
|
1207
|
+
return false;
|
|
1208
|
+
recommendations.push({ action: recommendation.action.trim(), relatedHypotheses });
|
|
1209
|
+
}
|
|
1210
|
+
const rawMethods = record.reportDecisionActionMethods;
|
|
1211
|
+
const rawPrimaryActions = record.reportDecisionPrimaryActions;
|
|
1212
|
+
if (rawMethods === null || typeof rawMethods !== 'object' || Array.isArray(rawMethods) ||
|
|
1213
|
+
rawPrimaryActions === null || typeof rawPrimaryActions !== 'object' || Array.isArray(rawPrimaryActions))
|
|
1214
|
+
return false;
|
|
1215
|
+
const methods = new Map();
|
|
1216
|
+
const primaryActions = new Map();
|
|
1217
|
+
for (const [id, rawMethod] of Object.entries(rawMethods)) {
|
|
1218
|
+
if (!knownIds.has(id) || !MCP_REPORT_DECISION_ACTION_METHODS.includes(rawMethod))
|
|
1219
|
+
return false;
|
|
1220
|
+
methods.set(id, rawMethod);
|
|
1221
|
+
}
|
|
1222
|
+
for (const [id, rawAction] of Object.entries(rawPrimaryActions)) {
|
|
1223
|
+
if (!knownIds.has(id) || typeof rawAction !== 'string' || !rawAction.trim())
|
|
1224
|
+
return false;
|
|
1225
|
+
primaryActions.set(id, rawAction);
|
|
1226
|
+
}
|
|
1227
|
+
if (methods.size !== reconciledIds.size || primaryActions.size !== reconciledIds.size ||
|
|
1228
|
+
[...methods.keys()].some((id) => !reconciledIds.has(id)) ||
|
|
1229
|
+
[...primaryActions.keys()].some((id) => !reconciledIds.has(id)))
|
|
1230
|
+
return false;
|
|
1231
|
+
for (const id of reconciledIds) {
|
|
1232
|
+
const method = methods.get(id);
|
|
1233
|
+
const primaryAction = primaryActions.get(id);
|
|
1234
|
+
const expectedPrefix = method ? MCP_REPORT_DECISION_ACTION_PREFIXES[method] : null;
|
|
1235
|
+
const matchingRecommendations = recommendations.filter((recommendation) => recommendation.relatedHypotheses.length === 1 &&
|
|
1236
|
+
recommendation.relatedHypotheses[0] === id &&
|
|
1237
|
+
recommendation.action === primaryAction);
|
|
1238
|
+
if (!expectedPrefix || !primaryAction?.startsWith(expectedPrefix) || matchingRecommendations.length !== 1)
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1133
1243
|
export function projectReportDataForMcp(raw) {
|
|
1134
1244
|
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
1135
1245
|
return raw;
|
|
@@ -1153,6 +1263,9 @@ export function projectReportDataForMcp(raw) {
|
|
|
1153
1263
|
value.replace(/[\p{C}\p{Z}\p{Default_Ignorable_Code_Point}]/gu, '').length > 0;
|
|
1154
1264
|
const hasHighlights = Object.prototype.hasOwnProperty.call(record, 'interviewHighlights');
|
|
1155
1265
|
const hasHypothesisResults = Object.prototype.hasOwnProperty.call(record, 'hypothesisResults');
|
|
1266
|
+
const hasClaims = Object.prototype.hasOwnProperty.call(record, 'claims');
|
|
1267
|
+
const hasReportClaims = Object.prototype.hasOwnProperty.call(record, 'reportClaims');
|
|
1268
|
+
const hasReportEvidence = Object.prototype.hasOwnProperty.call(record, 'reportEvidence');
|
|
1156
1269
|
const hasReportTitle = Object.prototype.hasOwnProperty.call(record, 'reportTitle');
|
|
1157
1270
|
const hasOverview = Object.prototype.hasOwnProperty.call(record, 'marketOverview') ||
|
|
1158
1271
|
Object.prototype.hasOwnProperty.call(record, 'communityOverview');
|
|
@@ -1160,15 +1273,88 @@ export function projectReportDataForMcp(raw) {
|
|
|
1160
1273
|
const hasPersonas = Object.prototype.hasOwnProperty.call(record, 'personas');
|
|
1161
1274
|
const hasForumResearch = Object.prototype.hasOwnProperty.call(record, 'forumResearch');
|
|
1162
1275
|
const hasHypothesisSemanticsVersion = Object.prototype.hasOwnProperty.call(record, 'hypothesisSemanticsVersion');
|
|
1276
|
+
const hasDecisionSpineMetadata = Object.prototype.hasOwnProperty.call(record, 'reportDecisionSpineVersion') ||
|
|
1277
|
+
Object.prototype.hasOwnProperty.call(record, 'reportDecisionActionMethods') ||
|
|
1278
|
+
Object.prototype.hasOwnProperty.call(record, 'reportDecisionPrimaryActions');
|
|
1163
1279
|
const methodology = record.methodology;
|
|
1164
1280
|
const hasRedditNote = methodology !== null &&
|
|
1165
1281
|
typeof methodology === 'object' &&
|
|
1166
1282
|
!Array.isArray(methodology) &&
|
|
1167
1283
|
Object.prototype.hasOwnProperty.call(methodology, 'redditRetrieval');
|
|
1168
|
-
if (!hasHighlights && !hasRedditNote && !hasHypothesisResults && !hasOverview && !hasReportTitle && !hasCrossCutting && !hasPersonas && !hasForumResearch && !hasHypothesisSemanticsVersion)
|
|
1284
|
+
if (!hasHighlights && !hasRedditNote && !hasHypothesisResults && !hasClaims && !hasReportClaims && !hasReportEvidence && !hasOverview && !hasReportTitle && !hasCrossCutting && !hasPersonas && !hasForumResearch && !hasHypothesisSemanticsVersion && !hasDecisionSpineMetadata)
|
|
1169
1285
|
return raw;
|
|
1170
1286
|
const projected = { ...record };
|
|
1287
|
+
// These fields authenticate producer-owned repair, but are not report evidence and never cross
|
|
1288
|
+
// the public MCP boundary on either the typed or schema-drift exit.
|
|
1289
|
+
delete projected.reportDecisionSpineVersion;
|
|
1290
|
+
delete projected.reportDecisionActionMethods;
|
|
1291
|
+
delete projected.reportDecisionPrimaryActions;
|
|
1292
|
+
// Source excerpts are immutable evidence in storage, but every MCP channel is a display
|
|
1293
|
+
// surface. Rebuild only the containers which can carry verbatim source text, preserving safe
|
|
1294
|
+
// strings byte-for-byte and omitting an unsafe exact receipt rather than repairing its text.
|
|
1295
|
+
const projectReceipts = (value) => {
|
|
1296
|
+
if (!Array.isArray(value))
|
|
1297
|
+
return undefined;
|
|
1298
|
+
const safeReceipts = [];
|
|
1299
|
+
for (const rawReceipt of value) {
|
|
1300
|
+
const receipt = asProjectionRecord(rawReceipt);
|
|
1301
|
+
if (!receipt || typeof receipt.excerpt !== 'string')
|
|
1302
|
+
return undefined;
|
|
1303
|
+
if (isDisplaySuitableExcerpt(receipt.excerpt))
|
|
1304
|
+
safeReceipts.push({ ...receipt });
|
|
1305
|
+
}
|
|
1306
|
+
return safeReceipts;
|
|
1307
|
+
};
|
|
1308
|
+
const projectSource = (rawSource) => {
|
|
1309
|
+
const source = asProjectionRecord(rawSource);
|
|
1310
|
+
if (!source)
|
|
1311
|
+
return rawSource;
|
|
1312
|
+
const next = { ...source };
|
|
1313
|
+
if (Object.prototype.hasOwnProperty.call(source, 'quote')) {
|
|
1314
|
+
if (typeof source.quote === 'string' && !isDisplaySuitableExcerpt(source.quote)) {
|
|
1315
|
+
// `undefined` is absent on the JSON wire but remains distinguishable from a historical
|
|
1316
|
+
// source which never had a quote, so renderers can downgrade the dependent receipt.
|
|
1317
|
+
next.quote = undefined;
|
|
1318
|
+
}
|
|
1319
|
+
else if (typeof source.quote !== 'string') {
|
|
1320
|
+
delete next.quote;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (Object.prototype.hasOwnProperty.call(source, 'receipts')) {
|
|
1324
|
+
const receipts = projectReceipts(source.receipts);
|
|
1325
|
+
if (receipts === undefined)
|
|
1326
|
+
delete next.receipts;
|
|
1327
|
+
else
|
|
1328
|
+
next.receipts = receipts;
|
|
1329
|
+
}
|
|
1330
|
+
return next;
|
|
1331
|
+
};
|
|
1332
|
+
if (hasReportEvidence) {
|
|
1333
|
+
projected.reportEvidence = Array.isArray(record.reportEvidence)
|
|
1334
|
+
? record.reportEvidence.map(projectSource)
|
|
1335
|
+
: [];
|
|
1336
|
+
}
|
|
1337
|
+
const reportEvidence = Array.isArray(projected.reportEvidence) ? projected.reportEvidence : [];
|
|
1338
|
+
const rejectedMarketEconomics = (Array.isArray(record.reportClaims) ? record.reportClaims : [])
|
|
1339
|
+
.some((rawClaim) => {
|
|
1340
|
+
const claim = asProjectionRecord(rawClaim);
|
|
1341
|
+
if (!claim || !reportClaimNeedsIndependentMarketAuthority(claim.section, claim.text))
|
|
1342
|
+
return false;
|
|
1343
|
+
const state = claim.state;
|
|
1344
|
+
const attestation = asProjectionRecord(claim.attestation);
|
|
1345
|
+
const sourceId = state === 'GROUNDED'
|
|
1346
|
+
? claim.sourceId
|
|
1347
|
+
: state === 'NO_RECEIPT' && hasProjectionVisibleContent(attestation?.quote)
|
|
1348
|
+
? attestation?.sourceId
|
|
1349
|
+
: null;
|
|
1350
|
+
const match = typeof sourceId === 'string' ? /^RRCP-s(0|[1-9]\d*)$/.exec(sourceId) : null;
|
|
1351
|
+
const source = match ? asProjectionRecord(reportEvidence[Number(match[1])]) : null;
|
|
1352
|
+
return Boolean(source && !sourceAuthenticatesMarketEconomics(source));
|
|
1353
|
+
});
|
|
1354
|
+
if (rejectedMarketEconomics)
|
|
1355
|
+
delete projected.recommendations;
|
|
1171
1356
|
let clauseSemanticsFailure = false;
|
|
1357
|
+
const reconciledClauseDowngradeIds = new Set();
|
|
1172
1358
|
const outerVersion = record.hypothesisSemanticsVersion;
|
|
1173
1359
|
const outerVersionPresent = hasHypothesisSemanticsVersion;
|
|
1174
1360
|
const rawHypothesisRows = Array.isArray(record.hypothesisResults) ? record.hypothesisResults : [];
|
|
@@ -1192,6 +1378,10 @@ export function projectReportDataForMcp(raw) {
|
|
|
1192
1378
|
if (!persona)
|
|
1193
1379
|
return rawPersona;
|
|
1194
1380
|
const nextPersona = { ...persona };
|
|
1381
|
+
if (Array.isArray(persona.sources))
|
|
1382
|
+
nextPersona.sources = persona.sources.map(projectSource);
|
|
1383
|
+
else if (Object.prototype.hasOwnProperty.call(persona, 'sources'))
|
|
1384
|
+
delete nextPersona.sources;
|
|
1195
1385
|
const portrait = ReportPortraitSchema.safeParse(persona.portrait);
|
|
1196
1386
|
if (portrait.success)
|
|
1197
1387
|
nextPersona.portrait = portrait.data;
|
|
@@ -1219,6 +1409,19 @@ export function projectReportDataForMcp(raw) {
|
|
|
1219
1409
|
if (!thread)
|
|
1220
1410
|
return rawThread;
|
|
1221
1411
|
const nextThread = { ...thread };
|
|
1412
|
+
if (Array.isArray(thread.relevantQuotes)) {
|
|
1413
|
+
nextThread.relevantQuotes = thread.relevantQuotes.filter(isDisplaySuitableExcerpt);
|
|
1414
|
+
}
|
|
1415
|
+
else if (Object.prototype.hasOwnProperty.call(thread, 'relevantQuotes')) {
|
|
1416
|
+
delete nextThread.relevantQuotes;
|
|
1417
|
+
}
|
|
1418
|
+
if (Object.prototype.hasOwnProperty.call(thread, 'receipts')) {
|
|
1419
|
+
const receipts = projectReceipts(thread.receipts);
|
|
1420
|
+
if (receipts === undefined)
|
|
1421
|
+
delete nextThread.receipts;
|
|
1422
|
+
else
|
|
1423
|
+
nextThread.receipts = receipts;
|
|
1424
|
+
}
|
|
1222
1425
|
const theme = typeof thread.title === 'string'
|
|
1223
1426
|
? readSemanticTheme(thread.theme, thread.title)
|
|
1224
1427
|
: null;
|
|
@@ -1240,6 +1443,105 @@ export function projectReportDataForMcp(raw) {
|
|
|
1240
1443
|
delete projected.forumResearch;
|
|
1241
1444
|
}
|
|
1242
1445
|
}
|
|
1446
|
+
const projectionSupportsSpan = (excerpt, span) => {
|
|
1447
|
+
if (!isDisplaySuitableExcerpt(excerpt))
|
|
1448
|
+
return false;
|
|
1449
|
+
if (span === undefined || span === null)
|
|
1450
|
+
return true;
|
|
1451
|
+
if (typeof span !== 'string')
|
|
1452
|
+
return false;
|
|
1453
|
+
const normalize = (value) => value
|
|
1454
|
+
.normalize('NFC')
|
|
1455
|
+
.replace(/\s+/gu, ' ')
|
|
1456
|
+
.trim()
|
|
1457
|
+
.toLocaleLowerCase('en-US');
|
|
1458
|
+
const normalizedSpan = normalize(span);
|
|
1459
|
+
return normalizedSpan.length > 0 && normalize(excerpt).includes(normalizedSpan);
|
|
1460
|
+
};
|
|
1461
|
+
const omitClaimReceipt = (claim) => {
|
|
1462
|
+
const next = { ...claim };
|
|
1463
|
+
delete next.sourceId;
|
|
1464
|
+
delete next.receiptId;
|
|
1465
|
+
delete next.quoteSpan;
|
|
1466
|
+
delete next.attestation;
|
|
1467
|
+
return next;
|
|
1468
|
+
};
|
|
1469
|
+
if (hasReportClaims) {
|
|
1470
|
+
projected.reportClaims = Array.isArray(record.reportClaims)
|
|
1471
|
+
? record.reportClaims.map((rawClaim) => {
|
|
1472
|
+
const claim = asProjectionRecord(rawClaim);
|
|
1473
|
+
if (!claim)
|
|
1474
|
+
return rawClaim;
|
|
1475
|
+
if (claim.state === 'GROUNDED') {
|
|
1476
|
+
const hasSourceId = Object.prototype.hasOwnProperty.call(claim, 'sourceId');
|
|
1477
|
+
const match = typeof claim.sourceId === 'string'
|
|
1478
|
+
? /^RRCP-s(0|[1-9]\d*)$/.exec(claim.sourceId)
|
|
1479
|
+
: null;
|
|
1480
|
+
const rawSource = match
|
|
1481
|
+
? asProjectionRecord(Array.isArray(record.reportEvidence)
|
|
1482
|
+
? record.reportEvidence[Number(match[1])]
|
|
1483
|
+
: undefined)
|
|
1484
|
+
: null;
|
|
1485
|
+
if (!hasSourceId && Object.prototype.hasOwnProperty.call(claim, 'quoteSpan')) {
|
|
1486
|
+
return omitClaimReceipt(claim);
|
|
1487
|
+
}
|
|
1488
|
+
if (hasSourceId && (!rawSource || !projectionSupportsSpan(rawSource.quote, claim.quoteSpan))) {
|
|
1489
|
+
return omitClaimReceipt(claim);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
else if (claim.state === 'NO_RECEIPT') {
|
|
1493
|
+
const attestation = asProjectionRecord(claim.attestation);
|
|
1494
|
+
if (attestation && !isDisplaySuitableExcerpt(attestation.quote)) {
|
|
1495
|
+
const next = { ...claim };
|
|
1496
|
+
delete next.attestation;
|
|
1497
|
+
return next;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return { ...claim };
|
|
1501
|
+
})
|
|
1502
|
+
: [];
|
|
1503
|
+
}
|
|
1504
|
+
if (Object.prototype.hasOwnProperty.call(record, 'claims')) {
|
|
1505
|
+
projected.claims = Array.isArray(record.claims)
|
|
1506
|
+
? record.claims.map((rawClaim) => {
|
|
1507
|
+
const claim = asProjectionRecord(rawClaim);
|
|
1508
|
+
if (!claim || claim.state !== 'GROUNDED')
|
|
1509
|
+
return rawClaim;
|
|
1510
|
+
const hasSourceId = Object.prototype.hasOwnProperty.call(claim, 'sourceId');
|
|
1511
|
+
const match = typeof claim.sourceId === 'string'
|
|
1512
|
+
? /^RCP-p(0|[1-9]\d*)-s(0|[1-9]\d*)$/.exec(claim.sourceId)
|
|
1513
|
+
: null;
|
|
1514
|
+
const personas = Array.isArray(record.personas) ? record.personas : [];
|
|
1515
|
+
const persona = match ? asProjectionRecord(personas[Number(match[1])]) : null;
|
|
1516
|
+
const sources = Array.isArray(persona?.sources) ? persona.sources : [];
|
|
1517
|
+
const source = match ? asProjectionRecord(sources[Number(match[2])]) : null;
|
|
1518
|
+
if (!hasSourceId && Object.prototype.hasOwnProperty.call(claim, 'quoteSpan')) {
|
|
1519
|
+
return omitClaimReceipt(claim);
|
|
1520
|
+
}
|
|
1521
|
+
if (hasSourceId && !source)
|
|
1522
|
+
return omitClaimReceipt(claim);
|
|
1523
|
+
if (!source)
|
|
1524
|
+
return { ...claim };
|
|
1525
|
+
const receipts = Array.isArray(source.receipts) ? source.receipts : [];
|
|
1526
|
+
const hasReceiptId = Object.prototype.hasOwnProperty.call(claim, 'receiptId');
|
|
1527
|
+
if (hasReceiptId) {
|
|
1528
|
+
const matches = receipts.filter((rawReceipt) => asProjectionRecord(rawReceipt)?.receiptId === claim.receiptId);
|
|
1529
|
+
const receipt = matches.length === 1 ? asProjectionRecord(matches[0]) : null;
|
|
1530
|
+
return receipt && projectionSupportsSpan(receipt.excerpt, claim.quoteSpan)
|
|
1531
|
+
? { ...claim }
|
|
1532
|
+
: omitClaimReceipt(claim);
|
|
1533
|
+
}
|
|
1534
|
+
if (typeof source.quote === 'string' &&
|
|
1535
|
+
!projectionSupportsSpan(source.quote, claim.quoteSpan)) {
|
|
1536
|
+
return omitClaimReceipt(claim);
|
|
1537
|
+
}
|
|
1538
|
+
if (claim.quoteSpan !== undefined && typeof source.quote !== 'string') {
|
|
1539
|
+
return omitClaimReceipt(claim);
|
|
1540
|
+
}
|
|
1541
|
+
return { ...claim };
|
|
1542
|
+
})
|
|
1543
|
+
: [];
|
|
1544
|
+
}
|
|
1243
1545
|
const projectOverviewRef = (rawRef) => {
|
|
1244
1546
|
if (rawRef === null || typeof rawRef !== 'object' || Array.isArray(rawRef))
|
|
1245
1547
|
return null;
|
|
@@ -1288,6 +1590,32 @@ export function projectReportDataForMcp(raw) {
|
|
|
1288
1590
|
const safeRef = projectOverviewRef(rawRef);
|
|
1289
1591
|
if (!safeRef)
|
|
1290
1592
|
continue;
|
|
1593
|
+
if (safeRef.kind === 'report_evidence') {
|
|
1594
|
+
const match = typeof safeRef.sourceId === 'string'
|
|
1595
|
+
? /^RRCP-s(0|[1-9]\d*)$/.exec(safeRef.sourceId)
|
|
1596
|
+
: null;
|
|
1597
|
+
const source = match
|
|
1598
|
+
? asProjectionRecord(Array.isArray(projected.reportEvidence)
|
|
1599
|
+
? projected.reportEvidence[Number(match[1])]
|
|
1600
|
+
: undefined)
|
|
1601
|
+
: null;
|
|
1602
|
+
if (!source || !projectionSupportsSpan(source.quote, claim.span))
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
if (safeRef.kind === 'forum_receipt') {
|
|
1606
|
+
const matches = (Array.isArray(asProjectionRecord(projected.forumResearch)?.threads)
|
|
1607
|
+
? asProjectionRecord(projected.forumResearch).threads
|
|
1608
|
+
: []).flatMap((rawThread) => {
|
|
1609
|
+
const thread = asProjectionRecord(rawThread);
|
|
1610
|
+
return (Array.isArray(thread?.receipts) ? thread.receipts : []).filter((rawReceipt) => {
|
|
1611
|
+
const receipt = asProjectionRecord(rawReceipt);
|
|
1612
|
+
return receipt !== null && receipt.receiptId === safeRef.receiptId &&
|
|
1613
|
+
projectionSupportsSpan(receipt.excerpt, claim.span);
|
|
1614
|
+
});
|
|
1615
|
+
});
|
|
1616
|
+
if (matches.length !== 1)
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1291
1619
|
const refKey = safeRef.kind === 'report_evidence'
|
|
1292
1620
|
? `${safeRef.kind}:${safeRef.sourceId}`
|
|
1293
1621
|
: safeRef.kind === 'forum_receipt'
|
|
@@ -1303,13 +1631,13 @@ export function projectReportDataForMcp(raw) {
|
|
|
1303
1631
|
return [{
|
|
1304
1632
|
span: claim.span,
|
|
1305
1633
|
claimType: claim.claimType,
|
|
1306
|
-
evidenceRefsTruncated: rawEvidenceRefs.length > MCP_OVERVIEW_REF_CAP,
|
|
1634
|
+
evidenceRefsTruncated: claim.evidenceRefsTruncated === true || rawEvidenceRefs.length > MCP_OVERVIEW_REF_CAP,
|
|
1307
1635
|
evidenceRefs,
|
|
1308
1636
|
}];
|
|
1309
1637
|
});
|
|
1310
1638
|
projected[key] = {
|
|
1311
1639
|
text: overviewRecord.text,
|
|
1312
|
-
claimSpansTruncated: overviewRecord.claimSpans.length > MCP_OVERVIEW_SPAN_CAP,
|
|
1640
|
+
claimSpansTruncated: overviewRecord.claimSpansTruncated === true || overviewRecord.claimSpans.length > MCP_OVERVIEW_SPAN_CAP,
|
|
1313
1641
|
claimSpans,
|
|
1314
1642
|
};
|
|
1315
1643
|
}
|
|
@@ -1330,22 +1658,24 @@ export function projectReportDataForMcp(raw) {
|
|
|
1330
1658
|
const resolvesCrossCuttingRef = (ref) => {
|
|
1331
1659
|
if (ref.kind === 'report_evidence') {
|
|
1332
1660
|
const match = /^RRCP-s(0|[1-9]\d*)$/.exec(ref.sourceId ?? '');
|
|
1333
|
-
const source = match && asProjectionRecord(Array.isArray(
|
|
1334
|
-
?
|
|
1661
|
+
const source = match && asProjectionRecord(Array.isArray(projected.reportEvidence)
|
|
1662
|
+
? projected.reportEvidence[Number(match[1])]
|
|
1335
1663
|
: undefined);
|
|
1336
1664
|
return Boolean(source &&
|
|
1337
1665
|
safeProjectionUrl(source.url) &&
|
|
1666
|
+
(!Object.prototype.hasOwnProperty.call(source, 'quote') ||
|
|
1667
|
+
isDisplaySuitableExcerpt(source.quote)) &&
|
|
1338
1668
|
(source.section === 'market' || source.section === 'competitor' || source.section === 'summary'));
|
|
1339
1669
|
}
|
|
1340
1670
|
if (ref.kind === 'forum_receipt') {
|
|
1341
|
-
const matches = (Array.isArray(asProjectionRecord(
|
|
1342
|
-
? asProjectionRecord(
|
|
1671
|
+
const matches = (Array.isArray(asProjectionRecord(projected.forumResearch)?.threads)
|
|
1672
|
+
? asProjectionRecord(projected.forumResearch).threads
|
|
1343
1673
|
: []).flatMap((rawThread) => {
|
|
1344
1674
|
const thread = asProjectionRecord(rawThread);
|
|
1345
1675
|
return (Array.isArray(thread?.receipts) ? thread.receipts : []).filter((rawReceipt) => {
|
|
1346
1676
|
const receipt = asProjectionRecord(rawReceipt);
|
|
1347
1677
|
return Boolean(receipt && receipt.receiptId === ref.receiptId &&
|
|
1348
|
-
|
|
1678
|
+
isDisplaySuitableExcerpt(receipt.excerpt));
|
|
1349
1679
|
});
|
|
1350
1680
|
});
|
|
1351
1681
|
return matches.length === 1;
|
|
@@ -1493,12 +1823,23 @@ export function projectReportDataForMcp(raw) {
|
|
|
1493
1823
|
? { ...safeRow, final: { version: HYPOTHESIS_SEMANTICS_VERSION } }
|
|
1494
1824
|
: safeRow;
|
|
1495
1825
|
const normalized = normalizeFinalHypothesisState(rowForNormalization);
|
|
1496
|
-
|
|
1826
|
+
const isCoherentIntentionalDowngrade = Boolean(!semanticsMismatch &&
|
|
1827
|
+
!clauseIntegrityMismatch &&
|
|
1828
|
+
normalized?.clauseCoverage !== undefined &&
|
|
1829
|
+
normalized.clauseCoverage !== 'invalid' &&
|
|
1830
|
+
normalized.clauseCoverage !== 'complete' &&
|
|
1831
|
+
normalized.originalVerdict !== normalized.verdict);
|
|
1832
|
+
if (isCoherentIntentionalDowngrade) {
|
|
1833
|
+
if (typeof safeRow.hypothesisId === 'string' && safeRow.hypothesisId.trim()) {
|
|
1834
|
+
reconciledClauseDowngradeIds.add(safeRow.hypothesisId);
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
clauseSemanticsFailure = true;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
else if (semanticsMismatch ||
|
|
1497
1841
|
clauseIntegrityMismatch ||
|
|
1498
1842
|
normalized?.clauseCoverage === 'invalid' ||
|
|
1499
|
-
(normalized?.clauseCoverage !== undefined &&
|
|
1500
|
-
normalized.clauseCoverage !== 'complete' &&
|
|
1501
|
-
normalized.originalVerdict !== normalized.verdict) ||
|
|
1502
1843
|
(requiresClauseIntegrity && normalized === null))
|
|
1503
1844
|
clauseSemanticsFailure = true;
|
|
1504
1845
|
if (normalized)
|
|
@@ -1552,6 +1893,9 @@ export function projectReportDataForMcp(raw) {
|
|
|
1552
1893
|
return safeRow;
|
|
1553
1894
|
});
|
|
1554
1895
|
}
|
|
1896
|
+
if (reconciledClauseDowngradeIds.size > 0 &&
|
|
1897
|
+
!authenticatesMcpDecisionSpine(record, Array.isArray(projected.hypothesisResults) ? projected.hypothesisResults : [], projected.recommendations, reconciledClauseDowngradeIds))
|
|
1898
|
+
clauseSemanticsFailure = true;
|
|
1555
1899
|
if (clauseSemanticsFailure) {
|
|
1556
1900
|
projected.executiveSummary = 'This run did not establish every material part of at least one hypothesis. Treat the affected hypotheses as unresolved.';
|
|
1557
1901
|
projected.verdictSummary = projected.executiveSummary;
|