@nebulacomponents/citable 1.20.0 → 1.21.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.
Files changed (163) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/universal/.agents/skills/citable/SKILL.md +1 -1
  3. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  4. package/dist/universal/.agents/skills/citable/manifest.json +14 -5
  5. package/dist/universal/.agents/skills/citable/schemas/claim-evidence.schema.json +21 -0
  6. package/dist/universal/.agents/skills/citable/schemas/claim-verification.schema.json +18 -0
  7. package/dist/universal/.agents/skills/citable/schemas/derivation.schema.json +18 -0
  8. package/dist/universal/.agents/skills/citable/schemas/external-comparison.schema.json +16 -0
  9. package/dist/universal/.agents/skills/citable/schemas/external-observation.schema.json +32 -0
  10. package/dist/universal/.agents/skills/citable/schemas/prompt-set.schema.json +47 -0
  11. package/dist/universal/.agents/skills/citable/schemas/representation-data.schema.json +21 -0
  12. package/dist/universal/.agents/skills/citable/schemas/research-dataset.schema.json +27 -0
  13. package/dist/universal/.agents/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  14. package/dist/universal/.claude/agents/citable/manifest.json +2 -2
  15. package/dist/universal/.claude/skills/citable/SKILL.md +1 -1
  16. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  17. package/dist/universal/.claude/skills/citable/manifest.json +14 -5
  18. package/dist/universal/.claude/skills/citable/schemas/claim-evidence.schema.json +21 -0
  19. package/dist/universal/.claude/skills/citable/schemas/claim-verification.schema.json +18 -0
  20. package/dist/universal/.claude/skills/citable/schemas/derivation.schema.json +18 -0
  21. package/dist/universal/.claude/skills/citable/schemas/external-comparison.schema.json +16 -0
  22. package/dist/universal/.claude/skills/citable/schemas/external-observation.schema.json +32 -0
  23. package/dist/universal/.claude/skills/citable/schemas/prompt-set.schema.json +47 -0
  24. package/dist/universal/.claude/skills/citable/schemas/representation-data.schema.json +21 -0
  25. package/dist/universal/.claude/skills/citable/schemas/research-dataset.schema.json +27 -0
  26. package/dist/universal/.claude/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  27. package/dist/universal/.cursor/skills/citable/SKILL.md +1 -1
  28. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  29. package/dist/universal/.cursor/skills/citable/manifest.json +14 -5
  30. package/dist/universal/.cursor/skills/citable/schemas/claim-evidence.schema.json +21 -0
  31. package/dist/universal/.cursor/skills/citable/schemas/claim-verification.schema.json +18 -0
  32. package/dist/universal/.cursor/skills/citable/schemas/derivation.schema.json +18 -0
  33. package/dist/universal/.cursor/skills/citable/schemas/external-comparison.schema.json +16 -0
  34. package/dist/universal/.cursor/skills/citable/schemas/external-observation.schema.json +32 -0
  35. package/dist/universal/.cursor/skills/citable/schemas/prompt-set.schema.json +47 -0
  36. package/dist/universal/.cursor/skills/citable/schemas/representation-data.schema.json +21 -0
  37. package/dist/universal/.cursor/skills/citable/schemas/research-dataset.schema.json +27 -0
  38. package/dist/universal/.cursor/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  39. package/dist/universal/.gemini/skills/citable/SKILL.md +1 -1
  40. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  41. package/dist/universal/.gemini/skills/citable/manifest.json +14 -5
  42. package/dist/universal/.gemini/skills/citable/schemas/claim-evidence.schema.json +21 -0
  43. package/dist/universal/.gemini/skills/citable/schemas/claim-verification.schema.json +18 -0
  44. package/dist/universal/.gemini/skills/citable/schemas/derivation.schema.json +18 -0
  45. package/dist/universal/.gemini/skills/citable/schemas/external-comparison.schema.json +16 -0
  46. package/dist/universal/.gemini/skills/citable/schemas/external-observation.schema.json +32 -0
  47. package/dist/universal/.gemini/skills/citable/schemas/prompt-set.schema.json +47 -0
  48. package/dist/universal/.gemini/skills/citable/schemas/representation-data.schema.json +21 -0
  49. package/dist/universal/.gemini/skills/citable/schemas/research-dataset.schema.json +27 -0
  50. package/dist/universal/.gemini/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  51. package/dist/universal/.github/skills/citable/SKILL.md +1 -1
  52. package/dist/universal/.github/skills/citable/VERSION +1 -1
  53. package/dist/universal/.github/skills/citable/manifest.json +14 -5
  54. package/dist/universal/.github/skills/citable/schemas/claim-evidence.schema.json +21 -0
  55. package/dist/universal/.github/skills/citable/schemas/claim-verification.schema.json +18 -0
  56. package/dist/universal/.github/skills/citable/schemas/derivation.schema.json +18 -0
  57. package/dist/universal/.github/skills/citable/schemas/external-comparison.schema.json +16 -0
  58. package/dist/universal/.github/skills/citable/schemas/external-observation.schema.json +32 -0
  59. package/dist/universal/.github/skills/citable/schemas/prompt-set.schema.json +47 -0
  60. package/dist/universal/.github/skills/citable/schemas/representation-data.schema.json +21 -0
  61. package/dist/universal/.github/skills/citable/schemas/research-dataset.schema.json +27 -0
  62. package/dist/universal/.github/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  63. package/dist/universal/.kiro/skills/citable/SKILL.md +1 -1
  64. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  65. package/dist/universal/.kiro/skills/citable/manifest.json +14 -5
  66. package/dist/universal/.kiro/skills/citable/schemas/claim-evidence.schema.json +21 -0
  67. package/dist/universal/.kiro/skills/citable/schemas/claim-verification.schema.json +18 -0
  68. package/dist/universal/.kiro/skills/citable/schemas/derivation.schema.json +18 -0
  69. package/dist/universal/.kiro/skills/citable/schemas/external-comparison.schema.json +16 -0
  70. package/dist/universal/.kiro/skills/citable/schemas/external-observation.schema.json +32 -0
  71. package/dist/universal/.kiro/skills/citable/schemas/prompt-set.schema.json +47 -0
  72. package/dist/universal/.kiro/skills/citable/schemas/representation-data.schema.json +21 -0
  73. package/dist/universal/.kiro/skills/citable/schemas/research-dataset.schema.json +27 -0
  74. package/dist/universal/.kiro/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  75. package/dist/universal/.opencode/skills/citable/SKILL.md +1 -1
  76. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  77. package/dist/universal/.opencode/skills/citable/manifest.json +14 -5
  78. package/dist/universal/.opencode/skills/citable/schemas/claim-evidence.schema.json +21 -0
  79. package/dist/universal/.opencode/skills/citable/schemas/claim-verification.schema.json +18 -0
  80. package/dist/universal/.opencode/skills/citable/schemas/derivation.schema.json +18 -0
  81. package/dist/universal/.opencode/skills/citable/schemas/external-comparison.schema.json +16 -0
  82. package/dist/universal/.opencode/skills/citable/schemas/external-observation.schema.json +32 -0
  83. package/dist/universal/.opencode/skills/citable/schemas/prompt-set.schema.json +47 -0
  84. package/dist/universal/.opencode/skills/citable/schemas/representation-data.schema.json +21 -0
  85. package/dist/universal/.opencode/skills/citable/schemas/research-dataset.schema.json +27 -0
  86. package/dist/universal/.opencode/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  87. package/dist/universal/.pi/agent/skills/citable/SKILL.md +1 -1
  88. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  89. package/dist/universal/.pi/agent/skills/citable/manifest.json +14 -5
  90. package/dist/universal/.pi/agent/skills/citable/schemas/claim-evidence.schema.json +21 -0
  91. package/dist/universal/.pi/agent/skills/citable/schemas/claim-verification.schema.json +18 -0
  92. package/dist/universal/.pi/agent/skills/citable/schemas/derivation.schema.json +18 -0
  93. package/dist/universal/.pi/agent/skills/citable/schemas/external-comparison.schema.json +16 -0
  94. package/dist/universal/.pi/agent/skills/citable/schemas/external-observation.schema.json +32 -0
  95. package/dist/universal/.pi/agent/skills/citable/schemas/prompt-set.schema.json +47 -0
  96. package/dist/universal/.pi/agent/skills/citable/schemas/representation-data.schema.json +21 -0
  97. package/dist/universal/.pi/agent/skills/citable/schemas/research-dataset.schema.json +27 -0
  98. package/dist/universal/.pi/agent/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  99. package/dist/universal/.qoder/skills/citable/SKILL.md +1 -1
  100. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  101. package/dist/universal/.qoder/skills/citable/manifest.json +14 -5
  102. package/dist/universal/.qoder/skills/citable/schemas/claim-evidence.schema.json +21 -0
  103. package/dist/universal/.qoder/skills/citable/schemas/claim-verification.schema.json +18 -0
  104. package/dist/universal/.qoder/skills/citable/schemas/derivation.schema.json +18 -0
  105. package/dist/universal/.qoder/skills/citable/schemas/external-comparison.schema.json +16 -0
  106. package/dist/universal/.qoder/skills/citable/schemas/external-observation.schema.json +32 -0
  107. package/dist/universal/.qoder/skills/citable/schemas/prompt-set.schema.json +47 -0
  108. package/dist/universal/.qoder/skills/citable/schemas/representation-data.schema.json +21 -0
  109. package/dist/universal/.qoder/skills/citable/schemas/research-dataset.schema.json +27 -0
  110. package/dist/universal/.qoder/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  111. package/dist/universal/.rovodev/skills/citable/SKILL.md +1 -1
  112. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  113. package/dist/universal/.rovodev/skills/citable/manifest.json +14 -5
  114. package/dist/universal/.rovodev/skills/citable/schemas/claim-evidence.schema.json +21 -0
  115. package/dist/universal/.rovodev/skills/citable/schemas/claim-verification.schema.json +18 -0
  116. package/dist/universal/.rovodev/skills/citable/schemas/derivation.schema.json +18 -0
  117. package/dist/universal/.rovodev/skills/citable/schemas/external-comparison.schema.json +16 -0
  118. package/dist/universal/.rovodev/skills/citable/schemas/external-observation.schema.json +32 -0
  119. package/dist/universal/.rovodev/skills/citable/schemas/prompt-set.schema.json +47 -0
  120. package/dist/universal/.rovodev/skills/citable/schemas/representation-data.schema.json +21 -0
  121. package/dist/universal/.rovodev/skills/citable/schemas/research-dataset.schema.json +27 -0
  122. package/dist/universal/.rovodev/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  123. package/dist/universal/.trae/skills/citable/SKILL.md +1 -1
  124. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  125. package/dist/universal/.trae/skills/citable/manifest.json +14 -5
  126. package/dist/universal/.trae/skills/citable/schemas/claim-evidence.schema.json +21 -0
  127. package/dist/universal/.trae/skills/citable/schemas/claim-verification.schema.json +18 -0
  128. package/dist/universal/.trae/skills/citable/schemas/derivation.schema.json +18 -0
  129. package/dist/universal/.trae/skills/citable/schemas/external-comparison.schema.json +16 -0
  130. package/dist/universal/.trae/skills/citable/schemas/external-observation.schema.json +32 -0
  131. package/dist/universal/.trae/skills/citable/schemas/prompt-set.schema.json +47 -0
  132. package/dist/universal/.trae/skills/citable/schemas/representation-data.schema.json +21 -0
  133. package/dist/universal/.trae/skills/citable/schemas/research-dataset.schema.json +27 -0
  134. package/dist/universal/.trae/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  135. package/dist/universal/.trae-cn/skills/citable/SKILL.md +1 -1
  136. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  137. package/dist/universal/.trae-cn/skills/citable/manifest.json +14 -5
  138. package/dist/universal/.trae-cn/skills/citable/schemas/claim-evidence.schema.json +21 -0
  139. package/dist/universal/.trae-cn/skills/citable/schemas/claim-verification.schema.json +18 -0
  140. package/dist/universal/.trae-cn/skills/citable/schemas/derivation.schema.json +18 -0
  141. package/dist/universal/.trae-cn/skills/citable/schemas/external-comparison.schema.json +16 -0
  142. package/dist/universal/.trae-cn/skills/citable/schemas/external-observation.schema.json +32 -0
  143. package/dist/universal/.trae-cn/skills/citable/schemas/prompt-set.schema.json +47 -0
  144. package/dist/universal/.trae-cn/skills/citable/schemas/representation-data.schema.json +21 -0
  145. package/dist/universal/.trae-cn/skills/citable/schemas/research-dataset.schema.json +27 -0
  146. package/dist/universal/.trae-cn/skills/citable/schemas/search-discovery-data.schema.json +20 -0
  147. package/dist/universal/manifest.json +172 -64
  148. package/package.json +1 -1
  149. package/schemas/claim-evidence.schema.json +21 -0
  150. package/schemas/claim-verification.schema.json +18 -0
  151. package/schemas/derivation.schema.json +18 -0
  152. package/schemas/external-comparison.schema.json +16 -0
  153. package/schemas/external-observation.schema.json +32 -0
  154. package/schemas/prompt-set.schema.json +47 -0
  155. package/schemas/representation-data.schema.json +21 -0
  156. package/schemas/research-dataset.schema.json +27 -0
  157. package/schemas/search-discovery-data.schema.json +20 -0
  158. package/skill/SKILL.md +1 -1
  159. package/src/connectors/gsc.js +4 -0
  160. package/src/discovery/searchConsole.js +186 -0
  161. package/src/evidence/external.js +251 -0
  162. package/src/representation/prompts.js +117 -0
  163. package/src/representation/representation.js +186 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebulacomponents/citable",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "Production-grade SEO, AEO, and GEO agent skill with auditable detection, claim governance, evidence validation, and multi-agent installation.",
5
5
  "keywords": [
6
6
  "seo",
@@ -0,0 +1,21 @@
1
+ {
2
+ "$id": "citable://schemas/claim-evidence.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Claim to evidence lineage",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["claim_id", "claim_version", "proposition", "declared_scope", "evidence_references", "observation_references", "dataset_references", "derivation_references", "assertion", "limitations", "claim_hash"],
8
+ "properties": {
9
+ "claim_id": { "type": "string", "minLength": 1 },
10
+ "claim_version": { "type": "integer", "minimum": 1 },
11
+ "proposition": { "type": "string", "minLength": 3 },
12
+ "declared_scope": { "type": "object", "additionalProperties": false, "required": ["type", "generalization"], "properties": { "type": { "type": "string", "enum": ["observation", "dataset", "declared_population"] }, "observation_reference": { "type": "string" }, "dataset_reference": { "type": "string" }, "generalization": { "type": "string", "enum": ["bounded", "universal"] } } },
13
+ "evidence_references": { "type": "array", "items": { "type": "string" } },
14
+ "observation_references": { "type": "array", "items": { "type": "string" } },
15
+ "dataset_references": { "type": "array", "items": { "type": "string" } },
16
+ "derivation_references": { "type": "array", "items": { "type": "string" } },
17
+ "assertion": { "type": "object" },
18
+ "limitations": { "type": "array", "items": { "type": "string" } },
19
+ "claim_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
20
+ }
21
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$id": "citable://schemas/claim-verification.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Bounded claim verification result",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["claim_id", "claim_version", "overall", "proposition_support", "scope_support", "reasons", "lineage"],
8
+ "properties": {
9
+ "claim_id": { "type": "string" },
10
+ "claim_version": { "type": "integer", "minimum": 1 },
11
+ "overall": { "type": "string", "enum": ["SUPPORTED", "PARTIALLY_SUPPORTED", "UNSUPPORTED", "CONTRADICTED", "INDETERMINATE", "NOT_OBSERVED"] },
12
+ "proposition_support": { "type": "string", "enum": ["SUPPORTED", "PARTIALLY_SUPPORTED", "UNSUPPORTED", "CONTRADICTED", "INDETERMINATE", "NOT_OBSERVED"] },
13
+ "scope_support": { "type": "string", "enum": ["SUPPORTED", "PARTIALLY_SUPPORTED", "UNSUPPORTED", "INDETERMINATE", "NOT_OBSERVED"] },
14
+ "reasons": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["code", "message"], "properties": { "code": { "type": "string" }, "message": { "type": "string" } } } },
15
+ "lineage": { "type": "object", "additionalProperties": false, "required": ["claim_reference", "observation_references", "dataset_references", "derivation_references"], "properties": { "claim_reference": { "type": "string" }, "observation_references": { "type": "array", "items": { "type": "string" } }, "dataset_references": { "type": "array", "items": { "type": "string" } }, "derivation_references": { "type": "array", "items": { "type": "string" } } } },
16
+ "limitations": { "type": "array", "items": { "type": "string" } }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$id": "citable://schemas/derivation.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Evidence derivation",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["derivation_id", "derivation_version", "operation", "input_observation_references", "result", "implementation", "derivation_hash"],
8
+ "properties": {
9
+ "derivation_id": { "type": "string", "minLength": 1 },
10
+ "derivation_version": { "type": "integer", "minimum": 1 },
11
+ "operation": { "type": "string", "minLength": 1 },
12
+ "input_observation_references": { "type": "array", "minItems": 1, "items": { "type": "string" } },
13
+ "predicate": { "type": "object", "additionalProperties": false, "required": ["path", "operator", "value"], "properties": { "path": { "type": "string" }, "operator": { "type": "string", "enum": ["equals"] }, "value": {} } },
14
+ "result": { "type": "object" },
15
+ "implementation": { "type": "object", "additionalProperties": false, "required": ["id", "version"], "properties": { "id": { "type": "string" }, "version": { "type": "string" } } },
16
+ "derivation_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
17
+ }
18
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$id": "citable://schemas/external-comparison.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "External evidence comparison",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["comparison_id", "left_reference", "right_reference", "status", "dimensions", "limitations"],
8
+ "properties": {
9
+ "comparison_id": { "type": "string", "minLength": 1 },
10
+ "left_reference": { "type": "string", "minLength": 1 },
11
+ "right_reference": { "type": "string", "minLength": 1 },
12
+ "status": { "type": "string", "enum": ["COMPARABLE", "PARTIALLY_COMPARABLE", "NOT_COMPARABLE", "INDETERMINATE"] },
13
+ "dimensions": { "type": "object", "additionalProperties": { "type": "string", "enum": ["match", "mismatch", "missing", "unknown"] } },
14
+ "limitations": { "type": "array", "items": { "type": "string" } }
15
+ }
16
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$id": "citable://schemas/external-observation.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Provider-neutral external observation",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["observation_id", "observation_type", "subject", "source", "collector", "observed_at", "population", "observation_status", "retrieval_status", "data", "evidence_hash"],
8
+ "properties": {
9
+ "observation_id": { "type": "string", "minLength": 1 },
10
+ "observation_type": { "type": "string", "enum": ["PAGE_OBSERVATION", "EXTERNAL_OBSERVATION", "DERIVED_OBSERVATION"] },
11
+ "subject": { "type": "object", "additionalProperties": false, "required": ["type", "id"], "properties": { "type": { "type": "string", "minLength": 1 }, "id": { "type": "string", "minLength": 1 } } },
12
+ "source": { "type": "object", "additionalProperties": false, "required": ["provider", "method", "authority"], "properties": { "provider": { "type": "string", "minLength": 1 }, "method": { "type": "string", "minLength": 1 }, "authority": { "type": "string", "enum": ["first_party", "external_provider", "third_party", "synthetic", "inferred", "unknown"] }, "account_or_property": { "type": ["string", "null"] } } },
13
+ "collector": { "type": "object", "additionalProperties": false, "required": ["id", "version"], "properties": { "id": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } },
14
+ "parser": { "type": ["object", "null"], "additionalProperties": false, "required": ["id", "version"], "properties": { "id": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } },
15
+ "configuration": { "type": ["object", "null"], "additionalProperties": false, "required": ["id", "version"], "properties": { "id": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } },
16
+ "observed_at": { "type": "string", "format": "date-time" },
17
+ "retrieved_at": { "type": ["string", "null"], "format": "date-time" },
18
+ "executed_at": { "type": ["string", "null"], "format": "date-time" },
19
+ "population": { "$ref": "#/definitions/population" },
20
+ "observation_status": { "type": "string", "enum": ["observed", "not_observed", "incomplete", "indeterminate", "failed"] },
21
+ "retrieval_status": { "type": "string", "enum": ["retrieved", "not_retrieved", "partial", "indeterminate", "failed"] },
22
+ "determination_status": { "type": ["string", "null"], "enum": ["pass", "fail", "warning", "indeterminate", "not_tested", "error", "not_applicable", null] },
23
+ "data": { "type": "object" },
24
+ "context": { "type": "object", "additionalProperties": false, "properties": { "observed": { "type": "object" }, "supplied": { "type": "object" } } },
25
+ "lineage": { "type": "object", "additionalProperties": false, "properties": { "source_evidence": { "type": "array", "items": { "type": "string" } }, "predecessor_references": { "type": "array", "items": { "type": "string" } }, "successor_references": { "type": "array", "items": { "type": "string" } }, "derivation_inputs": { "type": "array", "items": { "type": "string" } }, "package_reference": { "type": ["string", "null"] } } },
26
+ "limitations": { "type": "array", "items": { "type": "string", "minLength": 1 } },
27
+ "evidence_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
28
+ },
29
+ "definitions": {
30
+ "population": { "type": "object", "additionalProperties": false, "required": ["declared", "observed", "retrieved", "evaluated", "unavailable", "failed"], "properties": { "declared": { "type": ["object", "null"], "additionalProperties": false, "properties": { "id": { "type": "string" }, "size": { "type": "integer", "minimum": 0 } } }, "observed": { "type": "integer", "minimum": 0 }, "retrieved": { "type": "integer", "minimum": 0 }, "evaluated": { "type": "integer", "minimum": 0 }, "unavailable": { "type": "integer", "minimum": 0 }, "failed": { "type": "integer", "minimum": 0 } } }
31
+ }
32
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$id": "citable://schemas/prompt-set.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Immutable versioned prompt population",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["prompt_set_id", "prompt_set_version", "purpose", "generation_method", "generated_at", "population_size", "sampling_method", "included_prompts", "excluded_prompts", "intended_population", "limitations", "prompt_set_hash"],
8
+ "properties": {
9
+ "prompt_set_id": { "type": "string", "minLength": 1 },
10
+ "prompt_set_version": { "type": "integer", "minimum": 1 },
11
+ "purpose": { "type": "string", "minLength": 1 },
12
+ "generation_method": { "type": "string", "minLength": 1 },
13
+ "generation_method_version": { "type": ["string", "null"] },
14
+ "generation_input": {},
15
+ "generated_at": { "type": "string", "format": "date-time" },
16
+ "population_size": { "type": "integer", "minimum": 0 },
17
+ "sampling_method": { "type": "string", "minLength": 1 },
18
+ "included_prompts": { "type": "array", "items": { "$ref": "#/definitions/prompt" } },
19
+ "excluded_prompts": { "type": "array", "items": { "$ref": "#/definitions/excludedPrompt" } },
20
+ "intended_population": { "type": "string", "minLength": 1 },
21
+ "limitations": { "type": "array", "items": { "type": "string", "minLength": 1 } },
22
+ "prompt_set_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
23
+ },
24
+ "definitions": {
25
+ "prompt": {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": ["prompt_id", "prompt_text"],
29
+ "properties": {
30
+ "prompt_id": { "type": "string", "minLength": 1 },
31
+ "prompt_text": { "type": "string", "minLength": 1 },
32
+ "context": { "type": ["object", "null"] },
33
+ "locale": { "type": ["string", "null"] }
34
+ }
35
+ },
36
+ "excludedPrompt": {
37
+ "type": "object",
38
+ "additionalProperties": false,
39
+ "required": ["prompt_id", "exclusion_reason"],
40
+ "properties": {
41
+ "prompt_id": { "type": "string", "minLength": 1 },
42
+ "prompt_text": { "type": ["string", "null"] },
43
+ "exclusion_reason": { "type": "string", "minLength": 1 }
44
+ }
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$id": "citable://schemas/representation-data.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Provider-neutral AI representation observation data",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["provider", "product", "surface", "model", "prompt", "prompt_set", "execution", "response", "representation", "evidence_origin"],
8
+ "properties": {
9
+ "provider": { "type": "string", "minLength": 1 },
10
+ "product": { "type": "string", "minLength": 1 },
11
+ "surface": { "type": "string", "minLength": 1 },
12
+ "model": { "type": "object", "additionalProperties": false, "required": ["value", "status"], "properties": { "value": { "type": ["string", "null"] }, "status": { "type": "string", "enum": ["known", "UNKNOWN", "UNAVAILABLE", "NOT_DISCLOSED"] } } },
13
+ "prompt": { "type": "object", "additionalProperties": false, "required": ["prompt_id", "prompt_version", "prompt_text"], "properties": { "prompt_id": { "type": "string", "minLength": 1 }, "prompt_version": { "type": "integer", "minimum": 1 }, "prompt_text": { "type": "string", "minLength": 1 } } },
14
+ "prompt_set": { "type": "object", "additionalProperties": false, "required": ["prompt_set_id", "prompt_set_version"], "properties": { "prompt_set_id": { "type": "string", "minLength": 1 }, "prompt_set_version": { "type": "integer", "minimum": 1 } } },
15
+ "execution": { "type": "object", "additionalProperties": false, "required": ["executed_at", "configuration"], "properties": { "executed_at": { "type": "string", "format": "date-time" }, "configuration": { "type": "object", "additionalProperties": false, "required": ["id", "version"], "properties": { "id": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } } } },
16
+ "response": { "type": "object", "additionalProperties": false, "required": ["status", "response_hash", "parser", "raw_response_retained"], "properties": { "status": { "type": "string", "enum": ["succeeded", "refused", "error", "timeout", "rate_limited", "malformed", "unavailable", "parser_failed"] }, "response_hash": { "type": ["string", "null"], "pattern": "^[a-f0-9]{64}$" }, "parser": { "type": "object", "additionalProperties": false, "required": ["id", "version"], "properties": { "id": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } }, "raw_response_retained": { "type": "boolean" }, "provider_metadata": { "type": "object" } } },
17
+ "representation": { "type": "object", "additionalProperties": false, "required": ["brand_mentioned", "domain_mentioned", "domain_cited", "cited_urls", "mentioned_entities", "cited_domains"], "properties": { "brand_mentioned": { "type": ["boolean", "null"] }, "domain_mentioned": { "type": ["boolean", "null"] }, "domain_cited": { "type": ["boolean", "null"] }, "cited_urls": { "type": "array", "items": { "type": "string", "format": "uri" } }, "mentioned_entities": { "type": "array", "items": { "type": "string" } }, "cited_domains": { "type": "array", "items": { "type": "string" } } } },
18
+ "evidence_origin": { "type": "string", "enum": ["LIVE_PROVIDER", "RECORDED_FIXTURE", "SYNTHETIC_TEST"] },
19
+ "limitations": { "type": "array", "items": { "type": "string" } }
20
+ }
21
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$id": "citable://schemas/research-dataset.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Immutable research dataset",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["dataset_id", "dataset_version", "declared_purpose", "declared_population", "population_size", "inclusion_criteria", "exclusion_criteria", "collection", "source_references", "observation_references", "normalization", "derivation_references", "coverage", "coverage_status", "limitations", "dataset_hash"],
8
+ "properties": {
9
+ "dataset_id": { "type": "string", "minLength": 1 },
10
+ "dataset_version": { "type": "integer", "minimum": 1 },
11
+ "declared_purpose": { "type": "string", "minLength": 1 },
12
+ "declared_population": { "type": "object", "additionalProperties": false, "required": ["type", "id"], "properties": { "type": { "type": "string", "minLength": 1 }, "id": { "type": "string", "minLength": 1 }, "size": { "type": ["integer", "null"], "minimum": 0 } } },
13
+ "population_size": { "type": ["integer", "null"], "minimum": 0 },
14
+ "inclusion_criteria": { "type": "array", "items": { "type": "string" } },
15
+ "exclusion_criteria": { "type": "array", "items": { "type": "string" } },
16
+ "collection": { "type": "object", "additionalProperties": false, "required": ["started_at", "ended_at"], "properties": { "started_at": { "type": "string", "format": "date-time" }, "ended_at": { "type": "string", "format": "date-time" } } },
17
+ "source_references": { "type": "array", "items": { "type": "string" } },
18
+ "source_retrieval_timestamps": { "type": "array", "items": { "type": "string", "format": "date-time" } },
19
+ "observation_references": { "type": "array", "items": { "type": "string" } },
20
+ "normalization": { "type": "object", "additionalProperties": false, "required": ["method", "version"], "properties": { "method": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } },
21
+ "derivation_references": { "type": "array", "items": { "type": "string" } },
22
+ "coverage": { "type": "object", "additionalProperties": false, "required": ["declared", "retrieved", "evaluated", "unavailable", "failed"], "properties": { "declared": { "type": "integer", "minimum": 0 }, "retrieved": { "type": "integer", "minimum": 0 }, "evaluated": { "type": "integer", "minimum": 0 }, "unavailable": { "type": "integer", "minimum": 0 }, "failed": { "type": "integer", "minimum": 0 } } },
23
+ "coverage_status": { "type": "string", "enum": ["complete_for_declared_population", "partial", "truncated", "indeterminate", "error"] },
24
+ "limitations": { "type": "array", "items": { "type": "string" } },
25
+ "dataset_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
26
+ }
27
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$id": "citable://schemas/search-discovery-data.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Search discovery observation data",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["query", "page", "interval", "metrics", "request", "coverage_status", "evidence_origin"],
8
+ "properties": {
9
+ "query": { "type": ["string", "null"] },
10
+ "page": { "type": ["string", "null"], "format": "uri" },
11
+ "interval": { "type": "object", "additionalProperties": false, "required": ["start_date", "end_date"], "properties": { "start_date": { "type": "string", "format": "date" }, "end_date": { "type": "string", "format": "date" } } },
12
+ "metrics": { "type": "object", "additionalProperties": false, "required": ["impressions", "clicks", "ctr", "average_position"], "properties": { "impressions": { "type": ["number", "null"], "minimum": 0 }, "clicks": { "type": ["number", "null"], "minimum": 0 }, "ctr": { "type": ["number", "null"], "minimum": 0 }, "average_position": { "type": ["number", "null"], "minimum": 0 } } },
13
+ "dimensions": { "type": "object", "additionalProperties": false, "properties": { "country": { "type": ["string", "null"] }, "device": { "type": ["string", "null"] }, "search_appearance": { "type": ["string", "null"] }, "date": { "type": ["string", "null"], "format": "date" } } },
14
+ "request": { "type": "object", "additionalProperties": false, "required": ["property_id", "dimensions", "filters", "row_limit"], "properties": { "property_id": { "type": "string", "minLength": 1 }, "dimensions": { "type": "array", "items": { "type": "string" } }, "filters": { "type": "object" }, "row_limit": { "type": "integer", "minimum": 1, "maximum": 25000 } } },
15
+ "provider_reported": { "type": "object", "additionalProperties": false, "properties": { "row_count": { "type": ["integer", "null"], "minimum": 0 }, "page_start": { "type": "integer", "minimum": 0 } } },
16
+ "response_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
17
+ "coverage_status": { "type": "string", "enum": ["complete", "truncated", "indeterminate", "provider_bounded"] },
18
+ "evidence_origin": { "type": "string", "enum": ["LIVE_PROVIDER", "RECORDED_FIXTURE", "SYNTHETIC_TEST"] }
19
+ }
20
+ }
package/skill/SKILL.md CHANGED
@@ -7,7 +7,7 @@ description: >
7
7
  generative engine optimization, structured data governance, claim substantiation,
8
8
  crawler policy, entity consistency, content discoverability, or wants a site audited
9
9
  for how search and AI systems will retrieve, understand, cite, or recommend it.
10
- version: 1.20.0
10
+ version: 1.21.0
11
11
  ---
12
12
 
13
13
  # Citable — evidence and change control for search and AI citation readiness
@@ -1,5 +1,6 @@
1
1
  import { providerRequest } from './http.js';
2
2
  import { collectionResult, errorMessage, paginationBoundary } from './collectionResult.js';
3
+ import { collectSearchConsoleObservations } from '../discovery/searchConsole.js';
3
4
 
4
5
  const BASE = 'https://www.googleapis.com/webmasters/v3';
5
6
  const METRICS = {
@@ -73,4 +74,7 @@ export const gscConnector = {
73
74
  });
74
75
  return { rows, cursor: endDate, limitations: collection.limitations, collection, ...(connectorError ? { connectorError } : {}) };
75
76
  },
77
+ async collectObservations(connection, request, context = {}) {
78
+ return collectSearchConsoleObservations({ connection, request, ...context });
79
+ },
76
80
  };
@@ -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 { providerRequest } from '../connectors/http.js';
6
+ import { collectionResult, errorMessage } from '../connectors/collectionResult.js';
7
+
8
+ const SEARCH_DATA_SCHEMA = 'search-discovery-data.schema.json';
9
+ const BASE = 'https://www.googleapis.com/webmasters/v3';
10
+ const DIMENSIONS = new Set(['date', 'query', 'page', 'country', 'device', 'searchAppearance']);
11
+ const COLLECTOR = { id: 'citable-search-console-observations', version: '1.0.0' };
12
+ const PARSER = { id: 'google-search-analytics-response', version: '1.0.0' };
13
+
14
+ function assertDate(value, name) {
15
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00Z`))) {
16
+ throw new TypeError(`${name} must be an ISO date (YYYY-MM-DD)`);
17
+ }
18
+ }
19
+
20
+ function validateRequest(request) {
21
+ if (!request || typeof request !== 'object' || Array.isArray(request)) throw new TypeError('Search Console request is required');
22
+ if (typeof request.property_id !== 'string' || !request.property_id) throw new TypeError('property_id is required');
23
+ assertDate(request.start_date, 'start_date');
24
+ assertDate(request.end_date, 'end_date');
25
+ if (request.start_date > request.end_date) throw new RangeError('start_date must not be after end_date');
26
+ const dimensions = request.dimensions ?? ['query', 'page'];
27
+ if (!Array.isArray(dimensions) || dimensions.length === 0 || dimensions.some((item) => !DIMENSIONS.has(item))) throw new TypeError('dimensions contain an unsupported Search Console dimension');
28
+ const filters = request.filters ?? {};
29
+ if (!filters || typeof filters !== 'object' || Array.isArray(filters)) throw new TypeError('filters must be an object');
30
+ const rowLimit = request.row_limit ?? 25000;
31
+ if (!Number.isInteger(rowLimit) || rowLimit < 1 || rowLimit > 25000) throw new RangeError('row_limit must be an integer from 1 to 25000');
32
+ return { property_id: request.property_id, start_date: request.start_date, end_date: request.end_date, dimensions: [...new Set(dimensions)], filters, row_limit: rowLimit };
33
+ }
34
+
35
+ function configurationFor(request) {
36
+ const configuration = { property_id: request.property_id, dimensions: request.dimensions, filters: request.filters, row_limit: request.row_limit };
37
+ return { id: `gsc-${sha256(canonicalEvidenceJson(configuration)).slice(0, 16)}`, version: '1.0.0' };
38
+ }
39
+
40
+ function numberOrNull(value) {
41
+ return value == null || value === '' || !Number.isFinite(Number(value)) ? null : Number(value);
42
+ }
43
+
44
+ function requestBody(request, startRow) {
45
+ const body = { startDate: request.start_date, endDate: request.end_date, dimensions: request.dimensions, rowLimit: request.row_limit, startRow, dataState: 'final' };
46
+ if (Object.keys(request.filters).length) body.dimensionFilterGroups = [{ groupType: 'and', filters: Object.entries(request.filters).map(([dimension, expression]) => ({ dimension, operator: 'equals', expression: String(expression) })) }];
47
+ return body;
48
+ }
49
+
50
+ function normalizeRow(row, request, dimensions, pageStart, coverageStatus, providerTotal, responseHash, origin) {
51
+ if (!row || !Array.isArray(row.keys)) throw new TypeError('Search Console row is malformed');
52
+ const values = Object.fromEntries(dimensions.map((name, index) => [name, row.keys[index] ?? null]));
53
+ const data = {
54
+ query: values.query ?? null,
55
+ page: values.page ?? null,
56
+ interval: { start_date: request.start_date, end_date: request.end_date },
57
+ metrics: { impressions: numberOrNull(row.impressions), clicks: numberOrNull(row.clicks), ctr: numberOrNull(row.ctr), average_position: numberOrNull(row.position) },
58
+ dimensions: { country: values.country ?? null, device: values.device ?? null, search_appearance: values.searchAppearance ?? null, date: values.date ?? null },
59
+ request: { property_id: request.property_id, dimensions: request.dimensions, filters: request.filters, row_limit: request.row_limit },
60
+ provider_reported: { row_count: providerTotal, page_start: pageStart },
61
+ coverage_status: coverageStatus,
62
+ response_hash: responseHash,
63
+ evidence_origin: origin,
64
+ };
65
+ const check = validateAgainst(SEARCH_DATA_SCHEMA, data);
66
+ if (!check.valid) throw new TypeError(`search discovery data violates contract: ${check.errors.join('; ')}`);
67
+ const subjectId = `query:${data.query ?? ''}|page:${data.page ?? ''}`;
68
+ return createExternalObservation({
69
+ observation_id: `GSC-${sha256(canonicalEvidenceJson({ request, values, row })).slice(0, 24)}`,
70
+ observation_type: 'EXTERNAL_OBSERVATION',
71
+ subject: { type: 'search_query_page', id: subjectId || `row:${pageStart}` },
72
+ source: { provider: 'google_search_console', method: 'search_analytics_api', authority: 'external_provider', account_or_property: request.property_id },
73
+ collector: COLLECTOR,
74
+ parser: PARSER,
75
+ configuration: configurationFor(request),
76
+ observed_at: nowIso(),
77
+ retrieved_at: nowIso(),
78
+ population: { declared: { id: `GSC-${sha256(canonicalEvidenceJson(request)).slice(0, 16)}`, size: providerTotal }, observed: 1, retrieved: 1, evaluated: 1, unavailable: 0, failed: 0 },
79
+ observation_status: coverageStatus === 'complete' ? 'observed' : 'incomplete',
80
+ retrieval_status: coverageStatus === 'complete' ? 'retrieved' : coverageStatus === 'indeterminate' ? 'indeterminate' : 'partial',
81
+ data,
82
+ lineage: { source_evidence: [responseHash] },
83
+ limitations: ['Search Console reports provider-returned search analytics rows, not universal search demand.', 'Provider privacy filtering, aggregation, and reporting revisions may limit representativeness.'],
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Collect normalized Search Console search-analytics observations. This is a
89
+ * provider adapter: credentials are supplied only to the transport and are
90
+ * never written into observations or continuation state.
91
+ */
92
+ export async function collectSearchConsoleObservations({ connection, request, token, fetchImpl = globalThis.fetch, maxPages = 1000, evidenceOrigin = 'LIVE_PROVIDER' } = {}) {
93
+ const normalized = validateRequest(request);
94
+ if (!['LIVE_PROVIDER', 'RECORDED_FIXTURE', 'SYNTHETIC_TEST'].includes(evidenceOrigin)) throw new TypeError('evidenceOrigin is invalid');
95
+ if (!Number.isInteger(maxPages) || maxPages < 1 || maxPages > 10000) throw new RangeError('maxPages must be an integer from 1 to 10000');
96
+ if (connection?.property_id && connection.property_id !== normalized.property_id) throw new Error('connection property_id does not match request property_id');
97
+ const dimensions = normalized.dimensions;
98
+ const rows = [];
99
+ const observations = [];
100
+ const errors = [];
101
+ let startRow = 0;
102
+ let providerTotal = null;
103
+ let continuationState = null;
104
+ let pagesRequested = 0;
105
+ let pagesRetrieved = 0;
106
+ let lastResponseHash = null;
107
+ let response;
108
+ do {
109
+ pagesRequested += 1;
110
+ try {
111
+ response = await providerRequest(`${BASE}/sites/${encodeURIComponent(normalized.property_id)}/searchAnalytics/query`, {
112
+ token, fetchImpl, method: 'POST', body: requestBody(normalized, startRow),
113
+ });
114
+ if (!response || !Array.isArray(response.rows)) throw new Error('Search Console response rows must be an array');
115
+ } catch (error) {
116
+ errors.push(`start row ${startRow}: ${errorMessage(error)}`);
117
+ continuationState = { start_row: startRow };
118
+ break;
119
+ }
120
+ pagesRetrieved += 1;
121
+ lastResponseHash = sha256(canonicalEvidenceJson(response));
122
+ if (Number.isInteger(response.rowCount) && response.rowCount >= 0) providerTotal = response.rowCount;
123
+ rows.push(...response.rows.map((row) => ({ row, pageStart: startRow, responseHash: lastResponseHash })));
124
+ const moreRows = providerTotal !== null && rows.length < providerTotal;
125
+ if (providerTotal !== null && !moreRows) break;
126
+ if (providerTotal === null && response.rows.length < normalized.row_limit) break;
127
+ startRow += normalized.row_limit;
128
+ if (pagesRequested >= maxPages) {
129
+ continuationState = { start_row: startRow };
130
+ break;
131
+ }
132
+ } while (true);
133
+
134
+ const collection = collectionResult({
135
+ items: rows,
136
+ paginationState: { pages_requested: pagesRequested, pages_retrieved: pagesRetrieved, boundary: { max_pages: maxPages, row_limit: normalized.row_limit } },
137
+ providerReportedTotal: providerTotal,
138
+ continuationState,
139
+ providerCompleteness: providerTotal !== null ? 'declared' : 'unknown',
140
+ limitations: ['Search Console privacy filtering and aggregation apply.', 'A complete result is complete only for the declared API request; it is not a census of all search behavior.'],
141
+ errors,
142
+ });
143
+ const normalizedRows = rows.map(({ row, pageStart, responseHash }) => normalizeRow(row, normalized, dimensions, pageStart, collection.coverage_status, providerTotal, responseHash || sha256(canonicalEvidenceJson({ request: normalized, rows: [] })), evidenceOrigin));
144
+ observations.push(...normalizedRows);
145
+ return { observations, collection, request: normalized, provider: 'google_search_console', response_hash: lastResponseHash, errors };
146
+ }
147
+
148
+ export function compareSearchDiscoveryObservations(left, right) {
149
+ if (!verifyExternalObservation(left).valid || !verifyExternalObservation(right).valid) return { status: 'INDETERMINATE', dimensions: {}, limitations: ['Observation integrity could not be verified.'] };
150
+ const dimensions = {
151
+ provider: left.source.provider === right.source.provider ? 'match' : 'mismatch',
152
+ property: left.source.account_or_property === right.source.account_or_property ? 'match' : 'mismatch',
153
+ query: left.data.query === right.data.query ? 'match' : 'mismatch',
154
+ page: left.data.page === right.data.page ? 'match' : 'mismatch',
155
+ configuration: left.configuration?.id === right.configuration?.id ? 'match' : 'mismatch',
156
+ interval: JSON.stringify(left.data.interval) === JSON.stringify(right.data.interval) ? 'match' : 'mismatch',
157
+ coverage: left.data.coverage_status === right.data.coverage_status ? 'match' : 'mismatch',
158
+ };
159
+ const hardMismatch = ['provider', 'property', 'query', 'page', 'configuration'].some((key) => dimensions[key] === 'mismatch');
160
+ const incomplete = [left.data.coverage_status, right.data.coverage_status].some((status) => !['complete'].includes(status));
161
+ const status = hardMismatch ? 'NOT_COMPARABLE' : incomplete ? 'INDETERMINATE' : dimensions.interval === 'mismatch' ? 'PARTIALLY_COMPARABLE' : 'COMPARABLE';
162
+ return {
163
+ comparison_id: `GSC-CMP-${sha256(canonicalEvidenceJson({ left: left.observation_id, right: right.observation_id, dimensions })).slice(0, 16)}`,
164
+ left_reference: left.observation_id,
165
+ right_reference: right.observation_id,
166
+ status,
167
+ dimensions,
168
+ limitations: status === 'COMPARABLE' ? [] : ['Search Console interval, configuration, and coverage limitations remain attached; no business opportunity or ranking conclusion is inferred.'],
169
+ };
170
+ }
171
+
172
+ function validSearchObservations(observations) {
173
+ return (Array.isArray(observations) ? observations : []).filter((item) => verifyExternalObservation(item).valid && item.source?.provider === 'google_search_console');
174
+ }
175
+
176
+ export function pagesForQuery(observations, query) {
177
+ return validSearchObservations(observations).filter((item) => item.data.query === query && item.data.page).map((item) => item);
178
+ }
179
+
180
+ export function queriesForPage(observations, page) {
181
+ return validSearchObservations(observations).filter((item) => item.data.page === page && item.data.query).map((item) => item);
182
+ }
183
+
184
+ export function relationshipObservations(observations, { query = null, page = null } = {}) {
185
+ return validSearchObservations(observations).filter((item) => (query == null || item.data.query === query) && (page == null || item.data.page === page));
186
+ }