From 2784cd7c8b8aafdeaf6b48d35135a6ff902cf396 Mon Sep 17 00:00:00 2001 From: cat101 Date: Fri, 2 Oct 2026 17:53:14 -0300 Subject: [PATCH 1/2] ci(spec-sync): read the V2 spec from aide's S3 publish, not the gateway aide is narrowing each gateway's public /openapi.json to the documented subset (no aliases, no undocumented routes such as v2 workflow and v2 build-schema). The SDK tracks the full customer surface, which aide's publish-openapi.yml already uploads from release/staging to public S3. Byte-identical to aide.staging.landing.ai/openapi.json today, so this switch draws no drift on its own. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/spec-sync.yml | 10 ++++++---- CONTRIBUTING.md | 9 +++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/spec-sync.yml b/.github/workflows/spec-sync.yml index aba9c22..28f5a41 100644 --- a/.github/workflows/spec-sync.yml +++ b/.github/workflows/spec-sync.yml @@ -298,15 +298,17 @@ jobs: # and a separate sync branch, with a V2-tailored wiring prompt. Kept as a distinct job (not a # matrix) so the proven V1 loop is untouched and the two prompts can diverge freely. # - # HOSTS (do not conflate): the V2 *spec* is published at aide.[env]/openapi.json; the V2 *API* - # (what the SDK and contract tests call) is api.ade.[env]. We fetch drift from aide; the SDK - # never talks to aide (its paths are staff-SSO-gated). + # SOURCE: the V2 spec is the FULL customer surface aide's publish-openapi.yml generates from + # release/staging and uploads to public S3 — including routes that are not documented yet + # (v2 workflow, v2 build-schema). The gateways' own /openapi.json serves only the documented + # subset, so it is not a drift source. The V2 *API* (what the SDK and contract tests call) + # is api.ade.[env]. spec-sync-v2: if: github.repository == 'landing-ai/ade-python' runs-on: ubuntu-latest timeout-minutes: 60 # headroom for the AI wiring step (up to --max-turns 250) plus rye sync env: - V2_SPEC_URL: https://aide.staging.landing.ai/openapi.json # staging drives the loop + V2_SPEC_URL: https://ade-specs.s3.amazonaws.com/v2/staging/openapi.json # staging drives the loop SYNC_BRANCH: spec-sync/v2 # separate branch so V1 and V2 sync PRs never collide SPEC_LABEL: V2 # used only in Slack/PR-comment copy to disambiguate the two jobs steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58fb822..5932c99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -144,10 +144,11 @@ production spec), is committed but **not wired** into `release.yml` — deferred "Known gaps" explains what to fix before wiring it. It runs **two independent loops** (one job each): the **V1** loop tracks the V1 spec against -`specs/v1-ade.json`, and the **V2** loop tracks the V2 spec on the AIDE gateway -(`aide.[env]/openapi.json`) against `specs/v2-aide.json` on a separate `spec-sync/v2` branch. Both -reuse the same scripts. (Note the host split: the V2 *spec* is published at `aide.[env]`, but the -V2 *API* the SDK calls is `api.ade.[env]`.) +`specs/v1-ade.json`, and the **V2** loop tracks the full V2 customer spec aide publishes to +`https://ade-specs.s3.amazonaws.com/v2/staging/openapi.json` against `specs/v2-aide.json` on a +separate `spec-sync/v2` branch. Both reuse the same scripts. (The gateway's own `/openapi.json` +serves only the documented subset, so it is not the V2 drift source; the V2 *API* the SDK calls +is `api.ade.[env]`.) On each run a loop fetches and normalizes its live spec (`scripts/spec-sync/fetch-normalize.sh`) and diffs it against its committed snapshot (`scripts/spec-sync/check-drift.sh`). Staging auto-reclaims From e52182eec7732821d8d94d241aaac3e69183fc02 Mon Sep 17 00:00:00 2001 From: cat101 Date: Fri, 2 Oct 2026 18:00:01 -0300 Subject: [PATCH 2/2] ci(spec-sync): an unavailable V2 source is an incident, not a skip The exit-20 no-op exists because an unbooked staging cluster 404s. The V2 source is now a published S3 artifact that does not go away with staging, so a 404 there means a broken publish, and skipping it silently would stop V2 drift detection. V1 keeps its skip. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/spec-sync.yml | 27 ++++++++++----------------- CONTRIBUTING.md | 5 +++-- docs/spec-sync-runbook.md | 3 ++- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/.github/workflows/spec-sync.yml b/.github/workflows/spec-sync.yml index 28f5a41..2689e40 100644 --- a/.github/workflows/spec-sync.yml +++ b/.github/workflows/spec-sync.yml @@ -341,22 +341,16 @@ jobs: if: steps.drift.outputs.code == '0' run: echo "specs in sync; nothing to do." - # An unavailable spec source (check-drift exit 20) is the EXPECTED case on staging, not an - # incident: staging auto-reclaims and must be booked to come back — an unbooked cluster serves a - # 404 (or the host stops answering entirely). Treat ONLY this as a no-op — log and end the run - # cleanly (no drift, no PR, no Slack); the next hourly run picks up any drift once staging is - # booked. Every other failure (a reachable source returning 401/403/5xx, an empty/invalid spec, - # or a script error) falls through to "Fail on spec error" below and alerts. - - name: Spec source unavailable — skip - if: steps.drift.outputs.code == '20' - run: echo "spec source unavailable (exit 20); staging is likely unbooked — skipping this run." + # The V2 source is a published S3 artifact, not a bookable cluster: it does not go away + # when staging is unbooked, so an unavailable source (exit 20) is an incident like any + # other fetch failure, not an expected no-op. # A reachable-but-invalid spec (empty/whitespace body, malformed JSON) or a script error is a - # real problem — fail loudly so the catch-all alert fires. Distinct from the exit-20 skip above. + # real problem — fail loudly so the catch-all alert fires. - name: Fail on spec error - if: steps.drift.outputs.code != '0' && steps.drift.outputs.code != '10' && steps.drift.outputs.code != '20' + if: steps.drift.outputs.code != '0' && steps.drift.outputs.code != '10' run: | - echo "spec fetch/normalize failed (exit ${{ steps.drift.outputs.code }}); staging was reachable but returned an error status (401/403/5xx), an empty/invalid spec, or a script errored." + echo "spec fetch/normalize failed (exit ${{ steps.drift.outputs.code }}); the published S3 spec was unreachable, returned an error status, was empty/invalid, or a script errored." exit 1 - name: Check for an open sync PR @@ -968,9 +962,8 @@ jobs: text: ${{ steps.ai_commit.outputs.summary }} thread_ts: ${{ steps.root.outputs.ts }} - # Catch-all failure alert for this job. An unavailable spec source (exit 20) is skipped earlier - # and never reaches here; what this calls out is a reachable source that errored — an HTTP error - # status / empty / invalid spec, or a script error (exit not 0/10/20) — and an AI-wiring crash + # Catch-all failure alert for this job: a spec fetch that failed — the S3 source unreachable, + # an HTTP error status / empty / invalid spec, or a script error (exit not 0/10) — and an AI-wiring crash # (PR left mechanical-only), with a generic fallback. - name: Slack — spec-sync failed if: failure() @@ -981,8 +974,8 @@ jobs: status: failure title: 'spec-sync ${{ env.SPEC_LABEL }}: run failed' text: >- - ${{ (steps.drift.outputs.code != '' && steps.drift.outputs.code != '0' && steps.drift.outputs.code != '10' && steps.drift.outputs.code != '20') - && format('Spec fetch/normalize failed (exit {0}) — staging was reachable but returned an error status (401/403/5xx), an empty/invalid spec, or a script errored (an unbooked cluster 404s → exit 20, skipped, not alerted). Investigate.', steps.drift.outputs.code) + ${{ (steps.drift.outputs.code != '' && steps.drift.outputs.code != '0' && steps.drift.outputs.code != '10') + && format('Spec fetch/normalize failed (exit {0}) — the published S3 spec was unreachable, returned an error status, was empty/invalid, or a script errored. Investigate.', steps.drift.outputs.code) || (steps.ai_wiring.outcome == 'failure' && 'AI wiring step failed. Any open sync PR has only the mechanical commit and needs manual wiring.' || 'spec-sync run failed. See the workflow run for details.') }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5932c99..9420fb5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -151,10 +151,11 @@ serves only the documented subset, so it is not the V2 drift source; the V2 *API is `api.ade.[env]`.) On each run a loop fetches and normalizes its live spec (`scripts/spec-sync/fetch-normalize.sh`) and -diffs it against its committed snapshot (`scripts/spec-sync/check-drift.sh`). Staging auto-reclaims +diffs it against its committed snapshot (`scripts/spec-sync/check-drift.sh`). For the V1 loop, staging auto-reclaims and must be booked, so an unavailable spec source (an unbooked cluster 404s, or the host stops answering) is treated as an expected no-op — the run ends cleanly with no PR and no Slack alert, and -the next run picks up drift once staging is booked. A reachable source that returns an error status +the next run picks up drift once staging is booked. The V2 loop reads a published S3 artifact that +does not depend on staging being booked, so an unavailable V2 source fails and alerts. A reachable source that returns an error status (401/403/5xx) or an empty/invalid spec still fails loudly and alerts. On drift it opens one PR with two attributed commits (paths shown for V1; the V2 loop uses the `v2-aide`/`v2_models` equivalents): diff --git a/docs/spec-sync-runbook.md b/docs/spec-sync-runbook.md index a7f2b34..bf4120c 100644 --- a/docs/spec-sync-runbook.md +++ b/docs/spec-sync-runbook.md @@ -44,7 +44,8 @@ Silence is also information — but not always: an unavailable staging spec sour 404) is a deliberate **no-op with no Slack alert**, so "no messages" can mean either "no drift" or "staging is unbooked". A staging cluster that stays unbooked for days silently stops all drift detection. If the channel has been quiet for an unusually long stretch, check that staging is booked -before assuming the spec is stable. +before assuming the spec is stable. This applies to the V1 loop only: the V2 loop reads a +published S3 artifact, and an unavailable V2 source fails and alerts. ## 2. Reviewing a drift PR