diff --git a/.changeset/fix-compliance-cache-version-marker.md b/.changeset/fix-compliance-cache-version-marker.md new file mode 100644 index 000000000..b0f7d2c42 --- /dev/null +++ b/.changeset/fix-compliance-cache-version-marker.md @@ -0,0 +1,25 @@ +--- +"@adcp/sdk": patch +--- + +Fix compliance cache staleness: write tracked `compliance/CACHE_VERSION` marker + +`compliance/cache/` is gitignored (populated at sync/publish time) so +compliance-only spec bumps — new storyboard YAMLs, fixed `idempotency_key` +strings — never triggered a PR from `schema-sync.yml`. The diff guard only +watched `src/lib/types/`, `src/lib/agents/`, and `package.json`, making it +blind to spec changes that don't touch TypeScript schemas. + +Fixes the silent mismatch where `--version` advertised one AdCP version while +`compliance/cache/` contained storyboards from an older release — causing false +positives and false negatives in buyer-side storyboard validation. + +Changes: +- `scripts/sync-schemas.ts` now writes `compliance/CACHE_VERSION` (tracked in + git, one level above the gitignored `compliance/cache/`) after every tarball + sync; the file contains the `adcp_version` from the tarball's `index.json`. +- `schema-sync.yml` diff guard now includes `compliance/CACHE_VERSION` so + compliance-only spec bumps trigger an automated PR. +- `ci.yml` gains a post-sync validation step that asserts + `compliance/CACHE_VERSION` matches `ADCP_VERSION`, catching version drift + before it reaches npm. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce2dd4f0e..aa37c565b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,23 @@ jobs: - name: Sync schemas from AdCP run: npm run sync-schemas:all + - name: Validate compliance cache version matches spec + run: | + EXPECTED=$(cat ADCP_VERSION | tr -d '[:space:]') + if [ ! -f compliance/CACHE_VERSION ]; then + echo "❌ compliance/CACHE_VERSION not found after sync — sync-schemas may have failed silently." + echo " Run: npm run sync-schemas:all" + exit 1 + fi + CACHED=$(cat compliance/CACHE_VERSION | tr -d '[:space:]') + if [ "$CACHED" != "$EXPECTED" ]; then + echo "❌ Compliance cache version mismatch: bundled '$CACHED', expected '$EXPECTED'" + echo " Stale storyboards cause false pass/fail in buyer-side validation." + echo " Run: npm run sync-schemas:all" + exit 1 + fi + echo "✅ compliance/cache version matches spec: $CACHED" + - name: Validate generated files are in sync run: | npm run generate-types diff --git a/.github/workflows/schema-sync.yml b/.github/workflows/schema-sync.yml index 27c7ed51d..ed47de03f 100644 --- a/.github/workflows/schema-sync.yml +++ b/.github/workflows/schema-sync.yml @@ -47,19 +47,24 @@ jobs: npm run generate-types npm run generate-wellknown-schemas - if git diff --exit-code src/lib/types/ src/lib/agents/ package.json; then + # Always capture version so PR title is correct for compliance-only bumps too. + # compliance/CACHE_VERSION is always written by sync-schemas (tarball or per-file path). + ADCP_VERSION=$(cat compliance/CACHE_VERSION | tr -d '[:space:]') + echo "adcp_version=$ADCP_VERSION" >> $GITHUB_OUTPUT + + # Include compliance/CACHE_VERSION in the diff guard so compliance-only + # spec bumps (e.g. fixed storyboard YAML, new idempotency_key strings) + # trigger a PR even when TypeScript types are unchanged. + # git status --short detects both new (untracked) and modified marker files. + COMPLIANCE_MARKER_CHANGED="$(git status --short compliance/CACHE_VERSION 2>/dev/null)" + + if git diff --exit-code src/lib/types/ src/lib/agents/ package.json && \ + [ -z "$COMPLIANCE_MARKER_CHANGED" ]; then echo "changes_detected=false" >> $GITHUB_OUTPUT echo "✅ No schema changes detected" else echo "changes_detected=true" >> $GITHUB_OUTPUT echo "📋 Schema changes detected" - - ADCP_VERSION=$(node -e " - const fs = require('fs'); - const index = JSON.parse(fs.readFileSync('schemas/cache/latest/index.json', 'utf8')); - console.log(index.adcp_version); - ") - echo "adcp_version=$ADCP_VERSION" >> $GITHUB_OUTPUT fi - name: Sync version if schemas changed diff --git a/scripts/sync-schemas.ts b/scripts/sync-schemas.ts index 4193d60f8..4599e615c 100644 --- a/scripts/sync-schemas.ts +++ b/scripts/sync-schemas.ts @@ -380,6 +380,15 @@ async function syncFromTarball(version: string): Promise { updateLatestSymlink(SCHEMA_CACHE_DIR, version); updateLatestSymlink(COMPLIANCE_CACHE_DIR, version); + // Write a tracked marker so schema-sync CI can detect compliance-only spec + // bumps. compliance/cache/ is gitignored (populated at sync/publish time), + // but compliance/CACHE_VERSION sits one level above and IS tracked in git. + // This lets the diff guard in schema-sync.yml fire even when no TypeScript + // types changed — preventing stale storyboards from shipping in npm releases. + const cacheVersionMarker = path.join(path.dirname(COMPLIANCE_CACHE_DIR), 'CACHE_VERSION'); + writeFileSync(cacheVersionMarker, `${semanticVersion}\n`); + console.log(`📝 compliance/CACHE_VERSION → ${semanticVersion}`); + console.log(`📁 Schemas: ${path.join(SCHEMA_CACHE_DIR, version)}`); console.log(`📁 Compliance: ${path.join(COMPLIANCE_CACHE_DIR, version)}`); if (existsSync(`${path.join(SCHEMA_CACHE_DIR, version)}.previous`)) { @@ -503,6 +512,13 @@ async function sync(version?: string): Promise { const viaTarball = await syncFromTarball(adcpVersion); if (!viaTarball) { await syncSchemasPerFile(adcpVersion); + // Per-file fallback doesn't sync compliance/. Write the marker anyway so + // ci:schema-check doesn't hard-fail with "not found". Version reflects the + // ADCP_VERSION pin — the mismatch risk is pre-existing in this fallback path. + const marker = path.join(path.dirname(COMPLIANCE_CACHE_DIR), 'CACHE_VERSION'); + mkdirSync(path.dirname(marker), { recursive: true }); + writeFileSync(marker, `${adcpVersion}\n`); + console.warn(`📝 compliance/CACHE_VERSION → ${adcpVersion} (per-file fallback — compliance storyboards not synced)`); } console.log(`✅ Sync complete for AdCP ${adcpVersion}`);