@nebulacomponents/citable 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/dist/universal/.agents/skills/citable/SKILL.md +1 -1
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/manifest.json +18 -5
- package/dist/universal/.agents/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.agents/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.agents/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.agents/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.agents/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.agents/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.agents/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.agents/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.agents/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.agents/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.agents/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.agents/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.agents/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.claude/agents/citable/manifest.json +2 -2
- package/dist/universal/.claude/skills/citable/SKILL.md +1 -1
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/manifest.json +18 -5
- package/dist/universal/.claude/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.claude/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.claude/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.claude/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.claude/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.claude/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.claude/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.claude/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.claude/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.claude/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.claude/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.claude/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.claude/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.cursor/skills/citable/SKILL.md +1 -1
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/manifest.json +18 -5
- package/dist/universal/.cursor/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.cursor/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.cursor/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.cursor/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.cursor/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.cursor/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.cursor/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.cursor/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.cursor/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.cursor/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.cursor/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.cursor/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.cursor/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.gemini/skills/citable/SKILL.md +1 -1
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/manifest.json +18 -5
- package/dist/universal/.gemini/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.gemini/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.gemini/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.gemini/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.gemini/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.gemini/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.gemini/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.gemini/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.gemini/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.gemini/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.gemini/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.gemini/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.gemini/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.github/skills/citable/SKILL.md +1 -1
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/manifest.json +18 -5
- package/dist/universal/.github/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.github/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.github/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.github/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.github/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.github/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.github/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.github/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.github/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.github/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.github/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.github/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.github/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.kiro/skills/citable/SKILL.md +1 -1
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/manifest.json +18 -5
- package/dist/universal/.kiro/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.kiro/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.kiro/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.kiro/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.kiro/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.kiro/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.kiro/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.kiro/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.kiro/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.kiro/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.kiro/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.kiro/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.kiro/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.opencode/skills/citable/SKILL.md +1 -1
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/manifest.json +18 -5
- package/dist/universal/.opencode/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.opencode/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.opencode/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.opencode/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.opencode/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.opencode/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.opencode/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.opencode/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.opencode/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.opencode/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.opencode/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.opencode/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.opencode/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +1 -1
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/manifest.json +18 -5
- package/dist/universal/.pi/agent/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.qoder/skills/citable/SKILL.md +1 -1
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/manifest.json +18 -5
- package/dist/universal/.qoder/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.qoder/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.qoder/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.qoder/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.qoder/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.qoder/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.qoder/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.qoder/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.qoder/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.qoder/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.qoder/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.qoder/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.qoder/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.rovodev/skills/citable/SKILL.md +1 -1
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/manifest.json +18 -5
- package/dist/universal/.rovodev/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.rovodev/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.rovodev/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.rovodev/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.rovodev/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.rovodev/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.rovodev/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.rovodev/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.rovodev/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.rovodev/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.rovodev/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.rovodev/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.rovodev/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.trae/skills/citable/SKILL.md +1 -1
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/manifest.json +18 -5
- package/dist/universal/.trae/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.trae/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.trae/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.trae/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.trae/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.trae/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.trae/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.trae/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.trae/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.trae/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.trae/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.trae/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.trae/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +1 -1
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/manifest.json +18 -5
- package/dist/universal/.trae-cn/skills/citable/schemas/citation-provenance.schema.json +59 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/claim-evidence.schema.json +21 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/claim-verification.schema.json +18 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/competitor-representation.schema.json +36 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/derivation.schema.json +18 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/external-comparison.schema.json +16 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/external-observation.schema.json +32 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/prompt-set.schema.json +47 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/representation-data.schema.json +21 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/research-dataset.schema.json +27 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/response-span.schema.json +16 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/search-discovery-data.schema.json +20 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/source-retrieval.schema.json +41 -0
- package/dist/universal/manifest.json +220 -64
- package/package.json +1 -1
- package/schemas/citation-provenance.schema.json +59 -0
- package/schemas/claim-evidence.schema.json +21 -0
- package/schemas/claim-verification.schema.json +18 -0
- package/schemas/competitor-representation.schema.json +36 -0
- package/schemas/derivation.schema.json +18 -0
- package/schemas/external-comparison.schema.json +16 -0
- package/schemas/external-observation.schema.json +32 -0
- package/schemas/prompt-set.schema.json +47 -0
- package/schemas/representation-data.schema.json +21 -0
- package/schemas/research-dataset.schema.json +27 -0
- package/schemas/response-span.schema.json +16 -0
- package/schemas/search-discovery-data.schema.json +20 -0
- package/schemas/source-retrieval.schema.json +41 -0
- package/skill/SKILL.md +1 -1
- package/src/connectors/gsc.js +4 -0
- package/src/discovery/searchConsole.js +186 -0
- package/src/evidence/external.js +251 -0
- package/src/representation/competitors.js +113 -0
- package/src/representation/prompts.js +117 -0
- package/src/representation/provenance.js +214 -0
- package/src/representation/representation.js +186 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { canonicalEvidenceJson } from '../evidence/hashes.js';
|
|
2
|
+
import { sha256 } from '../shared/io.js';
|
|
3
|
+
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
4
|
+
|
|
5
|
+
const PROMPT_SCHEMA = 'prompt-set.schema.json';
|
|
6
|
+
|
|
7
|
+
function without(value, key) {
|
|
8
|
+
const copy = { ...value };
|
|
9
|
+
delete copy[key];
|
|
10
|
+
return copy;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function promptId({ prompt_text, context = null, locale = null }) {
|
|
14
|
+
return `PROMPT-${sha256(canonicalEvidenceJson({ prompt_text, context, locale })).slice(0, 24)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createPrompt(input) {
|
|
18
|
+
if (!input || typeof input.prompt_text !== 'string' || !input.prompt_text.trim()) throw new TypeError('prompt_text is required');
|
|
19
|
+
const prompt = {
|
|
20
|
+
prompt_id: input.prompt_id || promptId(input),
|
|
21
|
+
prompt_text: input.prompt_text,
|
|
22
|
+
...(input.context == null ? {} : { context: input.context }),
|
|
23
|
+
...(input.locale == null ? {} : { locale: input.locale }),
|
|
24
|
+
};
|
|
25
|
+
return prompt;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeIncluded(prompts = []) {
|
|
29
|
+
if (!Array.isArray(prompts)) throw new TypeError('included_prompts must be an array');
|
|
30
|
+
const normalized = prompts.map(createPrompt);
|
|
31
|
+
const ids = new Set();
|
|
32
|
+
for (const item of normalized) {
|
|
33
|
+
if (ids.has(item.prompt_id)) throw new Error(`duplicate prompt_id: ${item.prompt_id}`);
|
|
34
|
+
ids.add(item.prompt_id);
|
|
35
|
+
}
|
|
36
|
+
return normalized.sort((left, right) => left.prompt_id.localeCompare(right.prompt_id));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeExcluded(prompts = []) {
|
|
40
|
+
if (!Array.isArray(prompts)) throw new TypeError('excluded_prompts must be an array');
|
|
41
|
+
return prompts.map((item) => ({
|
|
42
|
+
prompt_id: item.prompt_id || promptId(item),
|
|
43
|
+
...(item.prompt_text == null ? {} : { prompt_text: item.prompt_text }),
|
|
44
|
+
exclusion_reason: item.exclusion_reason,
|
|
45
|
+
})).sort((left, right) => left.prompt_id.localeCompare(right.prompt_id));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hashPromptSet(item) {
|
|
49
|
+
const value = without(item, 'prompt_set_hash');
|
|
50
|
+
return sha256(canonicalEvidenceJson({
|
|
51
|
+
...value,
|
|
52
|
+
included_prompts: [...(value.included_prompts || [])].sort((left, right) => left.prompt_id.localeCompare(right.prompt_id)),
|
|
53
|
+
excluded_prompts: [...(value.excluded_prompts || [])].sort((left, right) => left.prompt_id.localeCompare(right.prompt_id)),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createPromptSet(input, { previous = null } = {}) {
|
|
58
|
+
if (!input || typeof input.prompt_set_id !== 'string' || !input.prompt_set_id) throw new TypeError('prompt_set_id is required');
|
|
59
|
+
if (!Number.isInteger(input.prompt_set_version) || input.prompt_set_version < 1) throw new TypeError('prompt_set_version must be a positive integer');
|
|
60
|
+
const included = normalizeIncluded(input.included_prompts);
|
|
61
|
+
const excluded = normalizeExcluded(input.excluded_prompts || []);
|
|
62
|
+
const item = {
|
|
63
|
+
prompt_set_id: input.prompt_set_id,
|
|
64
|
+
prompt_set_version: input.prompt_set_version,
|
|
65
|
+
purpose: input.purpose,
|
|
66
|
+
generation_method: input.generation_method,
|
|
67
|
+
generation_method_version: input.generation_method_version ?? null,
|
|
68
|
+
generation_input: input.generation_input ?? null,
|
|
69
|
+
generated_at: input.generated_at,
|
|
70
|
+
population_size: included.length,
|
|
71
|
+
sampling_method: input.sampling_method,
|
|
72
|
+
included_prompts: included,
|
|
73
|
+
excluded_prompts: excluded,
|
|
74
|
+
intended_population: input.intended_population,
|
|
75
|
+
limitations: [...new Set(input.limitations || [])],
|
|
76
|
+
};
|
|
77
|
+
item.prompt_set_hash = hashPromptSet(item);
|
|
78
|
+
const check = validateAgainst(PROMPT_SCHEMA, item);
|
|
79
|
+
if (!check.valid) throw new TypeError(`prompt set violates contract: ${check.errors.join('; ')}`);
|
|
80
|
+
if (previous && previous.prompt_set_id === item.prompt_set_id && previous.prompt_set_version === item.prompt_set_version && previous.prompt_set_hash !== item.prompt_set_hash) {
|
|
81
|
+
throw new Error('prompt set version is immutable; create a new prompt_set_version');
|
|
82
|
+
}
|
|
83
|
+
return item;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function verifyPromptSet(item, { previous = null } = {}) {
|
|
87
|
+
const failures = [];
|
|
88
|
+
const check = validateAgainst(PROMPT_SCHEMA, item);
|
|
89
|
+
if (!check.valid) failures.push(...check.errors);
|
|
90
|
+
if (item?.prompt_set_hash !== hashPromptSet(item || {})) failures.push('prompt set hash is inconsistent');
|
|
91
|
+
if (previous && previous.prompt_set_id === item?.prompt_set_id && previous.prompt_set_version === item?.prompt_set_version && previous.prompt_set_hash !== item?.prompt_set_hash) failures.push('prompt set version is immutable');
|
|
92
|
+
return { valid: failures.length === 0, failures };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function promptReference(promptSet, promptIdValue) {
|
|
96
|
+
if (!verifyPromptSet(promptSet).valid) throw new Error('cannot reference an unverifiable prompt set');
|
|
97
|
+
const prompt = promptSet.included_prompts.find((item) => item.prompt_id === promptIdValue);
|
|
98
|
+
if (!prompt) throw new Error(`prompt ${promptIdValue} is not included in prompt set ${promptSet.prompt_set_id}@${promptSet.prompt_set_version}`);
|
|
99
|
+
return `${promptSet.prompt_set_id}@${promptSet.prompt_set_version}:${prompt.prompt_id}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function comparePromptSets(left, right) {
|
|
103
|
+
const validLeft = verifyPromptSet(left).valid;
|
|
104
|
+
const validRight = verifyPromptSet(right).valid;
|
|
105
|
+
if (!validLeft || !validRight) return { status: 'INDETERMINATE', dimensions: {}, limitations: ['Prompt-set integrity could not be verified.'] };
|
|
106
|
+
const dimensions = {
|
|
107
|
+
prompt_set_id: left.prompt_set_id === right.prompt_set_id ? 'match' : 'mismatch',
|
|
108
|
+
prompt_set_version: left.prompt_set_version === right.prompt_set_version ? 'match' : 'mismatch',
|
|
109
|
+
population: left.population_size === right.population_size ? 'match' : 'mismatch',
|
|
110
|
+
sampling_method: left.sampling_method === right.sampling_method ? 'match' : 'mismatch',
|
|
111
|
+
};
|
|
112
|
+
const status = dimensions.prompt_set_id === 'match' && dimensions.prompt_set_version === 'match' && dimensions.population === 'match' && dimensions.sampling_method === 'match'
|
|
113
|
+
? 'COMPARABLE' : 'NOT_COMPARABLE';
|
|
114
|
+
return { status, left_reference: `${left.prompt_set_id}@${left.prompt_set_version}`, right_reference: `${right.prompt_set_id}@${right.prompt_set_version}`, dimensions, limitations: status === 'COMPARABLE' ? [] : ['Prompt population, sampling, or version changed; no denominator is reused silently.'] };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { promptId };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createExternalObservation, createDerivation, verifyExternalObservation, verifyClaim } from '../evidence/external.js';
|
|
2
|
+
import { canonicalEvidenceJson } from '../evidence/hashes.js';
|
|
3
|
+
import { sha256, nowIso } from '../shared/io.js';
|
|
4
|
+
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
5
|
+
|
|
6
|
+
const SPAN_SCHEMA = 'response-span.schema.json';
|
|
7
|
+
const CITATION_SCHEMA = 'citation-provenance.schema.json';
|
|
8
|
+
const SOURCE_SCHEMA = 'source-retrieval.schema.json';
|
|
9
|
+
const COLLECTOR = { id: 'citable-citation-provenance', version: '1.0.0' };
|
|
10
|
+
const PARSER = { id: 'provider-neutral-citation-parser', version: '1.0.0' };
|
|
11
|
+
|
|
12
|
+
function validate(schema, value, label) {
|
|
13
|
+
const result = validateAgainst(schema, value);
|
|
14
|
+
if (!result.valid) throw new TypeError(`${label} violates contract: ${result.errors.join('; ')}`);
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function validUrl(value) {
|
|
19
|
+
try { return new URL(value).toString(); } catch { return null; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function executionReference(execution) {
|
|
23
|
+
if (!execution?.observation_id || !verifyExternalObservation(execution).valid) {
|
|
24
|
+
throw new TypeError('citation execution must be a verified external observation');
|
|
25
|
+
}
|
|
26
|
+
if (execution.data?.response?.status !== 'succeeded') throw new Error('citation requires a substantive provider response');
|
|
27
|
+
return execution.observation_id;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Create a stable, exact text span without normalizing provider prose. */
|
|
31
|
+
export function createResponseSpan({ execution, start, end, text, spanId = null } = {}) {
|
|
32
|
+
const executionId = execution?.observation_id || execution;
|
|
33
|
+
if (typeof executionId !== 'string' || !executionId) throw new TypeError('execution reference is required');
|
|
34
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) throw new TypeError('span offsets are invalid');
|
|
35
|
+
if (typeof text !== 'string') throw new TypeError('span text is required');
|
|
36
|
+
if (end - start !== text.length) throw new TypeError('span offsets must bound the exact response text');
|
|
37
|
+
const item = {
|
|
38
|
+
span_id: spanId || `SPAN-${sha256(canonicalEvidenceJson({ execution_reference: executionId, start, end, text })).slice(0, 24)}`,
|
|
39
|
+
execution_reference: executionId,
|
|
40
|
+
start,
|
|
41
|
+
end,
|
|
42
|
+
text,
|
|
43
|
+
text_hash: sha256(text),
|
|
44
|
+
};
|
|
45
|
+
return validate(SPAN_SCHEMA, item, 'response span');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sourceShape(citation, source = {}) {
|
|
49
|
+
const displayed = citation.displayed_url ?? citation.url ?? null;
|
|
50
|
+
const resolved = validUrl(source.resolved_url ?? citation.resolved_url);
|
|
51
|
+
const status = source.identity_status || (resolved ? 'IDENTIFIED' : displayed ? 'AMBIGUOUS' : 'NOT_IDENTIFIED');
|
|
52
|
+
return {
|
|
53
|
+
identity_status: status,
|
|
54
|
+
resolved_url: resolved,
|
|
55
|
+
redirect_chain: (source.redirect_chain || []).map(validUrl).filter(Boolean),
|
|
56
|
+
canonical_url: validUrl(source.canonical_url) || null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function citationData({ execution, span, citation, proposition, source, retrieval, support }) {
|
|
61
|
+
const data = {
|
|
62
|
+
execution_reference: execution.observation_id,
|
|
63
|
+
response_reference: execution.data.response.response_hash,
|
|
64
|
+
provider: execution.data.provider,
|
|
65
|
+
span,
|
|
66
|
+
citation: {
|
|
67
|
+
citation_id: citation.citation_id || `CIT-${sha256(canonicalEvidenceJson({ execution: execution.observation_id, span: span.span_id, ordinal: citation.ordinal || 1, marker: citation.raw_marker || null })).slice(0, 24)}`,
|
|
68
|
+
ordinal: citation.ordinal || 1,
|
|
69
|
+
raw_marker: citation.raw_marker ?? null,
|
|
70
|
+
placement: citation.placement || 'unknown',
|
|
71
|
+
displayed_title: citation.displayed_title ?? citation.title ?? null,
|
|
72
|
+
displayed_domain: citation.displayed_domain ?? citation.domain ?? null,
|
|
73
|
+
displayed_url: citation.displayed_url ?? citation.url ?? null,
|
|
74
|
+
},
|
|
75
|
+
source: sourceShape(citation, source),
|
|
76
|
+
retrieval: { status: retrieval?.status || 'NOT_ATTEMPTED', observation_reference: retrieval?.observation_reference || null },
|
|
77
|
+
proposition: { proposition_id: proposition?.proposition_id || `PROP-${sha256(proposition?.text || span.text).slice(0, 24)}`, text: proposition?.text || span.text },
|
|
78
|
+
support: {
|
|
79
|
+
relevance_status: support?.relevance_status || 'NOT_ASSESSED',
|
|
80
|
+
support_status: support?.support_status || 'NOT_ASSESSED',
|
|
81
|
+
evidence_references: [...new Set(support?.evidence_references || [])],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return validate(CITATION_SCHEMA, data, 'citation provenance');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Create an immutable citation linked to one exact response span. */
|
|
88
|
+
export function createCitationObservation({ execution, span, citation, proposition, source = {}, retrieval = null, support = null, evidenceOrigin = 'RECORDED_FIXTURE', observedAt = nowIso(), limitations = [] } = {}) {
|
|
89
|
+
executionReference(execution);
|
|
90
|
+
const responseSpan = span?.span_id ? validate(SPAN_SCHEMA, span, 'response span') : createResponseSpan({ execution, ...(span || {}) });
|
|
91
|
+
if (responseSpan.execution_reference !== execution.observation_id) throw new Error('span does not belong to execution');
|
|
92
|
+
const data = citationData({ execution, span: responseSpan, citation: citation || {}, proposition, source, retrieval, support });
|
|
93
|
+
if (!['LIVE_PROVIDER', 'RECORDED_FIXTURE', 'SYNTHETIC_TEST'].includes(evidenceOrigin)) throw new TypeError('evidenceOrigin is invalid');
|
|
94
|
+
return createExternalObservation({
|
|
95
|
+
observation_id: `CIT-${sha256(canonicalEvidenceJson({ execution: execution.observation_id, data })).slice(0, 24)}`,
|
|
96
|
+
observation_type: 'EXTERNAL_OBSERVATION',
|
|
97
|
+
subject: { type: 'citation', id: data.citation.citation_id },
|
|
98
|
+
source: { provider: execution.data.provider, method: 'citation_provenance', authority: 'external_provider' },
|
|
99
|
+
collector: COLLECTOR,
|
|
100
|
+
parser: PARSER,
|
|
101
|
+
configuration: execution.data.execution.configuration,
|
|
102
|
+
observed_at: observedAt,
|
|
103
|
+
retrieved_at: observedAt,
|
|
104
|
+
executed_at: execution.data.execution.executed_at,
|
|
105
|
+
population: { declared: execution.population?.declared || null, observed: 1, retrieved: 1, evaluated: 1, unavailable: 0, failed: 0 },
|
|
106
|
+
observation_status: 'observed',
|
|
107
|
+
retrieval_status: 'retrieved',
|
|
108
|
+
data: { ...data, evidence_origin: evidenceOrigin },
|
|
109
|
+
lineage: { source_evidence: [execution.observation_id], predecessor_references: [execution.observation_id], package_reference: null },
|
|
110
|
+
limitations: [...new Set(limitations)],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Build a set while refusing duplicate citation ordinals or identifiers. */
|
|
115
|
+
export function createCitationObservations(input = {}) {
|
|
116
|
+
const citations = input.citations || [];
|
|
117
|
+
const ids = new Set(), ordinals = new Set();
|
|
118
|
+
return citations.map((citation) => {
|
|
119
|
+
const item = createCitationObservation({ ...input, citation });
|
|
120
|
+
const id = item.data.citation.citation_id;
|
|
121
|
+
const ordinal = item.data.citation.ordinal;
|
|
122
|
+
if (ids.has(id)) throw new Error(`duplicate citation_id: ${id}`);
|
|
123
|
+
if (ordinals.has(ordinal)) throw new Error(`duplicate citation ordinal: ${ordinal}`);
|
|
124
|
+
ids.add(id); ordinals.add(ordinal);
|
|
125
|
+
return item;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function verifyCitationObservation(item) {
|
|
130
|
+
return verifyExternalObservation(item);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Preserve source retrieval as a separate observation; failed retrieval is never support failure. */
|
|
134
|
+
export function createSourceRetrievalObservation({ citation, url, status = 'SUCCEEDED', body = null, finalUrl = null, redirectChain = [], httpStatus = null, attemptedAt = nowIso(), relevanceStatus = 'NOT_ASSESSED', supportStatus = 'NOT_ASSESSED', evidenceReferences = [], rawRetained = false, evidenceOrigin = 'RECORDED_FIXTURE', limitations = [] } = {}) {
|
|
135
|
+
if (!citation?.observation_id || !verifyCitationObservation(citation).valid) throw new TypeError('citation must be a verified citation observation');
|
|
136
|
+
const sourceUrl = validUrl(url || citation.data.source.resolved_url);
|
|
137
|
+
if (!sourceUrl) throw new TypeError('retrieval URL must be a valid URL');
|
|
138
|
+
const contentHash = body == null ? null : sha256(body);
|
|
139
|
+
const data = validate(SOURCE_SCHEMA, {
|
|
140
|
+
citation_reference: citation.observation_id,
|
|
141
|
+
url: sourceUrl,
|
|
142
|
+
retrieval: { status, attempted_at: attemptedAt, final_url: validUrl(finalUrl) || (status === 'SUCCEEDED' ? sourceUrl : null), redirect_chain: redirectChain.map(validUrl).filter(Boolean), http_status: status === 'SUCCEEDED' ? (httpStatus || 200) : httpStatus },
|
|
143
|
+
content: { observed: status === 'SUCCEEDED' && body != null, content_hash: contentHash, raw_retained: Boolean(rawRetained) },
|
|
144
|
+
relevance: { status: relevanceStatus, support_status: supportStatus, evidence_references: [...new Set(evidenceReferences)] },
|
|
145
|
+
}, 'source retrieval');
|
|
146
|
+
return createExternalObservation({
|
|
147
|
+
observation_id: `SRC-${sha256(canonicalEvidenceJson({ citation: citation.observation_id, data })).slice(0, 24)}`,
|
|
148
|
+
observation_type: 'EXTERNAL_OBSERVATION',
|
|
149
|
+
subject: { type: 'citation_source', id: sourceUrl },
|
|
150
|
+
source: { provider: citation.data.provider, method: 'citation_source_retrieval', authority: 'third_party' },
|
|
151
|
+
collector: COLLECTOR,
|
|
152
|
+
parser: PARSER,
|
|
153
|
+
configuration: null,
|
|
154
|
+
observed_at: attemptedAt,
|
|
155
|
+
retrieved_at: attemptedAt,
|
|
156
|
+
executed_at: null,
|
|
157
|
+
population: { declared: null, observed: status === 'SUCCEEDED' ? 1 : 0, retrieved: status === 'SUCCEEDED' ? 1 : 0, evaluated: status === 'SUCCEEDED' ? 1 : 0, unavailable: 0, failed: status === 'FAILED' ? 1 : 0 },
|
|
158
|
+
observation_status: status === 'SUCCEEDED' ? 'observed' : status === 'FAILED' ? 'failed' : 'indeterminate',
|
|
159
|
+
retrieval_status: status === 'SUCCEEDED' ? 'retrieved' : status === 'FAILED' ? 'failed' : 'indeterminate',
|
|
160
|
+
data: { ...data, evidence_origin: evidenceOrigin },
|
|
161
|
+
lineage: { source_evidence: [citation.observation_id], predecessor_references: [citation.observation_id], package_reference: null },
|
|
162
|
+
limitations: [...new Set([...(status === 'FAILED' ? ['Source retrieval failed; proposition support is indeterminate.'] : []), ...limitations])],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function verifySourceRetrievalObservation(item) {
|
|
167
|
+
return verifyExternalObservation(item);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function assessPropositionSupport({ retrieval, relevanceStatus, supportStatus, evidenceReferences = [] } = {}) {
|
|
171
|
+
if (!retrieval || !verifySourceRetrievalObservation(retrieval).valid) return { status: 'INDETERMINATE', reasons: ['source retrieval evidence is missing or unverifiable'], evidence_references: [] };
|
|
172
|
+
if (retrieval.data.retrieval.status !== 'SUCCEEDED') return { status: 'INDETERMINATE', reasons: ['source retrieval did not succeed'], evidence_references: [retrieval.observation_id] };
|
|
173
|
+
if (relevanceStatus === 'IRRELEVANT') return { status: 'UNSUPPORTED', reasons: ['retrieved source was assessed as irrelevant to the proposition'], evidence_references: [retrieval.observation_id, ...evidenceReferences] };
|
|
174
|
+
const supportedStates = new Set(['SUPPORTED', 'PARTIALLY_SUPPORTED', 'UNSUPPORTED', 'CONTRADICTED', 'INDETERMINATE', 'NOT_OBSERVED']);
|
|
175
|
+
if (relevanceStatus !== 'RELEVANT' || !supportStatus || supportStatus === 'NOT_ASSESSED' || !supportedStates.has(supportStatus)) return { status: 'INDETERMINATE', reasons: ['source relevance or proposition support was not established'], evidence_references: [retrieval.observation_id, ...evidenceReferences] };
|
|
176
|
+
return { status: supportStatus, reasons: [], evidence_references: [retrieval.observation_id, ...evidenceReferences] };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Derive prevalence over successful/evaluable executions only, retaining exclusions. */
|
|
180
|
+
export function deriveRepresentationPrevalence(observations, { predicatePath = 'data.representation.brand_mentioned', expected = true, derivationId = 'REPRESENTATION-PREVALENCE', derivationVersion = 1 } = {}) {
|
|
181
|
+
if (!Array.isArray(observations) || !observations.length) throw new TypeError('observations are required');
|
|
182
|
+
const valid = observations.filter((item) => verifyExternalObservation(item).valid);
|
|
183
|
+
const evaluable = valid.filter((item) => item.data?.response?.status === 'succeeded');
|
|
184
|
+
const excluded = observations.filter((item) => !verifyExternalObservation(item).valid || item.data?.response?.status !== 'succeeded');
|
|
185
|
+
const valueAt = (item) => predicatePath.split('.').reduce((v, key) => v == null ? undefined : v[key], item);
|
|
186
|
+
const numerator = evaluable.filter((item) => valueAt(item) === expected).length;
|
|
187
|
+
return createDerivation({
|
|
188
|
+
derivation_id: derivationId,
|
|
189
|
+
derivation_version: derivationVersion,
|
|
190
|
+
operation: 'representation_prevalence',
|
|
191
|
+
input_observation_references: observations.map((item) => item.observation_id),
|
|
192
|
+
predicate: { path: predicatePath, operator: 'equals', value: expected },
|
|
193
|
+
result: {
|
|
194
|
+
numerator,
|
|
195
|
+
denominator: evaluable.length,
|
|
196
|
+
rate: evaluable.length ? numerator / evaluable.length : null,
|
|
197
|
+
evaluable_observation_references: evaluable.map((item) => item.observation_id),
|
|
198
|
+
excluded_observation_references: excluded.map((item) => item.observation_id),
|
|
199
|
+
exclusions: excluded.map((item) => ({ observation_reference: item.observation_id, reason: verifyExternalObservation(item).valid ? (item.data?.response?.status || 'not_evaluable') : 'integrity_unverifiable' })),
|
|
200
|
+
population_scope: 'successful_representation_executions_in_supplied_observations',
|
|
201
|
+
},
|
|
202
|
+
implementation: { id: 'citable-representation-prevalence', version: '1.0.0' },
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function citationsForExecution(citations, executionId) { return (citations || []).filter((item) => item.data?.execution_reference === executionId); }
|
|
207
|
+
export function citationsForProposition(citations, propositionId) { return (citations || []).filter((item) => item.data?.proposition?.proposition_id === propositionId); }
|
|
208
|
+
export function sourceForCitation(retrievals, citationId) { return (retrievals || []).find((item) => item.data?.citation_reference === citationId) || null; }
|
|
209
|
+
export function evidenceForProposition(retrievals, propositionId, citations = []) {
|
|
210
|
+
const citationIds = new Set(citationsForProposition(citations, propositionId).map((item) => item.observation_id));
|
|
211
|
+
return (retrievals || []).filter((item) => citationIds.has(item.data?.citation_reference));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { verifyClaim };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createExternalObservation, verifyExternalObservation } from '../evidence/external.js';
|
|
2
|
+
import { canonicalEvidenceJson } from '../evidence/hashes.js';
|
|
3
|
+
import { sha256, nowIso } from '../shared/io.js';
|
|
4
|
+
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
5
|
+
import { verifyPromptSet, promptReference } from './prompts.js';
|
|
6
|
+
|
|
7
|
+
const REPRESENTATION_SCHEMA = 'representation-data.schema.json';
|
|
8
|
+
const COLLECTOR = { id: 'citable-representation-observations', version: '1.0.0' };
|
|
9
|
+
const PARSER = { id: 'provider-neutral-representation-parser', version: '1.0.0' };
|
|
10
|
+
|
|
11
|
+
function responseHash(rawResponse, response) {
|
|
12
|
+
return sha256(typeof rawResponse === 'string' ? rawResponse : canonicalEvidenceJson(response ?? null));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function modelIdentity(model) {
|
|
16
|
+
if (model && typeof model === 'string') return { value: model, status: 'known' };
|
|
17
|
+
if (model && typeof model === 'object' && ['known', 'UNKNOWN', 'UNAVAILABLE', 'NOT_DISCLOSED'].includes(model.status)) return { value: model.value ?? null, status: model.status };
|
|
18
|
+
return { value: null, status: 'UNKNOWN' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hostOf(value) {
|
|
22
|
+
try { return new URL(value).hostname.toLowerCase(); } catch { return null; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeUrls(value) {
|
|
26
|
+
if (!Array.isArray(value)) throw new TypeError('provider citations must be an array');
|
|
27
|
+
const urls = value.map((item) => typeof item === 'string' ? item : item?.url);
|
|
28
|
+
if (urls.some((item) => typeof item !== 'string' || !hostOf(item))) throw new TypeError('provider citation URL is malformed');
|
|
29
|
+
return [...new Set(urls)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SENSITIVE_KEY = /(^|[-_])(authorization|cookie|set-cookie|access[-_]?token|refresh[-_]?token|api[-_]?key|password|secret)($|[-_])/i;
|
|
33
|
+
|
|
34
|
+
function safeMetadata(value, depth = 0) {
|
|
35
|
+
if (depth > 4 || value == null) return value == null ? null : '[bounded]';
|
|
36
|
+
if (Array.isArray(value)) return value.slice(0, 50).map((item) => safeMetadata(item, depth + 1));
|
|
37
|
+
if (typeof value !== 'object') return typeof value === 'string' ? value.slice(0, 500) : value;
|
|
38
|
+
return Object.fromEntries(Object.entries(value).slice(0, 50).filter(([key]) => !SENSITIVE_KEY.test(key)).map(([key, item]) => [key, safeMetadata(item, depth + 1)]));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function executionStatusFor(status) {
|
|
42
|
+
if (status === 'succeeded') return { observation_status: 'observed', retrieval_status: 'retrieved' };
|
|
43
|
+
if (status === 'malformed' || status === 'parser_failed') return { observation_status: 'indeterminate', retrieval_status: 'indeterminate' };
|
|
44
|
+
return { observation_status: 'failed', retrieval_status: 'failed' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parserErrorObservation({ provider, product, surface, prompt, promptSet, executionConfiguration, model, evidenceOrigin, executedAt, rawResponse, status, errorMessage }) {
|
|
48
|
+
const hash = responseHash(rawResponse, null);
|
|
49
|
+
const execution = executionConfiguration || { id: 'default', version: '1.0.0' };
|
|
50
|
+
const state = executionStatusFor(status);
|
|
51
|
+
return createRepresentationObservation({
|
|
52
|
+
provider, product, surface, prompt, promptSet, executionConfiguration: execution, model, evidenceOrigin, executedAt,
|
|
53
|
+
response: { status, response_hash: hash, parser: PARSER, raw_response_retained: false, provider_metadata: {} },
|
|
54
|
+
representation: { brand_mentioned: null, domain_mentioned: null, domain_cited: null, cited_urls: [], mentioned_entities: [], cited_domains: [] },
|
|
55
|
+
observationStatus: state.observation_status, retrievalStatus: state.retrieval_status,
|
|
56
|
+
limitations: [`Representation response could not be normalized: ${String(errorMessage || status).slice(0, 240)}`, 'Raw provider response is not retained; response_hash is retained for identity.'],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createRepresentationObservation({ provider, product, surface, prompt, promptSet, executionConfiguration, model, evidenceOrigin, executedAt = nowIso(), response, representation, observationStatus = 'observed', retrievalStatus = 'retrieved', limitations = [] }) {
|
|
61
|
+
if (!provider || !product || !surface) throw new TypeError('provider, product, and surface are required');
|
|
62
|
+
if (!prompt || !prompt.prompt_id || !prompt.prompt_text) throw new TypeError('prompt identity and text are required');
|
|
63
|
+
if (!promptSet || !verifyPromptSet(promptSet).valid) throw new TypeError('prompt set is missing or unverifiable');
|
|
64
|
+
if (!promptSet.included_prompts.some((item) => item.prompt_id === prompt.prompt_id)) throw new Error(`prompt ${prompt.prompt_id} is not included in the prompt set`);
|
|
65
|
+
if (!['LIVE_PROVIDER', 'RECORDED_FIXTURE', 'SYNTHETIC_TEST'].includes(evidenceOrigin)) throw new TypeError('evidenceOrigin is invalid');
|
|
66
|
+
const config = executionConfiguration || { id: 'default', version: '1.0.0' };
|
|
67
|
+
const normalizedModel = modelIdentity(model);
|
|
68
|
+
const data = {
|
|
69
|
+
provider,
|
|
70
|
+
product,
|
|
71
|
+
surface,
|
|
72
|
+
model: normalizedModel,
|
|
73
|
+
prompt: { prompt_id: prompt.prompt_id, prompt_version: promptSet.prompt_set_version, prompt_text: prompt.prompt_text },
|
|
74
|
+
prompt_set: { prompt_set_id: promptSet.prompt_set_id, prompt_set_version: promptSet.prompt_set_version },
|
|
75
|
+
execution: { executed_at: executedAt, configuration: config },
|
|
76
|
+
response,
|
|
77
|
+
representation,
|
|
78
|
+
evidence_origin: evidenceOrigin,
|
|
79
|
+
limitations: [...new Set(limitations)],
|
|
80
|
+
};
|
|
81
|
+
const check = validateAgainst(REPRESENTATION_SCHEMA, data);
|
|
82
|
+
if (!check.valid) throw new TypeError(`representation data violates contract: ${check.errors.join('; ')}`);
|
|
83
|
+
const state = executionStatusFor(response.status);
|
|
84
|
+
const observation = createExternalObservation({
|
|
85
|
+
observation_id: `REP-${sha256(canonicalEvidenceJson({ provider, product, surface, prompt, promptSet: promptSet.prompt_set_hash, executedAt, response })).slice(0, 24)}`,
|
|
86
|
+
observation_type: 'EXTERNAL_OBSERVATION',
|
|
87
|
+
subject: { type: 'prompt_execution', id: promptReference(promptSet, prompt.prompt_id) },
|
|
88
|
+
source: { provider, method: 'representation_adapter', authority: 'external_provider' },
|
|
89
|
+
collector: COLLECTOR,
|
|
90
|
+
parser: PARSER,
|
|
91
|
+
configuration: config,
|
|
92
|
+
observed_at: executedAt,
|
|
93
|
+
retrieved_at: executedAt,
|
|
94
|
+
executed_at: executedAt,
|
|
95
|
+
population: { declared: { id: `${promptSet.prompt_set_id}@${promptSet.prompt_set_version}`, size: promptSet.population_size }, observed: 1, retrieved: response.status === 'succeeded' ? 1 : 0, evaluated: response.status === 'succeeded' ? 1 : 0, unavailable: response.status === 'unavailable' ? 1 : 0, failed: response.status === 'succeeded' ? 0 : 1 },
|
|
96
|
+
observation_status: observationStatus || state.observation_status,
|
|
97
|
+
retrieval_status: retrievalStatus || state.retrieval_status,
|
|
98
|
+
data,
|
|
99
|
+
lineage: { source_evidence: response.response_hash ? [response.response_hash] : [] },
|
|
100
|
+
limitations: data.limitations,
|
|
101
|
+
});
|
|
102
|
+
return observation;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function normalizeRepresentationResponse({ provider, product, surface, prompt, promptSet, executionConfiguration, model, response, rawResponse = null, evidenceOrigin = 'RECORDED_FIXTURE', executedAt = nowIso(), brand = null, firstPartyDomains = [], retainRawResponse = false }) {
|
|
106
|
+
const base = { provider, product, surface, prompt, promptSet, executionConfiguration, model, evidenceOrigin, executedAt };
|
|
107
|
+
const rawHash = responseHash(rawResponse, response);
|
|
108
|
+
try {
|
|
109
|
+
if (!response || typeof response !== 'object' || Array.isArray(response)) throw new TypeError('provider response must be an object');
|
|
110
|
+
const answerText = response.answer_text ?? response.answer ?? response.text;
|
|
111
|
+
if (typeof answerText !== 'string') throw new TypeError('provider response requires answer_text');
|
|
112
|
+
const citations = normalizeUrls(response.citations || []);
|
|
113
|
+
const mentionedEntities = Array.isArray(response.mentions) ? response.mentions.filter((item) => typeof item === 'string') : [];
|
|
114
|
+
const firstParty = new Set(firstPartyDomains.map((item) => String(item).toLowerCase()));
|
|
115
|
+
const citedDomains = [...new Set(citations.map(hostOf).filter(Boolean))];
|
|
116
|
+
const domainMentioned = firstParty.size ? [...firstParty].some((domain) => answerText.toLowerCase().includes(domain)) : null;
|
|
117
|
+
const domainCited = firstParty.size ? citedDomains.some((domain) => firstParty.has(domain)) : null;
|
|
118
|
+
const brandMentioned = typeof brand === 'string' ? answerText.toLowerCase().includes(brand.toLowerCase()) : null;
|
|
119
|
+
return createRepresentationObservation({
|
|
120
|
+
...base,
|
|
121
|
+
response: { status: 'succeeded', response_hash: rawHash, parser: PARSER, raw_response_retained: retainRawResponse, provider_metadata: { ...safeMetadata(response.provider_metadata || {}), ...(retainRawResponse ? { raw_response: rawResponse ?? response } : {}) } },
|
|
122
|
+
representation: { brand_mentioned: brandMentioned, domain_mentioned: domainMentioned, domain_cited: domainCited, cited_urls: citations, mentioned_entities: mentionedEntities, cited_domains: citedDomains },
|
|
123
|
+
limitations: retainRawResponse ? [] : ['Raw provider response is not retained; response_hash is retained for identity.'],
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return parserErrorObservation({ ...base, rawResponse, status: 'parser_failed', errorMessage: error.message });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function collectRepresentationObservations({ promptSet, adapter, provider, product = 'unknown', surface = 'unknown', model = null, executionConfiguration = { id: 'default', version: '1.0.0' }, evidenceOrigin = 'LIVE_PROVIDER', brand = null, firstPartyDomains = [] } = {}) {
|
|
131
|
+
if (!promptSet || !verifyPromptSet(promptSet).valid) throw new TypeError('prompt set is missing or unverifiable');
|
|
132
|
+
if (typeof adapter !== 'function') throw new TypeError('representation adapter is required');
|
|
133
|
+
const observations = [];
|
|
134
|
+
for (const prompt of promptSet.included_prompts) {
|
|
135
|
+
const executedAt = nowIso();
|
|
136
|
+
try {
|
|
137
|
+
const result = await adapter(prompt, { provider, product, surface, model, promptSet, executionConfiguration });
|
|
138
|
+
if (!result || typeof result !== 'object') throw new Error('adapter returned an invalid result');
|
|
139
|
+
const status = result.status || 'succeeded';
|
|
140
|
+
if (status !== 'succeeded') {
|
|
141
|
+
observations.push(parserErrorObservation({ provider, product, surface, prompt, promptSet, executionConfiguration, model, evidenceOrigin, executedAt, rawResponse: result.raw ?? result.body ?? null, status, errorMessage: result.error || status }));
|
|
142
|
+
} else {
|
|
143
|
+
observations.push(normalizeRepresentationResponse({ provider, product, surface, prompt, promptSet, executionConfiguration, model, response: result.body ?? result.response ?? result, rawResponse: result.raw ?? null, evidenceOrigin, executedAt, brand, firstPartyDomains }));
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
observations.push(parserErrorObservation({ provider, product, surface, prompt, promptSet, executionConfiguration, model, evidenceOrigin, executedAt, rawResponse: null, status: error.connectorState === 'quota_limited' ? 'rate_limited' : error.name === 'AbortError' ? 'timeout' : 'error', errorMessage: error.message }));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { observations, coverage: { declared: promptSet.population_size, executed: observations.filter((item) => item.data.response.status === 'succeeded').length, failed: observations.filter((item) => item.data.response.status !== 'succeeded').length, coverage_status: observations.every((item) => item.data.response.status === 'succeeded') ? 'complete_for_declared_prompt_set' : 'incomplete' }, prompt_set_reference: `${promptSet.prompt_set_id}@${promptSet.prompt_set_version}` };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function createHttpRepresentationAdapter({ endpoint, fetchImpl = globalThis.fetch, accessToken = null } = {}) {
|
|
153
|
+
if (typeof endpoint !== 'string' || !/^https:\/\//.test(endpoint)) throw new TypeError('representation adapter endpoint must use HTTPS');
|
|
154
|
+
return async (prompt) => {
|
|
155
|
+
const response = await fetchImpl(endpoint, { method: 'POST', redirect: 'error', headers: { accept: 'application/json', 'content-type': 'application/json', ...(accessToken ? { authorization: `Bearer ${accessToken}` } : {}) }, body: JSON.stringify({ prompt_id: prompt.prompt_id, prompt_text: prompt.prompt_text }) });
|
|
156
|
+
const raw = await response.text();
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
const status = response.status === 429 ? 'rate_limited' : response.status === 408 ? 'timeout' : response.status >= 500 ? 'unavailable' : 'refused';
|
|
159
|
+
return { status, raw, error: `provider returned ${response.status}` };
|
|
160
|
+
}
|
|
161
|
+
let body;
|
|
162
|
+
try { body = JSON.parse(raw); } catch { return { status: 'malformed', raw, error: 'provider response was not valid JSON' }; }
|
|
163
|
+
return { status: 'succeeded', body, raw };
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function verifyRepresentationObservation(item) {
|
|
168
|
+
return verifyExternalObservation(item);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function compareRepresentationObservations(left, right) {
|
|
172
|
+
if (!verifyExternalObservation(left).valid || !verifyExternalObservation(right).valid) return { status: 'INDETERMINATE', dimensions: {}, limitations: ['Representation observation integrity could not be verified.'] };
|
|
173
|
+
const dimensions = {
|
|
174
|
+
provider: left.data.provider === right.data.provider ? 'match' : 'mismatch',
|
|
175
|
+
product: left.data.product === right.data.product ? 'match' : 'mismatch',
|
|
176
|
+
surface: left.data.surface === right.data.surface ? 'match' : 'mismatch',
|
|
177
|
+
model: left.data.model.status !== 'known' || right.data.model.status !== 'known' ? 'missing' : left.data.model.value === right.data.model.value ? 'match' : 'mismatch',
|
|
178
|
+
prompt: left.data.prompt.prompt_id === right.data.prompt.prompt_id && left.data.prompt.prompt_text === right.data.prompt.prompt_text ? 'match' : 'mismatch',
|
|
179
|
+
prompt_set: left.data.prompt_set.prompt_set_id === right.data.prompt_set.prompt_set_id && left.data.prompt_set.prompt_set_version === right.data.prompt_set.prompt_set_version ? 'match' : 'mismatch',
|
|
180
|
+
configuration: left.data.execution.configuration.id === right.data.execution.configuration.id && left.data.execution.configuration.version === right.data.execution.configuration.version ? 'match' : 'mismatch',
|
|
181
|
+
parser: left.data.response.parser.id === right.data.response.parser.id && left.data.response.parser.version === right.data.response.parser.version ? 'match' : 'mismatch',
|
|
182
|
+
};
|
|
183
|
+
const hardMismatch = ['provider', 'product', 'surface', 'prompt', 'prompt_set', 'configuration', 'parser'].some((key) => dimensions[key] === 'mismatch');
|
|
184
|
+
const status = hardMismatch ? 'NOT_COMPARABLE' : dimensions.model === 'mismatch' ? 'NOT_COMPARABLE' : dimensions.model === 'missing' ? 'PARTIALLY_COMPARABLE' : 'COMPARABLE';
|
|
185
|
+
return { comparison_id: `REP-CMP-${sha256(canonicalEvidenceJson({ left: left.observation_id, right: right.observation_id, dimensions })).slice(0, 16)}`, left_reference: left.observation_id, right_reference: right.observation_id, status, dimensions, limitations: status === 'COMPARABLE' ? [] : ['Provider, model, prompt, execution, and parser identity remain explicit; no visibility delta is inferred from non-comparable observations.'] };
|
|
186
|
+
}
|