agentlinkops 0.6.11 → 0.6.16

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 (311) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +2 -2
  3. package/.mcp.json +0 -0
  4. package/CHANGELOG.md +35 -0
  5. package/LICENSE +0 -0
  6. package/NOTICE +0 -0
  7. package/PACK-ARTIFACT.json +16 -0
  8. package/PACKS.json +591 -0
  9. package/README.md +34 -48
  10. package/bin/agentlinkops.mjs +0 -0
  11. package/bin/linktrail.mjs +0 -0
  12. package/cli/adapters/csv.js +0 -0
  13. package/cli/adapters/sqlite-crm.js +0 -0
  14. package/cli/adapters/suppliers.js +1 -1
  15. package/cli/admission.js +0 -0
  16. package/cli/agent-setup.js +101 -17
  17. package/cli/agentlinkops.mjs +0 -0
  18. package/cli/args.js +0 -0
  19. package/cli/campaign-bridge.js +325 -0
  20. package/cli/campaign-outcomes.js +107 -0
  21. package/cli/catalog.js +47686 -16398
  22. package/cli/check.js +40 -10
  23. package/cli/chronology.js +39 -0
  24. package/cli/citation.js +0 -0
  25. package/cli/client.js +2 -1
  26. package/cli/cloud-error.js +0 -0
  27. package/cli/commands.js +17 -8
  28. package/cli/config.js +0 -0
  29. package/cli/connection.js +92 -18
  30. package/cli/contact-adoption.js +91 -0
  31. package/cli/context-mcp.js +0 -0
  32. package/cli/context.js +13 -2
  33. package/cli/delivery.js +0 -0
  34. package/cli/disavow.js +0 -0
  35. package/cli/doctor.js +15 -4
  36. package/cli/env.js +0 -0
  37. package/cli/fleet.js +30 -19
  38. package/cli/gsc-browser.js +0 -0
  39. package/cli/import.js +7 -1
  40. package/cli/index-observations.js +0 -0
  41. package/cli/index-submissions.js +0 -0
  42. package/cli/key-store.js +115 -0
  43. package/cli/ledger.js +1 -0
  44. package/cli/lifecycle.js +0 -0
  45. package/cli/linktrail.mjs +0 -0
  46. package/cli/local-result.js +41 -6
  47. package/cli/main.js +85 -20
  48. package/cli/mcp-client.js +2 -2
  49. package/cli/migration.js +308 -0
  50. package/cli/mirror.js +0 -0
  51. package/cli/mix.js +0 -0
  52. package/cli/native-lock.js +0 -0
  53. package/cli/oauth-credentials.js +0 -0
  54. package/cli/oauth-flow.js +0 -0
  55. package/cli/placement-projection.js +106 -0
  56. package/cli/platforms.js +0 -0
  57. package/cli/prompt.js +25 -0
  58. package/cli/public-http.js +168 -0
  59. package/cli/receipt-performance.js +0 -0
  60. package/cli/receipts.js +0 -0
  61. package/cli/report.js +51 -19
  62. package/cli/setup-plan.js +0 -0
  63. package/cli/skill.js +0 -0
  64. package/cli/state.js +76 -29
  65. package/cli/status.js +14 -18
  66. package/cli/sync-lock.js +0 -0
  67. package/cli/sync.js +23 -4
  68. package/package.json +7 -8
  69. package/references/citation-panels.md +2 -0
  70. package/references/cli.md +2 -2
  71. package/references/disavow.md +1 -1
  72. package/references/discovery-handoff.md +1 -1
  73. package/references/local-crm-draft-handoff.md +1 -1
  74. package/references/portable-ledger-handoff.md +11 -10
  75. package/references/recipes/catalog.json +263 -7
  76. package/references/recipes/command-notes.md +5 -3
  77. package/references/recipes/internal-link-improvement.md +25 -0
  78. package/references/recipes/placement-reconciliation.md +53 -1
  79. package/references/recipes/qualified-campaign-handoff.md +0 -0
  80. package/references/recipes/site-context-brief.md +0 -0
  81. package/references/recipes/sourced-linkable-asset.md +0 -0
  82. package/references/reports.md +0 -0
  83. package/scripts/agentlinkops.py +0 -0
  84. package/scripts/disavow.py +0 -0
  85. package/scripts/linktrail.py +0 -0
  86. package/scripts/migrations/001_crm.sql +0 -0
  87. package/scripts/migrations/002_cloud_events.sql +0 -0
  88. package/scripts/migrations/003_disavow.sql +0 -0
  89. package/scripts/migrations/004_placement_costs.sql +0 -0
  90. package/scripts/outreach-crm-draft.py +0 -0
  91. package/scripts/portable-handoff.py +17 -4
  92. package/scripts/report.py +0 -0
  93. package/shared/admission-contract.js +3 -2
  94. package/shared/admission-report-contract.js +3 -2
  95. package/shared/agent-clients.js +0 -0
  96. package/shared/analytics-history-contract.js +15 -0
  97. package/shared/campaign-outcome-contract.js +12 -0
  98. package/shared/campaign-prospect-contract.js +23 -0
  99. package/shared/citation-evidence-contract.js +0 -0
  100. package/shared/classification-contract.js +54 -0
  101. package/shared/contact-record-contract.js +27 -0
  102. package/shared/disavow-contract.js +37 -9
  103. package/shared/exec-grammar.js +0 -0
  104. package/shared/index-observation-contract.js +5 -4
  105. package/shared/internal-links-contract.js +143 -0
  106. package/shared/lifecycle-contract.js +0 -0
  107. package/shared/lifecycle-money.js +0 -0
  108. package/shared/link-category-contract.js +35 -0
  109. package/shared/link-category.js +138 -0
  110. package/shared/link-labels.js +26 -0
  111. package/shared/link-summary-contract.js +17 -0
  112. package/shared/monitoring-chronology-export.js +57 -0
  113. package/shared/monitoring-read-contract.js +41 -0
  114. package/shared/observation-event.js +1 -1
  115. package/shared/onboarding.js +20 -0
  116. package/shared/opportunity-record.js +0 -0
  117. package/shared/response-contract.js +41 -5
  118. package/shared/search-connection-contract.js +2 -1
  119. package/shared/table-page-contract.js +13 -0
  120. package/skills/agentlinkops-assets/SKILL-DEPS.json +290 -0
  121. package/skills/agentlinkops-assets/SKILL.md +3 -1
  122. package/skills/agentlinkops-assets/agents/openai.yaml +1 -1
  123. package/skills/agentlinkops-assets/references/portable/references/cli.md +220 -0
  124. package/skills/agentlinkops-assets/references/portable/references/disavow.md +73 -0
  125. package/skills/agentlinkops-assets/references/portable/references/reports.md +53 -0
  126. package/skills/agentlinkops-assets/references/portable/scripts/agentlinkops.py +676 -0
  127. package/skills/agentlinkops-assets/references/portable/scripts/disavow.py +207 -0
  128. package/skills/agentlinkops-assets/references/portable/scripts/migrations/001_crm.sql +45 -0
  129. package/skills/agentlinkops-assets/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  130. package/skills/agentlinkops-assets/references/portable/scripts/migrations/003_disavow.sql +20 -0
  131. package/skills/agentlinkops-assets/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  132. package/skills/agentlinkops-assets/references/portable/scripts/report.py +113 -0
  133. package/skills/agentlinkops-assets/references/portable/templates/campaigns.v1.json +65 -0
  134. package/skills/agentlinkops-campaigns/SKILL-DEPS.json +336 -0
  135. package/skills/agentlinkops-campaigns/SKILL.md +7 -5
  136. package/skills/agentlinkops-campaigns/agents/openai.yaml +1 -1
  137. package/skills/agentlinkops-campaigns/references/first-campaign.md +31 -0
  138. package/skills/agentlinkops-campaigns/references/mailbox-campaign.md +46 -0
  139. package/skills/agentlinkops-campaigns/references/portable/references/cli.md +220 -0
  140. package/skills/agentlinkops-campaigns/references/portable/references/disavow.md +73 -0
  141. package/skills/agentlinkops-campaigns/references/portable/references/reports.md +53 -0
  142. package/skills/agentlinkops-campaigns/references/portable/scripts/agentlinkops.py +676 -0
  143. package/skills/agentlinkops-campaigns/references/portable/scripts/disavow.py +207 -0
  144. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/001_crm.sql +45 -0
  145. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  146. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/003_disavow.sql +20 -0
  147. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  148. package/skills/agentlinkops-campaigns/references/portable/scripts/report.py +113 -0
  149. package/skills/agentlinkops-campaigns/references/portable/templates/campaigns.v1.json +65 -0
  150. package/skills/agentlinkops-connect/SKILL-DEPS.json +104 -0
  151. package/skills/agentlinkops-connect/SKILL.md +2 -0
  152. package/skills/agentlinkops-connect/agents/openai.yaml +1 -1
  153. package/skills/agentlinkops-connect/references/agentlinkops.md +164 -61
  154. package/skills/agentlinkops-connect/references/placement-workflows.md +19 -0
  155. package/skills/agentlinkops-crm/SKILL-DEPS.json +381 -0
  156. package/skills/agentlinkops-crm/SKILL.md +26 -9
  157. package/skills/agentlinkops-crm/agents/openai.yaml +1 -1
  158. package/skills/agentlinkops-crm/references/portable/references/cli.md +220 -0
  159. package/skills/agentlinkops-crm/references/portable/references/disavow.md +73 -0
  160. package/skills/agentlinkops-crm/references/portable/references/local-crm-draft-handoff.md +76 -0
  161. package/skills/agentlinkops-crm/references/portable/references/portable-ledger-handoff.md +51 -0
  162. package/skills/agentlinkops-crm/references/portable/references/reports.md +53 -0
  163. package/skills/agentlinkops-crm/references/portable/scripts/agentlinkops.py +676 -0
  164. package/skills/agentlinkops-crm/references/portable/scripts/disavow.py +207 -0
  165. package/skills/agentlinkops-crm/references/portable/scripts/migrations/001_crm.sql +45 -0
  166. package/skills/agentlinkops-crm/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  167. package/skills/agentlinkops-crm/references/portable/scripts/migrations/003_disavow.sql +20 -0
  168. package/skills/agentlinkops-crm/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  169. package/skills/agentlinkops-crm/references/portable/scripts/outreach-crm-draft.py +121 -0
  170. package/skills/agentlinkops-crm/references/portable/scripts/portable-handoff.py +197 -0
  171. package/skills/agentlinkops-crm/references/portable/scripts/report.py +113 -0
  172. package/skills/agentlinkops-crm/references/portable/templates/campaigns.v1.json +65 -0
  173. package/skills/agentlinkops-discovery/SKILL-DEPS.json +301 -0
  174. package/skills/agentlinkops-discovery/SKILL.md +4 -2
  175. package/skills/agentlinkops-discovery/references/portable/references/cli.md +220 -0
  176. package/skills/agentlinkops-discovery/references/portable/references/disavow.md +73 -0
  177. package/skills/agentlinkops-discovery/references/portable/references/discovery-handoff.md +32 -0
  178. package/skills/agentlinkops-discovery/references/portable/references/reports.md +53 -0
  179. package/skills/agentlinkops-discovery/references/portable/scripts/agentlinkops.py +676 -0
  180. package/skills/agentlinkops-discovery/references/portable/scripts/disavow.py +207 -0
  181. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/001_crm.sql +45 -0
  182. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  183. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/003_disavow.sql +20 -0
  184. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  185. package/skills/agentlinkops-discovery/references/portable/scripts/report.py +113 -0
  186. package/skills/agentlinkops-discovery/references/portable/templates/campaigns.v1.json +65 -0
  187. package/skills/agentlinkops-discovery/references/saved-backlink-workflows.md +23 -0
  188. package/src/citations/adapter.js +0 -0
  189. package/src/citations/alert.js +0 -0
  190. package/src/citations/browser/chatgpt-citations.js +0 -0
  191. package/src/citations/browser/engine.js +0 -0
  192. package/src/citations/browser/gen/core/providers/_shared/rawSourcesDom.js +0 -0
  193. package/src/citations/browser/gen/core/providers/_shared/resetProviderPage.js +0 -0
  194. package/src/citations/browser/gen/core/providers/_shared/sourceUtils.js +0 -0
  195. package/src/citations/browser/gen/core/providers/ai-overview/index.js +0 -0
  196. package/src/citations/browser/gen/core/providers/ai-overview/lib/expand.js +0 -0
  197. package/src/citations/browser/gen/core/providers/ai-overview/lib/extractSources.js +0 -0
  198. package/src/citations/browser/gen/core/providers/ai-overview/lib/pageLifecycle.js +0 -0
  199. package/src/citations/browser/gen/core/providers/ai-overview/lib/session.js +0 -0
  200. package/src/citations/browser/gen/core/providers/chatgpt/index.js +0 -0
  201. package/src/citations/browser/gen/core/providers/chatgpt/lib/dismissAuthModal.js +0 -0
  202. package/src/citations/browser/gen/core/providers/chatgpt/lib/extractSources.js +0 -0
  203. package/src/citations/browser/gen/core/providers/chatgpt/lib/pageLifecycle.js +0 -0
  204. package/src/citations/browser/gen/core/providers/claude/index.js +0 -0
  205. package/src/citations/browser/gen/core/providers/claude/lib/extractSources.js +0 -0
  206. package/src/citations/browser/gen/core/providers/claude/lib/pageLifecycle.js +0 -0
  207. package/src/citations/browser/gen/core/providers/gemini/index.js +0 -0
  208. package/src/citations/browser/gen/core/providers/gemini/lib/extractSources.js +0 -0
  209. package/src/citations/browser/gen/core/providers/gemini/lib/pageLifecycle.js +0 -0
  210. package/src/citations/browser/gen/core/providers/gemini/lib/session.js +0 -0
  211. package/src/citations/browser/gen/core/providers/index.js +0 -0
  212. package/src/citations/browser/gen/core/providers/perplexity/index.js +0 -0
  213. package/src/citations/browser/gen/core/providers/perplexity/lib/extractSources.js +0 -0
  214. package/src/citations/browser/gen/core/providers/perplexity/lib/navigation.js +0 -0
  215. package/src/citations/browser/gen/lib/browser/camoufox.js +0 -0
  216. package/src/citations/browser/gen/lib/browser/display.js +0 -0
  217. package/src/citations/browser/gen/lib/browser/domOps.js +0 -0
  218. package/src/citations/browser/gen/lib/browser/humanBehavior.js +0 -0
  219. package/src/citations/browser/gen/lib/browser/navigate.js +0 -0
  220. package/src/citations/browser/gen/lib/extraction/sourceUtils.js +0 -0
  221. package/src/citations/browser/gen/lib/input/editor/clearInput.js +0 -0
  222. package/src/citations/browser/gen/lib/input/editor/findEditor.js +0 -0
  223. package/src/citations/browser/gen/lib/input/editor/promptInput.js +0 -0
  224. package/src/citations/browser/gen/lib/input/markdown/converter.js +0 -0
  225. package/src/citations/browser/gen/lib/input/markdown/toMarkdown.js +0 -0
  226. package/src/citations/browser/gen/lib/input/response/detectBotPage.js +0 -0
  227. package/src/citations/browser/gen/lib/input/response/isGenerating.js +0 -0
  228. package/src/citations/browser/gen/lib/input/response/waitForFinish.js +0 -0
  229. package/src/citations/browser/gen/lib/input/sources/findButton.js +0 -0
  230. package/src/citations/browser/gen/lib/input/sources/openPanel.js +0 -0
  231. package/src/citations/browser/gen/utils/agent-constants.js +0 -0
  232. package/src/citations/browser/providers.js +0 -0
  233. package/src/citations/browser/proxy-admission.js +0 -0
  234. package/src/citations/browser/shims/errors.js +0 -0
  235. package/src/citations/browser/shims/types.js +0 -0
  236. package/src/citations/browser/shims/utils.js +0 -0
  237. package/src/citations/contract.js +0 -0
  238. package/src/citations/egress-receipts.js +0 -0
  239. package/src/citations/evidence-integrity.js +0 -0
  240. package/src/citations/inventory.js +0 -0
  241. package/src/citations/login.js +0 -0
  242. package/src/citations/match.js +0 -0
  243. package/src/citations/panel-builder.js +0 -0
  244. package/src/citations/report.js +0 -0
  245. package/src/citations/runner.js +0 -0
  246. package/src/citations/stats.js +0 -0
  247. package/src/citations/sync.js +0 -0
  248. package/src/citations/watch.js +0 -0
  249. package/src/competitors/contract.js +17 -1
  250. package/src/competitors/domain-mix.js +0 -0
  251. package/src/context/ga4.js +0 -0
  252. package/src/context/gsc.js +0 -0
  253. package/src/context/site-profile.js +34 -121
  254. package/src/context/util.js +0 -0
  255. package/src/context-tool-registry.js +0 -0
  256. package/src/corpus/acquire.js +68 -32
  257. package/src/corpus/extract.js +147 -0
  258. package/src/corpus/frontier.js +151 -0
  259. package/src/corpus/seeds.js +0 -0
  260. package/src/discovery/contract.js +8 -0
  261. package/src/discovery/import.js +13 -1
  262. package/src/evidence-reference.js +0 -0
  263. package/src/index-observation.js +0 -0
  264. package/src/locator.js +0 -0
  265. package/src/mentions.js +193 -0
  266. package/src/page-acquisition/acquire.js +50 -0
  267. package/src/page-acquisition/bytes.js +138 -0
  268. package/src/page-acquisition/capture.js +11 -0
  269. package/src/page-acquisition/contracts.js +67 -0
  270. package/src/page-acquisition/documents.js +166 -0
  271. package/src/page-acquisition/http.js +307 -0
  272. package/src/page-acquisition/policy.js +20 -0
  273. package/src/page-acquisition/recovery.js +164 -0
  274. package/src/page-acquisition/render-failure.js +82 -0
  275. package/src/page-extraction/html.js +359 -0
  276. package/src/page-extraction/index.js +30 -0
  277. package/src/page-extraction/metadata.js +16 -0
  278. package/src/response-shaping.js +4 -3
  279. package/src/tool-views.js +23 -12
  280. package/src/verifier/fetch.js +4 -339
  281. package/src/verifier/index.js +81 -23
  282. package/src/verifier/observation-kind.js +0 -0
  283. package/src/verifier/page-quality.js +14 -0
  284. package/src/verifier/parser.js +2 -241
  285. package/src/verifier/readiness.js +64 -13
  286. package/src/verifier/render-evidence.js +25 -2
  287. package/src/verifier/robots.js +0 -0
  288. package/src/verifier/target.js +201 -0
  289. package/src/verifier/transition.js +18 -3
  290. package/src/verifier/url.js +31 -0
  291. package/src/watch-crawlability.js +144 -0
  292. package/templates/SITE.template.md +0 -0
  293. package/templates/campaigns.v1.json +0 -0
  294. package/skills/content-defingerprinting/SKILL.md +0 -29
  295. package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -102
  296. package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -26
  297. package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -52
  298. package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -21
  299. package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -96
  300. package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -432
  301. package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -86
  302. package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -460
  303. package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -651
  304. package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -27
  305. package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -212
  306. package/skills/internal-linking-optimizer/SKILL.md +0 -56
  307. package/skills/internal-linking-optimizer/references/source-prompt.md +0 -76
  308. package/skills/pre-publish-review/SKILL.md +0 -179
  309. package/skills/source-cited-content-builder/SKILL.md +0 -56
  310. package/skills/source-cited-content-builder/references/source-prompt.md +0 -19
  311. package/skills/source-cited-humanizer/SKILL.md +0 -20
@@ -0,0 +1,57 @@
1
+ // Offline frozen chronology export. No hosted store, authentication, network or
2
+ // collection dependency belongs in the customer's CLI export closure.
3
+ const fail=(status,code,message)=>{throw Object.assign(new Error(message),{name:'ChronologyExportError',status,code});};
4
+ const iso=value=>typeof value==='string'&&Number.isFinite(Date.parse(value))?new Date(value).toISOString():null;
5
+ const validIdentity=value=>{
6
+ if(!value||typeof value!=='object'||Array.isArray(value))return false;
7
+ const id=v=>typeof v==='string'&&v.length>0&&v.length<=200&&!/[\s\x00-\x1f\x7f]/u.test(v);
8
+ const url=v=>{if(typeof v!=='string'||!v||v.length>4096)return false;try{const u=new URL(v);return ['http:','https:'].includes(u.protocol)&&!u.username&&!u.password;}catch{return false;}};
9
+ return ['workspaceId','projectId','watchId'].every(k=>id(value[k]))&&url(value.sourceUrl)&&url(value.targetUrl)
10
+ &&['exact','domain','subdomain','path'].includes(value.targetScope)&&(value.targetId===null||id(value.targetId))
11
+ &&(value.localReference===null||typeof value.localReference==='string'&&value.localReference.length<=2048)
12
+ &&value.matchBasis==='configured_target_url';
13
+ };
14
+ const compare=(a,b)=>a.sortAt>b.sortAt?-1:a.sortAt<b.sortAt?1:a.id>b.id?-1:a.id<b.id?1:0;
15
+ const sha256=async value=>Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',new TextEncoder().encode(value))),b=>b.toString(16).padStart(2,'0')).join('');
16
+ const baseItem=(stream,row,identity,observedAt,recordedAt,kind)=>({id:`${stream}:${row.id}`,rawId:row.id,stream,kind,identity,observedAt:iso(observedAt),recordedAt:iso(recordedAt),sortAt:iso(observedAt)??iso(recordedAt),state:null,reason:null,sourceKey:null,method:null,checkerVersion:null,evidence:{sha256:null,reference:null,expiresAt:null,availability:'not_checked'},data:{}});
17
+
18
+ // Export only a supplied frozen dataset. This function never reads current DB
19
+ // state or fills a missing origin with today's projection.
20
+ export async function freezeChronology(pages,{localItems=[]}={}){
21
+ if(!Array.isArray(pages)||!pages.length||pages.length>10000||!Array.isArray(localItems)||localItems.length>10000)fail(400,'INVALID_CHRONOLOGY_EXPORT','Provide bounded chronology pages.');
22
+ const first=pages[0];if(!validIdentity(first?.identity)||iso(first?.asOf)!==first.asOf||!/^[a-f0-9]{64}$/.test(first?.snapshotId??''))fail(400,'INVALID_CHRONOLOGY_EXPORT','A frozen page requires an exact placement identity, watermark and snapshot digest.');
23
+ const byId=new Map();let continuous=first.pageCursor===null;
24
+ for(let i=0;i<pages.length;i++){
25
+ const page=pages[i];if(!page||page.schemaVersion!==1||page.snapshotId!==first.snapshotId||page.asOf!==first.asOf||JSON.stringify(page.identity)!==JSON.stringify(first.identity)||!Array.isArray(page.items))fail(400,'CHRONOLOGY_EXPORT_SCOPE_MISMATCH','Export pages must belong to one frozen placement read.');
26
+ if(typeof page.hasMore!=='boolean'||(page.hasMore?typeof page.nextCursor!=='string'||!page.nextCursor||page.nextCursor.length>12000:page.nextCursor!==null))fail(400,'INVALID_CHRONOLOGY_EXPORT','Each frozen page must declare whether another page exists with a consistent continuation cursor.');
27
+ if(i&&pages[i-1].nextCursor!==page.pageCursor)continuous=false;
28
+ for(const row of page.items){if(!row||JSON.stringify(row.identity)!==JSON.stringify(first.identity))fail(400,'CHRONOLOGY_EXPORT_SCOPE_MISMATCH','Every retained row must belong to this exact frozen placement.');const previous=byId.get(row.id);if(previous&&JSON.stringify(previous)!==JSON.stringify(row))fail(400,'CHRONOLOGY_EXPORT_CONFLICT','A frozen row changed between pages.');byId.set(row.id,row);}
29
+ }
30
+ for(const row of localItems){if(row.stream!=='local_submissions'||JSON.stringify(row.identity)!==JSON.stringify(first.identity)||!iso(row.sortAt)||row.sortAt>first.asOf)fail(400,'CHRONOLOGY_EXPORT_SCOPE_MISMATCH','Local submission context must match this placement and cutoff.');const previous=byId.get(row.id);if(previous&&JSON.stringify(previous)!==JSON.stringify(row))fail(400,'CHRONOLOGY_EXPORT_CONFLICT','A local receipt identity changed.');byId.set(row.id,row);}
31
+ const complete=continuous&&pages.at(-1).hasMore===false,coverage=JSON.parse(JSON.stringify(pages.at(-1).coverage));
32
+ if(localItems.length)coverage.local_submissions={status:'available',reason:'validated_local_reported_receipts',hasMore:false,providerAuthenticity:'not_established'};
33
+ const items=[...byId.values()].sort(compare),gaps=[];
34
+ for(const [stream,origin] of Object.entries(coverage)){
35
+ if(origin.status==='available'){const loaded=items.filter(row=>row.stream===stream);origin.loadedRange={newestAt:loaded[0]?.sortAt??null,oldestAt:loaded.at(-1)?.sortAt??null,itemCount:loaded.length};}
36
+ if(origin.status!=='available'||origin.compactedBefore||origin.retentionFloor>0)gaps.push({stream,reason:origin.reason,...(origin.compactedBefore?{compactedBefore:origin.compactedBefore}:{}),...(origin.retentionFloor>0?{retentionFloor:origin.retentionFloor}:{})});
37
+ }
38
+ if(!complete)gaps.push({stream:'pagination',reason:'partial_pagination'});
39
+ const data={schemaVersion:1,identity:first.identity,asOf:first.asOf,snapshotId:first.snapshotId,items,coverage,gaps,pagination:{complete,pageCount:pages.length,reason:complete?'all_loaded_pages':'partial_pagination',nextCursor:pages.at(-1).nextCursor??null},historyMeaning:first.historyMeaning};
40
+ const frozen=JSON.parse(JSON.stringify(data));return {...frozen,contentHash:await sha256(JSON.stringify(frozen))};
41
+ }
42
+
43
+ // This is an offline normalization boundary, not server ingestion, collection or
44
+ // proof of provider authenticity. The hash uses the existing CLI receipt format.
45
+ export async function normalizeLocalSubmissions(rows,identity){
46
+ if(!validIdentity(identity))fail(400,'INVALID_SUBMISSION_RECEIPT','An exact placement identity is required.');
47
+ if(!Array.isArray(rows)||rows.length>10000)fail(400,'INVALID_SUBMISSION_RECEIPT','Provide bounded local submission receipts.');
48
+ const fields=['v','id','project_id','url','channel','endpoint','submitted_at','http_status','evidence_sha256'],items=[],byId=new Map();
49
+ for(const row of rows){
50
+ if(!row||Object.keys(row).some(k=>!fields.includes(k))||fields.some(k=>!Object.hasOwn(row,k))||row.v!==1||!/^[\w-]{1,128}$/.test(row.id??'')||!/^[\w-]{1,128}$/.test(row.project_id??'')||row.channel!=='indexnow'||row.endpoint!=='https://api.indexnow.org/indexnow'||iso(row.submitted_at)!==row.submitted_at||row.http_status!==null&&(!Number.isInteger(row.http_status)||row.http_status<100||row.http_status>599))fail(400,'INVALID_SUBMISSION_RECEIPT','Use the existing validated local submission format.');
51
+ let url;try{url=new URL(row.url);}catch{fail(400,'INVALID_SUBMISSION_RECEIPT','The submission URL is invalid.');}if(url.href!==row.url||!['http:','https:'].includes(url.protocol)||url.username||url.password||url.hash)fail(400,'INVALID_SUBMISSION_RECEIPT','The submission URL must be exact HTTP(S).');
52
+ const body=Object.fromEntries(fields.filter(k=>k!=='evidence_sha256').map(k=>[k,row[k]]));if(await sha256(JSON.stringify(body))!==row.evidence_sha256)fail(400,'INVALID_SUBMISSION_RECEIPT','The local receipt hash does not match.');
53
+ const key=`${row.project_id}:${row.id}`,previous=byId.get(key);if(previous&&JSON.stringify(previous)!==JSON.stringify(row))fail(409,'SUBMISSION_IDENTITY_CONFLICT','A local submission ID has conflicting contents.');byId.set(key,row);if(previous||row.project_id!==identity.projectId||![identity.sourceUrl,identity.targetUrl].includes(row.url))continue;
54
+ const item=baseItem('local_submissions',{id:key},identity,null,null,'reported_submission');items.push({...item,sortAt:row.submitted_at,reason:row.http_status===200?'received':row.http_status===202?'key_validation_pending':row.http_status===null?'unknown':'failed',method:'reported_indexnow_http_response',evidence:{...item.evidence,sha256:row.evidence_sha256,availability:'local_receipt'},data:{...row,subject:row.url===identity.sourceUrl?'source':'target',reportedSubmissionAt:row.submitted_at,sortTimeMeaning:'reported_submission_time',retrievedAt:null,providerAuthenticity:'not_established',participantIndexingVerified:false,causationEstablished:false}});
55
+ }
56
+ return items.sort(compare);
57
+ }
@@ -0,0 +1,41 @@
1
+ import {z} from 'zod';
2
+ const id=z.string().min(1).max(200),time=z.string().datetime(),nullableTime=time.nullable(),count=z.number().int().nonnegative();
3
+ export const chronologyStreams=['context','link_observations','target_observations','link_events','target_events','index_observations','index_events','local_submissions'];
4
+ export const chronologyInput=z.object({watchId:id,limit:z.number().int().min(1).max(100).optional(),cursor:z.string().max(12000).optional(),streams:z.array(z.enum(chronologyStreams)).min(1).max(8).refine(values=>new Set(values).size===values.length).optional()}).strict();
5
+ const identity=z.object({workspaceId:id,projectId:id,watchId:id,sourceUrl:z.url(),targetUrl:z.url(),targetScope:z.enum(['exact','domain','subdomain','path']),targetId:id.nullable(),localReference:z.string().nullable(),matchBasis:z.literal('configured_target_url')});
6
+ export const chronologyResponse=z.object({schemaVersion:z.literal(1),identity,asOf:time,snapshotId:z.string().regex(/^[a-f0-9]{64}$/),items:z.array(z.looseObject({id:z.string(),rawId:z.string(),stream:z.enum(chronologyStreams),kind:z.string(),identity,observedAt:nullableTime,recordedAt:nullableTime,sortAt:time,state:z.string().nullable(),reason:z.string().nullable(),sourceKey:z.string().nullable(),method:z.string().nullable(),checkerVersion:z.string().nullable(),evidence:z.looseObject({sha256:z.string().nullable(),reference:z.string().nullable(),expiresAt:nullableTime,availability:z.string()}),data:z.record(z.string(),z.unknown())})),pageCursor:z.string().nullable(),nextCursor:z.string().nullable(),hasMore:z.boolean(),coverage:z.partialRecord(z.enum(chronologyStreams),z.looseObject({status:z.enum(['available','denied','unavailable']),reason:z.string()})),historyMeaning:z.string()}).passthrough();
7
+ // Additive retained-data projection; absence preserves older backend responses.
8
+ const nullableSeconds=z.number().nonnegative().nullable();
9
+ const coverageProjection=z.object({
10
+ supplied:count,active:count,paused:count,recordedHeld:count,schedulingCandidates:count,busy:count,dueCandidates:count,
11
+ eligibility:z.object({status:z.literal('unavailable'),eligible:z.null(),reason:z.literal('admission_not_rechecked')}),
12
+ denominatorMeaning:z.string(),freshnessWindow:z.string(),cadenceSeconds:z.object({minimum:count.nullable(),maximum:count.nullable()}),
13
+ oldestOverdueAt:nullableTime,oldestOverdueAgeSeconds:nullableSeconds,nextScheduledAt:nullableTime,nextEligibleAt:z.null(),nextEligibleReason:z.literal('admission_not_rechecked'),
14
+ neverAttempted:count,neverConclusive:count,currentUnknown:count,unknownReasons:z.record(z.string(),count),freshConclusive:count,staleConclusive:count,
15
+ shares:z.object({currentUnknown:z.number().min(0).max(1).nullable(),neverAttempted:z.number().min(0).max(1).nullable(),freshConclusive:z.number().min(0).max(1).nullable()}),shareMeaning:z.string(),
16
+ freshRetainedLinkVerification:count.optional(),linkVerificationMeaning:z.string().optional(),reasonCounts:z.object({due:z.record(z.string(),count),deferred:z.record(z.string(),count),unavailable:z.record(z.string(),count).optional()})
17
+ });
18
+ const projectedJobs=z.object({
19
+ retained:count,queued:count,running:count,succeeded:count,failed:count,cancelled:count,everClaimed:count,claimAttempts:count,retryClaims:count,retryExhausted:count,expiredRunningLeases:count,
20
+ oldestQueuedAt:nullableTime,oldestQueuedAgeSeconds:nullableSeconds,oldestRunningAdmissionAt:nullableTime,oldestRunningAdmissionAgeSeconds:nullableSeconds,latestAdmissionAt:nullableTime,latestCompletionAt:nullableTime,
21
+ scopedRetainedDeadLetters:count,deadLetterMeaning:z.string(),admissionToTerminal:z.object({samples:count,minimumSeconds:nullableSeconds,maximumSeconds:nullableSeconds,meaning:z.string()}),
22
+ startedAt:z.null(),queueWaitSeconds:z.null(),providerLatencySeconds:z.null(),timingReason:z.literal('first_start_and_provider_timing_not_retained'),
23
+ dispatch:z.object({outstanding:count,deferred:count,expiredSendingLeases:count,nextDispatchAt:nullableTime,meaning:z.string()}),historyMeaning:z.string()
24
+ });
25
+ const projectedPlane=z.object({coverage:coverageProjection,jobs:projectedJobs});
26
+ const indexProjection=z.discriminatedUnion('status',[
27
+ z.object({status:z.literal('denied'),reason:z.string()}).strict(),z.object({status:z.literal('unavailable'),reason:z.string()}).strict(),
28
+ z.object({status:z.literal('available'),reason:z.string(),suppliedSourceWatches:count,activeSourceWatches:count,
29
+ lanes:z.array(z.object({lane:z.enum(['gsc','licensed_site']),retainedStates:count,observedActiveWatches:count,neverObservedActiveWatches:count,currentUnknownStates:count,latestObservationAt:nullableTime,oldestObservationAt:nullableTime,oldestObservationAgeSeconds:nullableSeconds})).length(2),
30
+ schedule:z.discriminatedUnion('status',[z.object({status:z.literal('unavailable'),reason:z.literal('schema_unavailable')}),z.object({status:z.literal('available'),projectOptedIn:z.boolean(),inspectionEnabled:z.boolean(),schedulerEnabled:z.boolean(),cadenceSeconds:count.nullable(),dailyLimit:count.nullable(),nextProjectScanAt:nullableTime,propertyEligibleWatches:z.null(),propertyEligibilityReason:z.literal('current_source_property_grant_not_assessed'),eligibleWatches:z.null(),eligibilityReason:z.literal('admission_not_rechecked')})]),meaning:z.string()})
31
+ ]);
32
+ export const monitoringAssuranceProjection=z.object({schemaVersion:z.literal(1),source:projectedPlane,target:projectedPlane,index:indexProjection,
33
+ serviceTickWindow:z.object({expectedTicks:z.null(),completedTicks:z.null(),availabilityShare:z.null(),reason:z.literal('tick_history_not_retained')}),
34
+ capacity:z.object({dueOpportunities:z.null(),missedOpportunities:z.null(),checksPerEligibleWatch:z.null(),reason:z.literal('bounded_roster_opportunity_history_not_retained')}),meaning:z.string()});
35
+ export const assuranceInput=z.object({projectId:id}).strict();
36
+ const metric=z.object({supplied:count,active:count,paused:count,neverAttempted:count,neverConclusive:count,due:count,overdue:count,currentUnknown:count,latestAttemptAt:nullableTime,oldestAttemptAt:nullableTime,oldestAttemptAgeSeconds:z.number().nonnegative().nullable(),latestConclusiveAt:nullableTime,oldestConclusiveAt:nullableTime,oldestConclusiveAgeSeconds:z.number().nonnegative().nullable()});
37
+ export const assuranceResponse=z.object({schemaVersion:z.literal(1),workspaceId:id,projectId:id,asOf:time,source:metric,target:metric,monitoringProjection:monitoringAssuranceProjection.optional(),sourceLinkVerification:z.object({latestRetainedAt:nullableTime,oldestRetainedAt:nullableTime,observedWatches:count,meaning:z.string()}),sourceJobs:z.object({queued:count,running:count}),overdueReasons:z.record(z.string(),count),index:z.discriminatedUnion('status',[z.object({status:z.literal('available'),reason:z.string(),observedWatches:count,latestObservationAt:nullableTime,oldestObservationAt:nullableTime,oldestObservationAgeSeconds:z.number().nonnegative().nullable(),denominator:z.string()}),z.object({status:z.literal('denied'),reason:z.string()}),z.object({status:z.literal('unavailable'),reason:z.string()})]),serviceTicks:z.object({status:z.enum(['available','unavailable']),meaning:z.string(),items:z.array(z.object({plan:z.string(),latestAttemptAt:time,latestCompletionAt:nullableTime,latestSuccessAt:nullableTime,latestOutcome:z.enum(['running','succeeded','failed']),ageSeconds:z.number().nonnegative()}))}),assurance:z.object({elapsedSoak:z.literal('not_established'),backupRestore:z.literal('not_established'),externalDelivery:z.literal('not_established')}),denominator:z.string()}).passthrough();
38
+ export const monitoringReadResponses={get_placement_chronology:chronologyResponse,get_monitoring_service_assurance:assuranceResponse};
39
+ const at='2026-10-07T00:00:00.000Z',projectId='pr_example',workspaceId='ws_example';
40
+ const emptyMetric={supplied:0,active:0,paused:0,neverAttempted:0,neverConclusive:0,due:0,overdue:0,currentUnknown:0,latestAttemptAt:null,oldestAttemptAt:null,oldestAttemptAgeSeconds:null,latestConclusiveAt:null,oldestConclusiveAt:null,oldestConclusiveAgeSeconds:null};
41
+ export const monitoringReadExamples={get_placement_chronology:{schemaVersion:1,identity:{workspaceId,projectId,watchId:'watch_example',sourceUrl:'https://publisher.example.org/resource',targetUrl:'https://example.org/guide',targetScope:'exact',targetId:null,localReference:null,matchBasis:'configured_target_url'},asOf:at,snapshotId:'a'.repeat(64),items:[],pageCursor:null,nextCursor:null,hasMore:false,coverage:{context:{status:'available',reason:'recorded_declarations_only'},local_submissions:{status:'unavailable',reason:'local_only_not_hosted'}},historyMeaning:'Retained evidence only; gaps do not establish inactivity.'},get_monitoring_service_assurance:{schemaVersion:1,workspaceId,projectId,asOf:at,source:emptyMetric,target:emptyMetric,sourceLinkVerification:{latestRetainedAt:null,oldestRetainedAt:null,observedWatches:0,meaning:'Retained complete link receipts.'},sourceJobs:{queued:0,running:0},overdueReasons:{},index:{status:'unavailable',reason:'not_configured'},serviceTicks:{status:'unavailable',meaning:'Scheduler metadata, not customer coverage.',items:[]},assurance:{elapsedSoak:'not_established',backupRestore:'not_established',externalDelivery:'not_established'},denominator:'Supplied project records; zero is not an inventory of all backlinks.'}};
@@ -6,7 +6,7 @@ export function eventSnapshot(state) {
6
6
  rel: (item.rel || []).slice(0, 20),
7
7
  }));
8
8
  return {
9
- schema_version: 1, target_scope: state.target_scope,
9
+ schema_version: 1, display_state: state.pipeline?.intent==='prospect' && !state.wasEverPresent ? 'prospect' : ['confirmed_missing','suspected_missing'].includes(state.state) && !state.wasEverPresent ? 'unverified' : state.state||'unknown', was_ever_present:state.wasEverPresent === true, intent:state.pipeline?.intent??'earned', target_scope: state.target_scope,
10
10
  check_state: latest.state ?? 'unknown', link_signature: latest.linkSignature ?? null,
11
11
  checker_version: latest.evidence?.checkerVersion ?? null,
12
12
  // Hash the captured document, not the enclosing R2 receipt object. Older events
@@ -0,0 +1,20 @@
1
+ import {z} from 'zod';
2
+
3
+ export const initialOnboarding = () => ({version:1,revision:0,step:'website',projectId:null,gsc:null,returnTo:null,completedAt:null});
4
+ export const onboardingState = z.object({
5
+ version:z.literal(1),revision:z.number().int().nonnegative(),step:z.enum(['website','google','finish','complete']),
6
+ projectId:z.string().nullable(),gsc:z.enum(['connected','later']).nullable(),returnTo:z.string().nullable(),completedAt:z.string().nullable(),
7
+ });
8
+ export const onboardingResponse = z.object({required:z.boolean(),state:onboardingState.nullable()});
9
+
10
+ // Keep only the two existing account-return destinations. Never persist an OAuth code,
11
+ // bearer, arbitrary URL or user-supplied permission scope in workspace metadata.
12
+ export function onboardingReturnTo(value) {
13
+ if(typeof value!=='string'||!value.startsWith('#/'))return null;
14
+ const [route,query]=value.slice(2).split('?'),params=new URLSearchParams(query);
15
+ if(route==='workspace'&&params.get('section')==='invitations')return '#/workspace?section=invitations';
16
+ const clientId=params.get('clientId');
17
+ if(route==='access'&&params.get('section')==='grants'&&params.getAll('clientId').length===1&&clientId&&clientId.length<=2048&&!/[\s\u0000-\u001f\u007f]/.test(clientId))
18
+ return `#/access?${new URLSearchParams({section:'grants',clientId})}`;
19
+ return null;
20
+ }
File without changes
@@ -1,18 +1,32 @@
1
+ import {onboardingResponse} from './onboarding.js';
2
+ import {internalLinkPreviewOutput,internalLinkSnapshotOutput,internalLinkListOutput,internalLinkQueryOutput} from './internal-links-contract.js';
3
+ import {profileHistoryOutput,profileHistoryRecordsOutput} from './analytics-history-contract.js';
4
+ import {classificationMetadataResponse,classificationMetadataWriteResponse,classificationMetadataOccurrenceResponse,classificationResponse,classificationHistoryResponse,classificationQuoteResponse,classificationJobResponse} from './classification-contract.js';
5
+ import {chronologyResponse,assuranceResponse} from './monitoring-read-contract.js';
1
6
  import {lifecycleOutput} from './lifecycle-contract.js';
7
+ import {contactResponses} from './contact-record-contract.js';
8
+ import {prospectResponses} from './campaign-prospect-contract.js';
9
+ import {campaignOutcomeResponses} from './campaign-outcome-contract.js';
2
10
  import {searchConnectionsResponse,searchRowsResponse,searchSyncResponse,searchPropertiesResponse,searchStartResponse,searchConnection,searchImportResponse} from './search-connection-contract.js';
11
+ import {linkSummaryResponse} from './link-summary-contract.js';
12
+ import {linkCategoryFields,linkLabelledWatchResponse,linkLabelledWatchesResponse} from './link-category-contract.js';
13
+ export {linkSummaryResponse};
3
14
  import {z} from 'zod';
4
15
  export const objectResponse=z.looseObject({});
5
16
  export const pageResponse=z.looseObject({items:z.array(objectResponse),next_cursor:z.string().nullable().optional()});
6
- export const watchResponse=z.looseObject({id:z.string(),source_url:z.string(),target_url:z.string(),state:z.string(),observation_state:objectResponse});
17
+ // Category fields share the documented contract while remaining additive for older
18
+ // deployments. List rows may be concise; detailed watch requirements stay separate.
19
+ export const watchPageResponse=pageResponse.extend({items:z.array(z.looseObject(linkCategoryFields))});
20
+ export const watchResponse=z.looseObject({...linkCategoryFields,id:z.string(),source_url:z.string(),target_url:z.string(),state:z.string(),observation_state:objectResponse});
7
21
  export const targetResponse=z.looseObject({id:z.string(),url:z.string(),state:z.string(),observation_state:objectResponse});
8
22
  export const historyResponse=z.looseObject({items:z.array(z.looseObject({id:z.string(),state:z.string(),checked_at:z.string(),result:objectResponse})),next_cursor:z.string().nullable().optional()});
9
23
  export const jobResponse=z.looseObject({id:z.string(),state:z.enum(['queued','running','succeeded','failed','cancelled'])});
10
24
  export const observationEvent=z.looseObject({id:z.string(),data:objectResponse});
11
- export const eventFeedResponse=z.looseObject({events:z.array(observationEvent),next_cursor:z.string(),has_more:z.boolean()});
25
+ export const eventFeedResponse=z.looseObject({events:z.array(observationEvent),next_cursor:z.string().nullable(),has_more:z.boolean()});
12
26
  export const linkChangeReportResponse=z.looseObject({schema_version:z.literal(1),workspace_id:z.string(),project_id:z.string(),coverage_scope:z.literal('retained_event_feed'),
13
27
  from:z.string(),as_of:z.string(),through_sequence:z.number().int().nonnegative(),count:z.number().int().nonnegative(),
14
28
  limit:z.literal(500),truncated:z.boolean(),complete:z.boolean(),retention_limited:z.boolean(),scan_limit:z.literal(10000),scan_limited:z.boolean(),
15
- events:z.array(z.looseObject({id:z.string(),workspace_id:z.string(),project_id:z.string(),type:z.enum(['placement_acquired','placement_recovered','placement_lost']),created_at:z.string()})).max(500)});
29
+ events:z.array(z.looseObject({id:z.string(),workspace_id:z.string(),project_id:z.string(),type:z.enum(['placement_acquired','placement_recovered','placement_lost','prospect_won']),created_at:z.string()})).max(500)});
16
30
  export const usageResponse=z.looseObject({
17
31
  workspace_id:z.string(),period:z.string().regex(/^\d{4}-\d{2}$/),plan:z.string(),
18
32
  check_reservations:z.looseObject({reserved:z.number().nonnegative(),consumed:z.number().nonnegative(),released:z.number().nonnegative()}),
@@ -41,11 +55,27 @@ export const candidateMonitoringResponse=z.looseObject({
41
55
  });
42
56
  /** Network boundary shared by the console and command adapters. Unknown fields survive evolution. */
43
57
  export function restResponseContract(path,method='GET') {
58
+ const contactCommand=path.split('?')[0].match(/^\/v1\/commands\/([^/]+)$/)?.[1];
59
+ const campaignResponse=prospectResponses[contactCommand]??campaignOutcomeResponses[contactCommand]??contactResponses[contactCommand];
60
+ if(method==='POST'&&campaignResponse)return campaignResponse;
61
+ if(/^\/v1\/projects\/[^/]+\/classification-metadata(?:\/(definitions|settings|occurrences))?(?:\?|$)/.test(path))return method==='GET'?(path.split('?')[0].endsWith('/occurrences')?classificationMetadataOccurrenceResponse:classificationMetadataResponse):classificationMetadataWriteResponse;
62
+ if(/^\/v1\/watches\/[^/]+\/classification\/history(?:\?|$)/.test(path))return classificationHistoryResponse;
63
+ if(/^\/v1\/watches\/[^/]+\/classification\/quote(?:\?|$)/.test(path))return classificationQuoteResponse;
64
+ if(/^\/v1\/watches\/[^/]+\/classification\/(?:classify|jobs\/[^/?]+(?:\/reconcile)?)(?:\?|$)/.test(path))return classificationJobResponse;
65
+ if(/^\/v1\/watches\/[^/]+\/classification(?:\/prepare)?(?:\?|$)/.test(path))return classificationResponse;
44
66
  const pathname=path.split('?')[0];
67
+ if(pathname==='/v1/onboarding')return onboardingResponse;
68
+ if(pathname==='/v1/internal-links/preview'&&method==='POST')return internalLinkPreviewOutput;
69
+ if(pathname==='/v1/internal-links/snapshots')return method==='GET'?internalLinkListOutput:internalLinkSnapshotOutput;
70
+ if(/^\/v1\/internal-links\/snapshots\/[^/]+\/query$/.test(pathname))return internalLinkQueryOutput;
71
+ if(/^\/v1\/internal-links\/snapshots\/[^/]+(?:\/process)?$/.test(pathname))return internalLinkSnapshotOutput;
72
+ if(method==='PUT' && /^\/v1\/watches\/[^/]+\/type$/.test(pathname))return linkLabelledWatchResponse;
73
+ if(method==='POST' && pathname==='/v1/watches/bulk-labels')return linkLabelledWatchesResponse;
74
+ if(['POST','DELETE'].includes(method) && /^\/v1\/watches\/[^/]+\/tags(?:\/[^/]+)?$/.test(pathname))return linkLabelledWatchesResponse;
45
75
  if(method==='POST' && /^\/v1\/discovery\/runs\/[^/]+\/verification-batches$/.test(pathname))return candidateVerificationBatchResponse;
46
76
  if(method==='GET' && /^\/v1\/discovery\/verification-batches\/[^/]+$/.test(pathname))return candidateVerificationBatchResponse;
47
77
  if(method==='POST' && /^\/v1\/discovery\/verifications\/[^/]+\/monitor$/.test(pathname))return candidateMonitoringResponse;
48
- if(pathname==='/v1/search-connections'&&method==='GET')return searchConnectionsResponse;
78
+ if(pathname==='/v1/search-connections'&&method==='GET')return /[?&]all=1(?:&|$)/.test(path)?z.looseObject({websites:z.array(z.looseObject({project:z.looseObject({id:z.string()})}))}):searchConnectionsResponse;
49
79
  if(pathname==='/v1/search-connections/start'&&method==='POST')return searchStartResponse;
50
80
  if(/^\/v1\/search-connections\/[^/]+\/rows$/.test(pathname)&&method==='GET')return searchRowsResponse;
51
81
  if(/^\/v1\/search-connections\/[^/]+\/properties$/.test(pathname)&&method==='GET')return searchPropertiesResponse;
@@ -55,12 +85,18 @@ export function restResponseContract(path,method='GET') {
55
85
  if(/^\/v1\/search-connections\/[^/]+\/disconnect$/.test(pathname)&&method==='POST')return z.object({disconnected:z.literal(true),retainedRows:z.literal(true)});
56
86
  if(/^\/v1\/search-connections\/[^/]+$/.test(pathname)&&method==='DELETE')return z.object({forgotten:z.literal(true)});
57
87
  if(method!=='GET')return objectResponse;
88
+ if(/^\/v1\/watches\/[^/]+\/chronology$/.test(pathname))return chronologyResponse;
89
+ if(/^\/v1\/projects\/[^/]+\/monitoring-assurance$/.test(pathname))return assuranceResponse;
58
90
  if(/^\/v1\/(?:watches|targets)\/[^/]+\/history$/.test(pathname))return historyResponse;
59
91
  if(/^\/v1\/watches\/[^/]+$/.test(pathname))return watchResponse;
60
92
  if(/^\/v1\/targets\/[^/]+$/.test(pathname))return targetResponse;
61
- if(['/v1/watches','/v1/targets','/v1/projects','/v1/exports/watches'].includes(pathname))return pageResponse;
93
+ if(['/v1/watches','/v1/exports/watches'].includes(pathname))return watchPageResponse;
94
+ if(['/v1/targets','/v1/projects'].includes(pathname))return pageResponse;
62
95
  if(pathname==='/v1/usage')return usageResponse;
63
96
  if(pathname==='/v1/reports/link-changes')return linkChangeReportResponse;
97
+ if(pathname==='/v1/reports/history')return profileHistoryOutput;
98
+ if(pathname==='/v1/reports/history/records')return profileHistoryRecordsOutput;
99
+ if(pathname==='/v1/reports/summary')return linkSummaryResponse;
64
100
  if(['/v1/events','/v1/target-events'].includes(pathname))return eventFeedResponse;
65
101
  if(/^\/v1\/(?:jobs|target-jobs)\/[^/]+$/.test(pathname))return jobResponse;
66
102
  return objectResponse;
@@ -1,4 +1,5 @@
1
1
  import {z} from 'zod';
2
+ import {browsePageOutput} from './table-page-contract.js';
2
3
  const text=z.string(),nullable=text.nullable();
3
4
  export const searchConnection=z.looseObject({
4
5
  id:text,project_id:text,provider:z.enum(['google','bing']),status:text,property:nullable,
@@ -11,7 +12,7 @@ export const searchProviderRow=z.discriminatedUnion('kind',[
11
12
  z.looseObject({...rowMeta,kind:z.literal('search_performance'),page:text,query:text,clicks:z.number().nonnegative(),impressions:z.number().nonnegative(),ctr:z.number().nonnegative(),position:z.number().nonnegative(),startDate:text,endDate:text}),
12
13
  z.looseObject({...rowMeta,kind:z.literal('backlinks'),source_url:text,target_url:text,anchor:nullable,supplier:z.literal('bing_webmaster_tools'),verification_status:z.literal('unverified')}),
13
14
  ]);
14
- export const searchRowsResponse=z.looseObject({connection:searchConnection,rows:z.array(searchProviderRow),nextCursor:nullable});
15
+ export const searchRowsResponse=z.looseObject({...browsePageOutput,connection:searchConnection,rows:z.array(searchProviderRow),nextCursor:nullable});
15
16
  export const searchSyncResponse=z.looseObject({connection:searchConnection,rowCount:z.number().int().nonnegative()});
16
17
  export const searchPropertiesResponse=z.looseObject({properties:z.array(z.looseObject({property:text,permission:text}))});
17
18
  export const searchStartResponse=z.looseObject({authorizationUrl:z.url(),expiresAt:text});
@@ -0,0 +1,13 @@
1
+ import {z} from 'zod';
2
+ const filter=z.strictObject({column:z.string().min(1).max(80),operator:z.enum(['contains','is','is_not','empty']),value:z.string().max(500)});
3
+ export const browsePageInput={
4
+ browse:z.boolean().optional().describe('Server-sorted authorized saved rows; omit for legacy paging.'),
5
+ sort:z.string().min(1).max(80).optional().describe('Allowlisted saved-field identifier.'),
6
+ order:z.enum(['asc','desc']).optional().describe('Sort direction; stable ID ties, nulls last.'),
7
+ q:z.string().max(500).optional().describe('Literal search of supported saved fields.'),
8
+ table_filters:z.union([z.string().max(8192),z.array(filter).max(12)]).optional().describe('Up to 12 allowlisted column filters before paging.'),
9
+ };
10
+ export const browseCursor=z.string().min(1).max(16384);
11
+ const count=z.number().int().nonnegative().nullable();
12
+ export const browsePageOutput={total:count.optional(),has_more:z.boolean().optional(),next_cursor:browseCursor.nullable().optional(),
13
+ page:z.strictObject({limit:z.number().int().positive(),sort:z.string(),order:z.enum(['asc','desc']),total:count,query_scope:z.literal('server_dataset'),nulls:z.literal('last')}).optional()};
@@ -0,0 +1,290 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "skill": "agentlinkops-assets",
4
+ "pack": "product",
5
+ "contractSha256": "e3d29a169dd1ecae2db26b22ab666419c4057e84831653df655f04ae61725e93",
6
+ "sourceManifest": [
7
+ {
8
+ "path": "references/cli.md",
9
+ "sha256": "7b665f9d9b60cdab1f0238b71500f8389e0a6df4ea9777db69af6627a000a766"
10
+ },
11
+ {
12
+ "path": "references/disavow.md",
13
+ "sha256": "f8a2ad2c779e15547c6cf6a199b1d4af18ad2ef2c0aa8f27094c00f366c55aaa"
14
+ },
15
+ {
16
+ "path": "references/reports.md",
17
+ "sha256": "361ea12f145daf15c30d35cfaec3f41bfa677fee9c5eabf7dc10b6004cbe617e"
18
+ },
19
+ {
20
+ "path": "scripts/agentlinkops.py",
21
+ "sha256": "91d541768f95e17c059396624c153ca86395bccfc565a8bf3470e2e006404e28"
22
+ },
23
+ {
24
+ "path": "scripts/disavow.py",
25
+ "sha256": "cae36ad7d9e4aef27510975b4c15a66e5649250f6e76df7ca2bfb025a696432e"
26
+ },
27
+ {
28
+ "path": "scripts/migrations/001_crm.sql",
29
+ "sha256": "049fbd38b05c5b8dbab00b6ebee5342b8ed5cf48f65fe6cc1bbf41330ceedf54"
30
+ },
31
+ {
32
+ "path": "scripts/migrations/002_cloud_events.sql",
33
+ "sha256": "9040c607408ffd9a168bb3c30431846ddbc7a3429b2a30ba3ecafd4048ccc0c3"
34
+ },
35
+ {
36
+ "path": "scripts/migrations/003_disavow.sql",
37
+ "sha256": "ff47da734afaff5ab364dc0ae53c3db58254d0a30ca5250de8172a67f8ec2772"
38
+ },
39
+ {
40
+ "path": "scripts/migrations/004_placement_costs.sql",
41
+ "sha256": "e25732a3abd95d54320984068d9a4242ee2d0b89f6c70330a24f63bd0a3671a2"
42
+ },
43
+ {
44
+ "path": "scripts/report.py",
45
+ "sha256": "c7088c3314d0453d62e8a70f0c80f535b219b2ea127ca7d1c0731b216a9cb602"
46
+ },
47
+ {
48
+ "path": "skills/agentlinkops-assets/SKILL.md",
49
+ "sha256": "c7a16a272103c062c1a846f4a5af23a89b5c06dacd4712bbfa3b5d68c912aed0"
50
+ },
51
+ {
52
+ "path": "skills/agentlinkops-assets/agents/openai.yaml",
53
+ "sha256": "ee49f3058b6fb130d8865c4811231579d65c2e58ac13f30cd010c317c1702504"
54
+ },
55
+ {
56
+ "path": "templates/campaigns.v1.json",
57
+ "sha256": "768e1610d64b82a4e5872c301eb2a537705821b2f01e9f2265c8056f7da4a496"
58
+ }
59
+ ],
60
+ "members": [
61
+ {
62
+ "path": "SKILL.md",
63
+ "sha256": "25dba1dbed4e524408dd41794eb7fb7c56c7334e3118b6b108d363534365a0d9"
64
+ },
65
+ {
66
+ "path": "agents/openai.yaml",
67
+ "sha256": "ee49f3058b6fb130d8865c4811231579d65c2e58ac13f30cd010c317c1702504"
68
+ },
69
+ {
70
+ "path": "references/portable/references/cli.md",
71
+ "sha256": "55ad759a920ed5bb683b0354739b7a929a0d733e987c6164977ea466ff606955"
72
+ },
73
+ {
74
+ "path": "references/portable/references/disavow.md",
75
+ "sha256": "47b4b335f7695209ad0758fc19f3b23b38792032a6850277e563a9b7d80eabf1"
76
+ },
77
+ {
78
+ "path": "references/portable/references/reports.md",
79
+ "sha256": "4fc4791d03c7497f0193fb3d6b4114095aad4d551bb063c8c9e9c485f0c8ca56"
80
+ },
81
+ {
82
+ "path": "references/portable/scripts/agentlinkops.py",
83
+ "sha256": "91d541768f95e17c059396624c153ca86395bccfc565a8bf3470e2e006404e28"
84
+ },
85
+ {
86
+ "path": "references/portable/scripts/disavow.py",
87
+ "sha256": "cae36ad7d9e4aef27510975b4c15a66e5649250f6e76df7ca2bfb025a696432e"
88
+ },
89
+ {
90
+ "path": "references/portable/scripts/migrations/001_crm.sql",
91
+ "sha256": "049fbd38b05c5b8dbab00b6ebee5342b8ed5cf48f65fe6cc1bbf41330ceedf54"
92
+ },
93
+ {
94
+ "path": "references/portable/scripts/migrations/002_cloud_events.sql",
95
+ "sha256": "9040c607408ffd9a168bb3c30431846ddbc7a3429b2a30ba3ecafd4048ccc0c3"
96
+ },
97
+ {
98
+ "path": "references/portable/scripts/migrations/003_disavow.sql",
99
+ "sha256": "ff47da734afaff5ab364dc0ae53c3db58254d0a30ca5250de8172a67f8ec2772"
100
+ },
101
+ {
102
+ "path": "references/portable/scripts/migrations/004_placement_costs.sql",
103
+ "sha256": "e25732a3abd95d54320984068d9a4242ee2d0b89f6c70330a24f63bd0a3671a2"
104
+ },
105
+ {
106
+ "path": "references/portable/scripts/report.py",
107
+ "sha256": "c7088c3314d0453d62e8a70f0c80f535b219b2ea127ca7d1c0731b216a9cb602"
108
+ },
109
+ {
110
+ "path": "references/portable/templates/campaigns.v1.json",
111
+ "sha256": "768e1610d64b82a4e5872c301eb2a537705821b2f01e9f2265c8056f7da4a496"
112
+ }
113
+ ],
114
+ "edges": [
115
+ {
116
+ "from": "references/portable/references/cli.md",
117
+ "target": "references/portable/references/disavow.md",
118
+ "kind": "required-bundled"
119
+ },
120
+ {
121
+ "from": "references/portable/references/cli.md",
122
+ "target": "references/portable/references/reports.md",
123
+ "kind": "required-bundled"
124
+ },
125
+ {
126
+ "from": "references/portable/references/cli.md",
127
+ "target": "references/portable/scripts/agentlinkops.py",
128
+ "kind": "required-bundled"
129
+ },
130
+ {
131
+ "from": "references/portable/references/cli.md",
132
+ "target": "references/portable/scripts/agentlinkops.py",
133
+ "kind": "required-bundled"
134
+ },
135
+ {
136
+ "from": "references/portable/references/cli.md",
137
+ "target": "references/portable/scripts/agentlinkops.py",
138
+ "kind": "required-bundled"
139
+ },
140
+ {
141
+ "from": "references/portable/references/cli.md",
142
+ "target": "references/portable/scripts/agentlinkops.py",
143
+ "kind": "required-bundled"
144
+ },
145
+ {
146
+ "from": "references/portable/references/cli.md",
147
+ "target": "references/portable/scripts/agentlinkops.py",
148
+ "kind": "required-bundled"
149
+ },
150
+ {
151
+ "from": "references/portable/references/cli.md",
152
+ "target": "references/portable/scripts/agentlinkops.py",
153
+ "kind": "required-bundled"
154
+ },
155
+ {
156
+ "from": "references/portable/references/cli.md",
157
+ "target": "references/portable/scripts/agentlinkops.py",
158
+ "kind": "required-bundled"
159
+ },
160
+ {
161
+ "from": "references/portable/references/cli.md",
162
+ "target": "references/portable/scripts/agentlinkops.py",
163
+ "kind": "required-bundled"
164
+ },
165
+ {
166
+ "from": "references/portable/references/disavow.md",
167
+ "target": "references/portable/scripts/agentlinkops.py",
168
+ "kind": "required-bundled"
169
+ },
170
+ {
171
+ "from": "references/portable/references/disavow.md",
172
+ "target": "references/portable/scripts/agentlinkops.py",
173
+ "kind": "required-bundled"
174
+ },
175
+ {
176
+ "from": "references/portable/references/disavow.md",
177
+ "target": "references/portable/scripts/agentlinkops.py",
178
+ "kind": "required-bundled"
179
+ },
180
+ {
181
+ "from": "references/portable/references/disavow.md",
182
+ "target": "references/portable/scripts/agentlinkops.py",
183
+ "kind": "required-bundled"
184
+ },
185
+ {
186
+ "from": "references/portable/references/disavow.md",
187
+ "target": "references/portable/scripts/agentlinkops.py",
188
+ "kind": "required-bundled"
189
+ },
190
+ {
191
+ "from": "references/portable/references/reports.md",
192
+ "target": "references/portable/scripts/agentlinkops.py",
193
+ "kind": "required-bundled"
194
+ },
195
+ {
196
+ "from": "references/portable/references/reports.md",
197
+ "target": "references/portable/scripts/agentlinkops.py",
198
+ "kind": "required-bundled"
199
+ },
200
+ {
201
+ "from": "references/portable/scripts/agentlinkops.py",
202
+ "target": "references/portable/scripts/disavow.py",
203
+ "kind": "required-bundled"
204
+ },
205
+ {
206
+ "from": "references/portable/scripts/agentlinkops.py",
207
+ "target": "references/portable/scripts/migrations/001_crm.sql",
208
+ "kind": "required-bundled"
209
+ },
210
+ {
211
+ "from": "references/portable/scripts/agentlinkops.py",
212
+ "target": "references/portable/scripts/report.py",
213
+ "kind": "required-bundled"
214
+ },
215
+ {
216
+ "from": "references/portable/scripts/agentlinkops.py",
217
+ "target": "references/portable/templates/campaigns.v1.json",
218
+ "kind": "required-bundled"
219
+ },
220
+ {
221
+ "from": "SKILL.md",
222
+ "target": "../../references/recipes/internal-link-improvement.md",
223
+ "kind": "optional-selected",
224
+ "selection": "agentlinkops agent setup --recipe internal-link-improvement"
225
+ },
226
+ {
227
+ "from": "SKILL.md",
228
+ "target": "../../references/recipes/sourced-linkable-asset.md",
229
+ "kind": "optional-selected",
230
+ "selection": "agentlinkops agent setup --recipe sourced-linkable-asset"
231
+ },
232
+ {
233
+ "from": "SKILL.md",
234
+ "target": "references/portable/references/cli.md",
235
+ "kind": "required-bundled"
236
+ }
237
+ ],
238
+ "externalRuntime": [
239
+ {
240
+ "kind": "external-runtime",
241
+ "id": "agentlinkops-cli",
242
+ "commands": [
243
+ "agentlinkops",
244
+ "linktrail",
245
+ "node cli/agentlinkops.mjs"
246
+ ],
247
+ "availability": "Install separately for a standalone skill; inspect the installed version and help before using a local command. Hosted discovery does not install local CLI features."
248
+ },
249
+ {
250
+ "kind": "external-runtime",
251
+ "id": "python-standard-library",
252
+ "minimumVersion": "3.10",
253
+ "imports": [
254
+ "argparse",
255
+ "contextlib",
256
+ "csv",
257
+ "datetime",
258
+ "decimal",
259
+ "errno",
260
+ "fcntl",
261
+ "hashlib",
262
+ "html",
263
+ "importlib",
264
+ "io",
265
+ "ipaddress",
266
+ "json",
267
+ "os",
268
+ "pathlib",
269
+ "re",
270
+ "sqlite3",
271
+ "stat",
272
+ "sys",
273
+ "tempfile",
274
+ "time",
275
+ "urllib"
276
+ ]
277
+ },
278
+ {
279
+ "kind": "external-runtime",
280
+ "id": "agentlinkops-hosted-mcp",
281
+ "availability": "Hosted commands require separately connected customer access and current catalog exposure. Installing a skill alone does not establish access."
282
+ }
283
+ ],
284
+ "privateExcluded": [
285
+ "private Fleet/account files",
286
+ "customer records and credentials",
287
+ "unselected or uncleared publisher material"
288
+ ],
289
+ "coverage": "Assembler records source hashes; independent archive verification recomputes required members, supported links/imports and declared resources. Optional selections and external runtimes remain separate."
290
+ }
@@ -10,6 +10,8 @@ Preserve the project's rendering and routing conventions. Inspect nearby pages b
10
10
 
11
11
  Write plainly and specifically. Remove repetition and generic filler without deleting necessary evidence. Add internal links only where the destination answers the reader's next relevant question; inspect the actual routes before linking. Check for an existing competing page before creating a new URL.
12
12
 
13
- Use the site's existing validation commands appropriate to the change. Record the final target URL, reader benefit and evidence in the customer's chosen files, CRM or other record system. The bundled local CRM is optional; use [its contract](../../references/cli.md) only when the customer chooses that store. A completed local edit does not establish that the page is deployed or that a publisher accepted it.
13
+ Use the site's existing validation commands appropriate to the change. Record the final target URL, reader benefit and evidence in the customer's chosen files, CRM or other record system. The bundled local CRM is optional; use [its contract](references/portable/references/cli.md) only when the customer chooses that store. A completed local edit does not establish that the page is deployed or that a publisher accepted it.
14
14
 
15
15
  Recipes are optional. Choose `agentlinkops agent setup --recipe sourced-linkable-asset` before reading `../../references/recipes/sourced-linkable-asset.md` for draft review receipts and publication state. Select `--recipe site-context-brief` separately before reading that recipe if the audience or evidence needs clarification. Your own brief and editorial process can serve the same task.
16
+
17
+ For an optional internal-link plan, select `agentlinkops agent setup --recipe internal-link-improvement` before reading `../../references/recipes/internal-link-improvement.md`. It keeps source evidence, semantic choices, site edits, publication claims and independent verification separate. Supplied pages remain sufficient for a useful plan without hosted access.
@@ -1,5 +1,5 @@
1
1
  interface:
2
2
  display_name: AgentLinkOps Assets
3
- short_description: Improve a code-built site's resource, guide or research page for a specific backlink campaign, with sourced claims and u
3
+ short_description: "Improve sourced site pages for a specific backlink campaign"
4
4
  policy:
5
5
  allow_implicit_invocation: true