@cliwant/mcp-sam-gov 0.3.0 → 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 +226 -64
  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 +2 -0
  14. package/dist/cache.d.ts.map +1 -1
  15. package/dist/cache.js +4 -0
  16. package/dist/cache.js.map +1 -1
  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 +74 -1
  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 +26 -0
  70. package/dist/errors.d.ts.map +1 -1
  71. package/dist/errors.js +69 -1
  72. package/dist/errors.js.map +1 -1
  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 +419 -3
  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 +111 -6
  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 +3889 -296
  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 +1629 -201
  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 +5 -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 +98 -1
  198. package/src/echo.ts +496 -0
  199. package/src/edgar.ts +3014 -0
  200. package/src/errors.ts +84 -1
  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 +512 -3
  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 +139 -7
  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 +4551 -328
  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 +1946 -214
  228. package/src/usitc.ts +420 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * disclosure.ts — the single audited disclosure-note TOKENIZER shared across the
3
+ * keyless DataSources (ADR-0022: an honesty-layer primitive, split into its own
4
+ * tiny single-concern module — the sibling of coerce.ts's map-layer primitives).
5
+ *
6
+ * WHY IT EXISTS: two independent sources (NSF Awards C108 = the OR-note,
7
+ * ClinicalTrials.gov C109 = the AND-note) each built a mandatory multi-token
8
+ * disclosure note from the SAME character-for-character delimiter class, and
9
+ * adversarial verification caught the SAME latent gap on BOTH — a whitespace-only
10
+ * `.split(/\s+/)` misses the API's PUNCTUATION delimiters, so a compound that only
11
+ * LOOKS like one token ("coral-reef" = coral OR reef; "Sanofi-Aventis" = Sanofi
12
+ * AND Aventis) leaks through and the mandatory note is silently SKIPPED. Hoisting
13
+ * ONE audited tokenizer removes the drift risk (a class regression now fails NSF
14
+ * AND ClinicalTrials suites at once instead of silently in one) and gives the lint
15
+ * guardrail (lint-invariants.mjs) + the parity fault test a single home to point
16
+ * at: "the ONLY sanctioned way to tokenize a disclosure value lives here."
17
+ *
18
+ * THE CLASS (`DISCLOSURE_SPLIT_RE`) is the PRECISE ES/Essie confirmed-splitter set,
19
+ * live-verified byte-identically on BOTH sources 2026-07-12:
20
+ * - SPLIT (→ multi-token, the note fires): whitespace + `- , / ; + & | @ # =`
21
+ * - DO NOT split (→ one token, no note): `. : _ '` (NSF also `\` `*`)
22
+ * It is deliberately NOT a `[^A-Za-z0-9]+` superset — that would over-disclose a
23
+ * split the APIs did NOT make on `.`/`_`/`'` (a fabricated union/conjunction). `-`
24
+ * is placed LAST (a literal, not a range); `/` is escaped for the regex delimiter.
25
+ *
26
+ * NOTE-AGNOSTIC BY DESIGN: this returns ONLY the token array. The `.length > 1`
27
+ * decision, the per-source note WORDING (NSF = OR-union, ClinicalTrials = AND
28
+ * co-occurrence), and any downstream suppression (ClinicalTrials' sponsor
29
+ * broadening-note) stay in each CALLER — they are genuinely source-specific and do
30
+ * NOT belong to the tokenizer.
31
+ *
32
+ * THE OPTIONAL `splitRe` PARAM is the sanctioned, honest escape hatch: a FUTURE
33
+ * source whose analyzer genuinely splits on a DIFFERENT class routes through THIS
34
+ * helper with its own `splitRe` (explicit, greppable, reviewable) rather than
35
+ * re-inlining a raw `.split(/\s+/)`. That is exactly what lets the lint ban the
36
+ * bare whitespace split outright (there is one sanctioned tokenizer, not many).
37
+ *
38
+ * No new dep, no I/O, pure function — the exact shape of a coerce.ts primitive.
39
+ */
40
+ /**
41
+ * The shared ES/Essie confirmed-splitter class (byte-identical to NSF's former
42
+ * `NSF_KEYWORD_SPLIT_RE` and ClinicalTrials' former `CT_TOKEN_SPLIT_RE`). Stateless
43
+ * (no `/g`), so a single module-level RegExp is safe to share across callers.
44
+ */
45
+ export const DISCLOSURE_SPLIT_RE = /[\s,;+&|@#=\/-]+/;
46
+ /**
47
+ * Tokenize a disclosure value for a multi-token honesty note: trim, split on the
48
+ * confirmed-splitter class (default `DISCLOSURE_SPLIT_RE`), and drop empty tokens.
49
+ * Returns ONLY the token array — the caller owns the `.length > 1` gate + note
50
+ * wording. `tokenizeForDisclosure("coral-reef")` → `["coral", "reef"]`;
51
+ * `tokenizeForDisclosure("web_service")` → `["web_service"]` (the class does NOT
52
+ * split `_`); `tokenizeForDisclosure("robotics")` → `["robotics"]`.
53
+ */
54
+ export function tokenizeForDisclosure(value, splitRe = DISCLOSURE_SPLIT_RE) {
55
+ return value.trim().split(splitRe).filter((t) => t.length > 0);
56
+ }
57
+ //# sourceMappingURL=disclosure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disclosure.js","sourceRoot":"","sources":["../src/disclosure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAa,EACb,UAAkB,mBAAmB;IAErC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjE,CAAC"}
package/dist/ecfr.d.ts CHANGED
@@ -25,8 +25,9 @@ export declare function listTitles(): Promise<{
25
25
  export declare function search(args: {
26
26
  query: string;
27
27
  titleNumber?: number;
28
+ chapter?: number;
28
29
  perPage?: number;
29
- }): Promise<{
30
+ }): Promise<import("./meta.js").MetaBundle<{
30
31
  results: {
31
32
  type: string;
32
33
  title: string;
@@ -39,6 +40,7 @@ export declare function search(args: {
39
40
  score: number;
40
41
  ecfrUrl: string;
41
42
  effectiveOn: string;
43
+ endsOn: string | null;
42
44
  }[];
43
- }>;
45
+ }>>;
44
46
  //# sourceMappingURL=ecfr.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ecfr.d.ts","sourceRoot":"","sources":["../src/ecfr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAmBH,wBAAsB,UAAU;;;;;;;;;GAyB/B;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;;;GAkDA"}
1
+ {"version":3,"file":"ecfr.d.ts","sourceRoot":"","sources":["../src/ecfr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA2BH,wBAAsB,UAAU;;;;;;;;;GAyB/B;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;;;;IA0IA"}
package/dist/ecfr.js CHANGED
@@ -13,8 +13,15 @@
13
13
  * Both keyless. Documented at https://www.ecfr.gov/developers/.
14
14
  */
15
15
  import { fetchWithRetry } from "./errors.js";
16
+ import { driftError } from "./datasource.js";
16
17
  import { memoize } from "./cache.js";
18
+ import { withMeta } from "./meta.js";
17
19
  const ECFR = "https://www.ecfr.gov/api";
20
+ // eCFR's /search/v1/results `meta.total_count` is capped by Elasticsearch's
21
+ // `index.max_result_window` (live-verified 10,000). A total AT OR ABOVE this
22
+ // sentinel is a LOWER BOUND, not an exact count — see the totalIsLowerBound
23
+ // wiring in `search` (D1). A genuine count below this stays exact.
24
+ const ECFR_TOTAL_COUNT_CAP = 10000;
18
25
  async function fetchJson(url) {
19
26
  const r = await fetchWithRetry(url, {
20
27
  headers: { Accept: "application/json" },
@@ -47,8 +54,28 @@ export async function search(args) {
47
54
  // an "unpermitted parameter" error from the eCFR API).
48
55
  url.searchParams.set("hierarchy[title]", String(args.titleNumber));
49
56
  }
57
+ // Optional chapter filter (additive; existing ecfr_search callers pass none).
58
+ // Within Title 48: chapter 1 = FAR, chapter 2 = DFARS, chapter 5 = GSAM, etc.
59
+ // This is what lets far_search scope to FAR/DFARS and keep GSAM/agency
60
+ // supplements out server-side. Same hierarchy[…] contract as the title filter.
61
+ if (args.chapter !== undefined) {
62
+ url.searchParams.set("hierarchy[chapter]", String(args.chapter));
63
+ }
50
64
  const json = await fetchJson(url.toString());
51
- return {
65
+ // F6 (P2 empty-vs-outage): a 200 whose `results` is PRESENT-but-non-array — or
66
+ // a body carrying NEITHER a `results` array NOR a `meta` object — is drift / an
67
+ // unexpected shape, NOT a genuine no-match. Throw (schema_drift) rather than
68
+ // letting `(json.results ?? []).map` coalesce it into a fake AUTHORITATIVE empty.
69
+ // A GENUINE empty (results:[] with meta.total_count:0) flows through honestly
70
+ // below. (eCFR normally signals errors via HTTP status caught by fetchWithRetry;
71
+ // this closes the previously-unhandled + untested 200-body drift path.)
72
+ if (json.results !== undefined && !Array.isArray(json.results)) {
73
+ throw driftError("ecfr.gov", "eCFR search returned HTTP 200 but `results` is not an array — treating it as schema drift, NOT an empty result set.");
74
+ }
75
+ if (json.results === undefined && json.meta === undefined) {
76
+ throw driftError("ecfr.gov", "eCFR search returned HTTP 200 with neither a `results` array nor a `meta` object — an unexpected shape; treating it as schema drift, NOT an empty result set.");
77
+ }
78
+ const data = {
52
79
  results: (json.results ?? []).map((r) => ({
53
80
  type: r.type ?? "",
54
81
  title: r.hierarchy?.title ?? "",
@@ -66,8 +93,54 @@ export async function search(args) {
66
93
  ? buildEcfrUrl(r.hierarchy)
67
94
  : "",
68
95
  effectiveOn: r.starts_on ?? "",
96
+ // Additive: the version's end date. null = the CURRENT (in-force) version;
97
+ // a non-null date = a HISTORICAL version. eCFR search returns ~5 versions
98
+ // per section; existing ecfr_search callers simply ignore this extra field,
99
+ // while far_search uses it to collapse historical dups to the current one.
100
+ endsOn: r.ends_on ?? null,
69
101
  })),
70
102
  };
103
+ // Truthful `_meta` (spec §1.2 A6, §2.3). eCFR returns a hit count in
104
+ // `meta.total_count`, BUT that count is capped by Elasticsearch's
105
+ // `index.max_result_window` at 10,000 (a real ceiling — unlike a genuine
106
+ // small count, a value at/above the cap is a LOWER BOUND, not an exact
107
+ // total). Below the cap `totalAvailable` is exact (the AI can tell a top-N
108
+ // slice from the full match set); at/above the cap we flag
109
+ // `totalIsLowerBound:true` + a note so a broad query that truly matches
110
+ // >10,000 sections is NOT reported as if exactly 10,000 (D1). A6: echo the
111
+ // applied title scope so the AI can VERIFY it searched the intended corpus —
112
+ // Title 48 (FAR) vs every CFR title — rather than silently trusting a filter
113
+ // that could return cross-title results if the eCFR param contract ever changes.
114
+ const returned = data.results.length;
115
+ const totalAvailable = typeof json.meta?.total_count === "number" ? json.meta.total_count : null;
116
+ // eCFR's search total_count saturates at the Elasticsearch max_result_window
117
+ // (live-verified 10,000). Use `>= cap` (not `=== cap`) so a total AT OR ABOVE
118
+ // the ceiling is treated as a lower bound — strictly safe if the window were
119
+ // ever configured higher. Mirrors federal-register.ts's FR_COUNT_CAP and
120
+ // edgar.ts's FTS_WINDOW.
121
+ const totalIsLowerBound = totalAvailable !== null && totalAvailable >= ECFR_TOTAL_COUNT_CAP;
122
+ const scopeNote = args.titleNumber !== undefined
123
+ ? `searched CFR Title ${args.titleNumber}${args.titleNumber === 48 ? " (FAR — Federal Acquisition Regulation)" : ""} only`
124
+ : "searched all CFR titles (no title filter applied)";
125
+ const notes = [scopeNote];
126
+ if (totalIsLowerBound) {
127
+ notes.push(`eCFR caps total_count at ${ECFR_TOTAL_COUNT_CAP} (Elasticsearch index.max_result_window); totalAvailable is a LOWER BOUND — the true match count may be higher and is UNKNOWN. See totalIsLowerBound. Narrow by title/chapter/date for an exact count.`);
128
+ }
129
+ return withMeta(data, {
130
+ source: "ecfr.gov/api (search/v1)",
131
+ keylessMode: true,
132
+ returned,
133
+ totalAvailable,
134
+ // Explicit boolean (not conditional): a below-cap count is DEFINITIVELY
135
+ // exact (totalIsLowerBound:false), an at/above-cap count is a lower bound
136
+ // (true). The AI can trust `false` as "this is the real total".
137
+ totalIsLowerBound,
138
+ truncated: totalAvailable !== null ? returned < totalAvailable : undefined,
139
+ filtersApplied: args.titleNumber !== undefined ? ["titleNumber"] : [],
140
+ filtersDropped: [],
141
+ fieldsUnavailable: [],
142
+ notes,
143
+ });
71
144
  }
72
145
  function stripHtml(s) {
73
146
  return s
package/dist/ecfr.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ecfr.js","sourceRoot":"","sources":["../src/ecfr.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ecfr.js","sourceRoot":"","sources":["../src/ecfr.ts"],"names":[],"mappings":"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"}
package/dist/echo.d.ts ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * EPA ECHO REST services — keyless facility environmental compliance / enforcement
3
+ * screening (ADR-0009). A NEW capability axis for the product: facility &
4
+ * competitor environmental compliance-risk screening / due diligence (EPA
5
+ * enforcement, inspection, violation, and penalty history keyed to a facility),
6
+ * orthogonal to the spend/contract/regulatory layers.
7
+ *
8
+ * The THIRD source on the R2 `DataSource` port (ADR-0005, after Socrata/CKAN):
9
+ * `echo.ts` writes ZERO fetch/coercion/error code — it REUSES `getJson` /
10
+ * `driftError` / `num`·`str` / `withMeta`, and COPIES (does NOT import) the
11
+ * fixed-host SSRF + honesty PATTERN. Fully PUBLIC, KEYLESS (`keylessMode:true`,
12
+ * byte-clean init — NO headers, like ckan.ts). ECHO is neither Socrata nor CKAN:
13
+ * it is a bespoke Oracle-PL/SQL-fronted REST facade with a TWO-STEP QueryID
14
+ * pagination model and a 200-with-error-body failure mode.
15
+ *
16
+ * Step 1 (search): https://echodata.epa.gov/echo/echo_rest_services.get_facilities?output=JSON&p_st=…
17
+ * → { Results:{ Message, QueryRows:"N", …counts…, QueryID:"n" } } (NO rows)
18
+ * Step 2 (rows): https://echodata.epa.gov/echo/echo_rest_services.get_qid?output=JSON&qid=n&pageno=k
19
+ * → { Results:{ Message:"Working", Facilities:[ …rows… ] } }
20
+ * Detail (DFR): https://echodata.epa.gov/echo/dfr_rest_services.get_dfr?output=JSON&p_id={RegistryID}
21
+ * → { Results:{ Message:"Success", RegistryID, Reports, Permits, … } }
22
+ *
23
+ * ★ SSRF GUARD (policy① — the central design risk; a TIGHTER copy of the
24
+ * Socrata/CKAN fixed-host shape). The attack surface is SMALLER than CKAN's:
25
+ * (1) SINGLE fixed host constant `ECHO_HOST` — the caller NEVER supplies a host.
26
+ * (2) THREE fixed service-path constants (a frozen Set) — the caller NEVER
27
+ * supplies a path fragment; a service outside the Set ⇒ invalid_input before
28
+ * any fetch (the path-injection guard).
29
+ * (3) Every interpolated id is grammar-validated BEFORE use — `state` ∈ a frozen
30
+ * US state/territory enum (also the silent-zero guard, below); `naics`
31
+ * ^[0-9]{2,6}$ / `sic` ^[0-9]{2,4}$; `registryId` ^[0-9]{9,12}$ (FRS IDs are
32
+ * 12 digits; all-digit is the security property); the UPSTREAM-supplied
33
+ * `qid` is validated ^[0-9]+$ BECAUSE it is external (echodata.epa.gov mints
34
+ * it), before it is used in step 2; the internally-computed `pageno` is a
35
+ * plain integer. `facilityName` (p_fn) is a free-text filter VALUE — encoded
36
+ * through URLSearchParams, never touching the host/path.
37
+ * (4) Construct the URL, then ASSERT `new URL(built).hostname === ECHO_HOST` and
38
+ * `protocol === "https:"` ⇒ invalid_input on mismatch (belt-and-suspenders).
39
+ * B1 (redirect SSRF): every getJson sets `redirect:"error"` — a 3xx off
40
+ * echodata.epa.gov (migration / DNS-hijack / reused domain) throws; its body is
41
+ * never read. Adding a service/filter later = a CONSTANT edit + a live
42
+ * `output=JSON` verification — NEVER a free runtime host/path param.
43
+ *
44
+ * ★ 200-WITH-ERROR-BODY (the fake-empty trap — OBSERVED live, not defensive).
45
+ * A bogus `qid`, a bad DFR `p_id`, AND a queryset-limit overflow all return HTTP
46
+ * 200 carrying `{Results:{Error:{ErrorMessage}}}`. `errorFromResponse` keys off
47
+ * HTTP status and would pass a 200 straight through. So on EVERY response we
48
+ * detect `Results.Error` FIRST and THROW (classified) BEFORE reading
49
+ * QueryRows/Facilities — the ECHO analogue of CKAN's success:false-on-200 guard.
50
+ * Classification (by ErrorMessage):
51
+ * - "Queryset Limit would be exceeded" ⇒ invalid_input (narrow the query)
52
+ * - "…not found in ECHO" (recycled qid) ⇒ not_found, RETRYABLE (the QueryID
53
+ * is an ephemeral globally-recycled slot — a transient, not a missing
54
+ * facility; retry echo_search_facilities)
55
+ * - "ID … is invalid" (bad DFR id) ⇒ not_found (no report for that id)
56
+ * - anything else ⇒ schema_drift (surfaced, never
57
+ * silently swallowed)
58
+ *
59
+ * ★ TWO-STEP HIDDEN IN-CALL (ADR-0009 §1a). The QueryID is an ephemeral,
60
+ * globally-recycled, monotonically-incrementing cache slot (live-verified:
61
+ * IDs jumped 835→909 across a handful of calls) — NOT deterministic, NOT safe to
62
+ * persist across tool calls. `echo_search_facilities` therefore performs BOTH
63
+ * steps inside ONE invocation (get_facilities → capture QueryRows + fresh
64
+ * QueryID → immediately get_qid at the requested page) and NEVER exposes the
65
+ * QueryID to the caller. Paginating to page N re-runs get_facilities fresh.
66
+ * Two HTTP round-trips per search; robust against id recycling (memoize is
67
+ * unsafe here). Pagination is the standard offset/limit contract, translated to
68
+ * `pageno = offset/limit + 1` and `responseset = limit`; because ECHO can only
69
+ * page on page boundaries, `offset` MUST be an exact multiple of `limit`
70
+ * (else invalid_input locally, before any fetch).
71
+ *
72
+ * ★ M2 — NAICS vs SIC filtering, LIVE-VERIFIED 2026-07-12 (the data-lie guard).
73
+ * `p_st=DC` bare ⇒ QueryRows 4714. `p_st=DC&p_naics=325` / `=32511` / `=54` /
74
+ * even a bogus `=999999` ALL returned the identical 4714 ⇒ ECHO DROPS NAICS
75
+ * entirely (a real filter would return 0 for a nonexistent code). BUT
76
+ * `p_st=DC&p_sic=2911` ⇒ 1 and `&p_sic=9999`/`=8011` ⇒ 0 ⇒ SIC DOES narrow.
77
+ * So the two behave DIFFERENTLY (a MIXED outcome — a deviation from the ADR's
78
+ * unified Case-A/B framing):
79
+ * - `sic` = Case A (works) ⇒ a REAL filter; listed in filtersApplied.
80
+ * - `naics` = Case B (dropped) ⇒ BEST-EFFORT: marked best-effort in the
81
+ * tool-schema description, added to `_meta.filtersDropped` whenever passed,
82
+ * AND a `_meta.notes` disclosure warns the returned facilities are NOT
83
+ * guaranteed to match the NAICS code. NEVER silently presented as filtered.
84
+ *
85
+ * ★ HONESTY (`_meta`; REUSE withMeta/buildMeta). `totalAvailable = num(QueryRows)`
86
+ * — the EXACT upstream total, NEVER the page size. `returned =
87
+ * Results.Facilities.length`. `hasMore = offset + returned < total` (exact — no
88
+ * page-fullness hedge). Genuine-empty (`QueryRows:"0"`, no Results.Error) ⇒
89
+ * complete:true / totalAvailable:0. Outage/5xx/timeout ⇒ getJson throws (never a
90
+ * fake empty). `num`/`str` are null-never-0. Row-level currency/count fields
91
+ * (e.g. TotalPenalties "$1,056,616") pass through VERBATIM.
92
+ */
93
+ import { num } from "./coerce.js";
94
+ import { type MetaBundle } from "./meta.js";
95
+ export { num };
96
+ export declare const ECHO_STATES: readonly ["AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FL", "GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY", "AS", "GU", "MP", "PR", "VI"];
97
+ export type EchoState = (typeof ECHO_STATES)[number];
98
+ export type EchoRow = Record<string, unknown>;
99
+ /**
100
+ * GET one ECHO REST service. SSRF guard: `service` ∈ the frozen 3-member Set
101
+ * (the path-injection guard), params via URLSearchParams (encoded values, no
102
+ * host-alteration surface), then the CONSTRUCTED URL's hostname === ECHO_HOST
103
+ * (https) assertion. Sets `redirect:"error"` (B1); NO headers (keyless — ECHO is
104
+ * anonymous, byte-clean init). Reuses errors.ts retry/timeout/taxonomy (429 →
105
+ * rate_limited; 5xx → upstream_unavailable; 404 → not_found; 400 → invalid_input).
106
+ * Returns the parsed JSON (unknown; the caller validates the Results envelope).
107
+ */
108
+ export declare function echoGet(service: string, params: URLSearchParams): Promise<unknown>;
109
+ /**
110
+ * Search EPA-regulated facilities by state (+ optional sic / facilityName /
111
+ * majorOnly / federalOnly / naics-best-effort) with compliance/enforcement
112
+ * screening fields. The workhorse: state + industry + name + major/federal
113
+ * across CAA/CWA/RCRA/SDWA. `state` is REQUIRED (an unscoped national query is
114
+ * ~5.6M rows AND the state enum is the silent-zero guard).
115
+ *
116
+ * Hides the two-step QueryID pagination behind ONE call: internally get_facilities
117
+ * (→ exact QueryRows + a fresh QueryID) then get_qid?pageno=offset/limit+1 (→ the
118
+ * rows). The QueryID is captured and consumed in-call, NEVER exposed. Rows pass
119
+ * through verbatim. HONESTY: totalAvailable = num(QueryRows) (exact, never the page
120
+ * size); genuine-empty (QueryRows:"0") ⇒ complete:true/total:0; a Results.Error ⇒
121
+ * classified throw (never a fake empty); an outage ⇒ getJson throws.
122
+ */
123
+ export declare function searchFacilities(args: {
124
+ state: EchoState;
125
+ naics?: string;
126
+ sic?: string;
127
+ facilityName?: string;
128
+ majorOnly?: boolean;
129
+ federalOnly?: boolean;
130
+ limit?: number;
131
+ offset?: number;
132
+ }): Promise<MetaBundle>;
133
+ /**
134
+ * Fetch the Detailed Facility Report (DFR) for ONE facility by its FRS RegistryID
135
+ * (from echo_search_facilities rows): the per-facility compliance / enforcement /
136
+ * inspection / permit deep-dive for competitor / acquisition-target due diligence.
137
+ * Single record (no pagination). A bad/unknown RegistryID ⇒ the 200-with-error-
138
+ * body guard classifies "ID … is invalid" ⇒ not_found (never a fabricated report).
139
+ */
140
+ export declare function facilityReport(args: {
141
+ registryId: string;
142
+ }): Promise<MetaBundle>;
143
+ //# sourceMappingURL=echo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"echo.d.ts","sourceRoot":"","sources":["../src/echo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;AAIH,OAAO,EAAE,GAAG,EAAO,MAAM,aAAa,CAAC;AACvC,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAKzE,OAAO,EAAE,GAAG,EAAE,CAAC;AAkCf,eAAO,MAAM,WAAW,2VAOd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAiBrD,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE9C;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAwED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,UAAU,CAAC,CA0JtB;AAGD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,UAAU,CAAC,CAqCtB"}