@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/dist/fac.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fac.ts — CMS/GSA Federal Audit Clearinghouse (FAC) Single Audit vetting
|
|
3
|
+
* (ADR-0038). A NEW keyless PostgREST adapter over `api.fac.gov` (fronted by
|
|
4
|
+
* api.data.gov), housing TWO purpose-built audit-RISK vetting tools:
|
|
5
|
+
* - `fac_search_audits` — the `general` table (entity Single Audit summaries)
|
|
6
|
+
* - `fac_get_findings` — the `findings` table (the audit-RISK flags)
|
|
7
|
+
* The B2G unlock: before teaming with / subcontracting to a partner, vet their
|
|
8
|
+
* Single Audit RISK (material weaknesses, questioned costs, repeat findings,
|
|
9
|
+
* modified opinions) — joinable to SAM/USAspending/EDGAR by UEI/EIN. DISTINCT
|
|
10
|
+
* from `src/far.ts` (FAR clause compliance — no filename / gate-key collision).
|
|
11
|
+
*
|
|
12
|
+
* ★ KEYLESS-FIRST via the DEMO_KEY doctrine (the 3rd consumer of `datagovKey.ts`
|
|
13
|
+
* after datagov.ts + govinfo.ts). A bare api.fac.gov request → HTTP 403
|
|
14
|
+
* (API_KEY_MISSING); the SAME request + `X-Api-Key: DEMO_KEY` → 200/206 real
|
|
15
|
+
* data. `keyHeader()` places the secret in the `X-Api-Key` header ONLY (never the
|
|
16
|
+
* URL / label / _meta / a log); `resolvedKey()` falls back to the public literal
|
|
17
|
+
* "DEMO_KEY" when DATA_GOV_API_KEY is unset. `keylessMode:false` (honest — it
|
|
18
|
+
* genuinely sends a key, exactly like datagov.ts/govinfo.ts); `_meta.source`
|
|
19
|
+
* discloses the key-MODE only.
|
|
20
|
+
*
|
|
21
|
+
* R2 consumer — reuses the shared port: `getJsonWithHeaders` (ADR-0038 M1 — the
|
|
22
|
+
* header-exposing sibling of getJson; the EXACT total is a RESPONSE HEADER, not a
|
|
23
|
+
* body field), `driftError` / `throughGate` (datasource.ts), `num`·`str`
|
|
24
|
+
* (coerce.ts), `withMeta` (meta.ts). It writes ZERO bespoke fetch — a hand-rolled
|
|
25
|
+
* fetch inside fac.ts would re-open `redirect:"error"` (a 3xx off api.fac.gov
|
|
26
|
+
* carrying the X-Api-Key to a foreign host = key egress), the hostname/https
|
|
27
|
+
* assert, and `keyHeader()` placement, all of which the shared port keeps
|
|
28
|
+
* structurally present and ADR-0007-K-test-covered.
|
|
29
|
+
*
|
|
30
|
+
* ★ HONESTY (P1–P4; ADR §Q2 + the v2 review):
|
|
31
|
+
* (P1) The EXACT total is the `Content-Range` denominator (a RESPONSE HEADER
|
|
32
|
+
* under `Prefer: count=exact`; 206-on-subset). `parseTotal` takes the
|
|
33
|
+
* substring after the LAST `/`; a run of digits → num() EXACT; a `*`
|
|
34
|
+
* ("count not computed") / non-number / absent header → totalAvailable:null +
|
|
35
|
+
* a page-fullness `hasMore = returned >= limit` hedge + a disclosed note —
|
|
36
|
+
* NEVER 0 (the null-never-0 rule applied to a header).
|
|
37
|
+
* (P2) A genuine `[]` → honest empty (+ the M2 empty-findings note); a body
|
|
38
|
+
* that is not a JSON array → driftError. 400/403/5xx/timeout/off-host-redirect
|
|
39
|
+
* THROW via the port taxonomy (206 = success — `r.ok` covers 200-299). An
|
|
40
|
+
* HTML/non-JSON 200 (r.json() SyntaxError) → reclassified to schema_drift
|
|
41
|
+
* (S1) — never a fake empty.
|
|
42
|
+
* (P3) `total_amount_expended` is a JSON number → num() null-never-0. The `is_*`
|
|
43
|
+
* risk flags are STRINGS "Y"/"N" — surfaced verbatim (str → "Y"/"N"/null) AND
|
|
44
|
+
* mapped to a tri-state `riskFlags` object ("Y"→true / "N"→false / blank /
|
|
45
|
+
* absent / other → null=UNKNOWN). A null flag must NEVER read as false/"no
|
|
46
|
+
* material weakness" (the OFAC/sam_check_exclusions false-CLEAR class).
|
|
47
|
+
* (P4) Filters are server-side + self-policing: each caller filter maps to a
|
|
48
|
+
* KNOWN column + a FIXED operator, value-validated + URLSearchParams-encoded;
|
|
49
|
+
* a valid filter narrows the Content-Range total, a bad column → PostgREST 400
|
|
50
|
+
* → invalid_input. So `filtersDropped` is ALWAYS empty (no silent-drop path).
|
|
51
|
+
*
|
|
52
|
+
* ★ THE PII CRUX (load-bearing — §PII boundary, LIVE-confirmed SOUND): the
|
|
53
|
+
* `select=` is a HARDCODED MODULE CONSTANT allowlist that SURFACES only entity +
|
|
54
|
+
* audit-risk fields and EXCLUDES all 6 personal-contact columns (auditee_email,
|
|
55
|
+
* auditee_phone, auditee_certify_name, auditee_certify_title, auditee_contact_name,
|
|
56
|
+
* auditee_contact_title). PostgREST WILL project auditee_email the instant
|
|
57
|
+
* `select=` names it (live-verified), so the tools accept NO caller-supplied
|
|
58
|
+
* `select`/`order`/free-column param — the inputs are ONLY structured, validated
|
|
59
|
+
* filters. Purpose-built tools bake the allowlist in by construction (the reason
|
|
60
|
+
* v1 rejects a generic `fac_query{select}`). The mapper also picks ONLY the
|
|
61
|
+
* allowlist keys, so even a drifted server that returns an extra column cannot
|
|
62
|
+
* leak it into the output.
|
|
63
|
+
*
|
|
64
|
+
* ★ SSRF: fixed host `api.fac.gov` (single-entry FAC_HOSTS allowlist + a Set.has
|
|
65
|
+
* recheck); the table name is a FIXED per-tool path CONSTANT (`general`/`findings`
|
|
66
|
+
* — never caller free-text); filter values ride URLSearchParams as
|
|
67
|
+
* `col=eq.VALUE`; `redirect:"error"`; a post-construction hostname/https assert.
|
|
68
|
+
*/
|
|
69
|
+
import { num } from "./coerce.js";
|
|
70
|
+
import { type MetaBundle } from "./meta.js";
|
|
71
|
+
export { num };
|
|
72
|
+
export declare const FAC_HOSTS: readonly string[];
|
|
73
|
+
export declare const FAC_LABEL: string;
|
|
74
|
+
export declare const GENERAL_SELECT = "report_id,auditee_uei,audit_year,auditee_name,auditee_ein,auditee_state,auditee_city,total_amount_expended,fac_accepted_date";
|
|
75
|
+
export declare const FINDINGS_SELECT = "report_id,auditee_uei,audit_year,award_reference,reference_number,is_material_weakness,is_modified_opinion,is_questioned_costs,is_repeat_finding,is_significant_deficiency,is_other_findings,is_other_matters,type_requirement,prior_finding_ref_numbers";
|
|
76
|
+
/**
|
|
77
|
+
* ★ §PII(d) — the mandatory not-a-determination caveat carried on EVERY response
|
|
78
|
+
* (mirrors OFAC_NOT_DETERMINATION_NOTE / NPPES_NOT_DETERMINATION_NOTE). Kept
|
|
79
|
+
* verbatim so the fault suite can assert it.
|
|
80
|
+
*/
|
|
81
|
+
export declare const FAC_NOT_DETERMINATION_NOTE = "Public Single Audit data (Federal Audit Clearinghouse; 2 CFR 200 Subpart F / Single Audit Act). Reports the independent auditor's findings, opinions, and questioned costs AS SUBMITTED \u2014 it is NOT a debarment, suspension, exclusion, or fitness determination, and an audit finding is the auditor's opinion, not proof of wrongdoing (many findings are routine and remediated). This tool DELIBERATELY EXCLUDES the auditee's personal-contact fields (email / phone / certifying-official name) \u2014 the vetting subject is the entity, not the individual. Cross-check SAM exclusions + OFAC and read the specific finding text before acting.";
|
|
82
|
+
/** ★ P3 — the risk-flag typing disclosure carried on EVERY fac_get_findings response. */
|
|
83
|
+
export declare const FAC_VALUE_TYPING_NOTE = "Risk flags (is_material_weakness, is_modified_opinion, is_questioned_costs, is_repeat_finding, is_significant_deficiency, is_other_findings, is_other_matters) are surfaced VERBATIM as the auditor reported them (\"Y\"/\"N\") plus a typed riskFlags tri-state; a blank/absent flag is UNKNOWN (null), NOT a \"no\" \u2014 never read a null flag as 'no finding'.";
|
|
84
|
+
/** ★ M2 — the empty-findings ≠ clean-audit disclosure (any totalAvailable:0 / empty findings). */
|
|
85
|
+
export declare const FAC_EMPTY_FINDINGS_NOTE = "An empty findings list means NO findings are ON RECORD for this UEI/report \u2014 it does NOT confirm a clean audit. The entity may not have filed a Single Audit (below the $750K threshold), the audit may predate FAC coverage, or the UEI may be wrong. Confirm the entity has an ACCEPTED audit via fac_search_audits before treating empty as clean.";
|
|
86
|
+
/**
|
|
87
|
+
* Parse the EXACT total from `Content-Range: <start>-<end>/<TOTAL>` — take the
|
|
88
|
+
* substring after the LAST `/` and `num()` it. A run of digits → the EXACT total
|
|
89
|
+
* (`0` denominator → 0, a genuine-empty total); a `*` (PostgREST "count not
|
|
90
|
+
* computed") / non-number / absent header → null (NEVER 0 — the null-never-0 rule
|
|
91
|
+
* on a header). Exported for the Content-Range-matrix fault fixture.
|
|
92
|
+
*/
|
|
93
|
+
export declare function parseTotal(contentRange: string | null): number | null;
|
|
94
|
+
/**
|
|
95
|
+
* Map an FAC risk flag to an HONEST tri-state: "Y" → true, "N" → false, and a
|
|
96
|
+
* null / blank / absent / any-other value → null (UNKNOWN — NEVER false). A null
|
|
97
|
+
* `is_material_weakness` silently read as "no material weakness" is the false-CLEAR
|
|
98
|
+
* class OFAC/sam_check_exclusions forbid. Exported for the flag-type fault fixture.
|
|
99
|
+
*/
|
|
100
|
+
export declare function flagTri(v: unknown): boolean | null;
|
|
101
|
+
export type FacAudit = {
|
|
102
|
+
report_id: string | null;
|
|
103
|
+
auditee_uei: string | null;
|
|
104
|
+
audit_year: string | null;
|
|
105
|
+
auditee_name: string | null;
|
|
106
|
+
auditee_ein: string | null;
|
|
107
|
+
auditee_state: string | null;
|
|
108
|
+
auditee_city: string | null;
|
|
109
|
+
total_amount_expended: number | null;
|
|
110
|
+
fac_accepted_date: string | null;
|
|
111
|
+
};
|
|
112
|
+
export type FacFinding = {
|
|
113
|
+
report_id: string | null;
|
|
114
|
+
auditee_uei: string | null;
|
|
115
|
+
audit_year: string | null;
|
|
116
|
+
award_reference: string | null;
|
|
117
|
+
reference_number: string | null;
|
|
118
|
+
is_material_weakness: string | null;
|
|
119
|
+
is_modified_opinion: string | null;
|
|
120
|
+
is_questioned_costs: string | null;
|
|
121
|
+
is_repeat_finding: string | null;
|
|
122
|
+
is_significant_deficiency: string | null;
|
|
123
|
+
is_other_findings: string | null;
|
|
124
|
+
is_other_matters: string | null;
|
|
125
|
+
type_requirement: string | null;
|
|
126
|
+
prior_finding_ref_numbers: string | null;
|
|
127
|
+
riskFlags: {
|
|
128
|
+
materialWeakness: boolean | null;
|
|
129
|
+
modifiedOpinion: boolean | null;
|
|
130
|
+
questionedCosts: boolean | null;
|
|
131
|
+
repeatFinding: boolean | null;
|
|
132
|
+
significantDeficiency: boolean | null;
|
|
133
|
+
otherFindings: boolean | null;
|
|
134
|
+
otherMatters: boolean | null;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Search entity Single Audit summaries (`/general`). Structured filters (all
|
|
139
|
+
* optional, AND-combined): `auditeeUei` (^[A-Z0-9]{12}$), `auditeeState`
|
|
140
|
+
* (^[A-Z]{2}$), `auditYear` (int), `totalExpendedMin`/`totalExpendedMax` (finite
|
|
141
|
+
* → gte/lte on total_amount_expended). Plus `limit` (1..100, def 25) / `offset`.
|
|
142
|
+
* The `select=` is the HARDCODED GENERAL_SELECT allowlist (NO personal-contact
|
|
143
|
+
* columns; NO caller column param). Order is the fixed GENERAL_ORDER.
|
|
144
|
+
*
|
|
145
|
+
* HONESTY: totalAvailable is the EXACT Content-Range denominator (P1; a `*`/
|
|
146
|
+
* absent/non-numeric → null + a page-fullness hedge, never 0); a bad column ⇒
|
|
147
|
+
* PostgREST 400 ⇒ invalid_input (filtersDropped always empty, P4); a genuine `[]`
|
|
148
|
+
* ⇒ honest empty; 400/403/5xx/timeout/HTML(SyntaxError)/non-array THROW (P2, 206 =
|
|
149
|
+
* success). The FAC_NOT_DETERMINATION_NOTE rides every response.
|
|
150
|
+
*/
|
|
151
|
+
export declare function searchAudits(args: {
|
|
152
|
+
auditeeUei?: string;
|
|
153
|
+
auditeeState?: string;
|
|
154
|
+
auditYear?: number;
|
|
155
|
+
totalExpendedMin?: number;
|
|
156
|
+
totalExpendedMax?: number;
|
|
157
|
+
limit?: number;
|
|
158
|
+
offset?: number;
|
|
159
|
+
}): Promise<MetaBundle>;
|
|
160
|
+
/**
|
|
161
|
+
* Drill into the audit-RISK findings (`/findings`) for a UEI and/or report. At
|
|
162
|
+
* least ONE of `auditeeUei` (^[A-Z0-9]{12}$) / `reportId` (^[0-9A-Za-z-]+$) is
|
|
163
|
+
* REQUIRED (empty → invalid_input, never a no-op full-table scan). Optional
|
|
164
|
+
* `auditYear` (int), `limit` (1..100, def 50), `offset`. The `select=` is the
|
|
165
|
+
* HARDCODED FINDINGS_SELECT allowlist (NO caller column param). Order is fixed.
|
|
166
|
+
*
|
|
167
|
+
* HONESTY: EXACT Content-Range total (P1); risk flags "Y"/"N" verbatim + the tri-
|
|
168
|
+
* state riskFlags (P3, null-is-UNKNOWN); a bad column ⇒ 400 ⇒ invalid_input (P4);
|
|
169
|
+
* 400/403/5xx/timeout/HTML/non-array THROW (P2); a genuine empty ⇒ honest empty +
|
|
170
|
+
* the ★M2 empty≠clean note. FAC_NOT_DETERMINATION_NOTE + FAC_VALUE_TYPING_NOTE
|
|
171
|
+
* ride every response.
|
|
172
|
+
*/
|
|
173
|
+
export declare function getFindings(args: {
|
|
174
|
+
auditeeUei?: string;
|
|
175
|
+
reportId?: string;
|
|
176
|
+
auditYear?: number;
|
|
177
|
+
limit?: number;
|
|
178
|
+
offset?: number;
|
|
179
|
+
}): Promise<MetaBundle>;
|
|
180
|
+
//# sourceMappingURL=fac.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fac.d.ts","sourceRoot":"","sources":["../src/fac.ts"],"names":[],"mappings":"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"}
|
package/dist/fac.js
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fac.ts — CMS/GSA Federal Audit Clearinghouse (FAC) Single Audit vetting
|
|
3
|
+
* (ADR-0038). A NEW keyless PostgREST adapter over `api.fac.gov` (fronted by
|
|
4
|
+
* api.data.gov), housing TWO purpose-built audit-RISK vetting tools:
|
|
5
|
+
* - `fac_search_audits` — the `general` table (entity Single Audit summaries)
|
|
6
|
+
* - `fac_get_findings` — the `findings` table (the audit-RISK flags)
|
|
7
|
+
* The B2G unlock: before teaming with / subcontracting to a partner, vet their
|
|
8
|
+
* Single Audit RISK (material weaknesses, questioned costs, repeat findings,
|
|
9
|
+
* modified opinions) — joinable to SAM/USAspending/EDGAR by UEI/EIN. DISTINCT
|
|
10
|
+
* from `src/far.ts` (FAR clause compliance — no filename / gate-key collision).
|
|
11
|
+
*
|
|
12
|
+
* ★ KEYLESS-FIRST via the DEMO_KEY doctrine (the 3rd consumer of `datagovKey.ts`
|
|
13
|
+
* after datagov.ts + govinfo.ts). A bare api.fac.gov request → HTTP 403
|
|
14
|
+
* (API_KEY_MISSING); the SAME request + `X-Api-Key: DEMO_KEY` → 200/206 real
|
|
15
|
+
* data. `keyHeader()` places the secret in the `X-Api-Key` header ONLY (never the
|
|
16
|
+
* URL / label / _meta / a log); `resolvedKey()` falls back to the public literal
|
|
17
|
+
* "DEMO_KEY" when DATA_GOV_API_KEY is unset. `keylessMode:false` (honest — it
|
|
18
|
+
* genuinely sends a key, exactly like datagov.ts/govinfo.ts); `_meta.source`
|
|
19
|
+
* discloses the key-MODE only.
|
|
20
|
+
*
|
|
21
|
+
* R2 consumer — reuses the shared port: `getJsonWithHeaders` (ADR-0038 M1 — the
|
|
22
|
+
* header-exposing sibling of getJson; the EXACT total is a RESPONSE HEADER, not a
|
|
23
|
+
* body field), `driftError` / `throughGate` (datasource.ts), `num`·`str`
|
|
24
|
+
* (coerce.ts), `withMeta` (meta.ts). It writes ZERO bespoke fetch — a hand-rolled
|
|
25
|
+
* fetch inside fac.ts would re-open `redirect:"error"` (a 3xx off api.fac.gov
|
|
26
|
+
* carrying the X-Api-Key to a foreign host = key egress), the hostname/https
|
|
27
|
+
* assert, and `keyHeader()` placement, all of which the shared port keeps
|
|
28
|
+
* structurally present and ADR-0007-K-test-covered.
|
|
29
|
+
*
|
|
30
|
+
* ★ HONESTY (P1–P4; ADR §Q2 + the v2 review):
|
|
31
|
+
* (P1) The EXACT total is the `Content-Range` denominator (a RESPONSE HEADER
|
|
32
|
+
* under `Prefer: count=exact`; 206-on-subset). `parseTotal` takes the
|
|
33
|
+
* substring after the LAST `/`; a run of digits → num() EXACT; a `*`
|
|
34
|
+
* ("count not computed") / non-number / absent header → totalAvailable:null +
|
|
35
|
+
* a page-fullness `hasMore = returned >= limit` hedge + a disclosed note —
|
|
36
|
+
* NEVER 0 (the null-never-0 rule applied to a header).
|
|
37
|
+
* (P2) A genuine `[]` → honest empty (+ the M2 empty-findings note); a body
|
|
38
|
+
* that is not a JSON array → driftError. 400/403/5xx/timeout/off-host-redirect
|
|
39
|
+
* THROW via the port taxonomy (206 = success — `r.ok` covers 200-299). An
|
|
40
|
+
* HTML/non-JSON 200 (r.json() SyntaxError) → reclassified to schema_drift
|
|
41
|
+
* (S1) — never a fake empty.
|
|
42
|
+
* (P3) `total_amount_expended` is a JSON number → num() null-never-0. The `is_*`
|
|
43
|
+
* risk flags are STRINGS "Y"/"N" — surfaced verbatim (str → "Y"/"N"/null) AND
|
|
44
|
+
* mapped to a tri-state `riskFlags` object ("Y"→true / "N"→false / blank /
|
|
45
|
+
* absent / other → null=UNKNOWN). A null flag must NEVER read as false/"no
|
|
46
|
+
* material weakness" (the OFAC/sam_check_exclusions false-CLEAR class).
|
|
47
|
+
* (P4) Filters are server-side + self-policing: each caller filter maps to a
|
|
48
|
+
* KNOWN column + a FIXED operator, value-validated + URLSearchParams-encoded;
|
|
49
|
+
* a valid filter narrows the Content-Range total, a bad column → PostgREST 400
|
|
50
|
+
* → invalid_input. So `filtersDropped` is ALWAYS empty (no silent-drop path).
|
|
51
|
+
*
|
|
52
|
+
* ★ THE PII CRUX (load-bearing — §PII boundary, LIVE-confirmed SOUND): the
|
|
53
|
+
* `select=` is a HARDCODED MODULE CONSTANT allowlist that SURFACES only entity +
|
|
54
|
+
* audit-risk fields and EXCLUDES all 6 personal-contact columns (auditee_email,
|
|
55
|
+
* auditee_phone, auditee_certify_name, auditee_certify_title, auditee_contact_name,
|
|
56
|
+
* auditee_contact_title). PostgREST WILL project auditee_email the instant
|
|
57
|
+
* `select=` names it (live-verified), so the tools accept NO caller-supplied
|
|
58
|
+
* `select`/`order`/free-column param — the inputs are ONLY structured, validated
|
|
59
|
+
* filters. Purpose-built tools bake the allowlist in by construction (the reason
|
|
60
|
+
* v1 rejects a generic `fac_query{select}`). The mapper also picks ONLY the
|
|
61
|
+
* allowlist keys, so even a drifted server that returns an extra column cannot
|
|
62
|
+
* leak it into the output.
|
|
63
|
+
*
|
|
64
|
+
* ★ SSRF: fixed host `api.fac.gov` (single-entry FAC_HOSTS allowlist + a Set.has
|
|
65
|
+
* recheck); the table name is a FIXED per-tool path CONSTANT (`general`/`findings`
|
|
66
|
+
* — never caller free-text); filter values ride URLSearchParams as
|
|
67
|
+
* `col=eq.VALUE`; `redirect:"error"`; a post-construction hostname/https assert.
|
|
68
|
+
*/
|
|
69
|
+
import { ToolErrorCarrier } from "./errors.js";
|
|
70
|
+
import { getJsonWithHeaders, driftError, throughGate } from "./datasource.js";
|
|
71
|
+
import { num, str } from "./coerce.js";
|
|
72
|
+
import { keyHeader, keyModeLabel, pushKeyNote } from "./datagovKey.js";
|
|
73
|
+
import { withMeta } from "./meta.js";
|
|
74
|
+
// Re-export the shared honesty coercion (single audited copy in ./coerce.js) so
|
|
75
|
+
// the fault suite's num-parity guard resolves fac.num === coerce.num, exactly
|
|
76
|
+
// like fdic.num / cms.num / datagov.num.
|
|
77
|
+
export { num };
|
|
78
|
+
// ─── Constants (SSRF core) ─────────────────────────────────────────
|
|
79
|
+
const FAC_HOST = "api.fac.gov";
|
|
80
|
+
// Single-entry host allowlist (structured for a later host-add; the belt-and-
|
|
81
|
+
// suspenders Set.has recheck runs in getFac).
|
|
82
|
+
export const FAC_HOSTS = [FAC_HOST];
|
|
83
|
+
const FAC_HOST_SET = new Set(FAC_HOSTS);
|
|
84
|
+
// One host = one rate budget. A modest self-throttle — courtesy to a shared
|
|
85
|
+
// api.data.gov-fronted host, doubly warranted by the ~10 req/hr DEMO_KEY ceiling.
|
|
86
|
+
const FAC_MIN_INTERVAL_MS = 200;
|
|
87
|
+
export const FAC_LABEL = "fac:" + FAC_HOST; // → ToolError.upstreamEndpoint (host-only)
|
|
88
|
+
const GATE_KEY = "fac";
|
|
89
|
+
// Fixed table path constants — the TOOL chooses these; NO caller value on the
|
|
90
|
+
// path (SSRF core / no table-injection surface).
|
|
91
|
+
const TABLE_GENERAL = "general";
|
|
92
|
+
const TABLE_FINDINGS = "findings";
|
|
93
|
+
// ★ The HARDCODED positive select-allowlists (the PII crux). These are the ONLY
|
|
94
|
+
// columns ever named on the wire — every personal-contact column
|
|
95
|
+
// (auditee_email/phone/certify_name/certify_title/contact_name/contact_title) is
|
|
96
|
+
// STRUCTURALLY excluded. NO caller can supply a `select`/column param.
|
|
97
|
+
export const GENERAL_SELECT = "report_id,auditee_uei,audit_year,auditee_name,auditee_ein,auditee_state,auditee_city,total_amount_expended,fac_accepted_date";
|
|
98
|
+
export const FINDINGS_SELECT = "report_id,auditee_uei,audit_year,award_reference,reference_number,is_material_weakness,is_modified_opinion,is_questioned_costs,is_repeat_finding,is_significant_deficiency,is_other_findings,is_other_matters,type_requirement,prior_finding_ref_numbers";
|
|
99
|
+
// Fixed default orders (deterministic offset windows; never caller-derived).
|
|
100
|
+
const GENERAL_ORDER = "fac_accepted_date.desc";
|
|
101
|
+
const FINDINGS_ORDER = "report_id.asc,reference_number.asc";
|
|
102
|
+
// The 7 audit-RISK flags (STRING "Y"/"N"/blank live) mapped to the tri-state.
|
|
103
|
+
const RISK_FLAG_COLUMNS = [
|
|
104
|
+
"is_material_weakness",
|
|
105
|
+
"is_modified_opinion",
|
|
106
|
+
"is_questioned_costs",
|
|
107
|
+
"is_repeat_finding",
|
|
108
|
+
"is_significant_deficiency",
|
|
109
|
+
"is_other_findings",
|
|
110
|
+
"is_other_matters",
|
|
111
|
+
];
|
|
112
|
+
// ─── Input grammars (belt-and-suspenders behind the server's Zod schemas — the
|
|
113
|
+
// fault suite calls these functions directly, bypassing Zod, so re-validation
|
|
114
|
+
// here is load-bearing for the SSRF/injection rejects). ────────────
|
|
115
|
+
const UEI_RE = /^[A-Z0-9]{12}$/;
|
|
116
|
+
const STATE_RE = /^[A-Z]{2}$/;
|
|
117
|
+
const REPORT_ID_RE = /^[0-9A-Za-z-]+$/;
|
|
118
|
+
const REPORT_ID_MAX = 64;
|
|
119
|
+
function invalidInput(message) {
|
|
120
|
+
return new ToolErrorCarrier({ kind: "invalid_input", message, retryable: false });
|
|
121
|
+
}
|
|
122
|
+
// ─── Disclosure constants (honesty obligations — verbatim, fault-asserted) ──
|
|
123
|
+
/**
|
|
124
|
+
* ★ §PII(d) — the mandatory not-a-determination caveat carried on EVERY response
|
|
125
|
+
* (mirrors OFAC_NOT_DETERMINATION_NOTE / NPPES_NOT_DETERMINATION_NOTE). Kept
|
|
126
|
+
* verbatim so the fault suite can assert it.
|
|
127
|
+
*/
|
|
128
|
+
export const FAC_NOT_DETERMINATION_NOTE = "Public Single Audit data (Federal Audit Clearinghouse; 2 CFR 200 Subpart F / Single Audit Act). Reports the independent auditor's findings, opinions, and questioned costs AS SUBMITTED — it is NOT a debarment, suspension, exclusion, or fitness determination, and an audit finding is the auditor's opinion, not proof of wrongdoing (many findings are routine and remediated). This tool DELIBERATELY EXCLUDES the auditee's personal-contact fields (email / phone / certifying-official name) — the vetting subject is the entity, not the individual. Cross-check SAM exclusions + OFAC and read the specific finding text before acting.";
|
|
129
|
+
/** ★ P3 — the risk-flag typing disclosure carried on EVERY fac_get_findings response. */
|
|
130
|
+
export const FAC_VALUE_TYPING_NOTE = "Risk flags (is_material_weakness, is_modified_opinion, is_questioned_costs, is_repeat_finding, is_significant_deficiency, is_other_findings, is_other_matters) are surfaced VERBATIM as the auditor reported them (\"Y\"/\"N\") plus a typed riskFlags tri-state; a blank/absent flag is UNKNOWN (null), NOT a \"no\" — never read a null flag as 'no finding'.";
|
|
131
|
+
/** ★ M2 — the empty-findings ≠ clean-audit disclosure (any totalAvailable:0 / empty findings). */
|
|
132
|
+
export const FAC_EMPTY_FINDINGS_NOTE = "An empty findings list means NO findings are ON RECORD for this UEI/report — it does NOT confirm a clean audit. The entity may not have filed a Single Audit (below the $750K threshold), the audit may predate FAC coverage, or the UEI may be wrong. Confirm the entity has an ACCEPTED audit via fac_search_audits before treating empty as clean.";
|
|
133
|
+
/** P1 — the Content-Range hedge disclosure (a `*`/absent/non-numeric denominator). */
|
|
134
|
+
const FAC_TOTAL_HEDGE_NOTE = "FAC did not return an exact Content-Range total (the denominator was '*', absent, or non-numeric); totalAvailable is UNKNOWN (null) and hasMore is inferred from page-fullness — page via offset until a short page confirms the end (never read this as 0 results).";
|
|
135
|
+
function facSource() {
|
|
136
|
+
return `${FAC_HOST} via Federal Audit Clearinghouse API (${keyModeLabel()})`;
|
|
137
|
+
}
|
|
138
|
+
// ─── P1 — Content-Range EXACT-total parse (num-guarded, null-never-0) ──
|
|
139
|
+
/**
|
|
140
|
+
* Parse the EXACT total from `Content-Range: <start>-<end>/<TOTAL>` — take the
|
|
141
|
+
* substring after the LAST `/` and `num()` it. A run of digits → the EXACT total
|
|
142
|
+
* (`0` denominator → 0, a genuine-empty total); a `*` (PostgREST "count not
|
|
143
|
+
* computed") / non-number / absent header → null (NEVER 0 — the null-never-0 rule
|
|
144
|
+
* on a header). Exported for the Content-Range-matrix fault fixture.
|
|
145
|
+
*/
|
|
146
|
+
export function parseTotal(contentRange) {
|
|
147
|
+
if (typeof contentRange !== "string")
|
|
148
|
+
return null;
|
|
149
|
+
const slash = contentRange.lastIndexOf("/");
|
|
150
|
+
if (slash < 0)
|
|
151
|
+
return null;
|
|
152
|
+
// num("*")→null, num("")→null, num("37144")→37144, num("0")→0 (genuine zero).
|
|
153
|
+
return num(contentRange.slice(slash + 1).trim());
|
|
154
|
+
}
|
|
155
|
+
// ─── P3 — risk-flag tri-state ("Y"→true / "N"→false / else → null=UNKNOWN) ──
|
|
156
|
+
/**
|
|
157
|
+
* Map an FAC risk flag to an HONEST tri-state: "Y" → true, "N" → false, and a
|
|
158
|
+
* null / blank / absent / any-other value → null (UNKNOWN — NEVER false). A null
|
|
159
|
+
* `is_material_weakness` silently read as "no material weakness" is the false-CLEAR
|
|
160
|
+
* class OFAC/sam_check_exclusions forbid. Exported for the flag-type fault fixture.
|
|
161
|
+
*/
|
|
162
|
+
export function flagTri(v) {
|
|
163
|
+
const s = typeof v === "string" ? v.trim().toUpperCase() : v;
|
|
164
|
+
if (s === "Y")
|
|
165
|
+
return true;
|
|
166
|
+
if (s === "N")
|
|
167
|
+
return false;
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
// ─── SSRF-guarded fetch (fixed host + hostname assert + redirect) ──
|
|
171
|
+
/**
|
|
172
|
+
* GET one FAC PostgREST table through the shared header-exposing port. `table` is
|
|
173
|
+
* a FIXED per-tool constant (general/findings) — NO caller value on the path. The
|
|
174
|
+
* key rides `X-Api-Key` ONLY (keyHeader), `Prefer: count=exact` requests the
|
|
175
|
+
* EXACT total, `redirect:"error"` refuses an off-host 3xx (no key egress), and the
|
|
176
|
+
* CONSTRUCTED URL's hostname must equal api.fac.gov over https (+ a Set.has
|
|
177
|
+
* recheck). Returns `{ body, contentRange }`. A 200 non-JSON body (r.json()
|
|
178
|
+
* SyntaxError, e.g. an HTML maintenance page) is reclassified to schema_drift
|
|
179
|
+
* (S1); the structured 400/403/429/5xx/timeout taxonomy propagates unchanged.
|
|
180
|
+
*/
|
|
181
|
+
async function getFac(table, params) {
|
|
182
|
+
const url = `https://${FAC_HOST}/${table}?${params.toString()}`;
|
|
183
|
+
const built = new URL(url);
|
|
184
|
+
if (built.hostname !== FAC_HOST ||
|
|
185
|
+
built.protocol !== "https:" ||
|
|
186
|
+
!FAC_HOST_SET.has(built.hostname)) {
|
|
187
|
+
throw invalidInput(`Constructed FAC URL host ${JSON.stringify(built.hostname)} (${built.protocol}) is not ${FAC_HOST} over https — refusing to fetch (SSRF safety).`);
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
return await throughGate(GATE_KEY, FAC_MIN_INTERVAL_MS, () => getJsonWithHeaders(url, {
|
|
191
|
+
label: FAC_LABEL,
|
|
192
|
+
headers: { ...keyHeader(), Prefer: "count=exact" },
|
|
193
|
+
redirect: "error",
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
// Preserve the structured taxonomy (400/403/429/5xx/timeout/off-host-redirect).
|
|
198
|
+
if (e instanceof ToolErrorCarrier)
|
|
199
|
+
throw e;
|
|
200
|
+
// ★ S1 — a 200 non-JSON body makes getJsonWithHeaders' r.json() throw a
|
|
201
|
+
// SyntaxError → reclassify to schema_drift (an honest THROW, never a fake
|
|
202
|
+
// empty). The shared port stays byte-identical (reclassification is HERE).
|
|
203
|
+
if (e instanceof SyntaxError) {
|
|
204
|
+
throw driftError(FAC_LABEL, "FAC returned a non-JSON body at HTTP 2xx — treating as schema drift.");
|
|
205
|
+
}
|
|
206
|
+
throw e;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/** Assert the parsed body is a JSON array (P2 drift guard); else driftError. */
|
|
210
|
+
function asArray(body, table) {
|
|
211
|
+
if (!Array.isArray(body)) {
|
|
212
|
+
throw driftError(FAC_LABEL, `FAC ${table} returned a non-array body — treating as schema drift (PostgREST returns a bare JSON array; never a fake empty).`);
|
|
213
|
+
}
|
|
214
|
+
return body;
|
|
215
|
+
}
|
|
216
|
+
function mapAudit(rec) {
|
|
217
|
+
return {
|
|
218
|
+
report_id: str(rec.report_id),
|
|
219
|
+
auditee_uei: str(rec.auditee_uei),
|
|
220
|
+
audit_year: str(rec.audit_year), // a STRING live ("2026")
|
|
221
|
+
auditee_name: str(rec.auditee_name),
|
|
222
|
+
auditee_ein: str(rec.auditee_ein), // a STRING live ("911767139")
|
|
223
|
+
auditee_state: str(rec.auditee_state),
|
|
224
|
+
auditee_city: str(rec.auditee_city),
|
|
225
|
+
total_amount_expended: num(rec.total_amount_expended), // JSON number, null-never-0
|
|
226
|
+
fac_accepted_date: str(rec.fac_accepted_date),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function mapFinding(rec) {
|
|
230
|
+
return {
|
|
231
|
+
report_id: str(rec.report_id),
|
|
232
|
+
auditee_uei: str(rec.auditee_uei),
|
|
233
|
+
audit_year: str(rec.audit_year),
|
|
234
|
+
award_reference: str(rec.award_reference),
|
|
235
|
+
reference_number: str(rec.reference_number),
|
|
236
|
+
// Flags surfaced VERBATIM ("Y"/"N"/null — str nulls ""/"null", so a blank
|
|
237
|
+
// flag is null=UNKNOWN, never "N").
|
|
238
|
+
is_material_weakness: str(rec.is_material_weakness),
|
|
239
|
+
is_modified_opinion: str(rec.is_modified_opinion),
|
|
240
|
+
is_questioned_costs: str(rec.is_questioned_costs),
|
|
241
|
+
is_repeat_finding: str(rec.is_repeat_finding),
|
|
242
|
+
is_significant_deficiency: str(rec.is_significant_deficiency),
|
|
243
|
+
is_other_findings: str(rec.is_other_findings),
|
|
244
|
+
is_other_matters: str(rec.is_other_matters),
|
|
245
|
+
type_requirement: str(rec.type_requirement),
|
|
246
|
+
prior_finding_ref_numbers: str(rec.prior_finding_ref_numbers),
|
|
247
|
+
riskFlags: {
|
|
248
|
+
materialWeakness: flagTri(rec.is_material_weakness),
|
|
249
|
+
modifiedOpinion: flagTri(rec.is_modified_opinion),
|
|
250
|
+
questionedCosts: flagTri(rec.is_questioned_costs),
|
|
251
|
+
repeatFinding: flagTri(rec.is_repeat_finding),
|
|
252
|
+
significantDeficiency: flagTri(rec.is_significant_deficiency),
|
|
253
|
+
otherFindings: flagTri(rec.is_other_findings),
|
|
254
|
+
otherMatters: flagTri(rec.is_other_matters),
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
void RISK_FLAG_COLUMNS; // documented flag inventory (mapper enumerates explicitly)
|
|
259
|
+
// ─── Tool 1: fac_search_audits (the `general` table) ───────────────
|
|
260
|
+
/**
|
|
261
|
+
* Search entity Single Audit summaries (`/general`). Structured filters (all
|
|
262
|
+
* optional, AND-combined): `auditeeUei` (^[A-Z0-9]{12}$), `auditeeState`
|
|
263
|
+
* (^[A-Z]{2}$), `auditYear` (int), `totalExpendedMin`/`totalExpendedMax` (finite
|
|
264
|
+
* → gte/lte on total_amount_expended). Plus `limit` (1..100, def 25) / `offset`.
|
|
265
|
+
* The `select=` is the HARDCODED GENERAL_SELECT allowlist (NO personal-contact
|
|
266
|
+
* columns; NO caller column param). Order is the fixed GENERAL_ORDER.
|
|
267
|
+
*
|
|
268
|
+
* HONESTY: totalAvailable is the EXACT Content-Range denominator (P1; a `*`/
|
|
269
|
+
* absent/non-numeric → null + a page-fullness hedge, never 0); a bad column ⇒
|
|
270
|
+
* PostgREST 400 ⇒ invalid_input (filtersDropped always empty, P4); a genuine `[]`
|
|
271
|
+
* ⇒ honest empty; 400/403/5xx/timeout/HTML(SyntaxError)/non-array THROW (P2, 206 =
|
|
272
|
+
* success). The FAC_NOT_DETERMINATION_NOTE rides every response.
|
|
273
|
+
*/
|
|
274
|
+
export async function searchAudits(args) {
|
|
275
|
+
const limit = args.limit ?? 25;
|
|
276
|
+
const offset = args.offset ?? 0;
|
|
277
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
278
|
+
throw invalidInput("fac_search_audits: limit must be an integer in 1..100.");
|
|
279
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
280
|
+
throw invalidInput("fac_search_audits: offset must be an integer ≥ 0.");
|
|
281
|
+
const params = new URLSearchParams();
|
|
282
|
+
params.set("select", GENERAL_SELECT); // ★ hardcoded allowlist — never caller-derived
|
|
283
|
+
const filtersApplied = [];
|
|
284
|
+
if (args.auditeeUei !== undefined) {
|
|
285
|
+
if (!UEI_RE.test(args.auditeeUei))
|
|
286
|
+
throw invalidInput("fac_search_audits: auditeeUei must match ^[A-Z0-9]{12}$ (12-char UEI).");
|
|
287
|
+
params.append("auditee_uei", `eq.${args.auditeeUei}`);
|
|
288
|
+
filtersApplied.push("auditeeUei");
|
|
289
|
+
}
|
|
290
|
+
if (args.auditeeState !== undefined) {
|
|
291
|
+
if (!STATE_RE.test(args.auditeeState))
|
|
292
|
+
throw invalidInput("fac_search_audits: auditeeState must match ^[A-Z]{2}$ (2-letter state).");
|
|
293
|
+
params.append("auditee_state", `eq.${args.auditeeState}`);
|
|
294
|
+
filtersApplied.push("auditeeState");
|
|
295
|
+
}
|
|
296
|
+
if (args.auditYear !== undefined) {
|
|
297
|
+
if (!Number.isInteger(args.auditYear))
|
|
298
|
+
throw invalidInput("fac_search_audits: auditYear must be an integer.");
|
|
299
|
+
params.append("audit_year", `eq.${args.auditYear}`);
|
|
300
|
+
filtersApplied.push("auditYear");
|
|
301
|
+
}
|
|
302
|
+
if (args.totalExpendedMin !== undefined) {
|
|
303
|
+
if (!Number.isFinite(args.totalExpendedMin))
|
|
304
|
+
throw invalidInput("fac_search_audits: totalExpendedMin must be a finite number.");
|
|
305
|
+
params.append("total_amount_expended", `gte.${args.totalExpendedMin}`);
|
|
306
|
+
filtersApplied.push("totalExpendedMin");
|
|
307
|
+
}
|
|
308
|
+
if (args.totalExpendedMax !== undefined) {
|
|
309
|
+
if (!Number.isFinite(args.totalExpendedMax))
|
|
310
|
+
throw invalidInput("fac_search_audits: totalExpendedMax must be a finite number.");
|
|
311
|
+
params.append("total_amount_expended", `lte.${args.totalExpendedMax}`);
|
|
312
|
+
filtersApplied.push("totalExpendedMax");
|
|
313
|
+
}
|
|
314
|
+
params.set("order", GENERAL_ORDER);
|
|
315
|
+
params.set("limit", String(limit));
|
|
316
|
+
params.set("offset", String(offset));
|
|
317
|
+
const { body, contentRange } = await getFac(TABLE_GENERAL, params);
|
|
318
|
+
const rows = asArray(body, TABLE_GENERAL).map((r) => mapAudit((r ?? {})));
|
|
319
|
+
const returned = rows.length;
|
|
320
|
+
const totalAvailable = parseTotal(contentRange); // EXACT or null
|
|
321
|
+
const exact = totalAvailable !== null;
|
|
322
|
+
const hasMore = exact ? offset + returned < totalAvailable : returned >= limit;
|
|
323
|
+
const nextOffset = hasMore ? offset + returned : null;
|
|
324
|
+
const notes = [];
|
|
325
|
+
pushKeyNote(notes);
|
|
326
|
+
if (!exact)
|
|
327
|
+
notes.push(FAC_TOTAL_HEDGE_NOTE);
|
|
328
|
+
notes.push(FAC_NOT_DETERMINATION_NOTE);
|
|
329
|
+
return withMeta({ audits: rows }, {
|
|
330
|
+
source: facSource(),
|
|
331
|
+
keylessMode: false,
|
|
332
|
+
returned,
|
|
333
|
+
totalAvailable,
|
|
334
|
+
filtersApplied,
|
|
335
|
+
filtersDropped: [],
|
|
336
|
+
fieldsUnavailable: [],
|
|
337
|
+
pagination: { offset, limit, hasMore, nextOffset },
|
|
338
|
+
notes,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
// ─── Tool 2: fac_get_findings (the `findings` table) ───────────────
|
|
342
|
+
/**
|
|
343
|
+
* Drill into the audit-RISK findings (`/findings`) for a UEI and/or report. At
|
|
344
|
+
* least ONE of `auditeeUei` (^[A-Z0-9]{12}$) / `reportId` (^[0-9A-Za-z-]+$) is
|
|
345
|
+
* REQUIRED (empty → invalid_input, never a no-op full-table scan). Optional
|
|
346
|
+
* `auditYear` (int), `limit` (1..100, def 50), `offset`. The `select=` is the
|
|
347
|
+
* HARDCODED FINDINGS_SELECT allowlist (NO caller column param). Order is fixed.
|
|
348
|
+
*
|
|
349
|
+
* HONESTY: EXACT Content-Range total (P1); risk flags "Y"/"N" verbatim + the tri-
|
|
350
|
+
* state riskFlags (P3, null-is-UNKNOWN); a bad column ⇒ 400 ⇒ invalid_input (P4);
|
|
351
|
+
* 400/403/5xx/timeout/HTML/non-array THROW (P2); a genuine empty ⇒ honest empty +
|
|
352
|
+
* the ★M2 empty≠clean note. FAC_NOT_DETERMINATION_NOTE + FAC_VALUE_TYPING_NOTE
|
|
353
|
+
* ride every response.
|
|
354
|
+
*/
|
|
355
|
+
export async function getFindings(args) {
|
|
356
|
+
const limit = args.limit ?? 50;
|
|
357
|
+
const offset = args.offset ?? 0;
|
|
358
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
359
|
+
throw invalidInput("fac_get_findings: limit must be an integer in 1..100.");
|
|
360
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
361
|
+
throw invalidInput("fac_get_findings: offset must be an integer ≥ 0.");
|
|
362
|
+
if (args.auditeeUei === undefined && args.reportId === undefined)
|
|
363
|
+
throw invalidInput("fac_get_findings: at least one of auditeeUei or reportId is REQUIRED (an empty query would scan the whole 670K-row findings table — refused).");
|
|
364
|
+
const params = new URLSearchParams();
|
|
365
|
+
params.set("select", FINDINGS_SELECT); // ★ hardcoded allowlist — never caller-derived
|
|
366
|
+
const filtersApplied = [];
|
|
367
|
+
if (args.auditeeUei !== undefined) {
|
|
368
|
+
if (!UEI_RE.test(args.auditeeUei))
|
|
369
|
+
throw invalidInput("fac_get_findings: auditeeUei must match ^[A-Z0-9]{12}$ (12-char UEI).");
|
|
370
|
+
params.append("auditee_uei", `eq.${args.auditeeUei}`);
|
|
371
|
+
filtersApplied.push("auditeeUei");
|
|
372
|
+
}
|
|
373
|
+
if (args.reportId !== undefined) {
|
|
374
|
+
if (!REPORT_ID_RE.test(args.reportId) || args.reportId.length > REPORT_ID_MAX)
|
|
375
|
+
throw invalidInput(`fac_get_findings: reportId must match ^[0-9A-Za-z-]+$ (≤ ${REPORT_ID_MAX} chars).`);
|
|
376
|
+
params.append("report_id", `eq.${args.reportId}`);
|
|
377
|
+
filtersApplied.push("reportId");
|
|
378
|
+
}
|
|
379
|
+
if (args.auditYear !== undefined) {
|
|
380
|
+
if (!Number.isInteger(args.auditYear))
|
|
381
|
+
throw invalidInput("fac_get_findings: auditYear must be an integer.");
|
|
382
|
+
params.append("audit_year", `eq.${args.auditYear}`);
|
|
383
|
+
filtersApplied.push("auditYear");
|
|
384
|
+
}
|
|
385
|
+
params.set("order", FINDINGS_ORDER);
|
|
386
|
+
params.set("limit", String(limit));
|
|
387
|
+
params.set("offset", String(offset));
|
|
388
|
+
const { body, contentRange } = await getFac(TABLE_FINDINGS, params);
|
|
389
|
+
const rows = asArray(body, TABLE_FINDINGS).map((r) => mapFinding((r ?? {})));
|
|
390
|
+
const returned = rows.length;
|
|
391
|
+
const totalAvailable = parseTotal(contentRange);
|
|
392
|
+
const exact = totalAvailable !== null;
|
|
393
|
+
const hasMore = exact ? offset + returned < totalAvailable : returned >= limit;
|
|
394
|
+
const nextOffset = hasMore ? offset + returned : null;
|
|
395
|
+
const notes = [];
|
|
396
|
+
pushKeyNote(notes);
|
|
397
|
+
if (!exact)
|
|
398
|
+
notes.push(FAC_TOTAL_HEDGE_NOTE);
|
|
399
|
+
// ★ M2 — an empty findings result is an AMBIGUOUS false-CLEAR; disclose that
|
|
400
|
+
// empty ≠ clean audit (fires on any empty page / totalAvailable:0).
|
|
401
|
+
if (returned === 0 || totalAvailable === 0)
|
|
402
|
+
notes.push(FAC_EMPTY_FINDINGS_NOTE);
|
|
403
|
+
notes.push(FAC_VALUE_TYPING_NOTE, FAC_NOT_DETERMINATION_NOTE);
|
|
404
|
+
return withMeta({ findings: rows }, {
|
|
405
|
+
source: facSource(),
|
|
406
|
+
keylessMode: false,
|
|
407
|
+
returned,
|
|
408
|
+
totalAvailable,
|
|
409
|
+
filtersApplied,
|
|
410
|
+
filtersDropped: [],
|
|
411
|
+
fieldsUnavailable: [],
|
|
412
|
+
pagination: { offset, limit, hasMore, nextOffset },
|
|
413
|
+
notes,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
//# sourceMappingURL=fac.js.map
|
package/dist/fac.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fac.js","sourceRoot":"","sources":["../src/fac.ts"],"names":[],"mappings":"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"}
|