@cliwant/mcp-sam-gov 0.2.1 → 1.0.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 (228) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +48 -16
  3. package/README.ko.md +48 -16
  4. package/README.md +279 -67
  5. package/dist/attachments.d.ts +63 -0
  6. package/dist/attachments.d.ts.map +1 -0
  7. package/dist/attachments.js +576 -0
  8. package/dist/attachments.js.map +1 -0
  9. package/dist/bls.d.ts +267 -0
  10. package/dist/bls.d.ts.map +1 -0
  11. package/dist/bls.js +1549 -0
  12. package/dist/bls.js.map +1 -0
  13. package/dist/cache.d.ts +42 -0
  14. package/dist/cache.d.ts.map +1 -0
  15. package/dist/cache.js +64 -0
  16. package/dist/cache.js.map +1 -0
  17. package/dist/census.d.ts +182 -0
  18. package/dist/census.d.ts.map +1 -0
  19. package/dist/census.js +576 -0
  20. package/dist/census.js.map +1 -0
  21. package/dist/ckan.d.ts +141 -0
  22. package/dist/ckan.d.ts.map +1 -0
  23. package/dist/ckan.js +393 -0
  24. package/dist/ckan.js.map +1 -0
  25. package/dist/clinicaltrials.d.ts +180 -0
  26. package/dist/clinicaltrials.d.ts.map +1 -0
  27. package/dist/clinicaltrials.js +730 -0
  28. package/dist/clinicaltrials.js.map +1 -0
  29. package/dist/cms.d.ts +140 -0
  30. package/dist/cms.d.ts.map +1 -0
  31. package/dist/cms.js +482 -0
  32. package/dist/cms.js.map +1 -0
  33. package/dist/coerce.d.ts +32 -0
  34. package/dist/coerce.d.ts.map +1 -0
  35. package/dist/coerce.js +50 -0
  36. package/dist/coerce.js.map +1 -0
  37. package/dist/datagov-catalog.d.ts +84 -0
  38. package/dist/datagov-catalog.d.ts.map +1 -0
  39. package/dist/datagov-catalog.js +233 -0
  40. package/dist/datagov-catalog.js.map +1 -0
  41. package/dist/datagov.d.ts +146 -0
  42. package/dist/datagov.d.ts.map +1 -0
  43. package/dist/datagov.js +689 -0
  44. package/dist/datagov.js.map +1 -0
  45. package/dist/datagovKey.d.ts +36 -0
  46. package/dist/datagovKey.d.ts.map +1 -0
  47. package/dist/datagovKey.js +60 -0
  48. package/dist/datagovKey.js.map +1 -0
  49. package/dist/datasource.d.ts +313 -0
  50. package/dist/datasource.d.ts.map +1 -0
  51. package/dist/datasource.js +551 -0
  52. package/dist/datasource.js.map +1 -0
  53. package/dist/disclosure.d.ts +55 -0
  54. package/dist/disclosure.d.ts.map +1 -0
  55. package/dist/disclosure.js +57 -0
  56. package/dist/disclosure.js.map +1 -0
  57. package/dist/ecfr.d.ts +4 -2
  58. package/dist/ecfr.d.ts.map +1 -1
  59. package/dist/ecfr.js +92 -17
  60. package/dist/ecfr.js.map +1 -1
  61. package/dist/echo.d.ts +143 -0
  62. package/dist/echo.d.ts.map +1 -0
  63. package/dist/echo.js +424 -0
  64. package/dist/echo.js.map +1 -0
  65. package/dist/edgar.d.ts +377 -0
  66. package/dist/edgar.d.ts.map +1 -0
  67. package/dist/edgar.js +2418 -0
  68. package/dist/edgar.js.map +1 -0
  69. package/dist/errors.d.ts +102 -0
  70. package/dist/errors.d.ts.map +1 -0
  71. package/dist/errors.js +247 -0
  72. package/dist/errors.js.map +1 -0
  73. package/dist/fac.d.ts +180 -0
  74. package/dist/fac.d.ts.map +1 -0
  75. package/dist/fac.js +416 -0
  76. package/dist/fac.js.map +1 -0
  77. package/dist/far.d.ts +170 -0
  78. package/dist/far.d.ts.map +1 -0
  79. package/dist/far.js +804 -0
  80. package/dist/far.js.map +1 -0
  81. package/dist/fdic.d.ts +599 -0
  82. package/dist/fdic.d.ts.map +1 -0
  83. package/dist/fdic.js +1624 -0
  84. package/dist/fdic.js.map +1 -0
  85. package/dist/federal-register.d.ts +139 -2
  86. package/dist/federal-register.d.ts.map +1 -1
  87. package/dist/federal-register.js +432 -15
  88. package/dist/federal-register.js.map +1 -1
  89. package/dist/fema.d.ts +181 -0
  90. package/dist/fema.d.ts.map +1 -0
  91. package/dist/fema.js +436 -0
  92. package/dist/fema.js.map +1 -0
  93. package/dist/fpds.d.ts +108 -0
  94. package/dist/fpds.d.ts.map +1 -0
  95. package/dist/fpds.js +519 -0
  96. package/dist/fpds.js.map +1 -0
  97. package/dist/gao.d.ts +64 -0
  98. package/dist/gao.d.ts.map +1 -0
  99. package/dist/gao.js +640 -0
  100. package/dist/gao.js.map +1 -0
  101. package/dist/govinfo.d.ts +111 -0
  102. package/dist/govinfo.d.ts.map +1 -0
  103. package/dist/govinfo.js +422 -0
  104. package/dist/govinfo.js.map +1 -0
  105. package/dist/grants.d.ts +27 -4
  106. package/dist/grants.d.ts.map +1 -1
  107. package/dist/grants.js +114 -11
  108. package/dist/grants.js.map +1 -1
  109. package/dist/gsa-csv.d.ts +249 -0
  110. package/dist/gsa-csv.d.ts.map +1 -0
  111. package/dist/gsa-csv.js +784 -0
  112. package/dist/gsa-csv.js.map +1 -0
  113. package/dist/integrity.d.ts +212 -0
  114. package/dist/integrity.d.ts.map +1 -0
  115. package/dist/integrity.js +707 -0
  116. package/dist/integrity.js.map +1 -0
  117. package/dist/meta.d.ts +165 -0
  118. package/dist/meta.d.ts.map +1 -0
  119. package/dist/meta.js +162 -0
  120. package/dist/meta.js.map +1 -0
  121. package/dist/nih.d.ts +117 -0
  122. package/dist/nih.d.ts.map +1 -0
  123. package/dist/nih.js +291 -0
  124. package/dist/nih.js.map +1 -0
  125. package/dist/nppes.d.ts +157 -0
  126. package/dist/nppes.d.ts.map +1 -0
  127. package/dist/nppes.js +648 -0
  128. package/dist/nppes.js.map +1 -0
  129. package/dist/nsf.d.ts +176 -0
  130. package/dist/nsf.d.ts.map +1 -0
  131. package/dist/nsf.js +554 -0
  132. package/dist/nsf.js.map +1 -0
  133. package/dist/nvd.d.ts +176 -0
  134. package/dist/nvd.d.ts.map +1 -0
  135. package/dist/nvd.js +912 -0
  136. package/dist/nvd.js.map +1 -0
  137. package/dist/ofac.d.ts +205 -0
  138. package/dist/ofac.d.ts.map +1 -0
  139. package/dist/ofac.js +919 -0
  140. package/dist/ofac.js.map +1 -0
  141. package/dist/pricing.d.ts +110 -0
  142. package/dist/pricing.d.ts.map +1 -0
  143. package/dist/pricing.js +843 -0
  144. package/dist/pricing.js.map +1 -0
  145. package/dist/sam-gov/client.d.ts +60 -2
  146. package/dist/sam-gov/client.d.ts.map +1 -1
  147. package/dist/sam-gov/client.js +320 -54
  148. package/dist/sam-gov/client.js.map +1 -1
  149. package/dist/sam-gov/index.d.ts +1 -1
  150. package/dist/sam-gov/index.d.ts.map +1 -1
  151. package/dist/sam-gov/index.js +1 -1
  152. package/dist/sam-gov/index.js.map +1 -1
  153. package/dist/sam-gov/types.d.ts +24 -0
  154. package/dist/sam-gov/types.d.ts.map +1 -1
  155. package/dist/sba.d.ts +72 -0
  156. package/dist/sba.d.ts.map +1 -0
  157. package/dist/sba.js +281 -0
  158. package/dist/sba.js.map +1 -0
  159. package/dist/server.d.ts +14 -2
  160. package/dist/server.d.ts.map +1 -1
  161. package/dist/server.js +3897 -295
  162. package/dist/server.js.map +1 -1
  163. package/dist/snapshot.d.ts +98 -0
  164. package/dist/snapshot.d.ts.map +1 -0
  165. package/dist/snapshot.js +146 -0
  166. package/dist/snapshot.js.map +1 -0
  167. package/dist/socrata.d.ts +157 -0
  168. package/dist/socrata.d.ts.map +1 -0
  169. package/dist/socrata.js +448 -0
  170. package/dist/socrata.js.map +1 -0
  171. package/dist/treasury.d.ts +143 -0
  172. package/dist/treasury.d.ts.map +1 -0
  173. package/dist/treasury.js +436 -0
  174. package/dist/treasury.js.map +1 -0
  175. package/dist/usaspending.d.ts +260 -65
  176. package/dist/usaspending.d.ts.map +1 -1
  177. package/dist/usaspending.js +1664 -228
  178. package/dist/usaspending.js.map +1 -1
  179. package/dist/usitc.d.ts +142 -0
  180. package/dist/usitc.d.ts.map +1 -0
  181. package/dist/usitc.js +339 -0
  182. package/dist/usitc.js.map +1 -0
  183. package/package.json +24 -2
  184. package/src/attachments.ts +652 -0
  185. package/src/bls.ts +1943 -0
  186. package/src/cache.ts +73 -0
  187. package/src/census.ts +735 -0
  188. package/src/ckan.ts +495 -0
  189. package/src/clinicaltrials.ts +923 -0
  190. package/src/cms.ts +634 -0
  191. package/src/coerce.ts +47 -0
  192. package/src/datagov-catalog.ts +296 -0
  193. package/src/datagov.ts +907 -0
  194. package/src/datagovKey.ts +68 -0
  195. package/src/datasource.ts +721 -0
  196. package/src/disclosure.ts +61 -0
  197. package/src/ecfr.ts +231 -127
  198. package/src/echo.ts +496 -0
  199. package/src/edgar.ts +3014 -0
  200. package/src/errors.ts +303 -0
  201. package/src/fac.ts +529 -0
  202. package/src/far.ts +1007 -0
  203. package/src/fdic.ts +2052 -0
  204. package/src/federal-register.ts +706 -191
  205. package/src/fema.ts +541 -0
  206. package/src/fpds.ts +620 -0
  207. package/src/gao.ts +744 -0
  208. package/src/govinfo.ts +497 -0
  209. package/src/grants.ts +290 -155
  210. package/src/gsa-csv.ts +992 -0
  211. package/src/integrity.ts +928 -0
  212. package/src/meta.ts +292 -0
  213. package/src/nih.ts +375 -0
  214. package/src/nppes.ts +834 -0
  215. package/src/nsf.ts +706 -0
  216. package/src/nvd.ts +1124 -0
  217. package/src/ofac.ts +1166 -0
  218. package/src/pricing.ts +1075 -0
  219. package/src/sam-gov/client.ts +345 -63
  220. package/src/sam-gov/index.ts +5 -1
  221. package/src/sam-gov/types.ts +22 -0
  222. package/src/sba.ts +357 -0
  223. package/src/server.ts +4559 -327
  224. package/src/snapshot.ts +192 -0
  225. package/src/socrata.ts +532 -0
  226. package/src/treasury.ts +575 -0
  227. package/src/usaspending.ts +2680 -925
  228. package/src/usitc.ts +420 -0
package/dist/ckan.d.ts ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * CKAN `datastore_search` — keyless open data for state / local (SLED) portals.
3
+ *
4
+ * Second SLED family after Socrata (ADR-0006) and the FIRST source built on the
5
+ * R2 `DataSource` port (ADR-0005): CKAN writes ZERO fetch/coercion/error code —
6
+ * it reuses `getJson` / `driftError` / `num`·`str` / `withMeta` and COPIES (does
7
+ * NOT import) Socrata's SSRF + honesty PATTERN. Fully PUBLIC, KEYLESS — the CKAN
8
+ * datastore is anonymous-readable, so there is NO token anywhere in scope (no
9
+ * `headers`, byte-clean init).
10
+ *
11
+ * CKAN is Socrata's sibling — another multi-portal keyless open-data platform
12
+ * reached by swapping a hostname + a dataset id on one identical JSON Action API.
13
+ * We keep the guard per-source (host lists, id grammar, and the response envelope
14
+ * all differ — ADR-0006 §7) and do NOT unify with `socrata.ts`.
15
+ * Row query: https://{host}/api/3/action/datastore_search?resource_id={uuid}&…
16
+ * Discovery: https://{host}/api/3/action/package_search?q=…&rows=N
17
+ *
18
+ * Unlike SODA (a bare array, NO total), CKAN WRAPS records in an envelope that
19
+ * carries `result.total` and `result.total_was_estimated` DIRECTLY — so no
20
+ * count(*) companion is needed; the total is read from the body.
21
+ *
22
+ * ★ SSRF GUARD (policy① — the central design risk). COPY Socrata's 4-step order:
23
+ * (1) `host` ∈ CKAN_HOSTS — a Zod enum at the server boundary (single source of
24
+ * truth) AND a belt-and-suspenders `Set.has` check in the fetch fn → else
25
+ * invalid_input BEFORE any fetch;
26
+ * (2) `resourceId` matches the CKAN UUID grammar — the server's Zod
27
+ * `.length(36).regex(UUID_RE)` (NO `.trim()`, NO `i` flag) PLUS a runtime
28
+ * recheck in the fetch fn (M1 — parallel to the host `Set.has` recheck;
29
+ * Socrata re-checks datasetId too). m1: JS `$` does NOT match before a
30
+ * trailing `\n` (unlike Python), so the regex ALONE rejects a newline;
31
+ * `.length(36)` is belt-and-suspenders, not the primary newline guard.
32
+ * m2: the grammar is LOWERCASE hex only — every live id on every host is a
33
+ * 36-char lowercase UUID (confirmed) — so we keep it strict (no `i` flag);
34
+ * (3) FIXED path `https://${host}/api/3/action/datastore_search` (only `host`
35
+ * interpolates into the authority; `resourceId` goes through URLSearchParams,
36
+ * NEVER the path). Then ASSERT `new URL(built).hostname === host` and
37
+ * `protocol === "https:"` → invalid_input on mismatch;
38
+ * (4) all params (`resource_id`, `q`, `filters`, `sort`, `limit`, `offset`) via
39
+ * URLSearchParams — encoded values, no host-alteration surface. Bad
40
+ * `filters`/`sort` are upstream-validated (409 → invalid_input, surfaced).
41
+ *
42
+ * B1 (SSRF, redirect) — `datastore_search`/`package_search` are direct-JSON
43
+ * endpoints, so a 3xx off an allowlisted host is anomalous (migration / DNS
44
+ * hijack / reused domain) and would otherwise be followed off-allowlist past the
45
+ * pre-check. We set `redirect:"error"` in EVERY getJson call (getJson forwards
46
+ * `init.redirect` to fetch verbatim — a redirect throws, its body is never read).
47
+ *
48
+ * Security non-goal (explicit): we expose ONLY `datastore_search` (parameterized)
49
+ * and `package_search`. We do NOT expose `datastore_search_sql` — the raw-SQL
50
+ * CKAN endpoint IS an injection surface and is out of scope. `filters` is a
51
+ * CONSTRAINED object we `JSON.stringify` (Q3), never caller-supplied raw SQL/JSON.
52
+ * We NEVER pass `total_estimation_threshold` or `include_total:false` (Q4) — we
53
+ * always request an EXACT total; the estimate path fires only if a host is
54
+ * server-side reconfigured, hence defensive.
55
+ *
56
+ * HONESTY (`_meta`) — the load-bearing B1 fix (ADR-0006 v2):
57
+ * - EXACT total (the default — `total_estimation_threshold` is null on all
58
+ * hosts → `total_was_estimated:false`): `totalAvailable = num(result.total)`
59
+ * (exact), `hasMore = offset + returned < total` (or the B2 hedge
60
+ * `returned >= limit` if `total` is absent — a host running include_total:
61
+ * false). Genuine-empty (records:[], total:0) → complete:true / total:0.
62
+ * - ESTIMATED total (`total_was_estimated:true` — rare/defensive): the estimate
63
+ * is a Postgres `reltuples` approximation that can land ABOVE OR BELOW the
64
+ * truth (live-verified 345285 vs 344504 — an OVERSHOOT), so it is NOT a lower
65
+ * bound and MUST NOT drive pagination. Pass `totalAvailable:null` (so
66
+ * buildMeta's `totalProvesTruncation` does not fire on the estimate),
67
+ * `hasMore = returned >= limit` (paginate by page-fullness — the anti-livelock
68
+ * guard), `totalIsEstimated:true`, and a `_meta.notes` disclosure carrying the
69
+ * estimate value.
70
+ * - `success:false` (even on HTTP 200) or `result`/`records` missing/non-array
71
+ * → THROW (never a fake empty). Modern hosts surface 404/409 (→ not_found /
72
+ * invalid_input via errorFromResponse); this guard covers a 200+success:false
73
+ * host/proxy. outage/5xx/timeout → getJson throws, never `[]`.
74
+ * - `num`/`str` are null-never-0 (a missing value is honest "unknown", never 0).
75
+ *
76
+ * ALLOWLIST — LIVE-VERIFIED 2026-07-12 (each `.gov`, government-controlled, a live
77
+ * CKAN datastore; each carries a real datastore-active sample resource_id that
78
+ * returned HTTP 200 + the envelope above). NO commercial-vendor domains (the
79
+ * Socrata Tyler-Technologies M1 mistake is avoided). `data.virginia.gov` is the
80
+ * same VA that CHURNED OFF Socrata (excluded from the SODA allowlist) — it belongs
81
+ * HERE on CKAN. `data.ok.gov` is DEFERRED to SOURCE_BACKLOG (only a 9-row thin
82
+ * datastore verified — require a 2nd procurement-relevant >1k-row resource before
83
+ * adding). `catalog.data.gov` (federal harvester, no active datastore) is dropped.
84
+ * Adding a host later = a CKAN_HOSTS SOURCE edit + a live `datastore_search?
85
+ * limit=1` verification + an ownership note + a test-fixture note — NEVER a free
86
+ * runtime param.
87
+ */
88
+ import { num } from "./coerce.js";
89
+ import { type MetaBundle } from "./meta.js";
90
+ export { num };
91
+ export declare const CKAN_HOSTS: readonly ["data.ca.gov", "data.virginia.gov", "data.boston.gov"];
92
+ export type CkanHost = (typeof CKAN_HOSTS)[number];
93
+ export type CkanRecord = Record<string, unknown>;
94
+ /**
95
+ * Query rows from an allowlisted CKAN datastore resource. The workhorse: reaches
96
+ * every datastore-active spend/checkbook/procurement/vendor/certification table on
97
+ * any allowlisted portal. Records pass through verbatim (values are typed per
98
+ * result.fields[].type).
99
+ *
100
+ * HONESTY: CKAN's envelope carries a real `result.total`. The DEFAULT path is an
101
+ * EXACT total (`total_was_estimated:false`) → exact totalAvailable + exact
102
+ * hasMore. The rare ESTIMATED path (`total_was_estimated:true`) is defused per
103
+ * ADR-0006 B1: totalAvailable:null + hasMore-by-page-fullness + totalIsEstimated +
104
+ * an estimate note (the estimate can be above OR below the truth, so it is NOT a
105
+ * lower bound and MUST NOT drive pagination). A genuine-empty (records:[],
106
+ * total:0) → complete:true/total:0; success:false / a missing-or-non-array
107
+ * `result.records` / a non-number `result.total` → THROWS (never a fake empty).
108
+ */
109
+ export declare function query(args: {
110
+ host: CkanHost;
111
+ resourceId: string;
112
+ q?: string;
113
+ filters?: Record<string, string | number | Array<string | number>>;
114
+ sort?: string;
115
+ limit?: number;
116
+ offset?: number;
117
+ }): Promise<MetaBundle>;
118
+ export type CkanDiscoveredResource = {
119
+ resourceId: string | null;
120
+ name: string | null;
121
+ datasetTitle: string | null;
122
+ format: string | null;
123
+ datastoreActive: boolean;
124
+ };
125
+ /**
126
+ * Discover datastore resource ids via CKAN `package_search` (memoized ~10 min).
127
+ * Returns per-RESOURCE rows `[{ resourceId, name, datasetTitle, format,
128
+ * datastoreActive }]` + `totalAvailable = result.count` (the DATASET match count).
129
+ * Feed a `datastoreActive:true` resource's `resourceId` to ckan_query.
130
+ *
131
+ * m6 — this is the catalog's PRIMARY response: a non-number `result.count` is a
132
+ * hard schema_drift throw (nothing valid to return), never a fabricated total —
133
+ * INSIDE the memoize callback so a bad shape is never cached as a success
134
+ * (ADR-0005 v2 test-2 rule).
135
+ */
136
+ export declare function discoverDatasets(args: {
137
+ host: CkanHost;
138
+ q: string;
139
+ limit?: number;
140
+ }): Promise<MetaBundle>;
141
+ //# sourceMappingURL=ckan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ckan.d.ts","sourceRoot":"","sources":["../src/ckan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AAIH,OAAO,EAAE,GAAG,EAAO,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAKzE,OAAO,EAAE,GAAG,EAAE,CAAC;AAUf,eAAO,MAAM,UAAU,kEAUb,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAiBnD,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAwHjD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE;IAChC,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4GtB;AAGD,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAyBF;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,IAAI,EAAE,QAAQ,CAAC;IACf,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,UAAU,CAAC,CA0DtB"}
package/dist/ckan.js ADDED
@@ -0,0 +1,393 @@
1
+ /**
2
+ * CKAN `datastore_search` — keyless open data for state / local (SLED) portals.
3
+ *
4
+ * Second SLED family after Socrata (ADR-0006) and the FIRST source built on the
5
+ * R2 `DataSource` port (ADR-0005): CKAN writes ZERO fetch/coercion/error code —
6
+ * it reuses `getJson` / `driftError` / `num`·`str` / `withMeta` and COPIES (does
7
+ * NOT import) Socrata's SSRF + honesty PATTERN. Fully PUBLIC, KEYLESS — the CKAN
8
+ * datastore is anonymous-readable, so there is NO token anywhere in scope (no
9
+ * `headers`, byte-clean init).
10
+ *
11
+ * CKAN is Socrata's sibling — another multi-portal keyless open-data platform
12
+ * reached by swapping a hostname + a dataset id on one identical JSON Action API.
13
+ * We keep the guard per-source (host lists, id grammar, and the response envelope
14
+ * all differ — ADR-0006 §7) and do NOT unify with `socrata.ts`.
15
+ * Row query: https://{host}/api/3/action/datastore_search?resource_id={uuid}&…
16
+ * Discovery: https://{host}/api/3/action/package_search?q=…&rows=N
17
+ *
18
+ * Unlike SODA (a bare array, NO total), CKAN WRAPS records in an envelope that
19
+ * carries `result.total` and `result.total_was_estimated` DIRECTLY — so no
20
+ * count(*) companion is needed; the total is read from the body.
21
+ *
22
+ * ★ SSRF GUARD (policy① — the central design risk). COPY Socrata's 4-step order:
23
+ * (1) `host` ∈ CKAN_HOSTS — a Zod enum at the server boundary (single source of
24
+ * truth) AND a belt-and-suspenders `Set.has` check in the fetch fn → else
25
+ * invalid_input BEFORE any fetch;
26
+ * (2) `resourceId` matches the CKAN UUID grammar — the server's Zod
27
+ * `.length(36).regex(UUID_RE)` (NO `.trim()`, NO `i` flag) PLUS a runtime
28
+ * recheck in the fetch fn (M1 — parallel to the host `Set.has` recheck;
29
+ * Socrata re-checks datasetId too). m1: JS `$` does NOT match before a
30
+ * trailing `\n` (unlike Python), so the regex ALONE rejects a newline;
31
+ * `.length(36)` is belt-and-suspenders, not the primary newline guard.
32
+ * m2: the grammar is LOWERCASE hex only — every live id on every host is a
33
+ * 36-char lowercase UUID (confirmed) — so we keep it strict (no `i` flag);
34
+ * (3) FIXED path `https://${host}/api/3/action/datastore_search` (only `host`
35
+ * interpolates into the authority; `resourceId` goes through URLSearchParams,
36
+ * NEVER the path). Then ASSERT `new URL(built).hostname === host` and
37
+ * `protocol === "https:"` → invalid_input on mismatch;
38
+ * (4) all params (`resource_id`, `q`, `filters`, `sort`, `limit`, `offset`) via
39
+ * URLSearchParams — encoded values, no host-alteration surface. Bad
40
+ * `filters`/`sort` are upstream-validated (409 → invalid_input, surfaced).
41
+ *
42
+ * B1 (SSRF, redirect) — `datastore_search`/`package_search` are direct-JSON
43
+ * endpoints, so a 3xx off an allowlisted host is anomalous (migration / DNS
44
+ * hijack / reused domain) and would otherwise be followed off-allowlist past the
45
+ * pre-check. We set `redirect:"error"` in EVERY getJson call (getJson forwards
46
+ * `init.redirect` to fetch verbatim — a redirect throws, its body is never read).
47
+ *
48
+ * Security non-goal (explicit): we expose ONLY `datastore_search` (parameterized)
49
+ * and `package_search`. We do NOT expose `datastore_search_sql` — the raw-SQL
50
+ * CKAN endpoint IS an injection surface and is out of scope. `filters` is a
51
+ * CONSTRAINED object we `JSON.stringify` (Q3), never caller-supplied raw SQL/JSON.
52
+ * We NEVER pass `total_estimation_threshold` or `include_total:false` (Q4) — we
53
+ * always request an EXACT total; the estimate path fires only if a host is
54
+ * server-side reconfigured, hence defensive.
55
+ *
56
+ * HONESTY (`_meta`) — the load-bearing B1 fix (ADR-0006 v2):
57
+ * - EXACT total (the default — `total_estimation_threshold` is null on all
58
+ * hosts → `total_was_estimated:false`): `totalAvailable = num(result.total)`
59
+ * (exact), `hasMore = offset + returned < total` (or the B2 hedge
60
+ * `returned >= limit` if `total` is absent — a host running include_total:
61
+ * false). Genuine-empty (records:[], total:0) → complete:true / total:0.
62
+ * - ESTIMATED total (`total_was_estimated:true` — rare/defensive): the estimate
63
+ * is a Postgres `reltuples` approximation that can land ABOVE OR BELOW the
64
+ * truth (live-verified 345285 vs 344504 — an OVERSHOOT), so it is NOT a lower
65
+ * bound and MUST NOT drive pagination. Pass `totalAvailable:null` (so
66
+ * buildMeta's `totalProvesTruncation` does not fire on the estimate),
67
+ * `hasMore = returned >= limit` (paginate by page-fullness — the anti-livelock
68
+ * guard), `totalIsEstimated:true`, and a `_meta.notes` disclosure carrying the
69
+ * estimate value.
70
+ * - `success:false` (even on HTTP 200) or `result`/`records` missing/non-array
71
+ * → THROW (never a fake empty). Modern hosts surface 404/409 (→ not_found /
72
+ * invalid_input via errorFromResponse); this guard covers a 200+success:false
73
+ * host/proxy. outage/5xx/timeout → getJson throws, never `[]`.
74
+ * - `num`/`str` are null-never-0 (a missing value is honest "unknown", never 0).
75
+ *
76
+ * ALLOWLIST — LIVE-VERIFIED 2026-07-12 (each `.gov`, government-controlled, a live
77
+ * CKAN datastore; each carries a real datastore-active sample resource_id that
78
+ * returned HTTP 200 + the envelope above). NO commercial-vendor domains (the
79
+ * Socrata Tyler-Technologies M1 mistake is avoided). `data.virginia.gov` is the
80
+ * same VA that CHURNED OFF Socrata (excluded from the SODA allowlist) — it belongs
81
+ * HERE on CKAN. `data.ok.gov` is DEFERRED to SOURCE_BACKLOG (only a 9-row thin
82
+ * datastore verified — require a 2nd procurement-relevant >1k-row resource before
83
+ * adding). `catalog.data.gov` (federal harvester, no active datastore) is dropped.
84
+ * Adding a host later = a CKAN_HOSTS SOURCE edit + a live `datastore_search?
85
+ * limit=1` verification + an ownership note + a test-fixture note — NEVER a free
86
+ * runtime param.
87
+ */
88
+ import { ToolErrorCarrier } from "./errors.js";
89
+ import { getJson, driftError } from "./datasource.js";
90
+ import { num, str } from "./coerce.js";
91
+ import { memoize } from "./cache.js";
92
+ import { withMeta } from "./meta.js";
93
+ // Re-export the shared honesty coercion (single audited copy in ./coerce.js —
94
+ // ADR-0005 v2 FIX-C) so the fault suite's num-parity guard resolves the SAME
95
+ // `num` from this module (ckan.num === socrata.num === treasury.num === coerce.num).
96
+ export { num };
97
+ // ─── Curated allowlist (SSRF core) ────────────────────────────────
98
+ // A frozen list of live-verified (2026-07-12) CKAN datastore hosts. The Zod
99
+ // `host` enum in server.ts is built FROM this array (single source of truth), and
100
+ // the fetch fns re-check membership (belt-and-suspenders). Each entry carries a
101
+ // real sample resource_id (datastore_active) confirmed to return HTTP 200 + the
102
+ // {success, result:{records, total, total_was_estimated, fields}} envelope.
103
+ // Re-verify each with `GET https://{host}/api/3/action/datastore_search?
104
+ // resource_id={uuid}&limit=1` when touching this list.
105
+ export const CKAN_HOSTS = [
106
+ // State of California (CDT/GovOps) — Statewide Purchase Order Data 2012–2015,
107
+ // resource_id bb82edc5-9c78-44e2-8947-68ece26197c5 (~344,504 rows).
108
+ "data.ca.gov",
109
+ // Commonwealth of Virginia (VITA) — the VA that churned off Socrata. Norfolk
110
+ // SWaM Certified Businesses, resource_id f6804560-bf9e-44a4-92bf-bf3dd7d1fd60.
111
+ "data.virginia.gov",
112
+ // City of Boston (DoIT) — Checkbook Explorer (city spend), resource_id
113
+ // d22fdd5c-7e4c-41b7-a3eb-dfc57a87b245 (~101,465 rows).
114
+ "data.boston.gov",
115
+ ];
116
+ const CKAN_HOST_SET = new Set(CKAN_HOSTS);
117
+ // A CKAN datastore resource_id is EXACTLY a 36-char LOWERCASE hex UUID. m1: JS
118
+ // `$` does NOT admit a trailing "\n" (unlike Python), so this regex alone rejects
119
+ // a newline; `.length(36)` (server + the runtime recheck below) is belt-and-
120
+ // suspenders. m2: lowercase-only — no `i` flag (all live ids are lowercase).
121
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
122
+ /**
123
+ * GET one CKAN `datastore_search` resource. SSRF guard (belt-and-suspenders
124
+ * behind the server's Zod enum+regex): host ∈ allowlist, resourceId is a 36-char
125
+ * lowercase UUID (M1 runtime recheck), and the CONSTRUCTED URL's hostname === host
126
+ * (https). Sets `redirect:"error"` (B1); NO headers (keyless — the datastore is
127
+ * anonymous). Reuses errors.ts retry/timeout/taxonomy (429 → rate_limited;
128
+ * 5xx → upstream_unavailable; 404 → not_found; 409/400 → invalid_input). Returns
129
+ * the parsed JSON (unknown; the caller validates the envelope shape).
130
+ */
131
+ async function getCkanDatastore(host, resourceId, params) {
132
+ if (!CKAN_HOST_SET.has(host)) {
133
+ throw new ToolErrorCarrier({
134
+ kind: "invalid_input",
135
+ message: `CKAN host ${JSON.stringify(host)} is not on the curated allowlist. Allowed: ${CKAN_HOSTS.join(", ")}.`,
136
+ retryable: false,
137
+ });
138
+ }
139
+ // M1 — runtime belt-and-suspenders on resourceId (parallel to the host Set.has
140
+ // recheck; faithful copy of Socrata's re-check). No URLSearchParams-bypass
141
+ // exists, but this is defense-in-depth for the SSRF path-interpolation vector.
142
+ if (resourceId.length !== 36 || !UUID_RE.test(resourceId)) {
143
+ throw new ToolErrorCarrier({
144
+ kind: "invalid_input",
145
+ message: `Invalid CKAN resourceId ${JSON.stringify(resourceId)} — expected a 36-char lowercase UUID ([0-9a-f]{8}-{4}-{4}-{4}-{12}).`,
146
+ retryable: false,
147
+ });
148
+ }
149
+ params.set("resource_id", resourceId);
150
+ const url = `https://${host}/api/3/action/datastore_search?${params.toString()}`;
151
+ // Belt-and-suspenders: the FIXED path leaves only host to interpolate into the
152
+ // authority; assert the built URL cannot have been steered off-host.
153
+ const built = new URL(url);
154
+ if (built.hostname !== host || built.protocol !== "https:") {
155
+ throw new ToolErrorCarrier({
156
+ kind: "invalid_input",
157
+ message: `Constructed CKAN URL host ${JSON.stringify(built.hostname)} (${built.protocol}) does not match the allowlisted host ${JSON.stringify(host)} over https — refusing to fetch (SSRF safety).`,
158
+ retryable: false,
159
+ });
160
+ }
161
+ // Shared fetch envelope (ADR-0005): keyless ⇒ NO headers key (byte-clean init);
162
+ // B1 redirect:"error"; host-only label → ToolError.upstreamEndpoint.
163
+ return getJson(url, { label: "ckan:" + host, redirect: "error" });
164
+ }
165
+ /**
166
+ * GET a CKAN `package_search` (discovery). Same host allowlist + fixed path +
167
+ * hostname assertion + redirect:"error" policy as getCkanDatastore.
168
+ */
169
+ async function getCkanPackageSearch(host, params) {
170
+ if (!CKAN_HOST_SET.has(host)) {
171
+ throw new ToolErrorCarrier({
172
+ kind: "invalid_input",
173
+ message: `CKAN host ${JSON.stringify(host)} is not on the curated allowlist. Allowed: ${CKAN_HOSTS.join(", ")}.`,
174
+ retryable: false,
175
+ });
176
+ }
177
+ const url = `https://${host}/api/3/action/package_search?${params.toString()}`;
178
+ const built = new URL(url);
179
+ if (built.hostname !== host || built.protocol !== "https:") {
180
+ throw new ToolErrorCarrier({
181
+ kind: "invalid_input",
182
+ message: `Constructed CKAN catalog URL host ${JSON.stringify(built.hostname)} does not match the allowlisted host ${JSON.stringify(host)} over https — refusing to fetch (SSRF safety).`,
183
+ retryable: false,
184
+ });
185
+ }
186
+ return getJson(url, { label: "ckan:" + host, redirect: "error" });
187
+ }
188
+ // ─── map + meta helpers ───────────────────────────────────────────
189
+ const VALUE_TYPING_NOTE = "Record values follow result.fields[].type (int/text/numeric/timestamp); a numeric column may arrive as a JSON number OR a string — parse client-side. A missing value is absent, never 0.";
190
+ const SOURCE_SUFFIX = "via CKAN datastore_search (keyless)";
191
+ /**
192
+ * The `body.success === false` taxonomy (m3). Parse `body.error.__type`:
193
+ * "Not Found Error" → not_found
194
+ * "Validation Error" → invalid_input
195
+ * anything else → schema_drift (NOT a blanket not_found/driftError of the
196
+ * wrong kind — an unknown __type is genuine drift)
197
+ * This is the DEFENSIVE guard for a host/proxy that returns HTTP 200 with
198
+ * success:false (modern hosts surface real 404/409, handled by errorFromResponse
199
+ * before we ever see the body).
200
+ */
201
+ function throwCkanApiError(host, err) {
202
+ const type = str(err?.__type);
203
+ const message = str(err?.message);
204
+ const label = "ckan:" + host;
205
+ if (type === "Not Found Error") {
206
+ throw new ToolErrorCarrier({
207
+ kind: "not_found",
208
+ message: `CKAN ${host} reported Not Found${message ? `: ${message}` : "."}`,
209
+ retryable: false,
210
+ upstreamEndpoint: label,
211
+ });
212
+ }
213
+ if (type === "Validation Error") {
214
+ throw new ToolErrorCarrier({
215
+ kind: "invalid_input",
216
+ message: `CKAN ${host} reported a Validation Error${message ? `: ${message}` : "."}`,
217
+ retryable: false,
218
+ upstreamEndpoint: label,
219
+ });
220
+ }
221
+ throw driftError(label, `CKAN ${host} returned success:false with an unrecognized error type ${JSON.stringify(type)} — treating as schema drift.`);
222
+ }
223
+ // ─── Tool 1: ckan_query ───────────────────────────────────────────
224
+ /**
225
+ * Query rows from an allowlisted CKAN datastore resource. The workhorse: reaches
226
+ * every datastore-active spend/checkbook/procurement/vendor/certification table on
227
+ * any allowlisted portal. Records pass through verbatim (values are typed per
228
+ * result.fields[].type).
229
+ *
230
+ * HONESTY: CKAN's envelope carries a real `result.total`. The DEFAULT path is an
231
+ * EXACT total (`total_was_estimated:false`) → exact totalAvailable + exact
232
+ * hasMore. The rare ESTIMATED path (`total_was_estimated:true`) is defused per
233
+ * ADR-0006 B1: totalAvailable:null + hasMore-by-page-fullness + totalIsEstimated +
234
+ * an estimate note (the estimate can be above OR below the truth, so it is NOT a
235
+ * lower bound and MUST NOT drive pagination). A genuine-empty (records:[],
236
+ * total:0) → complete:true/total:0; success:false / a missing-or-non-array
237
+ * `result.records` / a non-number `result.total` → THROWS (never a fake empty).
238
+ */
239
+ export async function query(args) {
240
+ const limit = args.limit ?? 100;
241
+ const offset = args.offset ?? 0;
242
+ const params = new URLSearchParams();
243
+ params.set("limit", String(limit));
244
+ params.set("offset", String(offset));
245
+ if (args.q)
246
+ params.set("q", args.q);
247
+ if (args.sort)
248
+ params.set("sort", args.sort);
249
+ // Q3 — `filters` is a constrained object WE serialize; never a caller-supplied
250
+ // raw JSON string. A bad field still surfaces as an upstream 409 → invalid_input.
251
+ if (args.filters !== undefined)
252
+ params.set("filters", JSON.stringify(args.filters));
253
+ const body = await getCkanDatastore(args.host, args.resourceId, params);
254
+ const b = (body ?? {});
255
+ // ── Shape guards (ORDER matters). ──
256
+ // (1) success:false (even on HTTP 200) → taxonomy-classified throw (m3), NEVER
257
+ // a fake empty.
258
+ if (b.success === false)
259
+ throwCkanApiError(args.host, b.error);
260
+ // (2) result / records must be an array (m5) — a missing result or a
261
+ // string/null `records` is drift, never `[]`.
262
+ if (!b.result || !Array.isArray(b.result.records)) {
263
+ throw driftError("ckan:" + args.host, `ckan:${args.host} datastore_search returned an unexpected shape (result.records must be an array).`);
264
+ }
265
+ // (3) a PRESENT `total` must be a number (m6) — num() cannot distinguish a
266
+ // non-number total from an absent one, so typeof-check BEFORE num().
267
+ if (b.result.total !== undefined && typeof b.result.total !== "number") {
268
+ throw driftError("ckan:" + args.host, `ckan:${args.host} datastore_search returned a non-number result.total — treating as schema drift.`);
269
+ }
270
+ const records = b.result.records;
271
+ const fields = Array.isArray(b.result.fields) ? b.result.fields : [];
272
+ const returned = records.length;
273
+ const estimated = b.result.total_was_estimated === true;
274
+ const filtersApplied = [];
275
+ if (args.q)
276
+ filtersApplied.push("q");
277
+ if (args.filters !== undefined)
278
+ filtersApplied.push("filters");
279
+ if (args.sort)
280
+ filtersApplied.push("sort");
281
+ const notes = [VALUE_TYPING_NOTE];
282
+ let totalAvailable;
283
+ let hasMore;
284
+ let totalIsEstimated;
285
+ if (estimated) {
286
+ // B1 — an ESTIMATE must NOT drive pagination. Pass null (so buildMeta derives
287
+ // truncated/complete from page-fullness alone), paginate by page-fullness, and
288
+ // disclose the estimate value + flag. The empty-trailing-page case (returned 0
289
+ // < limit) → hasMore:false → complete:true (the anti-livelock guard).
290
+ totalAvailable = null;
291
+ hasMore = returned >= limit;
292
+ totalIsEstimated = true;
293
+ const estValue = num(b.result.total);
294
+ notes.push(`totalAvailable is withheld because the upstream reported it as a server-reported ESTIMATE${estValue !== null ? ` ~${estValue}` : ""}; it is not exact and may be above OR below the true count — paginate by page-fullness (hasMore is inferred from whether the page filled), not by this estimate.`);
295
+ }
296
+ else {
297
+ // EXACT total (the default). B2 hedge: if `total` is somehow absent (a host
298
+ // running include_total:false), fall back to page-fullness so an unknown
299
+ // total never lies complete:true.
300
+ totalAvailable = num(b.result.total);
301
+ hasMore =
302
+ totalAvailable !== null
303
+ ? offset + returned < totalAvailable
304
+ : returned >= limit;
305
+ if (totalAvailable === null) {
306
+ notes.push("The upstream did not report result.total; completeness is inferred from page-fullness (CKAN returns a short page only when the result set is exhausted).");
307
+ }
308
+ }
309
+ const nextOffset = hasMore ? offset + returned : null;
310
+ const meta = {
311
+ source: `${args.host} ${SOURCE_SUFFIX}`,
312
+ keylessMode: true,
313
+ returned,
314
+ totalAvailable,
315
+ filtersApplied,
316
+ filtersDropped: [],
317
+ fieldsUnavailable: [],
318
+ pagination: { offset, limit, hasMore, nextOffset },
319
+ notes,
320
+ };
321
+ if (totalIsEstimated !== undefined)
322
+ meta.totalIsEstimated = totalIsEstimated;
323
+ return withMeta({ host: args.host, resourceId: args.resourceId, fields, records }, meta);
324
+ }
325
+ /** Flatten one package_search `results[]` package into its resource rows. */
326
+ function mapPackageResources(pkg) {
327
+ const p = (pkg ?? {});
328
+ const datasetTitle = str(p.title);
329
+ const resources = Array.isArray(p.resources) ? p.resources : [];
330
+ return resources.map((res) => {
331
+ const r = (res ?? {});
332
+ return {
333
+ resourceId: str(r.id),
334
+ name: str(r.name),
335
+ datasetTitle,
336
+ format: str(r.format),
337
+ // Only a queryable (datastore-active) resource can feed ckan_query.
338
+ datastoreActive: r.datastore_active === true,
339
+ };
340
+ });
341
+ }
342
+ /**
343
+ * Discover datastore resource ids via CKAN `package_search` (memoized ~10 min).
344
+ * Returns per-RESOURCE rows `[{ resourceId, name, datasetTitle, format,
345
+ * datastoreActive }]` + `totalAvailable = result.count` (the DATASET match count).
346
+ * Feed a `datastoreActive:true` resource's `resourceId` to ckan_query.
347
+ *
348
+ * m6 — this is the catalog's PRIMARY response: a non-number `result.count` is a
349
+ * hard schema_drift throw (nothing valid to return), never a fabricated total —
350
+ * INSIDE the memoize callback so a bad shape is never cached as a success
351
+ * (ADR-0005 v2 test-2 rule).
352
+ */
353
+ export async function discoverDatasets(args) {
354
+ const limit = args.limit ?? 20;
355
+ const params = new URLSearchParams();
356
+ params.set("q", args.q);
357
+ params.set("rows", String(limit));
358
+ const key = `ckan:package_search:${args.host}:${args.q}:${limit}`;
359
+ const { totalAvailable, results } = await memoize(key, async () => {
360
+ const body = await getCkanPackageSearch(args.host, params);
361
+ const b = (body ?? {});
362
+ if (b.success === false)
363
+ throwCkanApiError(args.host, b.error);
364
+ if (!b.result) {
365
+ throw driftError("ckan:" + args.host, `ckan:${args.host} package_search returned an unexpected shape (result missing).`);
366
+ }
367
+ // m6 — a non-number count is drift on the PRIMARY response (contrast a
368
+ // best-effort secondary). Check stays INSIDE memoize so it is never cached.
369
+ if (typeof b.result.count !== "number") {
370
+ throw driftError("ckan:" + args.host, `ckan:${args.host} package_search returned a non-number result.count — treating as schema drift.`);
371
+ }
372
+ const total = num(b.result.count);
373
+ const packages = Array.isArray(b.result.results) ? b.result.results : [];
374
+ const rows = packages.flatMap(mapPackageResources);
375
+ return { totalAvailable: total, results: rows };
376
+ }, 10 * 60 * 1000);
377
+ const returned = results.length;
378
+ const notes = [
379
+ `package_search over ${args.host} (rows=${limit}). Feed a datastoreActive:true result's resourceId to ckan_query; a datastoreActive:false resource is a raw file blob (CSV/PDF/…) NOT in the datastore and is NOT queryable.`,
380
+ "totalAvailable is the count of matching DATASETS (packages); the rows are per-RESOURCE (a dataset may expose several resources), so returned may differ from totalAvailable.",
381
+ ];
382
+ return withMeta({ host: args.host, query: args.q, results }, {
383
+ source: `${args.host} package_search ${SOURCE_SUFFIX}`,
384
+ keylessMode: true,
385
+ returned,
386
+ totalAvailable,
387
+ filtersApplied: ["q"],
388
+ filtersDropped: [],
389
+ fieldsUnavailable: [],
390
+ notes,
391
+ });
392
+ }
393
+ //# sourceMappingURL=ckan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ckan.js","sourceRoot":"","sources":["../src/ckan.ts"],"names":[],"mappings":"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"}