Customer-facing release notes for the ALETHEIA Safety Intelligence API (api.aletheia.holisticquality.io).
For the data-layer change feed (new compounds, updated regulatory classifications, sub-processor changes) see /api/changelog.
The machine-readable form of this file is served at /api/release-notes.
> Released 2026-05-22. v1.4.4 → v1.4.5. Closes both technical-gate > preconditions from the 2026-05-22 board decision memo > (tracking/board/2026-05-22-board-distribution-decision-memo.md). > Mechanically additive; no breaking changes; the data layer is unchanged.
The 2026-05-14 storefront price bump to Dev $79 / Pro $299 was missed by the API code itself across three successive releases (v1.4.2, v1.4.3, v1.4.4) — api/_lib/constants.js still encoded the old $29 / $99, and a shadow TIER_PRICES in api/_lib/admin-dashboard.js was the actual root cause of the admin dashboard showing wrong MRR numbers.
api/_lib/constants.js: TIERS prices synced; ANNUAL_TIERS recomputed ($790 / $2,990 yearly); new PRE_BUMP_TIER_PRICES export reserved for any future grandfathering needapi/_lib/admin-dashboard.js: shadow constant replaced with template- injected ${DASHBOARD_TIER_PRICES_JSON} derived from server-side constants — drift impossible by constructionauth.js, export.js, catalog.js, compounds/compare.js, playground.js, cron/trial-lifecycle.js) — all interpolated from TIER_PRICES, matching the storefront pricesscripts/build-openapi.js + scripts/export-rapidapi.js tier markdown tables + RapidAPI x-rapidapi-pricing plans derived from TIERS — closes the spec ↔ code drift class entirelyNet-new strategic-regulatory artifact ship gated on any future external customer signup. Three substantive additions:
terms.html v3.0 (April 2026) and privacy.html have linked to /disclaimer for ~5 weeks; the endpoint never existed (404). Closes the dead-link gap. Full Disclaimer document with explicit "reference data, not professional advice" framing; ?format=json contract mirrors /api/terms and /api/privacy.api/export.js streaming full-detail JSON + standard JSON envelope). API consumers programmatically see the disclaimer text on every export — not only when they visit /terms. Source string lives in api/_lib/data-quality.js::ENVELOPE_NOTICE, mirrored in api/disclaimer.js::ENVELOPE_NOTICE for /disclaimer's JSON payload.DataQualityAnnotation + EnvelopeNotice component schemas document the data_quality array shape, the [under-review:SCI-DAT-01] sentinel string semantics, and the envelope-notice fields. New "Data Quality and Disclaimer Fields" section in spec description.Plus: terms.html adds a top-of-document conspicuous "Reference data, not professional advice" banner (UCC §2-316 conspicuousness); terms.js TERMS_META carries last_reviewed: '2026-05-22'; privacy.html footer shows the same review date.
24,716 / 24,718 passing (2 skipped, 0 fail). No deprecations.
> Released 2026-05-19. v1.4.3 → v1.4.4. Data-only release — no API > contract, code, or middleware change. safety.holisticquality.io is > resynced by this release (66 compound records). The v1.4.3 export-surface > data-quality safeguard remains in force for records still under review.
Name-anchored corrections (PubChem PUG-REST resolved by name, never via the suspect cross-ref CAS/CID) landed across two operator-adjudicated batches:
66 compound records changed in total. Records that could not be safely resolved by the mandated name-anchored method are stamped _data_gaps: blocked for second-reviewer adjudication (e.g. the "RDP" abbreviation-name and glyphosate parent-vs-salt name-resolution traps) and remain covered by the v1.4.3 export safeguard. The deterministic SCI-DAT-01 ratchet floor burned 395 → 342 across the two batches. CI-3 and the wider SCI-DAT-01 remediation remain open (later batches).
> Released 2026-05-19 (api.aletheia only — safety.holisticquality.io is not affected by this release). v1.4.2 → v1.4.3. No breaking API contract change; one new advisory field on the bulk export.
A defined set of compounds is undergoing a chemical-identity review (the SCI-DAT-01 work tracked publicly in our changelog feed). For those records, the structural-identity fields — smiles, inchi, inchi_key, exact_mass, molecular_weight, molecular_formula (under identity.</code> / <code>crossrefs.) — previously could return a value sourced from the wrong compound.
As of v1.4.3, on the bulk export endpoints those specific fields for those specific compounds are returned as the literal sentinel <code>[under-review:SCI-DAT-01]</code> instead of a value we cannot currently vouch for, and the record carries a new advisory <code>data_quality</code> array ({field, status:"under_review", finding:"SCI-DAT-01", reference}). This is a correctness safeguard: we would rather return an explicit "under review" marker than a structure that may be wrong. CAS number, name, IUPAC name and synonyms are not affected.
Scope (what changed / what did not):
GET /api/export?type=compounds — both detail=full (CSV + JSON) and the standard index export. ~119 compounds out of the full corpus carry at least one sentineled field.GET /api/compound/{id} (synthesized risk responses do not emit these structural fields), /api/compounds/compare, /api/compounds/batch, /api/found-in, /api/regulatory. No change to risk synthesis, regulatory data, or any non-structural field.data_quality array on a record, or test a structural field === "[under-review:SCI-DAT-01]". Clean records are byte-for-byte unchanged and carry no data_quality key.Temporary by design. When the chemical-identity review completes and the underlying records are corrected, the sentinel and data_quality marker are removed and the corrected structural values are served normally. No client change is required in either direction; the marker is additive and the field type for affected cells is a recognizable string.
> Released 2026-05-16 (api.aletheia + safety.holisticquality.io). This is the "next data/audit batch deploy" the PC-2 hold (operator decision 2026-05-15) was waiting on — it ships the SCI-DAT-01 mechanical correction, the PC-2 edge-cache change, and the 2026-05-12 intelligence → reference rebrand sweeps together as one batch. v1.4.1 → v1.4.2.
33 PubChem-triple-verified <code>identity</code>/<code>crossrefs</code> corrections across 13 compounds (RDP → CID 93311, glyphosate IPA/ammonium crossrefs.inchi, ceftiofur, and 10 others). These rows previously served a different compound's structure on identity.</code> and/or <code>crossrefs.. Pinned by tests/sci-dat-01-residual-corrections.test.js (43/43) and gated against regression by a deterministic ratchet (scripts/sweep-sci-dat-01-residual.py --baseline, floor 395, its own CI job). The safety.holisticquality.io JSON-viewer mirror is synced to the same 13 corrected records in this release.
Still outstanding (operator-routed, NOT in this release): the non-mechanical SCI-DAT-01 remainder — 116 compounds / 184 manual_review rows incl. CI-2 (MeIQ/MeIQx mutual carcinogen-identity swap) — requires chemist judgement and is tracked in tracking/SCI-DAT-01-CHEMIST-QUEUE.md. It is not resolved by this deploy.
Customer-facing copy sweep across legal (/privacy, /terms), /docs, /playground, /og, release-notes, Postman, and the RapidAPI listing (aletheia-safety-intelligence → aletheia-safety; "Safety Intelligence" → "Safety Reference"). No API contract change.
One-line: GET /api/compound/{id} previously emitted Cache-Control: private, max-age=3600 for every response. It now upgrades to public, s-maxage=3600, stale-while-revalidate=7200 on the 200 success path when classifyCompound() returns no watch matches — i.e. for ordinary compounds whose response is deterministic in (id, context) and carries no per-user redaction.
Watched compounds remain private. Explosives precursors, narcotics precursors, CWC precursors, acute toxins, and radiological precursors continue to return private, max-age=3600 so the origin can register every access via the misuse tracker. Error paths (400/404/422) and the CRITICAL-threat 403 also stay private.
Cache-key safety. Public responses carry Vary: X-API-Key, X-RapidAPI-Subscription (auth state keyed into the CDN slot, as enforced in SCALE-2 Phase 1) plus Surrogate-Key: tier-b compound:{id} (per-compound + global tier-B purge tags) so a compound added to the watch list later can be evicted without a global flush.
Why now: the response payload was the largest remaining hot-path that bypassed Vercel's edge. Carries ~3600s of free p95 reduction on repeat reads for the broad compound corpus.
Closes audit finding PC-2 from 2026-05-04 perfcost (carried as YELLOW through release-readiness 2026-05-09 + v1.4.1 follow-up). Regression-pinned in tests/cache-headers.test.js (Tier B contract — /api/compound/[id] tiered cache (PC-2 Phase 3)) with 4 assertions: unwatched 200 → public, watched → private, 404 → private, malformed ID 400 → private.
POST batch is now exposed on the rebuilt RapidAPI listing (/api/compounds/batch through the marketplace gateway instead of using GET with query-string IDs. Origin handler unchanged.
One-line: v1.4.0 set methodology_note on disk for every synthesized product but the response shaper at api/product/[id].js:37-50 had an explicit field allowlist that omitted it, so the field was silently dropped before serialization.
Customer-visible: methodology_note (string, optional) now appears inside synthesis on every product response, matching the v1.4.0 CHANGELOG promise (CI-3 closure surface).
Why it slipped: test coverage at tests/product-e2e.test.js asserted synthesis_method and synthesis_version are present but did not assert methodology_note. v1.4.1 adds the missing regression test (it('3b. methodology_note surfaces in synthesis')) to prevent recurrence.
No data changes. No schema changes. No SDK schema changes — v0.9.0 typed the field as Optional[str], which is still correct.
> See tracking/DEPLOY-PLAN-2026-05-12.md for the deploy sequence and rollback plan. Deploy window: Tuesday 2026-05-12, 02:00–06:00 UTC.
> Paired SDK release: aletheia-safety v0.8.0 → v0.9.0 ships alongside this API release (Phase 73, 2026-05-09). Surfaces 166 lines of accumulated schema additions in TypeScript + Python types — methodology_note, _prior_agency, exposure_ocular, occupational_exposure, iupac_name, molecular_formula, tightened molecular_weight: number. Publishing to PyPI + npm happens after the API is live so types match production. Future cadence: SDK tracks API minor/major bumps only; patch deploys skip SDK.
brand_examples[] is non-empty): explicit framing that brand examples are category-representative, not brand-level allegations — concerning ingredients in materials.concerning[] apply to the category, not necessarily to every named brand. Closes CI-8./api/compound/{id}:hq-c-ino-000223 Sodium thiosulfate (aquarium dechlor + cyanide-antidote component)hq-c-org-002116 Methylene blue (aquarium therapeutic + methemoglobinemia antidote; flags G6PD-deficient + serotonergic-drug-MAOI populations)hq-c-org-002117 Terpinen-4-ol (tea tree principal terpene; flags cats EXTREME — feline glucuronidation deficit)hq-c-ino-000224 Copper sulfate pentahydrate (aquarium ich treatment + Bordeaux mixture vineyard fungicide; flags Wilson-disease patients + scaleless fish + aquarium invertebrates)hq-c-org-002118 Polyvinylidene fluoride PVDF (RO/UF membrane housing polymer + Li-ion battery binder)hq-c-org-002119 Pyrantel pamoate (pet/human dewormer salt; LD50 >5000 mg/kg rat oral due to non-absorbance)hq-c-mix-000089 Lily toxin (Lilium spp., principle structurally unidentified; cats EXTREME risk from any exposure incl. pollen-on-fur grooming; ASPCA APCC 1-888-426-4435 cited)hq-p-pet-000079..093) covering veterinary medications, cat-specific essential-oil exposure, aquarium chemistry, pet-food packaging migration, and smart-collar electronics. 10 WER-tier products (hq-p-wer-000095..104) covering high-iron well water, heavy-metal POU plumbing, Legionella in domestic hot water, oil & gas produced water (BTEX/NORM/biocides), microplastic-shedding water filters, and emerging contaminants in private wells (PFAS plumes, perchlorate ag/military legacy). Tier shares: PET 6.2% → 7.2% above floor; WER 7.4% → 8.1% above 8% floor.The 2026-05-05 sci-verify audit found that the synthesis engine processed only ~40% of the regulatory classification text in the corpus, silently dropping the other ~60% with unmapped_classification warnings or Source must have year field rejections. v1.4.0 closes the largest single gap:
EPA CTX / IARC → IARC (258 entries previously dropped) and EPA CTX / EPA OPP → US EPA (117 entries) to config/regulatory-mappings.json (1.0.0 → 1.1.0).year field — silently dropping ~1,000+ entries from EPA CompTox where the scrape doesn't populate year by design. The throw is now a soft default to currentYear - 5 with a year_inferred:<agency>:<year> warning surfaced through the synthesis response.Corpus impact (verified post-application on all 1,287 products): +16 products escalated to 'severe' from previously-dropped CTX-derived signals (notably for compounds with disputed status where CTX-derived IARC/EPA OPP entries were the most authoritative recent classifications); balanced shifts in adjacent bands (-11 'high', -9 'low', -5 'moderate_to_high', +9 'moderate'); zero regressions to insufficient_data; zero change in extreme/negligible/insufficient_data counts. Closes engine-level audit finding SCI-ENG-01.
/api/openapiThe OpenAPI 3.1.0 spec lived as a 152 KB JS object literal inlined in api/openapi.js (3,642 lines). @vercel/node bundled the entire object into every cold start at ~150 ms parse cost. v1.4.0 separates spec authoring from runtime:
scripts/build-openapi.js (3,624 lines, lives outside api/ so the runtime handler doesn't bundle it).api/openapi.js is now 154 lines / 5.5 KB (96% size reduction), reading openapi.json via readFileSync at module load and caching for the instance lifetime.vercel-build runs node scripts/export-openapi.js so openapi.json is fresh before any handler is invoked.Expected first-byte latency improvement on cold-start /api/openapi requests; will be validated post-deploy via Speed Insights. Closes perf-cost finding PC-4.
Four red-team Medium findings from the 2026-05-04 cross-audit were structurally non-functional pre-fix; v1.4.0 restores three claimed defense layers:
ACTION_PERMISSIONS map + role-rank gate now applied to every admin action — previously a readonly token could call mutating handlers.X-RapidAPI-Proxy-Secret instead of presence-checking it. Spoofed proxy-secrets fall through to direct-IP bucketing, so attackers pollute their own bucket.Plus three hygiene Lows from the Twelfth E2E review:
.pop() (Vercel-stamped trustworthy rightmost) instead of [0] (attacker-controlled leftmost). Aligns with codebase convention at api/_lib/auth.js and api/_lib/security.js.monitor:snapshot:* Redis writes removed (~864 commands/day saved against Upstash quota; no consumer)./api/health?check=integrity made async + 60 s cached. Pre-fix every probe paid for 9 synchronous file reads + 6 directory enumerations + 8 existence checks; Vercel platform polling and external uptime monitors hit this endpoint every few seconds. Now: all I/O is fs/promises and parallelized; results are cached for 60 s with deep-cloned issues arrays so per-request mutations cannot poison the cached snapshot. Cold path remains correct; hot path is effectively zero-I/O.stripe_customer:* email mapping TTL shortened from 5 years to 90 days on cancellation. Re-subscriptions inside 90 days re-establish the 5y TTL via the existing checkout handler.workflow_dispatch-triggered job (manual; pre/post-deploy operator tool). Default scenario is health against production; outputs a JSON artifact for before/after comparison. Note that auto-gating every push is incompatible with SecurityAuditor's per-IP burst threshold (a load-bearing security control) — see workflow comment in ci.yml for the design rationale.rel="noopener noreferrer" added to 3 target="_blank" links in api/badge.js, api/docs.js, api/playground.js.Stripe.customers.del) and the per-webhook webhook:${id}:deliveries log keys, not just the Redis key blocks. Closes the consumer-visible privacy-policy drift CI-4.subscription.deleted, subscription.updated, invoice.payment_failed) — covers Stripe's documented 3-day retry window.WEBHOOK_ENCRYPT_KEY set. The signing-secret encryption key was previously aliased to ADMIN_SECRET, so rotating the admin secret silently invalidated every encrypted webhook signing secret in Redis. The strict check fail-fast at deploy rather than at next-dispatch-after-rotation. Operator-confirmed on Vercel production env 2026-05-08.webhooks:all set self-cleans orphaned IDs during dispatch + erasure (mirrors the existing keys:active:paid cron pattern).hq-c-org-000001): the suspect "EPA CTX / CalEPA: Known human carcinogen" classification was a misparse of California Prop 65 listing via the IARC pull-through provision (Labor Code §6382). Reattributed to "OEHHA Prop 65: Listed via IARC 2A pull-through" with proper source citation. Footnote: 159 other compounds in the corpus carry the same pattern; many are legitimate (asbestos, benzene, formaldehyde have real CalEPA OEHHA TAC classifications) — flagged for case-by-case follow-up.hq-c-org-000002): genotoxicity call aligned with parent (positive → negative) per hierarchy.inherits_safety:true. Prior CTX 2-positive-reports preserved in notes with Roundup+POEA formulation-confounder explanation per IARC Vol 112.The 2026-05-04 claim-integrity audit flagged 5 areas where load-bearing factual claims about named entities needed inline citation, methodology disclosure, or framing tightening:
methodology_note, brand_examples_disclaimer — both optional fields; existing SDK consumers unaffected).WEBHOOK_ENCRYPT_KEY must be set on Vercel production env before vercel deploy --prod. Operator confirmed 2026-05-08.vercel promote <previous_deploy_id> --prod (≤30 sec restore). Triggers documented in tracking/DEPLOY-PLAN-2026-05-12.md./api/health?check=integrity data-delta spot-checks. 4-hour monitoring window per deploy plan.hq-coherence agent + cadence codification — 22 sister-property files updated to v1.4.0 canonical, new audit agent runs pre-deploy alongside any API minor/major bump), 76 (GDN-family coherence audit + new gdn-coherence agent — 5 GeodesicNexus files updated, CLAUDE.md gdn-core version drift fixed, domain-mapping rebuild deferred pending WER-tier routing rule fix), 77 (Rest-of-FTP family coherence audits — 5 parallel audits across ICS/Catalyst/DLF/Hexad/Standalone families, 11 mechanical fixes across 4 properties, 1 RED resolved (frictioncatalog.com Hard-Rule-2 violation closed via net-new vercel.json), 30 properties audited across 7 family types), 78 (Phase 77 deferred-item closeout — 30 additional fixes across ICS/Hexad/DLF/Catalyst/Frictioncatalog including ICS generate-registry.js:170 filter fix + Saga XII content authored + DLF canonical pillar counts identified (311 not 312) + Hexad cross-domain link integrity restored + React 18/19 peerDeps drift resolved). Phases 50–53 + 56 already shipped in the v1.3.x line; this entry covers v1.3.1 → v1.4.0.Companion fix to the SCI-DAT-02 Phase 2 wrong-CID corrections shipped earlier the same day. The 59 Phase-2 compounds (plus Plutonium-239 from session-64 Gap-2 cleanup) had identity.synonyms arrays still populated from the wrong-CID-active window — auto-enrichment had stamped each record with the wrong molecule's PubChem synonyms list. Phase 2 corrected the cross-references; Phase 4 corrects the synonyms.
For 60 compounds, every synonym entry that appears in the OLD (wrong) PubChem CID's synonym list AND does NOT appear in the canonical (NEW) CID's list was stripped. Canonical synonyms not already present were backfilled. 1,605 old-molecule synonym entries removed, 5,804 canonical synonym entries added.
Notable per-compound results (before → after):
hq-c-org-001395): 244 → 78 (-240 Triamcinolone Acetonide synonyms removed including "Azmacort", "Aristocort", "Kenalog-A"; +74 canonical Erucamide synonyms added)hq-c-org-001422): 103 → 146 (-98 Procarbazine synonyms; +141 canonical Phytate / IP6 / phytic-acid synonyms)hq-c-org-001428): 95 → 50 (-91 Apomorphine synonyms; +46 canonical Difluoromethane / HFC-32 synonyms)hq-c-ino-000213): 54 → 27 (-54 disodium arsenate heptahydrate synonyms — the wrong CID 61460's molecule; +27 canonical Pu-239 isotope synonyms)hq-c-org-001414): 31 → 159 (-26 Isoxazole synonyms; +154 canonical antiseptic synonyms)/api/search exact-match no longer returns the wrong compound for old-molecule queries:
The wrong-search-hit risk class is now closed for these 60 records.
scripts/apply-sci-dat-02-phase4-synonyms.py — data-driven OLD∖NEW PubChem-synonym discriminator (mirror-aware, idempotent, 250ms politeness, --dry-run / --force flags). Pattern is reusable for any future wrong-CID synonym cleanups.safety.holisticquality.io mirror synced across all 60 touched files.Data-layer fixes only — no API behavior change. 59 compound records now carry correct PubChem cross-references (crossrefs.pubchem_cid, identity.smiles, identity.inchi, identity.inchi_key, identity.iupac_name, identity.exact_mass, properties.chemical_properties.{molecular_formula, molecular_weight, exact_mass}) where they had previously been mis-mapped to wholly unrelated molecules by a 2026-03-22 wrong-CID batch merge. Live API still serves pre-fix data until the next major deploy (manual-deploy policy).
The 89 mismatch_recoverable rows from the SCI-DAT-02 name-lookup sweep (session 64) were re-triaged via PubChem InChIKey-prefix discriminator: same first-14 chars = same molecular connectivity (cosmetic / stereo difference); different prefix = different connectivity = genuine wrong-CID. Bucket distribution: 66 prefix_differs / 20 suffix_only / 3 key_matches / 0 fetch_error. Of the 66 prefix_differs, 59 had per-case PubChem Title showing wildly unrelated current_cid — those are this batch. 7 deferred as ambiguous (Phase-1 PeCDF dup; PCB 138; Phenylpropanol regio; Ambrettolide isomer; Ocimene specificity; Phenylpropionaldehyde regio; Satratoxin H untitled lookup).
hq-c-org-001395): CID 6436 (Triamcinolone Acetonide — corticosteroid drug) → 5365371 (Erucamide — fatty amide)hq-c-org-001422): CID 4915 (Procarbazine — anticancer drug) → 890 (Phytate)hq-c-org-001428): CID 6005 (Apomorphine — alkaloid drug) → 6345 (Difluoromethane)hq-c-org-001959): CID 11129 (bicyclic terpene) → 5948 (Tributylstannane)hq-c-org-001414, 001416): both shared CID 9254 (Isoxazole) — proof of bulk-import wrong-CID collision; corrected to 3607 and 62738 respectivelyhq-c-org-002069): CID 40326 (Permethrin — pyrethroid pesticide) → 73671 (Paclobutrazol — triazole growth regulator)hq-c-org-002021): CID 12498 (unrelated guanidine) → 4807 (Phthalaldehyde / Cidex OPA)scripts/triage-sci-dat-02-phase2.py — InChIKey-prefix triage (read-only, ~45s wall, 178 PubChem fetches)scripts/apply-sci-dat-02-phase2.py — full-field harmonization (mirror-aware, idempotent, --force for re-application)tracking/triage-sci-dat-02-phase2-20260507-1453.csv — raw triage outputtracking/SCI-DAT-02-NAME-LOOKUP-SWEEP.md — Phase 2 results sectionsafety.holisticquality.io mirror synced across all 59 touched files. Triage post-fix: HIGH=0 / MEDIUM=0 / CLEAN=1867. Tests 23,778 / 0 fail.
Data-layer fixes only. No API behavior changes — the only thing customers will notice is that 13 compound records now carry correct identity.smiles, identity.inchi_key, identity.inchi, and identity.exact_mass where one or more of those fields was previously contaminated by a wrong-CID PubChem batch merge from 2026-03-22.
A second sweep of the SCI-DAT-01 contamination class flagged in the May-5 sci-verify audit cleared the 14 compounds remaining after the May-7 morning pass:
crossrefs.pubchem_cid — identity.* was simply incomplete. Backfilled with PubChem canonical SMILES, InChI key, InChI, and monoisotopic mass: Ammonium nitrate (hq-c-ino-000218), Diammonium phosphate (hq-c-ino-000220), Ferric chloride (hq-c-ino-000222), Chlorine dioxide (hq-c-org-002022), Gibberellic acid (hq-c-org-002068), Imazethapyr (hq-c-org-002075), Fenbendazole (hq-c-org-002079), Benomyl (hq-c-org-002081).crossrefs.pubchem_cid itself contaminated — the wrong CID resolved to an entirely different molecule. Recovered via PubChem name-lookup → canonical CID → formula match: Saflufenacil (hq-c-org-002074, 11425923 → 11571392), Clethodim (hq-c-org-002076, 92433 → 136469009), Mecoprop / MCPP (hq-c-org-002077, 4038 → 7153), Chlortetracycline (hq-c-org-002080, 54675779 → 54675777), and Plutonium-239 (hq-c-ino-000213, 61460 → 61782).hq-c-org-002078). Both crossrefs.pubchem_cid (wrong) and molecular_formula: C18H23N3O3 (does not match canonical Zilpaterol C14H19N3O2) carry contamination; safer to strip suspect identity fields and stamp a _data_gaps entry than auto-overwrite without authoritative-source confirmation.The Plutonium-239 case is worth a specific call-out: the wrong CID (61460) is disodium hydrogen arsenate heptahydrate, which had been silently donating its exact_mass: 311.962569 and inchi_key: KOLXPEJIBITWIQ-UHFFFAOYSA-L to Pu-239's record since the bad batch import. The canonical Pu-239 record (CID 61782) returns [239Pu] SMILES, exact mass 239.05216, InChI key OYEHPCDNVJXUIW-FTXFMUIASA-N — all of which now appear correctly in the API.
safety.holisticquality.io mirror synced to match across all 14 touched files.
Post-fix triage-pubchem-contamination.py reports HIGH=0, MEDIUM=0, CLEAN=1873/1880 (99.6%) — up from 1860/1880 (99.0%) at the start of the day. The original audit estimate of 50–200 contaminated files of the 1,230 touched by the 2026-03-22 PubChem batch turned out to be substantially inflated by SMILES canonical-vs-expanded rendering false positives; the residual real signal was 14 files, of which 13 are now fixed.
scripts/fix-sci-dat-01-gap2-cluster.py (idempotent, dry-run flag, 250ms PUG-REST politeness, name-lookup recovery, per-write mirror to safety.holisticquality.io)./api/release-notes and /api/openapi.Data-layer corrections following the 2026-05-05 sci-verify audit.
hq-c-ino-000006 Methylmercury — IARC monograph reference + cancer siteTwo stale fields fixed in safety.carcinogenicity:
iarc_monograph was "Volume 115" — fabricated cite. IARC Vol 115 (2018) is on industrial chemicals and contains no mercury evaluation. Now correctly references "Vol 58" (1993), the actual mercury monograph that classified methylmercury as Group 2B.cancer_sites was ["colorectal"]. Methylmercury's evidence is renal (in animals, with limited human evidence); the dominant health concern is fetal/developmental neurotoxicity, not colon cancer. Now correctly reads ["renal (animal evidence; limited human evidence)"], matching what regulatory.listings.iarc_group.cancer_sites already said.safety.holisticquality.io mirror synced to match.
hq-c-ino-000001 Lead (Pb) — IARC Group 2A (Vol 87, 2006). Already corrected in a prior session; verified clean today.hq-c-ino-000006 Methylmercury — IARC Group 2B (Vol 58, 1993), renal cancer sites. Final two stale fields fixed today.hq-c-org-000008 Vinyl chloride — epa_classification: "known_carcinogen", matching EPA IRIS "Known human carcinogen by inhalation". Already corrected in a prior session; verified clean today.The 2026-03-22 PubChem batch enrichment merged data from the wrong CID into 4 confirmed compounds (Glyphosate IPA / K / ammonium salts; Ceftiofur). Those identity.* fields were stripped on 2026-05-05 with audit_tag: SCI-DAT-01 gap stamps.
Today's sweep with scripts/triage-pubchem-contamination.py scanned the remaining 1,876 compounds for the same shape using InChI-key, SMILES, and exact-mass-vs-MW signals:
exact_mass values from a wrong-CID merge; molecular_weight is correct)hq-c-org-00207[4-9]/0208[0-1], suggesting a single batch-import gapTriage output at tracking/SCI-DAT-01-triage.md. Audit's original estimate of 50–200 contaminated files was inflated by SMILES canonical-vs-expanded rendering false positives — the real residual count is 16.
scripts/fix-sci-dat-01-identity-contamination.py extended and applied to close two further classes:
identity.{smiles, iupac_name, exact_mass, inchi_key, inchi} from the wrong CID but correct molecular_formula and properties.molecular_weight. The 5 contaminated fields are now stripped with audit_tag: SCI-DAT-01 gap stamps; the canonical fields are preserved.After apply, triage re-run: 16 → 14 MEDIUM (Ptaquiloside + Maitotoxin now stamped + excluded). The remaining 14 are: 1 mass/MW divergence (Pu-239 — needs human review on whether its exact_mass is a radionuclide-bookkeeping convention) + 13 SMILES-only mismatches needing per-compound PubChem-CID cross-check.
No customer-impacting data served wrong values for any of these — they're all identity.* cosmetic fields, not used in the synthesis or risk-scoring path.
api/stripe/webhook.js)The webhook previously listened for only checkout.session.completed and customer.subscription.deleted. Two new handlers added today, modeled on the existing deleted-subscription pattern (idempotency SET NX, customer→email lookup, per-key stripe_customer ownership guard):
getPriceToTier(); updates keyData.tier, keyData.payment_status (active / past_due / unpaid), keyData.cancel_at_period_end, keyData.stripe_status, keyData.last_subscription_update. Refreshes the SCALE-2 Edge Config tier mirror only when tier actually changes. Does NOT touch the active flag — deactivation remains subscription.deleted's job.keyData.payment_status: "past_due", records last_payment_failure timestamp + payment_failure_attempts count. Does NOT deactivate during the ~3-week Stripe Smart Retries window; that escalation arrives later as subscription.deleted.Closes the carry-forward HIGH from the 2026-05-04 coherence audit (CO-1). Indefinite drift between Stripe state and local keyData.tier is no longer possible — a Pro→Developer downgrade through the Billing Portal now propagates within ~1 second.
11 new tests added covering source-level structural assertions (idempotency pattern, tier mapping, ownership guard, no accidental deactivation during dunning, analytics emission). 31/31 stripe-webhook.test.js tests pass.
⚠️ Required Stripe-side config to activate: the new event types must be enabled on the Stripe webhook endpoint (Dashboard → Developers → Webhooks → select endpoint → Update details → add customer.subscription.updated + invoice.payment_failed). Without that step, Stripe never delivers the events. See tracking/RUNBOOK.md §6.4 for the verification recipe. (Confirmed enabled by Levi 2026-05-07 14:13 UTC — endpoint now subscribes to all four events.)
/api/searchSearch-quality fix surfaced in session 55 by a playground bug investigation: ?q=bpa was returning Bisphenol AF (score 76) instead of Bisphenol A (CAS 80-05-7) because the scorer ranks closest-by-substring without canonical-name semantics. Same pattern affected pfoa (could rank tetra-PFOAs above PFOA), pcb (mixture vs individual congeners), no2 (subscript-typeset name failed plain-text exact match), etc.
api/_lib/search-aliases.js exports a frozen SEARCH_ALIASES map (~50 curated entries) plus resolveAlias(query) helper.bpa/bps/bpf), heavy metals (lead/pb/cadmium/mehg/arsenic), PFAS (pfoa/pfos), classic carcinogens/pollutants (ddt/pcb/pcbs/pbde/tce/pce/perc/hcho/benzene/asbestos/radon/chlorpyrifos/atrazine/acrylamide), criteria pollutants typed without subscripts (no2/so2/co2/o3), vinyl chloride (vc/vcm), pet-toxin canonicals (xylitol/theobromine/caffeine/ibuprofen/apap/acetaminophen/paracetamol), drugs of abuse (thc/cbd/lsd/mdma), and common chemistry (chloroform/ozone/ammonia).as (Arsenic vs the English word), eg, co, no, so, cd are deliberately omitted.handleSearch resolves the query (case-insensitive, whitespace-tolerant) and either boosts an existing matching result to score 100 with alias_boosted: true, or injects the canonical compound synthetically if it didn't appear in the result set.type=product/type=material filters are present — alias targets are all compounds, so respecting the filter keeps semantics intuitive.meta.alias_resolved: <hq-id> field surfaces when an alias fires, so the playground / SDKs / analytics can distinguish boosted-canonical results from organic top hits.tests/search-handler.test.js. 37/37 pass.api/_lib/monitor.js, api/cron/monitor-push.js)VERIFY_SECRET is the deploy-verification bypass that skips audit / shed / rate-limit / auth in one step (api/_lib/auth.js:481-518). Any leak of that secret is a full compromise; until today there was zero observability on its use.
InstanceMonitor now tracks verify_bypass.{count, first_seen, last_seen, unique_ips, anomalous} per instance, with recordVerifyBypass(ip) called from the bypass code path. Per-IP map is bounded at 50 entries (LRU-style) so spoofed IPs can't OOM the instance.monitor-push cron checks verifyBypassAnomalous() every 5 min and sends a dedicated Slack alert when the heuristic flips: ≥10 bypasses on a single instance with sustained >10/hour rate (or 10 bypasses in <3 min for burst detection). Legitimate verify-deploy.py usage is well below the threshold./api/health?check=monitor (admin-only) for ad-hoc inspection.tests/monitor.test.js cover the full surface (initial state, increments, unique-IP tracking, bounded-growth cap, malformed-IP handling, reset semantics, anomaly heuristic in burst + sustained + legitimate-cadence regimes). 61/61 pass.Quarterly rotation cadence documented in tracking/RUNBOOK.md §5.4. Closes the carry-forward MEDIUM from the 2026-05-04 cross-audit (M-2).
OpenAPI spec version bumped from 1.4.0 to 1.5.0. Two functional changes that align the API's enforced behavior with what the RapidAPI Hub already advertises to subscribers.
GET /api/compounds/batch and POST /api/compounds/batch were previously gated to Developer tier and above (the public tier received 401 API key required). Public tier now receives a successful batch response, capped at 10 compounds per request to match the max_batch_size: 10 value configured on the RapidAPI Basic plan in the Hub Provider Dashboard. This was a Hub/code mismatch — Hub advertised batch on Basic, code rejected it.
The internal BATCH_LIMITS table is now:
| Internal tier | Batch cap | RapidAPI plan | |---------------|----------:|---------------| | public | 10 | Basic ($0) | | developer | 50 | Pro ($29/mo) | | pro | 100 | Ultra ($99/mo)| | enterprise | 100 | Enterprise (off-marketplace) |
Previous values (developer: 20, pro: 50, enterprise: 100) were inconsistent with the Hub's per-plan max_batch_size Feature configuration (10 / 50 / 100). Subscribers who composed batches at the Hub-advertised limit could receive 400 Too many compounds from the underlying API. That is now resolved.
/api/compounds/batch now returns a successful response for up to 10 compounds per request. Previously, Basic returned 401. No code change required on your side — just remove any client-side guard that skipped the batch call when no API key was present.developer 50, pro 100, enterprise 100. The synthesize=true option still requires Developer or higher (a reasonable gate since it costs 2 credits per compound).BATCH_SYNTHESIS_MAX = 10 (the cap when synthesize=true is requested) is unchanged. Synthesis still requires a Developer key or higher.
api/_lib/constants.js — BATCH_LIMITS table updated, with a comment block documenting the RapidAPI Hub mapping and a maintenance note ("keep in sync with Hub Provider Dashboard").api/compounds/batch.js — public-tier 401 short-circuit removed; per-tier cap derivation now uses req.auth?.tier || 'public'; synthesis-only 401 added for the synthesize=true + public-tier combination; error response includes upgrade_url for public and developer tiers.api/openapi.js — info.description quickstart and the two /api/compounds/batch operation descriptions (GET and POST) updated to reflect the new caps. Spec version bumped to 1.5.0.scripts/export-rapidapi.js — RapidAPI marketplace info.description overwrite block updated (tier table now shows Basic 10 / Pro 50 / Ultra 100, batch quickstart no longer says "Pro+").rapidapi-listing-copy.md — Plans table corrected, batch-section framing changed from "Pro+" to "open to all tiers", second tier table at the bottom of the doc also corrected.tests/batch-handler.test.js — public-tier-401 test replaced with two new tests covering the 10-compound cap on public; developer-tier tests updated for the new 50-cap.tests/batch-enhanced.test.js — developer cap test updated to 50, pro cap test updated to 100.rapidapi-openapi.json — regenerated.aletheia.holisticquality.io/marketplaces.html — tier cards rewritten to include Basic at the top of the ladder with its 10-compound cap; copy below the tier-cards section (Pro 50, Ultra 100, Enterprise 100) updated accordingly.None. Spec v2.3 is now live in the RapidAPI Hub Studio (set Current). The Definitions tab and the Plans tab Features panel both reflect the new caps; code BATCH_LIMITS matches what the Hub advertises. Existing subscriptions, API keys, and quotas are unchanged.
OpenAPI spec version bumped from 1.3.0 to 1.4.0. The change is primarily customer-discovery and onboarding surface — no breaking changes to any endpoint or response shape.
The RapidAPI listing description (auto-generated from the spec by scripts/export-rapidapi.js) was rewritten for conversion clarity based on a marketplace audit. Specifically:
hq-c-org-000001, CAS 1071-83-6, context human_adult) is shown in the listing so developers can evaluate response shape before signing up for a Basic key.GET /api/freshness for the live snapshot timestamp and per-source staleness buckets./api/health pointer.<img src="…/api/badge/{id}.svg"> example. Previously buried as a feature bullet; the badge endpoint itself is unchanged.The marketplace listing now points subscribers and evaluators to:
/api/playground (read-only queries without an API key — same surface as the 2026-04-26 fix).0.8.0.aletheia.holisticquality.io/docs is unchanged (its info.description was not touched; only the RapidAPI-specific overwrite in scripts/export-rapidapi.js was rewritten).None. The updated listing copy is now live at
200 OK in three clicks (caffeine by CAS number, no key required).api_key global (or rapidapi_key if you're a RapidAPI subscriber) and every request inherits authentication automatically./api/playground was returning an "ID must match format" error for non-CAS queries like bpa or formaldehyde. Two coupled bugs: the playground was reading a stale field name from /api/search results (hit.hq_id instead of hit.id), and was not filtering search hits to compound entities (top hits for short queries are often material or product records, which /api/compound/{id} does not resolve). Both fixed. Recommended search inputs: a compound's full canonical name (e.g. Bisphenol A, Formaldehyde) or its CAS number (e.g. 80-05-7). Common abbreviations like bpa or lead may not yet resolve to the canonical compound — alias-mapping is on the backlog./api/enterprise-inquiry.X-RapidAPI-User header) rather than the forwarded source IP. RapidAPI customers sharing proxy infrastructure no longer consume each other's quota.X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-API-Tier, X-Usage-Warning, X-Upgrade-URL, Retry-After) and calls out the **enterprise tier exception**: enterprise responses return X-API-Tier: enterprise but do not emit the numeric rate-limit headers or X-Upgrade-URL because the quota is unlimited.GET and POST for batch lookup; RapidAPI's auto-import propagated the GET endpoint only. GET /api/compounds/batch?ids=... works as documented through the gateway (up to ~30 IDs fits comfortably in the query string). The POST form remains fully available on the direct-origin host api.aletheia.holisticquality.io and is used by the official SDKs. A manual endpoint registration in the RapidAPI Hub listing will restore the gateway POST path in a follow-up release.This changelog begins with the RapidAPI launch. Earlier infrastructure and dataset changes are tracked in the internal tracking/ directory and were not published as customer-facing release notes.