# IronScout Data Services (IDS) — Changelog

This file logs versioning changes to the IDS contract per ADR-032 §5
(versioning policy). It follows the conventions of "Keep a Changelog"
adapted for a public integration contract:

- Breaking changes require a major version bump and a 90-day
  deprecation window.
- Additive changes (new optional fields, new endpoints, new schemas)
  ship within the same major version and appear here as minor or
  patch entries.
- Deprecations are announced here at the same time as their
  replacement and remain available for the deprecation window.

The IDS contract is at https://www.ironscout.ai/.well-known/ids.md.
The JSON Schemas are at https://www.ironscout.ai/ids/schemas/.

---

## [Unreleased]

Track changes here between releases. Move entries to the next versioned
section when that version ships.

- **Additive:** `market_snapshot_artifact_v1` gained an optional
  `insufficientSince` field (ISO timestamp or null). Backs an indexability
  grace period (ADR-028 hysteresis) so a caliber-type page/sitemap entry
  isn't noindexed/omitted the same build its sample count dips below the
  floor — only after it's stayed insufficient for a few days. Absent on
  pre-hysteresis artifacts; consumers should treat a missing field the same
  as `null`.

### Ratification fixes (pre-launch)

These came out of the line-by-line reviews against shipped reality
mandated by ADR-032 §2 and tracked in the v1.0.0 launch checklist.
Two passes; second-pass items use A/B prefixes (C-prefixed cosmetic
items from the second review remain open and tracked in the PR).

First pass (R1-R4):

- **R1.** Error responses on shipped IDS endpoints now emit the
  `error_v1` shape (`schemaVersion`, `requestId`, `errorCode`,
  `message`, optional `details`). Previously they returned
  `{ error: 'string' }` which contradicted §9.
- **R2.** §4.3 `computedAt` description clarified: the shipped meaning
  is "when the underlying data was computed," not "when the response
  payload was generated." For cached responses these differ by hours.
  Doc updated to match shipped semantics.
- **R3.** §5 gained a v1.0.0 implementation-status table at the top.
  `/search`, `/products`, `/retailers` are described in §5.2–§5.4 but
  aren't yet IDS-shaped; the table is the source of truth for what
  agents can rely on today. Prevents agents from writing code against
  shapes the implementation doesn't honor.
- **R4.** §5.1 now documents the static artifact's `recentLow` field
  (a 7-day sub-window minimum, distinct from `pricePerRound.min`).

Second pass (A1-A4, B1, B7):

- **A1-A4.** §4.3 was wholesale-rewritten to scope its "MUST" requirements
  to API responses with an explicit static-artifact exception. The
  prior universal phrasing contradicted §5.1 in four ways: artifacts
  have no `requestId`, may have `computedAt: null` for UNAVAILABLE
  placeholders, emit `methodology` as an object (not string), and
  omit `methodologyMeta`. §4.3 now matches what each surface actually
  emits.
- **B1.** §1.1 percentile example updated from `min/median/p10/p90/max`
  (deciles) to `min`/`p25`/`median`/`p75`/`max` (quartiles) to match
  shipped reality. The IAIS draft advertised deciles the snapshot
  never computed.
- **B7.** §5 status-callout `/out` row now states that `/out` is live at
  `app.ironscout.ai/out` (HMAC-signed redirect). The prior text
  ("exists internally") implied not-public, which was wrong. What's
  still pending is the IDS-named `outUrl` field on search responses.

### Known divergences (track and converge)

The static snapshot artifacts and the API endpoint are both first-class
IDS surfaces (per ADR-032 §3) but ship under different schemas at v1.
Each documented divergence is a candidate for convergence in v1.x or
a major bump in v2.

**Surface-level split:**
- API endpoint (`/api/market-snapshots/calibers[/:caliber]`) →
  `caliber_market_snapshot_v1` and `caliber_market_snapshot_list_v1`
- Static artifact (`/market-snapshots/{windowDays}d/{caliber}.json`) →
  `market_snapshot_artifact_v1`
- Static index (`/market-snapshots/{windowDays}d/index.json`) →
  `market_snapshot_index_v1`

**Specific divergences between the API endpoint and the static artifact:**

1. **Caliber identifier field name and value:**
   - API: `caliber` field, canonical CaliberValue (`"9mm"`, `".45 ACP"`)
   - Artifact: `caliberSlug` field, URL slug (`"9mm"`, `"45-acp"`)
2. **Percentile layout:**
   - API: flat (`median`, `p25`, `p75`, `min`, `max` at the row root)
   - Artifact: nested under `pricePerRound`
3. **Counts layout:**
   - API: flat (`sampleCount`, `daysWithData`, `productCount`, `retailerCount` at the row root)
   - Artifact: nested under `counts`
4. **methodology field shape:**
   - API: a human-readable string (with structured fields in `methodologyMeta`)
   - Artifact: an object (`{measurementTechnique, notes}`)
5. **schemaVersion VALUE format:**
   - API: snake_case-with-version (`caliber_market_snapshot_v1`)
   - Artifact: kebab-with-slash (`market-snapshot/v1`)
6. **Citation envelope:**
   - API: includes `schemaVersion`, `requestId` at the response root
   - Artifact: includes `schemaVersion` only; no `requestId` (static file)
7. **Extra fields:**
   - Artifact has `recentLow` (7-day sub-window min) that the API doesn't surface.

These divergences exist because the API endpoint (ADR-025) was built
after the IAIS draft was authored, and the static artifact pipeline
predates IDS formalization. The shipping reality is what's promised at
v1.0.0; v1.1 may converge specific fields where it's non-breaking.

## [1.0.0] — Unreleased

Initial public release of the IronScout Data Services v1 contract.

### Endpoints

- `GET /api/market-snapshots/calibers` — list of caliber market snapshots
  (schema: `caliber_market_snapshot_list_v1`)
- `GET /api/market-snapshots/calibers/:caliber` — single caliber market
  snapshot (schema: `caliber_market_snapshot_v1`)
- Static snapshot artifacts: `/market-snapshots/{windowDays}d/*.json`

### Schemas

- `caliber_market_snapshot_v1` (API endpoint, single)
- `caliber_market_snapshot_list_v1` (API endpoint, list)
- `market_snapshot_artifact_v1` (static artifact per caliber)
- `market_snapshot_index_v1` (static artifact index)
- `error_v1` (error responses from shipped IDS endpoints)
- (Additional schemas land as their endpoints ship: `search_results_v1`,
  `product_detail_v1`, `retailer_directory_v1`.)

### Citation fields required on API responses

- `schemaVersion`
- `requestId`
- `computedAt` (and `windowStart`/`windowEnd` for windowed computations)
- `methodology` (human-readable string)
- `methodologyMeta` object (`methodologyVersion`, `computationVersion`, `methodologyUrl`)

Static artifacts under `/market-snapshots/{windowDays}d/*.json` follow
`market_snapshot_artifact_v1` instead — see Known Divergences for the
specific deltas (no `requestId`, `methodology` as object, possibly null
`computedAt` for `UNAVAILABLE` placeholders).

### Hard exclusions (per ADR-032 §2 and ADR-006)

- No recommendations, verdicts, deal scores, or optimality claims
- No price predictions or implied certainty
- No surfacing of internal ranking signals
- Fail-closed on data ambiguity (return `unavailable` with reason codes)

### Notes (apply to API responses unless noted)

- JSON key casing on API responses is camelCase (amendment to the
  originating IAIS draft, which used snake_case). The static artifact's
  `schemaVersion` VALUE uses kebab-with-slash (`market-snapshot/v1`)
  for legacy back-compat; field NAMES on the artifact are also
  camelCase. See Known Divergences for the full list.
- On API responses, the `methodology` field is permanently present as
  both a human-readable string and the structured `methodologyMeta`
  object; neither is deprecated in v1.x. The static artifact emits
  `methodology` as a structured object (`{measurementTechnique, notes}`)
  and does not carry `methodologyMeta`. Each surface is bound by its
  own schema; both are first-class IDS components per ADR-032 §3.
