@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
@@ -0,0 +1,192 @@
1
+ /**
2
+ * snapshot.ts — the snapshot-mirror READER + config (ADR-0045 Phase 2+4).
3
+ *
4
+ * A snapshot is an HONEST cache of PUBLIC open data: the builder
5
+ * (`scripts/build-snapshots.mjs`) pulls high-value, slow-changing reference /
6
+ * aggregate data from a CLEAN egress and writes a static JSON envelope
7
+ * (`{ asOf, source, license, accessLevel, data }`) to a CDN/object store. A
8
+ * client reads that snapshot ONLY when the live source is unreachable from its
9
+ * egress (an edge/WAF IP-reputation block). This module is that reader; it slots
10
+ * into the Phase-1 `throughPathChain` as a LOWER-priority `ResiliencePath`.
11
+ *
12
+ * ★INERT BY DEFAULT (the pass/fail bar): the snapshot base URL comes from the
13
+ * env var `SAMGOV_SNAPSHOT_BASE_URL`, which is UNSET by default. When unset,
14
+ * `snapshotPath()` returns `null` — the path is simply NOT added to a source's
15
+ * chain, so every source stays single-path (live-only) and its output is
16
+ * byte-identical to today. A snapshot fallback exists ONLY when an operator
17
+ * explicitly configures a base URL.
18
+ *
19
+ * ★POLICY BOUNDARY (ADR-0045 §"정책 경계", invariant — mirrors datasource.ts):
20
+ * • PUBLIC-ONLY (M3/m2): the builder writes ONLY public + redistributable data
21
+ * (accessLevel==="public"); the reader trusts that manifest and, defensively,
22
+ * REFUSES to serve any envelope whose accessLevel is present and NOT "public".
23
+ * • NO auth / paywall / CAPTCHA bypass, NO proxy, NO egress-hunting. If the
24
+ * snapshot URL is ITSELF blocked, it fails HONESTLY (no route-around) — the
25
+ * path throws and the chain falls to the next path or fails honestly.
26
+ * • PROVENANCE + FRESHNESS disclosed (P5): a served snapshot stamps
27
+ * `{ dataPath:"snapshot", asOf }`, which `buildMeta` turns into a staleness
28
+ * note + gates `complete` off. A snapshot body can NEVER be labelled live.
29
+ */
30
+
31
+ import { getJson, driftError, type Provenance, type ResiliencePath } from "./datasource.js";
32
+ import type { ResponseMeta } from "./meta.js";
33
+
34
+ /**
35
+ * The static snapshot envelope the builder writes and the reader parses. `data`
36
+ * is the source-shaped payload (e.g. a Treasury `{data,meta}` envelope, a USAS
37
+ * agency list). The metadata fields carry P5 provenance + the public-only gate.
38
+ */
39
+ export type SnapshotEnvelope<T = unknown> = {
40
+ /** ISO-8601 UTC instant the origin data was retrieved (P5 freshness, m3). */
41
+ asOf: string;
42
+ /** Human+machine label of the ORIGIN this snapshot mirrors (attribution). */
43
+ source: string;
44
+ /** The origin's redistribution license (public-domain / CC0 / etc.). */
45
+ license: string;
46
+ /** Structural public-only gate (M3): the builder only writes "public". */
47
+ accessLevel: string;
48
+ /** The mirrored payload, in the SOURCE's native shape. */
49
+ data: T;
50
+ };
51
+
52
+ /** Env-driven resilience config. Read at CALL TIME so it is togglable per call. */
53
+ export type ResilienceConfig = {
54
+ /**
55
+ * The snapshot mirror base URL (no trailing slash), or `undefined` when the
56
+ * env var is unset/blank ⇒ snapshot DISABLED ⇒ every source stays live-only.
57
+ */
58
+ snapshotBaseUrl: string | undefined;
59
+ };
60
+
61
+ /**
62
+ * Resolve `SAMGOV_SNAPSHOT_BASE_URL` at CALL TIME (never cached at module load,
63
+ * so a test — or an operator flipping the env — takes effect immediately, and so
64
+ * importing this module has zero config side effects). Returns `undefined` when
65
+ * the var is unset or blank (the INERT default: snapshot disabled). A trailing
66
+ * slash is stripped so `${base}/${key}.json` is well-formed.
67
+ */
68
+ export function resolveSnapshotBaseUrl(): string | undefined {
69
+ const raw = process.env.SAMGOV_SNAPSHOT_BASE_URL;
70
+ if (raw === undefined) return undefined;
71
+ const trimmed = raw.trim().replace(/\/+$/, "");
72
+ return trimmed.length > 0 ? trimmed : undefined;
73
+ }
74
+
75
+ /** The env-driven resilience config (default = snapshot disabled). */
76
+ export function resilienceConfig(): ResilienceConfig {
77
+ return { snapshotBaseUrl: resolveSnapshotBaseUrl() };
78
+ }
79
+
80
+ /**
81
+ * P5 provenance → `_meta` partial (ADR-0045 B2/M1). The SHARED threading helper
82
+ * every opted-in adapter uses so a served body discloses its access path
83
+ * IDENTICALLY: it returns the EMPTY object `{}` for a `live` (or absent)
84
+ * provenance — so spreading it into a meta partial adds NO keys and the `_meta`
85
+ * stays BYTE-IDENTICAL to pre-ADR output (the INERT bar) — and returns
86
+ * `{ dataPath, asOf? }` ONLY for a NON-live (`snapshot`) body. This is exactly
87
+ * the guarded, `??`-free discipline treasury.ts inlines in `treasuryMeta`,
88
+ * factored out so the usaspending/sba reference opt-ins can't drift from it.
89
+ * buildMeta then surfaces the fields via its `if (partial.dataPath !== undefined)`
90
+ * passthrough (and forces `complete!==true` + `totalIsEstimated` on non-live).
91
+ */
92
+ export function provenanceMeta(
93
+ provenance: Provenance | undefined,
94
+ ): Partial<ResponseMeta> {
95
+ if (!provenance || provenance.dataPath === "live") return {};
96
+ const meta: Partial<ResponseMeta> = { dataPath: provenance.dataPath };
97
+ if (provenance.asOf !== undefined) meta.asOf = provenance.asOf;
98
+ return meta;
99
+ }
100
+
101
+ /** A snapshot key is an internal identifier — restrict to a safe charset so it
102
+ * can never inject a path segment / traversal into the fetch URL. */
103
+ const SNAPSHOT_KEY_RE = /^[a-z0-9_]+$/;
104
+
105
+ /**
106
+ * Parse + validate a fetched snapshot envelope. Throws `schema_drift` (a
107
+ * NON-retryable, NON-hard error — it does NOT trip the breaker and does NOT
108
+ * count as an outage) when the envelope is malformed or, per the public-only
109
+ * policy gate, when `accessLevel` is present and not "public". `asOf` is required
110
+ * (P5: a non-live body MUST disclose its freshness).
111
+ */
112
+ function parseSnapshotEnvelope<T>(
113
+ raw: unknown,
114
+ label: string,
115
+ ): { data: T; asOf: string } {
116
+ if (typeof raw !== "object" || raw === null) {
117
+ throw driftError(label, `Snapshot ${label} is not a JSON object.`);
118
+ }
119
+ const env = raw as Partial<SnapshotEnvelope<T>>;
120
+ if (typeof env.asOf !== "string" || env.asOf.length === 0) {
121
+ throw driftError(
122
+ label,
123
+ `Snapshot ${label} is missing a string 'asOf' (P5 freshness is mandatory for a non-live body).`,
124
+ );
125
+ }
126
+ if (!("data" in env)) {
127
+ throw driftError(label, `Snapshot ${label} is missing 'data'.`);
128
+ }
129
+ // ★Public-only gate (M3, defense-in-depth): the reader trusts the builder's
130
+ // manifest but REFUSES a non-public envelope rather than serve restricted data.
131
+ if (env.accessLevel !== undefined && env.accessLevel !== "public") {
132
+ throw driftError(
133
+ label,
134
+ `Snapshot ${label} accessLevel is ${JSON.stringify(env.accessLevel)}, not "public" — refusing to serve (public-only policy).`,
135
+ );
136
+ }
137
+ return { data: env.data as T, asOf: env.asOf };
138
+ }
139
+
140
+ /**
141
+ * Build a `ResiliencePath` that reads the snapshot for `key` — or `null` when
142
+ * the snapshot mirror is not configured (the INERT default).
143
+ *
144
+ * When configured, the path fetches `${base}/${key}.json` via the shipped
145
+ * `getJson` with `redirect:"error"` (off-host redirect ⇒ TypeError ⇒ honest
146
+ * failure — no SSRF, no route-around). On success it parses the envelope and
147
+ * returns `envelope.data` as the body; its `provenance` object is MUTATED in
148
+ * place to carry `{ dataPath:"snapshot", asOf }` before the body resolves —
149
+ * `throughPathChain` reads `path.provenance` AFTER awaiting `run()`, so the
150
+ * per-fetch `asOf` is captured (mirrors the `{body,provenance}` contract).
151
+ *
152
+ * ★A NULL return is how INERTness is achieved structurally: the Treasury pilot
153
+ * builds `[livePath, snapshotPath(key)].filter(Boolean)`, so when this returns
154
+ * null the chain is single-entry (live only) ⇒ `throughPathChain` fast-paths ⇒
155
+ * byte-identical to today.
156
+ */
157
+ export function snapshotPath<T = unknown>(
158
+ key: string,
159
+ ): ResiliencePath<T> | null {
160
+ const base = resolveSnapshotBaseUrl();
161
+ if (base === undefined) return null; // INERT: snapshot disabled ⇒ no path.
162
+ if (!SNAPSHOT_KEY_RE.test(key)) {
163
+ // A bad key is a programming error, not a runtime data condition — refuse to
164
+ // construct a path rather than build a URL that could traverse.
165
+ throw driftError(
166
+ `snapshot:${key}`,
167
+ `Invalid snapshot key ${JSON.stringify(key)} (must match ${SNAPSHOT_KEY_RE}).`,
168
+ );
169
+ }
170
+ const baseUrl = new URL(base);
171
+ const url = `${base}/${key}.json`;
172
+ const host = baseUrl.host;
173
+ const label = `snapshot:${host}`;
174
+ // The provenance object is shared by reference with what run() mutates; the
175
+ // chain reads it only after run() resolves, so asOf is populated by then.
176
+ const provenance: Provenance = { dataPath: "snapshot" };
177
+ return {
178
+ host,
179
+ provenance,
180
+ run: async () => {
181
+ // Host-assert: the fetch URL MUST stay on the configured base's host
182
+ // (defense-in-depth alongside redirect:"error").
183
+ if (new URL(url).host !== host) {
184
+ throw driftError(label, `Snapshot URL host drifted from ${host}.`);
185
+ }
186
+ const raw = await getJson<unknown>(url, { label, redirect: "error" });
187
+ const { data, asOf } = parseSnapshotEnvelope<T>(raw, label);
188
+ provenance.asOf = asOf;
189
+ return data;
190
+ },
191
+ };
192
+ }