@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.
- package/LICENSE +21 -21
- package/README.ja.md +48 -16
- package/README.ko.md +48 -16
- package/README.md +279 -67
- package/dist/attachments.d.ts +63 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +576 -0
- package/dist/attachments.js.map +1 -0
- package/dist/bls.d.ts +267 -0
- package/dist/bls.d.ts.map +1 -0
- package/dist/bls.js +1549 -0
- package/dist/bls.js.map +1 -0
- package/dist/cache.d.ts +42 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +64 -0
- package/dist/cache.js.map +1 -0
- package/dist/census.d.ts +182 -0
- package/dist/census.d.ts.map +1 -0
- package/dist/census.js +576 -0
- package/dist/census.js.map +1 -0
- package/dist/ckan.d.ts +141 -0
- package/dist/ckan.d.ts.map +1 -0
- package/dist/ckan.js +393 -0
- package/dist/ckan.js.map +1 -0
- package/dist/clinicaltrials.d.ts +180 -0
- package/dist/clinicaltrials.d.ts.map +1 -0
- package/dist/clinicaltrials.js +730 -0
- package/dist/clinicaltrials.js.map +1 -0
- package/dist/cms.d.ts +140 -0
- package/dist/cms.d.ts.map +1 -0
- package/dist/cms.js +482 -0
- package/dist/cms.js.map +1 -0
- package/dist/coerce.d.ts +32 -0
- package/dist/coerce.d.ts.map +1 -0
- package/dist/coerce.js +50 -0
- package/dist/coerce.js.map +1 -0
- package/dist/datagov-catalog.d.ts +84 -0
- package/dist/datagov-catalog.d.ts.map +1 -0
- package/dist/datagov-catalog.js +233 -0
- package/dist/datagov-catalog.js.map +1 -0
- package/dist/datagov.d.ts +146 -0
- package/dist/datagov.d.ts.map +1 -0
- package/dist/datagov.js +689 -0
- package/dist/datagov.js.map +1 -0
- package/dist/datagovKey.d.ts +36 -0
- package/dist/datagovKey.d.ts.map +1 -0
- package/dist/datagovKey.js +60 -0
- package/dist/datagovKey.js.map +1 -0
- package/dist/datasource.d.ts +313 -0
- package/dist/datasource.d.ts.map +1 -0
- package/dist/datasource.js +551 -0
- package/dist/datasource.js.map +1 -0
- package/dist/disclosure.d.ts +55 -0
- package/dist/disclosure.d.ts.map +1 -0
- package/dist/disclosure.js +57 -0
- package/dist/disclosure.js.map +1 -0
- package/dist/ecfr.d.ts +4 -2
- package/dist/ecfr.d.ts.map +1 -1
- package/dist/ecfr.js +92 -17
- package/dist/ecfr.js.map +1 -1
- package/dist/echo.d.ts +143 -0
- package/dist/echo.d.ts.map +1 -0
- package/dist/echo.js +424 -0
- package/dist/echo.js.map +1 -0
- package/dist/edgar.d.ts +377 -0
- package/dist/edgar.d.ts.map +1 -0
- package/dist/edgar.js +2418 -0
- package/dist/edgar.js.map +1 -0
- package/dist/errors.d.ts +102 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +247 -0
- package/dist/errors.js.map +1 -0
- package/dist/fac.d.ts +180 -0
- package/dist/fac.d.ts.map +1 -0
- package/dist/fac.js +416 -0
- package/dist/fac.js.map +1 -0
- package/dist/far.d.ts +170 -0
- package/dist/far.d.ts.map +1 -0
- package/dist/far.js +804 -0
- package/dist/far.js.map +1 -0
- package/dist/fdic.d.ts +599 -0
- package/dist/fdic.d.ts.map +1 -0
- package/dist/fdic.js +1624 -0
- package/dist/fdic.js.map +1 -0
- package/dist/federal-register.d.ts +139 -2
- package/dist/federal-register.d.ts.map +1 -1
- package/dist/federal-register.js +432 -15
- package/dist/federal-register.js.map +1 -1
- package/dist/fema.d.ts +181 -0
- package/dist/fema.d.ts.map +1 -0
- package/dist/fema.js +436 -0
- package/dist/fema.js.map +1 -0
- package/dist/fpds.d.ts +108 -0
- package/dist/fpds.d.ts.map +1 -0
- package/dist/fpds.js +519 -0
- package/dist/fpds.js.map +1 -0
- package/dist/gao.d.ts +64 -0
- package/dist/gao.d.ts.map +1 -0
- package/dist/gao.js +640 -0
- package/dist/gao.js.map +1 -0
- package/dist/govinfo.d.ts +111 -0
- package/dist/govinfo.d.ts.map +1 -0
- package/dist/govinfo.js +422 -0
- package/dist/govinfo.js.map +1 -0
- package/dist/grants.d.ts +27 -4
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +114 -11
- package/dist/grants.js.map +1 -1
- package/dist/gsa-csv.d.ts +249 -0
- package/dist/gsa-csv.d.ts.map +1 -0
- package/dist/gsa-csv.js +784 -0
- package/dist/gsa-csv.js.map +1 -0
- package/dist/integrity.d.ts +212 -0
- package/dist/integrity.d.ts.map +1 -0
- package/dist/integrity.js +707 -0
- package/dist/integrity.js.map +1 -0
- package/dist/meta.d.ts +165 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +162 -0
- package/dist/meta.js.map +1 -0
- package/dist/nih.d.ts +117 -0
- package/dist/nih.d.ts.map +1 -0
- package/dist/nih.js +291 -0
- package/dist/nih.js.map +1 -0
- package/dist/nppes.d.ts +157 -0
- package/dist/nppes.d.ts.map +1 -0
- package/dist/nppes.js +648 -0
- package/dist/nppes.js.map +1 -0
- package/dist/nsf.d.ts +176 -0
- package/dist/nsf.d.ts.map +1 -0
- package/dist/nsf.js +554 -0
- package/dist/nsf.js.map +1 -0
- package/dist/nvd.d.ts +176 -0
- package/dist/nvd.d.ts.map +1 -0
- package/dist/nvd.js +912 -0
- package/dist/nvd.js.map +1 -0
- package/dist/ofac.d.ts +205 -0
- package/dist/ofac.d.ts.map +1 -0
- package/dist/ofac.js +919 -0
- package/dist/ofac.js.map +1 -0
- package/dist/pricing.d.ts +110 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +843 -0
- package/dist/pricing.js.map +1 -0
- package/dist/sam-gov/client.d.ts +60 -2
- package/dist/sam-gov/client.d.ts.map +1 -1
- package/dist/sam-gov/client.js +320 -54
- package/dist/sam-gov/client.js.map +1 -1
- package/dist/sam-gov/index.d.ts +1 -1
- package/dist/sam-gov/index.d.ts.map +1 -1
- package/dist/sam-gov/index.js +1 -1
- package/dist/sam-gov/index.js.map +1 -1
- package/dist/sam-gov/types.d.ts +24 -0
- package/dist/sam-gov/types.d.ts.map +1 -1
- package/dist/sba.d.ts +72 -0
- package/dist/sba.d.ts.map +1 -0
- package/dist/sba.js +281 -0
- package/dist/sba.js.map +1 -0
- package/dist/server.d.ts +14 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3897 -295
- package/dist/server.js.map +1 -1
- package/dist/snapshot.d.ts +98 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +146 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/socrata.d.ts +157 -0
- package/dist/socrata.d.ts.map +1 -0
- package/dist/socrata.js +448 -0
- package/dist/socrata.js.map +1 -0
- package/dist/treasury.d.ts +143 -0
- package/dist/treasury.d.ts.map +1 -0
- package/dist/treasury.js +436 -0
- package/dist/treasury.js.map +1 -0
- package/dist/usaspending.d.ts +260 -65
- package/dist/usaspending.d.ts.map +1 -1
- package/dist/usaspending.js +1664 -228
- package/dist/usaspending.js.map +1 -1
- package/dist/usitc.d.ts +142 -0
- package/dist/usitc.d.ts.map +1 -0
- package/dist/usitc.js +339 -0
- package/dist/usitc.js.map +1 -0
- package/package.json +24 -2
- package/src/attachments.ts +652 -0
- package/src/bls.ts +1943 -0
- package/src/cache.ts +73 -0
- package/src/census.ts +735 -0
- package/src/ckan.ts +495 -0
- package/src/clinicaltrials.ts +923 -0
- package/src/cms.ts +634 -0
- package/src/coerce.ts +47 -0
- package/src/datagov-catalog.ts +296 -0
- package/src/datagov.ts +907 -0
- package/src/datagovKey.ts +68 -0
- package/src/datasource.ts +721 -0
- package/src/disclosure.ts +61 -0
- package/src/ecfr.ts +231 -127
- package/src/echo.ts +496 -0
- package/src/edgar.ts +3014 -0
- package/src/errors.ts +303 -0
- package/src/fac.ts +529 -0
- package/src/far.ts +1007 -0
- package/src/fdic.ts +2052 -0
- package/src/federal-register.ts +706 -191
- package/src/fema.ts +541 -0
- package/src/fpds.ts +620 -0
- package/src/gao.ts +744 -0
- package/src/govinfo.ts +497 -0
- package/src/grants.ts +290 -155
- package/src/gsa-csv.ts +992 -0
- package/src/integrity.ts +928 -0
- package/src/meta.ts +292 -0
- package/src/nih.ts +375 -0
- package/src/nppes.ts +834 -0
- package/src/nsf.ts +706 -0
- package/src/nvd.ts +1124 -0
- package/src/ofac.ts +1166 -0
- package/src/pricing.ts +1075 -0
- package/src/sam-gov/client.ts +345 -63
- package/src/sam-gov/index.ts +5 -1
- package/src/sam-gov/types.ts +22 -0
- package/src/sba.ts +357 -0
- package/src/server.ts +4559 -327
- package/src/snapshot.ts +192 -0
- package/src/socrata.ts +532 -0
- package/src/treasury.ts +575 -0
- package/src/usaspending.ts +2680 -925
- package/src/usitc.ts +420 -0
package/src/snapshot.ts
ADDED
|
@@ -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
|
+
}
|