Skip to main content

Changelog

Customer-facing release notes for the ALETHEIA Safety API. · Last synced 2026-05-15 from CHANGELOG.md

New endpoints, breaking changes, deprecations, marketplace events. The machine-readable JSON source lives at /changelog.json and is the same content the API serves at /api/release-notes. For the data-layer change feed (new compounds, updated regulatory classifications), see api/changelog.

UNRELEASED
Code shipped 2026-05-12, on main and tests-green. Customer-visible only after the next vercel --prod deploy. Move this heading to a dated form once deployed.

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.

RELEASED

POST batch is now exposed on the rebuilt RapidAPI listing (<https://rapidapi.com/holistic-quality-holistic-quality-default/api/aletheia-safety>). Closes RAPIDAPI-L5 carry-over from Session 52 (2026-04-23). Customers on Pro/Ultra who need URL-length relief for large ID lists can now POST a JSON body to /api/compounds/batch through the marketplace gateway instead of using GET with query-string IDs. Origin handler unchanged.

RELEASED

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.

RELEASED
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.

Customer-visible behavior changes

New API response fields

  • /api/product/{id}derived_synthesis.methodology_note: every product synthesis response now includes a one-paragraph disclosure that the route × duration × frequency multipliers are ALETHEIA-calibrated heuristics directionally informed by the EPA Exposure Factors Handbook (2011) and CalEPA OEHHA — engineering heuristics, not regulatory consensus. Closes claim-integrity finding CI-3.
  • /api/product/{id}brand_examples_disclaimer (when 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.
  • derived_synthesis.synthesis_version 1.1.0 → 1.2.0: bumped to reflect upstream engine alias additions and year-missing soft default in this batch.

Data deltas

  • Compound corpus: 1,879 → 1,886 (+7). New compounds queryable via /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)

  • Product corpus: 1,262 → 1,287 (+25). 15 PET-tier products (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.

Synthesis engine: regulatory-coverage expansion

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 CompTox IARC and EPA OPP aliases: added EPA CTX / IARCIARC (258 entries previously dropped) and EPA CTX / EPA OPPUS EPA (117 entries) to config/regulatory-mappings.json (1.0.0 → 1.1.0).
  • Year-missing soft default: the engine previously threw on any classification missing a 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.
  • Crash-free dedup: the pre-existing single-pass deduplication of same-resolved-agency classifications had a stale-index bug that surfaced when 3+ sources collided on one agency. Replaced with two-pass implementation.

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.

Performance: cold-start improvement on /api/openapi

The 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:

  • Spec source-of-truth: scripts/build-openapi.js (3,624 lines, lives outside api/ so the runtime handler doesn't bundle it).
  • Runtime handler: 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.
  • Build hook: 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.

Customer-invisible internal improvements

Security hardening (red-team Mediums + Hygiene Lows)

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:

  • RT-M1 Admin lockout escalation: counter TTL was equal to lockout TTL, so the counter reset every cycle and "escalating-lockout" was dead code. Decoupled to a fixed 48h counter window.
  • RT-M2 Per-admin token role enforcement: ACTION_PERMISSIONS map + role-rank gate now applied to every admin action — previously a readonly token could call mutating handlers.
  • RT-M3 RapidAPI proxy-secret bucket-hijack: SecurityAuditor now timing-safe-validates X-RapidAPI-Proxy-Secret instead of presence-checking it. Spoofed proxy-secrets fall through to direct-IP bucketing, so attackers pollute their own bucket.
  • RT-M4 SecurityAuditor cross-instance Redis-warmed blocklist: key-shape mismatch (raw IP vs hashed IP) made the warm blocklist non-functional. Aligned to hashed-IP throughout.

Plus three hygiene Lows from the Twelfth E2E review:

  • L48 RapidAPI XFF bucket pollution: failed-auth IP extraction now uses .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.
  • L49 monitor-push cron's write-only monitor:snapshot:* Redis writes removed (~864 commands/day saved against Upstash quota; no consumer).
  • L50 /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.
  • L51 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.
  • L7 load test added to CI as a 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.
  • L-4 rel="noopener noreferrer" added to 3 target="_blank" links in api/badge.js, api/docs.js, api/playground.js.

GDPR / coherence

  • CO-3 GDPR erasure flow now deletes the upstream Stripe customer record (via 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.
  • CO-4 Stripe webhook idempotency TTL bumped 48h → 72h on three handlers (subscription.deleted, subscription.updated, invoice.payment_failed) — covers Stripe's documented 3-day retry window.
  • CO-5 Production refuses to start without 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.
  • CO-6 webhooks:all set self-cleans orphaned IDs during dispatch + erasure (mirrors the existing keys:active:paid cron pattern).

Scientific accuracy (sci-verify follow-ups)

  • SCI-CLF-04 Glyphosate (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.
  • SCI-CLF-05 Glyphosate IPA salt (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.
  • SCI-SCH-01 Molecular weight bulk-coerced from string ("142.04") to number (142.04) across 459 compounds (identity + properties.chemical_properties). Polymer sentinel "variable (polymer)" preserved for chitin (the lone legitimate non-numeric MW).

Claims hygiene (defamation / factuality calibration)

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:

  • CI-2 12 new source records inline-cited across 10 mens-rea passages: Reuters 2018 (J&J talc), DuPont 1961 hepatomegaly findings, 3M late-1970s biopersistence studies (per EPA 2018 PFOA Stewardship Program), EPA 2009 CERCLA declaration (Libby asbestos), Schneider 1999 PI investigative series, Lilienfeld 1991 AJPH (lead paint), Markowitz & Rosner 2002, Cousins et al. 2022 EST (PFAS planetary boundary), US House Subcommittee 2021 staff report (baby food heavy metals), UK HSE colophony asthma surveillance.
  • CI-5 "15+ agencies" claim tightened to "15+ primary agencies-of-record" with a definitional footnote explaining the ~200-agency-string count reflects EPA CompTox sub-program designations as separate attribution lineages.
  • CI-6 Three industry-narrative passages softened with inline citations (NMP "never reformulated" → "most major American non-acetone formulations still contain NMP"; vinyl chloride mens-rea inline-cites Sass et al. 2005 EHP; PFAS reach claim inline-cites Cousins et al. 2022 EST).

Spec / SDK / pricing

  • No endpoint changes.
  • No request schema changes.
  • Response schema additions only (methodology_note, brand_examples_disclaimer — both optional fields; existing SDK consumers unaffected).
  • No pricing or rate-limit changes.

Operator notes (not customer-facing)

  • Pre-flight required: WEBHOOK_ENCRYPT_KEY must be set on Vercel production env before vercel deploy --prod. Operator confirmed 2026-05-08.
  • Rollback plan: standard Vercel vercel promote <previous_deploy_id> --prod (≤30 sec restore). Triggers documented in tracking/DEPLOY-PLAN-2026-05-12.md.
  • Post-deploy validation: Speed Insights dashboard + business-hours Stripe traffic for CO-4 idempotency window + /api/health?check=integrity data-delta spot-checks. 4-hour monitoring window per deploy plan.
  • Test suite: 24,528 / 24,530 PASS / 0 fail / 2 skipped. QA gate: 23/23 PASS.
  • Coherence: 1,886c / 959m / 1,287p across api + safety mirror + 5 sources.
  • Phases shipped: 54 (PET tier expansion), 55 (WER tier expansion), 57 (claims hygiene CI-2..8), 58 (hygiene lows CO-4/CO-6/L-4), 59 (compound mini-batch 1), 60 (CO-5 strict gate), 62 (compound mini-batch 2), 63 (PC-4 OpenAPI extraction), 64 (SCI-CLF-04 glyphosate CalEPA), 65 (SCI-CLF-05 IPA salt genotox), 66 (SCI-SCH-01 MW coercion), 67 (SCI-ENG-01 engine signal-loss), 68 (hygiene lows II L48/L49/L51), 70 (hygiene low III — L50 health-integrity async + cache), 71 (hygiene low IV — L7 manual load-test in CI via workflow_dispatch), 72 (tracking doc-sync close-out + DQ-I1 disposition — doc-only), 73 (SDK v0.8.0→v0.9.0 refresh + cadence codification — closes 166-line type drift, codifies SDK-tracks-API-minor/major rule), 74 (RapidAPI marketplace coherence + CI staleness gate fix — unbroke export-rapidapi.js, untracked rapidapi-openapi.json from .gitignore, generated Tuesday upload playbook), 75 (HQ-family coherence audit + new 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.
RELEASED

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.

What changed

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):

  • Erucamide (hq-c-org-001395): 244 → 78 (-240 Triamcinolone Acetonide synonyms removed including "Azmacort", "Aristocort", "Kenalog-A"; +74 canonical Erucamide synonyms added)
  • Phytic acid (hq-c-org-001422): 103 → 146 (-98 Procarbazine synonyms; +141 canonical Phytate / IP6 / phytic-acid synonyms)
  • R-32 refrigerant (hq-c-org-001428): 95 → 50 (-91 Apomorphine synonyms; +46 canonical Difluoromethane / HFC-32 synonyms)
  • Plutonium-239 (hq-c-ino-000213): 54 → 27 (-54 disodium arsenate heptahydrate synonyms — the wrong CID 61460's molecule; +27 canonical Pu-239 isotope synonyms)
  • Hexetidine (hq-c-org-001414): 31 → 159 (-26 Isoxazole synonyms; +154 canonical antiseptic synonyms)

Customer impact

/api/search exact-match no longer returns the wrong compound for old-molecule queries:

  • "Triamcinolone Acetonide" no longer returns Erucamide
  • "Procarbazine" no longer returns Phytic acid
  • "Apomorphine" no longer returns R-32
  • "Disodium arsenate heptahydrate" no longer returns Plutonium-239

The wrong-search-hit risk class is now closed for these 60 records.

Tooling

  • 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.
  • 4 of the session-64 SCI-DAT-01 wrong-CID-recovered compounds (Saflufenacil, Clethodim, Mecoprop, Chlortetracycline) had small curated synonym lists with no pollution and were excluded. Pu-239 was the only session-64 compound with synonym pollution; folded into Phase 4 TARGETS.
  • Tests 23,778 / 0 fail. safety.holisticquality.io mirror synced across all 60 touched files.
RELEASED

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).

Methodology

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).

Notable corrections

  • Erucamide (hq-c-org-001395): CID 6436 (Triamcinolone Acetonide — corticosteroid drug) → 5365371 (Erucamide — fatty amide)
  • Phytic acid (hq-c-org-001422): CID 4915 (Procarbazine — anticancer drug) → 890 (Phytate)
  • R-32 refrigerant (hq-c-org-001428): CID 6005 (Apomorphine — alkaloid drug) → 6345 (Difluoromethane)
  • Tributyltin (hq-c-org-001959): CID 11129 (bicyclic terpene) → 5948 (Tributylstannane)
  • Hexetidine + Diiodomethyl p-tolyl sulfone (hq-c-org-001414, 001416): both shared CID 9254 (Isoxazole) — proof of bulk-import wrong-CID collision; corrected to 3607 and 62738 respectively
  • Paclobutrazol (hq-c-org-002069): CID 40326 (Permethrin — pyrethroid pesticide) → 73671 (Paclobutrazol — triazole growth regulator)
  • Ortho-phthalaldehyde (hq-c-org-002021): CID 12498 (unrelated guanidine) → 4807 (Phthalaldehyde / Cidex OPA)

Tooling

  • 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 output
  • tracking/SCI-DAT-02-NAME-LOOKUP-SWEEP.md — Phase 2 results section

safety.holisticquality.io mirror synced across all 59 touched files. Triage post-fix: HIGH=0 / MEDIUM=0 / CLEAN=1867. Tests 23,778 / 0 fail.

RELEASED

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.

What changed

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:

  • 8 compounds confirmed with correct crossrefs.pubchem_cididentity.* 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).
  • 5 compounds had 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).
  • 1 compound escalated for chemist review — Zilpaterol (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.

Triage detail

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.

Operator notes (not customer-facing)

  • New script: 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).
  • Spec version bump 1.3.0 → 1.3.1; no endpoint, schema, or pricing changes. Listed at /api/release-notes and /api/openapi.
RELEASED

Data-layer corrections following the 2026-05-05 sci-verify audit.

hq-c-ino-000006 Methylmercury — IARC monograph reference + cancer site

Two 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.

Sci-verify P0 status

  • 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.

SCI-DAT-01 (chemical identity contamination) triage + Gap 1 / partial-class fixes

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:

  • HIGH confidence (InChI-key mismatch): 0
  • MEDIUM confidence (mass/MW Δ > 10%): 3 — Plutonium-239, Ptaquiloside, Maitotoxin (the latter two have wrong exact_mass values from a wrong-CID merge; molecular_weight is correct)
  • MEDIUM confidence (SMILES mismatch with no InChI arbitration): 13 — clustered in hq-c-org-00207[4-9]/0208[0-1], suggesting a single batch-import gap
  • CLEAN: 1,860 (99.0%)

Triage 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:

  • Gap 1 (synonym pollution on the 4 already-stamped compounds): stripped 34 wrong-CID synonyms from Glyphosate IPA salt (deoxyribose pollution), 17 from K salt (xanthene-isoquinoline-dione), 16 from ammonium salt (horhammericine + indole-alkaloid IUPAC variants), and 2 from Ceftiofur (chloroaniline ester remnants). Then backfilled 10 canonical Ceftiofur synonyms (Ceftiofur, Ceftiofur sodium/hydrochloride/free acid/HCl, Naxcel, Excenel, Excede, U-64279E, CAS 80370-57-6) — its synonym list was reduced to 2 wrong entries by the prior strip.
  • Partial-contamination class: Ptaquiloside and Maitotoxin had 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.

CO-1: Stripe subscription lifecycle handlers (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):

  • customer.subscription.updated — fires on tier change, mid-cycle proration, cancel-at-period-end. Re-derives tier from the new price ID via 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.
  • invoice.payment_failed — fires when a renewal payment fails. Sets 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.)

SEARCH-Q1: Canonical-abbreviation alias map for /api/search

Search-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.

  • New module api/_lib/search-aliases.js exports a frozen SEARCH_ALIASES map (~50 curated entries) plus resolveAlias(query) helper.
  • Coverage spans bisphenols (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).
  • Conservative inclusion rule: only abbreviations that unambiguously resolve to a single canonical compound. Tokens like 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.
  • Suppressed when type=product/type=material filters are present — alias targets are all compounds, so respecting the filter keeps semantics intuitive.
  • Response 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.
  • 14 new endpoint tests + 8 alias-module unit tests in tests/search-handler.test.js. 37/37 pass.

M-2: VERIFY_SECRET bypass observability (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.
  • Surfaced in /api/health?check=monitor (admin-only) for ad-hoc inspection.
  • 11 new unit tests in 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).

RELEASED

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.

Public tier now has batch access

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.

Per-tier batch caps raised

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.

What this means for subscribers

  • RapidAPI Basic subscribers: /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.

  • RapidAPI Pro subscribers: batch cap raised from an effective 20

to 50 compounds per request, matching the Hub's Features panel.

  • RapidAPI Ultra subscribers: batch cap raised from 50 to 100

compounds per request, matching the Hub's Features panel.

  • Direct API subscribers: the same caps apply at the internal tier

level — 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).

Synthesis cap unchanged

BATCH_SYNTHESIS_MAX = 10 (the cap when synthesize=true is requested) is unchanged. Synthesis still requires a Developer key or higher.

Files touched

  • api/_lib/constants.jsBATCH_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.jsinfo.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.

Action for RapidAPI subscribers

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.

RELEASED

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.

Marketplace listing improvements (RapidAPI)

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:

  • Sample response payload now visible pre-subscribe. A truncated

Glyphosate record (compound 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.

  • "Why this beats scraping agency sites yourself." Three concrete

differentiators added: cross-agency identifier resolution (CAS/InChI Key/PubChem CID/EPA DTXSID/ECHA EC into one record), life-stage and exposure-route normalization (single agency endpoint → 19 ALETHEIA contexts), and commercial-reuse legal clearance.

  • Source-agency transparency. Per-agency one-liners describe

what is pulled from each: IARC carcinogen Groups (1, 2A, 2B, 3, 4) with monograph year; EPA pesticide registration status, IRIS reference doses, CompTox DTXSID; EU CLP Annex VI harmonized classifications, hazard pictograms, H-statements; ECHA REACH dossiers, SVHC candidate list, Annex XVII restriction list; NTP Report on Carcinogens listings; CalEPA / OEHHA Prop 65 chemical list with NSRLs / MADLs; FDA GRAS status, food-additive determinations, CFSAN advisories; Health Canada / ECCC DSL/NDSL status; WHO / IPCS international concise chemical assessments; OSHA / NIOSH PELs, RELs, and IDLHs.

  • Tier table now includes prices and "Best for" guidance. Basic

framed as prototyping / evaluation, Pro as production with batch, Ultra as bulk compliance pipelines, Enterprise as custom off-marketplace.

  • Concrete freshness pointer. Replaced vague "published cadence"

language with an explicit pointer to GET /api/freshness for the live snapshot timestamp and per-source staleness buckets.

  • Latency commitment. "Typically <200ms on cached lookups" added

alongside the existing 99.9% uptime target and /api/health pointer.

  • Embeddable SVG safety badges promoted to a dedicated section

with an inline <img src="…/api/badge/{id}.svg"> example. Previously buried as a feature bullet; the badge endpoint itself is unchanged.

Distribution-surface cross-references

The marketplace listing now points subscribers and evaluators to:

  • The live playground at

/api/playground (read-only queries without an API key — same surface as the 2026-04-26 fix).

  • The three Postman collections published 2026-04-26 (Start Here, API

Reference, Use Cases) under the Holistic Quality team workspace.

What did not change

  • No endpoint paths, parameters, or response schemas were changed.
  • No pricing or rate-limit changes (Basic 500/day, Pro 10,000/day at

$29/mo, Ultra 100,000/day at $99/mo, Enterprise direct-sale).

  • No breaking changes to any SDK, webhook payload, or data export

format. SDK versions remain at 0.8.0.

  • The direct-API surface at aletheia.holisticquality.io/docs is

unchanged (its info.description was not touched; only the RapidAPI-specific overwrite in scripts/export-rapidapi.js was rewritten).

Action for RapidAPI subscribers

None. The updated listing copy is now live at <https://rapidapi.com/aletheia-safety-intelligence> as version v2.2 in the RapidAPI Hub. Existing subscriptions, API keys, and quotas are unchanged.

RELEASED

Availability

  • ALETHEIA is now listed on the Postman API Network as a public workspace

at <https://www.postman.com/leviprobey-23916/workspace/holistic-quality> (under the Holistic Quality team profile). Three curated collections ship with the workspace: - Start Here — five-step onboarding to a real 200 OK in three clicks (caffeine by CAS number, no key required). - Use Cases — problem-shaped requests for the most common integration patterns (single-compound lookup, regulatory consensus, regulatory disagreement detection, material comparison, product safety profile composition). - API Reference — exhaustive resource-oriented reference for every public endpoint, auto-generated from the OpenAPI spec.

  • Workspace variables are pre-wired: paste your direct ALETHEIA key

into the api_key global (or rapidapi_key if you're a RapidAPI subscriber) and every request inherits authentication automatically.

Bug fixes

  • Playground name search resolves correctly again. The interactive

playground at /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.

RELEASED

Availability

  • ALETHEIA Safety Intelligence is now listed on RapidAPI at

<https://rapidapi.com/aletheia-safety-intelligence>.

  • Public plans: Basic (free, 500 req/day, 60/min burst),

Pro ($29/mo, 10,000 req/day, 300/min burst), Ultra ($99/mo, 100,000 req/day, 1,200/min burst).

  • Enterprise (unlimited daily, unlimited burst) remains direct-sale

via /api/enterprise-inquiry.

Security & reliability

  • Per-identity response cache. Cached responses are now keyed by

caller identity (API key or RapidAPI user) in addition to the request parameters. Previously, cached payloads could be returned across different callers sharing the same upstream IP (for example, two RapidAPI customers behind the same proxy). No action required; existing integrations continue to work unchanged.

  • Identity-aware rate limiting. Rate-limit counters for RapidAPI

traffic are now scoped to the RapidAPI user identifier (X-RapidAPI-User header) rather than the forwarded source IP. RapidAPI customers sharing proxy infrastructure no longer consume each other's quota.

  • Stricter input validation. The injection-detection ruleset was

hardened to reduce false negatives on crafted separator sequences. Well-formed requests are unaffected.

  • Fail-closed rate limiting. A Redis error during rate-limit

checks now denies the request rather than passing through — this matches the behavior of other auth failures and prevents unbounded throughput during a storage incident.

Documentation

  • The OpenAPI spec now documents the full set of rate-limit response

headers (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.

Known RapidAPI gateway note

  • POST /api/compounds/batch via the RapidAPI gateway is

temporarily unavailable at launch. The OpenAPI spec declares both 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.

HISTORICAL

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.