{
  "source": "../api.aletheia.holisticquality.io/CHANGELOG.md",
  "syncedAt": "2026-05-15T13:16:48.662Z",
  "entryCount": 13,
  "preamble": "# ALETHEIA API Changelog\n\nCustomer-facing release notes for the ALETHEIA Safety Intelligence API\n(`api.aletheia.holisticquality.io`).\n\nFor the data-layer change feed (new compounds, updated regulatory\nclassifications, sub-processor changes) see\n[`/api/changelog`](https://api.aletheia.holisticquality.io/api/changelog).\n\nThe machine-readable form of this file is served at\n[`/api/release-notes`](https://api.aletheia.holisticquality.io/api/release-notes).\n\n---",
  "entries": [
    {
      "date": null,
      "label": "PC-2 / SCALE-2 Phase 3: tiered cache on /api/compound/{id} (unwatched compounds now edge-cacheable)",
      "raw": "Unreleased — PC-2 / SCALE-2 Phase 3: tiered cache on /api/compound/{id} (unwatched compounds now edge-cacheable)",
      "status": "unreleased",
      "body": "> 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.\n\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\nCloses 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."
    },
    {
      "date": "2026-05-12",
      "label": "RAPIDAPI: POST /api/compounds/batch endpoint card now live on Hub",
      "raw": "2026-05-12 — RAPIDAPI: POST /api/compounds/batch endpoint card now live on Hub",
      "status": "released",
      "body": "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."
    },
    {
      "date": "2026-05-12",
      "label": "v1.4.1: regression fix — methodology_note surfaces on /api/product/{id}",
      "raw": "2026-05-12 — v1.4.1: regression fix — methodology_note surfaces on /api/product/{id}",
      "status": "released",
      "body": "**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.\n\n**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).\n\n**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.\n\n**No data changes.** No schema changes. No SDK schema changes — v0.9.0 typed the field as `Optional[str]`, which is still correct."
    },
    {
      "date": "2026-05-12",
      "label": "v1.4.0: data expansion + audit-closure batch (23 phases shipped + 1 doc-sync, 19+ findings closed including 1 RED resolved)",
      "raw": "2026-05-12 — v1.4.0: data expansion + audit-closure batch (23 phases shipped + 1 doc-sync, 19+ findings closed including 1 RED resolved)",
      "status": "released",
      "body": "> 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.\n\n> **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.\n\n### Customer-visible behavior changes\n\n#### New API response fields\n\n- **`/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**.\n- **`/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**.\n- **`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.\n\n#### Data deltas\n\n- **Compound corpus:** 1,879 → **1,886** (+7). New compounds queryable via `/api/compound/{id}`:\n  - `hq-c-ino-000223` Sodium thiosulfate (aquarium dechlor + cyanide-antidote component)\n  - `hq-c-org-002116` Methylene blue (aquarium therapeutic + methemoglobinemia antidote; flags G6PD-deficient + serotonergic-drug-MAOI populations)\n  - `hq-c-org-002117` Terpinen-4-ol (tea tree principal terpene; flags cats EXTREME — feline glucuronidation deficit)\n  - `hq-c-ino-000224` Copper sulfate pentahydrate (aquarium ich treatment + Bordeaux mixture vineyard fungicide; flags Wilson-disease patients + scaleless fish + aquarium invertebrates)\n  - `hq-c-org-002118` Polyvinylidene fluoride PVDF (RO/UF membrane housing polymer + Li-ion battery binder)\n  - `hq-c-org-002119` Pyrantel pamoate (pet/human dewormer salt; LD50 >5000 mg/kg rat oral due to non-absorbance)\n  - `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)\n\n- **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.\n\n#### Synthesis engine: regulatory-coverage expansion\n\nThe 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:\n\n- **EPA CompTox `IARC` and `EPA OPP` aliases:** added `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).\n- **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.\n- **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.\n\n**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**.\n\n#### Performance: cold-start improvement on `/api/openapi`\n\nThe 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:\n\n- **Spec source-of-truth:** `scripts/build-openapi.js` (3,624 lines, lives outside `api/` so the runtime handler doesn't bundle it).\n- **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.\n- **Build hook:** `vercel-build` runs `node scripts/export-openapi.js` so `openapi.json` is fresh before any handler is invoked.\n\nExpected first-byte latency improvement on cold-start `/api/openapi` requests; will be validated post-deploy via Speed Insights. Closes perf-cost finding **PC-4**.\n\n### Customer-invisible internal improvements\n\n#### Security hardening (red-team Mediums + Hygiene Lows)\n\nFour 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:\n\n- **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.\n- **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.\n- **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.\n- **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.\n\nPlus three hygiene Lows from the Twelfth E2E review:\n\n- **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`.\n- **L49** monitor-push cron's write-only `monitor:snapshot:*` Redis writes removed (~864 commands/day saved against Upstash quota; no consumer).\n- **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.\n- **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.\n- **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.\n- **L-4** `rel=\"noopener noreferrer\"` added to 3 `target=\"_blank\"` links in `api/badge.js`, `api/docs.js`, `api/playground.js`.\n\n#### GDPR / coherence\n\n- **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**.\n- **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.\n- **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.\n- **CO-6** `webhooks:all` set self-cleans orphaned IDs during dispatch + erasure (mirrors the existing `keys:active:paid` cron pattern).\n\n#### Scientific accuracy (sci-verify follow-ups)\n\n- **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.\n- **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.\n- **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).\n\n#### Claims hygiene (defamation / factuality calibration)\n\nThe 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:\n\n- **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.\n- **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.\n- **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).\n\n### Spec / SDK / pricing\n\n- No endpoint changes.\n- No request schema changes.\n- Response schema additions only (`methodology_note`, `brand_examples_disclaimer` — both optional fields; existing SDK consumers unaffected).\n- No pricing or rate-limit changes.\n\n### Operator notes (not customer-facing)\n\n- **Pre-flight required:** `WEBHOOK_ENCRYPT_KEY` must be set on Vercel production env before `vercel deploy --prod`. Operator confirmed 2026-05-08.\n- **Rollback plan:** standard Vercel `vercel promote <previous_deploy_id> --prod` (≤30 sec restore). Triggers documented in `tracking/DEPLOY-PLAN-2026-05-12.md`.\n- **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.\n- **Test suite:** 24,528 / 24,530 PASS / 0 fail / 2 skipped. QA gate: 23/23 PASS.\n- **Coherence:** 1,886c / 959m / 1,287p across api + safety mirror + 5 sources.\n- **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."
    },
    {
      "date": "2026-05-07",
      "label": "SCI-DAT-02 Phase 4: synonym decontamination across 60 records (data-only, lands at next major deploy)",
      "raw": "2026-05-07 — SCI-DAT-02 Phase 4: synonym decontamination across 60 records (data-only, lands at next major deploy)",
      "status": "released",
      "body": "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.\n\n### What changed\n\nFor 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.**\n\nNotable per-compound results (before → after):\n\n- **Erucamide** (`hq-c-org-001395`): 244 → 78 (-240 Triamcinolone Acetonide synonyms removed including \"Azmacort\", \"Aristocort\", \"Kenalog-A\"; +74 canonical Erucamide synonyms added)\n- **Phytic acid** (`hq-c-org-001422`): 103 → 146 (-98 Procarbazine synonyms; +141 canonical Phytate / IP6 / phytic-acid synonyms)\n- **R-32 refrigerant** (`hq-c-org-001428`): 95 → 50 (-91 Apomorphine synonyms; +46 canonical Difluoromethane / HFC-32 synonyms)\n- **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)\n- **Hexetidine** (`hq-c-org-001414`): 31 → 159 (-26 Isoxazole synonyms; +154 canonical antiseptic synonyms)\n\n### Customer impact\n\n`/api/search` exact-match no longer returns the wrong compound for old-molecule queries:\n- \"Triamcinolone Acetonide\" no longer returns Erucamide\n- \"Procarbazine\" no longer returns Phytic acid\n- \"Apomorphine\" no longer returns R-32\n- \"Disodium arsenate heptahydrate\" no longer returns Plutonium-239\n\nThe wrong-search-hit risk class is now closed for these 60 records.\n\n### Tooling\n\n- `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.\n- 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.\n- Tests 23,778 / 0 fail. `safety.holisticquality.io` mirror synced across all 60 touched files."
    },
    {
      "date": "2026-05-07",
      "label": "SCI-DAT-02 Phase 2: 59 wrong-CID corrections via InChIKey-prefix triage (data-only, lands at next major deploy)",
      "raw": "2026-05-07 — SCI-DAT-02 Phase 2: 59 wrong-CID corrections via InChIKey-prefix triage (data-only, lands at next major deploy)",
      "status": "released",
      "body": "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).\n\n### Methodology\n\nThe 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).\n\n### Notable corrections\n\n- **Erucamide** (`hq-c-org-001395`): CID 6436 (Triamcinolone Acetonide — corticosteroid drug) → 5365371 (Erucamide — fatty amide)\n- **Phytic acid** (`hq-c-org-001422`): CID 4915 (Procarbazine — anticancer drug) → 890 (Phytate)\n- **R-32 refrigerant** (`hq-c-org-001428`): CID 6005 (Apomorphine — alkaloid drug) → 6345 (Difluoromethane)\n- **Tributyltin** (`hq-c-org-001959`): CID 11129 (bicyclic terpene) → 5948 (Tributylstannane)\n- **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\n- **Paclobutrazol** (`hq-c-org-002069`): CID 40326 (Permethrin — pyrethroid pesticide) → 73671 (Paclobutrazol — triazole growth regulator)\n- **Ortho-phthalaldehyde** (`hq-c-org-002021`): CID 12498 (unrelated guanidine) → 4807 (Phthalaldehyde / Cidex OPA)\n\n### Tooling\n\n- `scripts/triage-sci-dat-02-phase2.py` — InChIKey-prefix triage (read-only, ~45s wall, 178 PubChem fetches)\n- `scripts/apply-sci-dat-02-phase2.py` — full-field harmonization (mirror-aware, idempotent, `--force` for re-application)\n- `tracking/triage-sci-dat-02-phase2-20260507-1453.csv` — raw triage output\n- `tracking/SCI-DAT-02-NAME-LOOKUP-SWEEP.md` — Phase 2 results section\n\n`safety.holisticquality.io` mirror synced across all 59 touched files. Triage post-fix: HIGH=0 / MEDIUM=0 / CLEAN=1867. Tests 23,778 / 0 fail."
    },
    {
      "date": "2026-05-07",
      "label": "SCI-DAT-01 Gap 2 closeout: 13 compounds re-resolved against PubChem; Pu-239 wrong-CID fixed (v1.3.1)",
      "raw": "2026-05-07 — SCI-DAT-01 Gap 2 closeout: 13 compounds re-resolved against PubChem; Pu-239 wrong-CID fixed (v1.3.1)",
      "status": "released",
      "body": "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.\n\n### What changed\n\nA 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:\n\n- **8 compounds** confirmed with correct `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`).\n- **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).\n- **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.\n\nThe 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.\n\n`safety.holisticquality.io` mirror synced to match across all 14 touched files.\n\n### Triage detail\n\nPost-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.\n\n### Operator notes (not customer-facing)\n\n- 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).\n- Spec version bump 1.3.0 → 1.3.1; no endpoint, schema, or pricing changes. Listed at `/api/release-notes` and `/api/openapi`."
    },
    {
      "date": "2026-05-07",
      "label": "Scientific accuracy: methylmercury monograph + cancer-site correction; SCI-DAT-01 triage closed",
      "raw": "2026-05-07 — Scientific accuracy: methylmercury monograph + cancer-site correction; SCI-DAT-01 triage closed",
      "status": "released",
      "body": "Data-layer corrections following the 2026-05-05 sci-verify audit.\n\n### `hq-c-ino-000006` Methylmercury — IARC monograph reference + cancer site\n\nTwo stale fields fixed in `safety.carcinogenicity`:\n\n- `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.\n- `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.\n\n`safety.holisticquality.io` mirror synced to match.\n\n### Sci-verify P0 status\n\n- ✅ `hq-c-ino-000001` Lead (Pb) — IARC Group 2A (Vol 87, 2006). Already corrected in a prior session; verified clean today.\n- ✅ `hq-c-ino-000006` Methylmercury — IARC Group 2B (Vol 58, 1993), renal cancer sites. Final two stale fields fixed today.\n- ✅ `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.\n\n### SCI-DAT-01 (chemical identity contamination) triage + Gap 1 / partial-class fixes\n\nThe 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.\n\nToday'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:\n\n- HIGH confidence (InChI-key mismatch): **0**\n- 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)\n- 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\n- CLEAN: 1,860 (99.0%)\n\nTriage 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.\n\n`scripts/fix-sci-dat-01-identity-contamination.py` extended and applied to close two further classes:\n\n- **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.\n- **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.\n\nAfter 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.\n\nNo 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.\n\n### CO-1: Stripe subscription lifecycle handlers (`api/stripe/webhook.js`)\n\nThe 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):\n\n- **`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.\n- **`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`.\n\nCloses 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.\n\n11 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.\n\n⚠️ **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.)*\n\n### SEARCH-Q1: Canonical-abbreviation alias map for `/api/search`\n\nSearch-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.\n\n- New module `api/_lib/search-aliases.js` exports a frozen `SEARCH_ALIASES` map (~50 curated entries) plus `resolveAlias(query)` helper.\n- 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`).\n- 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.\n- `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.\n- Suppressed when `type=product`/`type=material` filters are present — alias targets are all compounds, so respecting the filter keeps semantics intuitive.\n- 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.\n- 14 new endpoint tests + 8 alias-module unit tests in `tests/search-handler.test.js`. 37/37 pass.\n\n### M-2: VERIFY_SECRET bypass observability (`api/_lib/monitor.js`, `api/cron/monitor-push.js`)\n\n`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.\n\n- `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.\n- `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.\n- Surfaced in `/api/health?check=monitor` (admin-only) for ad-hoc inspection.\n- 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.\n\nQuarterly rotation cadence documented in `tracking/RUNBOOK.md` §5.4. Closes the carry-forward MEDIUM from the 2026-05-04 cross-audit (M-2)."
    },
    {
      "date": "2026-04-27",
      "label": "Batch limits aligned with RapidAPI Hub plan configuration (v1.5.0)",
      "raw": "2026-04-27 — Batch limits aligned with RapidAPI Hub plan configuration (v1.5.0)",
      "status": "released",
      "body": "OpenAPI spec version bumped from `1.4.0` to `1.5.0`. Two functional\nchanges that align the API's enforced behavior with what the RapidAPI\nHub already advertises to subscribers.\n\n### Public tier now has batch access\n\n`GET /api/compounds/batch` and `POST /api/compounds/batch` were\npreviously gated to Developer tier and above (the public tier received\n`401 API key required`). Public tier now receives a successful batch\nresponse, capped at **10 compounds per request** to match the\n`max_batch_size: 10` value configured on the RapidAPI Basic plan in the\nHub Provider Dashboard. This was a Hub/code mismatch — Hub advertised\nbatch on Basic, code rejected it.\n\n### Per-tier batch caps raised\n\nThe internal `BATCH_LIMITS` table is now:\n\n| Internal tier | Batch cap | RapidAPI plan |\n|---------------|----------:|---------------|\n| `public`      | 10        | Basic ($0)    |\n| `developer`   | 50        | Pro ($29/mo)  |\n| `pro`         | 100       | Ultra ($99/mo)|\n| `enterprise`  | 100       | Enterprise (off-marketplace) |\n\nPrevious values (`developer: 20`, `pro: 50`, `enterprise: 100`) were\ninconsistent with the Hub's per-plan `max_batch_size` Feature\nconfiguration (10 / 50 / 100). Subscribers who composed batches at the\nHub-advertised limit could receive `400 Too many compounds` from the\nunderlying API. That is now resolved.\n\n### What this means for subscribers\n\n- **RapidAPI Basic subscribers:** `/api/compounds/batch` now returns a\n  successful response for up to 10 compounds per request. Previously,\n  Basic returned `401`. No code change required on your side — just\n  remove any client-side guard that skipped the batch call when no API\n  key was present.\n- **RapidAPI Pro subscribers:** batch cap raised from an effective 20\n  to **50** compounds per request, matching the Hub's Features panel.\n- **RapidAPI Ultra subscribers:** batch cap raised from 50 to **100**\n  compounds per request, matching the Hub's Features panel.\n- **Direct API subscribers:** the same caps apply at the internal tier\n  level — `developer` 50, `pro` 100, `enterprise` 100. The\n  `synthesize=true` option still requires Developer or higher (a\n  reasonable gate since it costs 2 credits per compound).\n\n### Synthesis cap unchanged\n\n`BATCH_SYNTHESIS_MAX = 10` (the cap when `synthesize=true` is requested)\nis unchanged. Synthesis still requires a Developer key or higher.\n\n### Files touched\n\n- `api/_lib/constants.js` — `BATCH_LIMITS` table updated, with a\n  comment block documenting the RapidAPI Hub mapping and a maintenance\n  note (\"keep in sync with Hub Provider Dashboard\").\n- `api/compounds/batch.js` — public-tier 401 short-circuit removed;\n  per-tier cap derivation now uses `req.auth?.tier || 'public'`;\n  synthesis-only 401 added for the `synthesize=true` + public-tier\n  combination; error response includes `upgrade_url` for public and\n  developer tiers.\n- `api/openapi.js` — `info.description` quickstart and the two\n  `/api/compounds/batch` operation descriptions (GET and POST) updated\n  to reflect the new caps. Spec version bumped to 1.5.0.\n- `scripts/export-rapidapi.js` — RapidAPI marketplace\n  `info.description` overwrite block updated (tier table now shows\n  Basic 10 / Pro 50 / Ultra 100, batch quickstart no longer says\n  \"Pro+\").\n- `rapidapi-listing-copy.md` — Plans table corrected, batch-section\n  framing changed from \"Pro+\" to \"open to all tiers\", second tier\n  table at the bottom of the doc also corrected.\n- `tests/batch-handler.test.js` — public-tier-401 test replaced with\n  two new tests covering the 10-compound cap on public; developer-tier\n  tests updated for the new 50-cap.\n- `tests/batch-enhanced.test.js` — developer cap test updated to 50,\n  pro cap test updated to 100.\n- `rapidapi-openapi.json` — regenerated.\n- `aletheia.holisticquality.io/marketplaces.html` — tier cards\n  rewritten to include Basic at the top of the ladder with its\n  10-compound cap; copy below the tier-cards section (Pro 50, Ultra\n  100, Enterprise 100) updated accordingly.\n\n### Action for RapidAPI subscribers\n\nNone. Spec v2.3 is now live in the RapidAPI Hub Studio (set Current).\nThe Definitions tab and the Plans tab Features panel both reflect the\nnew caps; code `BATCH_LIMITS` matches what the Hub advertises.\nExisting subscriptions, API keys, and quotas are unchanged."
    },
    {
      "date": "2026-04-27",
      "label": "Marketplace listing v2.2: source transparency, sample response, conversion polish",
      "raw": "2026-04-27 — Marketplace listing v2.2: source transparency, sample response, conversion polish",
      "status": "released",
      "body": "OpenAPI spec version bumped from `1.3.0` to `1.4.0`. The change is\nprimarily customer-discovery and onboarding surface — no breaking\nchanges to any endpoint or response shape.\n\n### Marketplace listing improvements (RapidAPI)\n\nThe RapidAPI listing description (auto-generated from the spec by\n`scripts/export-rapidapi.js`) was rewritten for conversion clarity\nbased on a marketplace audit. Specifically:\n\n- **Sample response payload now visible pre-subscribe.** A truncated\n  Glyphosate record (compound `hq-c-org-000001`, CAS `1071-83-6`,\n  context `human_adult`) is shown in the listing so developers can\n  evaluate response shape before signing up for a Basic key.\n- **\"Why this beats scraping agency sites yourself.\"** Three concrete\n  differentiators added: cross-agency identifier resolution\n  (CAS/InChI Key/PubChem CID/EPA DTXSID/ECHA EC into one record),\n  life-stage and exposure-route normalization (single agency endpoint\n  → 19 ALETHEIA contexts), and commercial-reuse legal clearance.\n- **Source-agency transparency.** Per-agency one-liners describe\n  what is pulled from each: IARC carcinogen Groups (1, 2A, 2B, 3, 4)\n  with monograph year; EPA pesticide registration status, IRIS\n  reference doses, CompTox DTXSID; EU CLP Annex VI harmonized\n  classifications, hazard pictograms, H-statements; ECHA REACH\n  dossiers, SVHC candidate list, Annex XVII restriction list; NTP\n  Report on Carcinogens listings; CalEPA / OEHHA Prop 65 chemical\n  list with NSRLs / MADLs; FDA GRAS status, food-additive\n  determinations, CFSAN advisories; Health Canada / ECCC DSL/NDSL\n  status; WHO / IPCS international concise chemical assessments;\n  OSHA / NIOSH PELs, RELs, and IDLHs.\n- **Tier table now includes prices and \"Best for\" guidance.** Basic\n  framed as prototyping / evaluation, Pro as production with batch,\n  Ultra as bulk compliance pipelines, Enterprise as custom\n  off-marketplace.\n- **Concrete freshness pointer.** Replaced vague \"published cadence\"\n  language with an explicit pointer to `GET /api/freshness` for the\n  live snapshot timestamp and per-source staleness buckets.\n- **Latency commitment.** \"Typically <200ms on cached lookups\" added\n  alongside the existing 99.9% uptime target and `/api/health`\n  pointer.\n- **Embeddable SVG safety badges** promoted to a dedicated section\n  with an inline `<img src=\"…/api/badge/{id}.svg\">` example. Previously\n  buried as a feature bullet; the badge endpoint itself is unchanged.\n\n### Distribution-surface cross-references\n\nThe marketplace listing now points subscribers and evaluators to:\n\n- The live playground at\n  [`/api/playground`](https://api.aletheia.holisticquality.io/api/playground)\n  (read-only queries without an API key — same surface as the\n  2026-04-26 fix).\n- The three Postman collections published 2026-04-26 (Start Here, API\n  Reference, Use Cases) under the\n  [Holistic Quality](https://www.postman.com/leviprobey-23916/workspace/holistic-quality)\n  team workspace.\n\n### What did not change\n\n- No endpoint paths, parameters, or response schemas were changed.\n- No pricing or rate-limit changes (Basic 500/day, Pro 10,000/day at\n  $29/mo, Ultra 100,000/day at $99/mo, Enterprise direct-sale).\n- No breaking changes to any SDK, webhook payload, or data export\n  format. SDK versions remain at `0.8.0`.\n- The direct-API surface at `aletheia.holisticquality.io/docs` is\n  unchanged (its `info.description` was not touched; only the\n  RapidAPI-specific overwrite in `scripts/export-rapidapi.js` was\n  rewritten).\n\n### Action for RapidAPI subscribers\n\nNone. The updated listing copy is now live at\n<https://rapidapi.com/aletheia-safety-intelligence> as version v2.2\nin the RapidAPI Hub. Existing subscriptions, API keys, and quotas\nare unchanged."
    },
    {
      "date": "2026-04-26",
      "label": "Postman API Network listing + playground name-search fix",
      "raw": "2026-04-26 — Postman API Network listing + playground name-search fix",
      "status": "released",
      "body": "### Availability\n\n- ALETHEIA is now listed on the Postman API Network as a public workspace\n  at <https://www.postman.com/leviprobey-23916/workspace/holistic-quality>\n  (under the Holistic Quality team profile). Three curated collections\n  ship with the workspace:\n  - **Start Here** — five-step onboarding to a real `200 OK` in three\n    clicks (caffeine by CAS number, no key required).\n  - **Use Cases** — problem-shaped requests for the most common\n    integration patterns (single-compound lookup, regulatory consensus,\n    regulatory disagreement detection, material comparison, product\n    safety profile composition).\n  - **API Reference** — exhaustive resource-oriented reference for\n    every public endpoint, auto-generated from the OpenAPI spec.\n- Workspace variables are pre-wired: paste your direct ALETHEIA key\n  into the `api_key` global (or `rapidapi_key` if you're a RapidAPI\n  subscriber) and every request inherits authentication automatically.\n\n### Bug fixes\n\n- **Playground name search resolves correctly again.** The interactive\n  playground at\n  [`/api/playground`](https://api.aletheia.holisticquality.io/api/playground)\n  was returning an \"ID must match format\" error for non-CAS queries\n  like `bpa` or `formaldehyde`. Two coupled bugs: the playground was\n  reading a stale field name from `/api/search` results (`hit.hq_id`\n  instead of `hit.id`), and was not filtering search hits to compound\n  entities (top hits for short queries are often material or product\n  records, which `/api/compound/{id}` does not resolve). Both fixed.\n  Recommended search inputs: a compound's full canonical name (e.g.\n  `Bisphenol A`, `Formaldehyde`) or its CAS number (e.g. `80-05-7`).\n  Common abbreviations like `bpa` or `lead` may not yet resolve to\n  the canonical compound — alias-mapping is on the backlog."
    },
    {
      "date": "2026-04-23",
      "label": "RapidAPI launch",
      "raw": "2026-04-23 — RapidAPI launch",
      "status": "released",
      "body": "### Availability\n\n- ALETHEIA Safety Intelligence is now listed on RapidAPI at\n  <https://rapidapi.com/aletheia-safety-intelligence>.\n- Public plans: **Basic** (free, 500 req/day, 60/min burst),\n  **Pro** ($29/mo, 10,000 req/day, 300/min burst),\n  **Ultra** ($99/mo, 100,000 req/day, 1,200/min burst).\n- **Enterprise** (unlimited daily, unlimited burst) remains direct-sale\n  via [`/api/enterprise-inquiry`](https://api.aletheia.holisticquality.io/api/enterprise-inquiry).\n\n### Security & reliability\n\n- **Per-identity response cache.** Cached responses are now keyed by\n  caller identity (API key or RapidAPI user) in addition to the request\n  parameters. Previously, cached payloads could be returned across\n  different callers sharing the same upstream IP (for example, two\n  RapidAPI customers behind the same proxy). No action required;\n  existing integrations continue to work unchanged.\n- **Identity-aware rate limiting.** Rate-limit counters for RapidAPI\n  traffic are now scoped to the RapidAPI user identifier\n  (`X-RapidAPI-User` header) rather than the forwarded source IP.\n  RapidAPI customers sharing proxy infrastructure no longer consume\n  each other's quota.\n- **Stricter input validation.** The injection-detection ruleset was\n  hardened to reduce false negatives on crafted separator sequences.\n  Well-formed requests are unaffected.\n- **Fail-closed rate limiting.** A Redis error during rate-limit\n  checks now denies the request rather than passing through — this\n  matches the behavior of other auth failures and prevents unbounded\n  throughput during a storage incident.\n\n### Documentation\n\n- The OpenAPI spec now documents the full set of rate-limit response\n  headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`,\n  `X-RateLimit-Reset`, `X-API-Tier`, `X-Usage-Warning`,\n  `X-Upgrade-URL`, `Retry-After`) and calls out the **enterprise tier\n  exception**: enterprise responses return `X-API-Tier: enterprise`\n  but do not emit the numeric rate-limit headers or `X-Upgrade-URL`\n  because the quota is unlimited.\n\n### Known RapidAPI gateway note\n\n- **`POST /api/compounds/batch` via the RapidAPI gateway** is\n  temporarily unavailable at launch. The OpenAPI spec declares both\n  `GET` and `POST` for batch lookup; RapidAPI's auto-import propagated\n  the `GET` endpoint only. `GET /api/compounds/batch?ids=...` works as\n  documented through the gateway (up to ~30 IDs fits comfortably in\n  the query string). The `POST` form remains fully available on the\n  direct-origin host `api.aletheia.holisticquality.io` and is used by\n  the official SDKs. A manual endpoint registration in the RapidAPI\n  Hub listing will restore the gateway `POST` path in a follow-up\n  release."
    },
    {
      "date": null,
      "label": "Earlier history",
      "raw": "Earlier history",
      "status": "historical",
      "body": "This changelog begins with the RapidAPI launch. Earlier infrastructure\nand dataset changes are tracked in the internal `tracking/` directory\nand were not published as customer-facing release notes."
    }
  ]
}
