-
Notifications
You must be signed in to change notification settings - Fork 500
feat: Add api-schema-drift-sentinel kit #341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mohamad-shafeez
wants to merge
16
commits into
Lamatic:main
Choose a base branch
from
mohamad-shafeez:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
ad34c60
Add API Schema Drift Sentinel kit
mohamad-shafeez 19ce46e
fix: add missing root structure files for PR validation
mohamad-shafeez 2ae66d7
fix: address schema sentinel review feedback for security, validation…
mohamad-shafeez 57aa74a
fix: harden schema drift input validation and populate flow nodes
mohamad-shafeez 7966cfd
feat: implement API schema drift sentinel
mohamad-shafeez b9dede0
fix: populate schema drift flow nodes
mohamad-shafeez 2c204ec
fix: convert flow node values to static JSON strings for Studio valid…
mohamad-shafeez 4385749
fix: resolve coderabbit review findings, add parameter inheritance, a…
mohamad-shafeez 0659c2c
fix(ui): sanitize AI payload fields, remove suppressHydrationWarning,…
mohamad-shafeez 0dd492c
fix: address CodeRabbit review findings
mohamad-shafeez 272b47e
fix: address CodeRabbit review findings
mohamad-shafeez 49671d9
docs: sync flow and model configuration with Studio
mohamad-shafeez 596719e
fix: correct flow trigger schema and align model config with deployed…
mohamad-shafeez ba06665
Improve schema normalization and Lamatic workflow observability
mohamad-shafeez ff214c1
Document deterministic and live test behavior
mohamad-shafeez 6f62dc6
fix: address final schema sentinel review findings
mohamad-shafeez File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| LAMATIC_API_KEY=your_lamatic_api_key_here | ||
| LAMATIC_API_URL=https://api.lamatic.ai | ||
| LAMATIC_DRIFT_FLOW_ID=your_id | ||
| LAMATIC_PROJECT_ID=your_project_id_here |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| node_modules/ | ||
| .next/ | ||
| .env | ||
| .env.* | ||
| !.env.example | ||
| .DS_Store | ||
| *.log | ||
| *.tsbuildinfo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,253 @@ | ||
| # API Schema Drift Sentinel | ||
|
|
||
| A **breaking-change detection and migration orchestration kit** for OpenAPI-based services. It uses deterministic structural comparison to identify schema changes, supplements the diff with direct path-parameter comparison where necessary, classifies the resulting changes by severity, and sends the confirmed change facts to a Lamatic workflow for executive impact analysis and migration guidance — exposed through a single `/api/analyze-drift` API call. | ||
|
|
||
| --- | ||
|
|
||
| ## The Problem | ||
|
|
||
| API schema drift is silent and expensive. When a service team renames a field, removes a response property, or changes a parameter type, the breakage shows up in downstream clients — frontends, SDKs, mobile apps — long after the deploy. Most teams catch this through manual spec review or at runtime during integration testing. | ||
|
|
||
| There is no easy way to: | ||
| 1. Automatically detect what broke between two spec versions | ||
| 2. Know which client systems will be affected | ||
| 3. Get a concrete migration plan without reading the full spec diff manually | ||
|
|
||
| --- | ||
|
|
||
| ## The Solution | ||
|
|
||
| API Schema Drift Sentinel combines two layers: | ||
|
|
||
| 1. **Deterministic AST diff** — `openapi-diff` performs structural comparison of the two OpenAPI specs and returns typed, structured breaking and non-breaking changes. This is computed locally, with no AI involved, so the facts are always accurate. | ||
|
|
||
| 2. **AI narrative synthesis** — the structured facts are forwarded to a Lamatic workflow where an LLM reasons about downstream impact, classifies deployment risk, and produces a migration guide grounded in the actual detected changes. | ||
|
|
||
| The result is exposed through a Next.js API endpoint and a minimal dashboard UI. | ||
|
|
||
| --- | ||
|
|
||
| ## Why the Two-Layer Architecture | ||
|
|
||
| Using AI alone to diff specs is unreliable — models hallucinate field names, miss subtle type changes, and produce inconsistent severity ratings. Using a pure diff tool alone gives you a machine-readable change list but no actionable guidance. | ||
|
|
||
| This kit separates the concerns: | ||
|
|
||
| | Layer | What it does | Why | | ||
| |---|---|---| | ||
| | `openapi-diff` (deterministic) | Structural AST diff | Deterministic and reproducible; no LLM hallucination risk | | ||
| | Lamatic LLM workflow | Narrative, impact, migration | Produces human-readable output grounded in confirmed facts | | ||
|
|
||
| The LLM receives a plain-text fact list derived from the deterministic layer — not the raw specs. This keeps the LLM grounded in the deterministic fact list and reduces the risk of unsupported claims. | ||
|
|
||
| --- | ||
|
|
||
| ## Architecture | ||
|
|
||
| ``` | ||
| Browser / test harness | ||
| │ | ||
| │ POST /api/analyze-drift { specA, specB } | ||
| ▼ | ||
| apps/app/api/analyze-drift/route.ts | ||
| │ | ||
| ├─ 1. runOpenApiDiff(specA, specB) ← openapi-diff AST comparison | ||
| │ | ||
| ├─ 2. normalizeDiff(rawDiff, specA, specB) ← typed SemanticChange[] facts | ||
| │ breaking: CRITICAL severity | ||
| │ non-breaking: INFO severity | ||
| │ | ||
| ├─ 3. Format fact lines for AI context | ||
| │ "Endpoint: GET /users/{id} | Field: email | Action: remove | ..." | ||
| │ | ||
| ├─ 4. triggerLamaticWorkflow({ sampleInput }) | ||
| │ Lamatic.executeFlow(flowId, payload) | ||
| │ │ | ||
| │ Lamatic Studio | ||
| │ ┌─────────────────────────────┐ | ||
| │ │ LLM Node (system prompt: │ | ||
| │ │ prompts/analyze-schema- │ | ||
| │ │ drift_llm-node_system.md) │ | ||
| │ │ │ | ||
| │ │ Returns JSON: │ | ||
| │ │ { executiveSummary, │ | ||
| │ │ detailedImpact[], │ | ||
| │ │ migrationGuide[], │ | ||
| │ │ deploymentRisk } │ | ||
| │ └─────────────────────────────┘ | ||
| │ | ||
| └─ 5. Merge AI output + deterministic counts → NextResponse | ||
| { breakingCount, nonBreakingCount, riskLevel, changes[], ... } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## API | ||
|
|
||
| ### `POST /api/analyze-drift` | ||
|
|
||
| **Request body:** | ||
| ```json | ||
| { | ||
| "specA": "<OpenAPI JSON string — baseline version>", | ||
| "specB": "<OpenAPI JSON string — target version>" | ||
| } | ||
| ``` | ||
|
|
||
| Both `specA` and `specB` are required. They must be valid OpenAPI 3.0 JSON (as a string or parsed object). | ||
|
|
||
| **Response:** | ||
| ```json | ||
| { | ||
| "success": true, | ||
| "data": { | ||
| "executiveSummary": { "recommendation": "...", "deploymentRisk": "HIGH" }, | ||
| "detailedImpact": ["...", "..."], | ||
| "migrationGuide": ["...", "..."], | ||
| "breakingCount": 3, | ||
| "nonBreakingCount": 0, | ||
| "riskLevel": "HIGH", | ||
| "changes": [ | ||
| { | ||
| "endpoint": "GET /users/{id}", | ||
| "field": "email", | ||
| "action": "remove", | ||
| "severity": "CRITICAL", | ||
| "code": "response.body.scope.remove", | ||
| "before": "string", | ||
| "after": "—", | ||
| "description": "Removed field 'email'", | ||
| "isBreaking": true | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| `breakingCount`, `nonBreakingCount`, and dashboard `riskLevel` are derived deterministically from the change classification. The narrative fields (`executiveSummary`, `detailedImpact`, `migrationGuide`) are produced by the Lamatic workflow. | ||
|
|
||
| --- | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| | Variable | Description | Where to find it | | ||
| |---|---|---| | ||
| | `LAMATIC_API_KEY` | Lamatic project API key | Studio → API Keys | | ||
| | `LAMATIC_PROJECT_ID` | Lamatic project UUID | Studio → Project Settings | | ||
| | `LAMATIC_API_URL` | Lamatic project API endpoint | Studio → Settings → API | | ||
| | `LAMATIC_DRIFT_FLOW_ID` | Deployed flow ID for the drift analysis flow | Studio → open flow → copy Flow ID | | ||
|
|
||
| --- | ||
|
|
||
| ## Setup | ||
|
|
||
| ### 1. Build the Lamatic flow | ||
|
|
||
| 1. Log in to [Lamatic Studio](https://studio.lamatic.ai) | ||
| 2. Create a new flow with a trigger that accepts `sampleInput` (string) | ||
| 3. Add an LLM node — use the system prompt from [`prompts/analyze-schema-drift_llm-node_system.md`](./prompts/analyze-schema-drift_llm-node_system.md) | ||
| 4. Configure the LLM node to return a JSON object with: `executiveSummary`, `detailedImpact`, `migrationGuide` | ||
| 5. Deploy the flow and copy the **Flow ID** | ||
|
|
||
| ### 2. Configure environment variables | ||
|
|
||
| ```bash | ||
| cd kits/api-schema-drift-sentinel/apps | ||
| cp .env.example .env.local | ||
| ``` | ||
|
|
||
| Fill in `.env.local` with your values: | ||
| ``` | ||
| LAMATIC_API_KEY=lt-... | ||
| LAMATIC_PROJECT_ID=... | ||
| LAMATIC_API_URL=https://your-project.lamatic.dev | ||
| LAMATIC_DRIFT_FLOW_ID=... | ||
| ``` | ||
|
|
||
| ### 3. Install and run | ||
|
|
||
| ```bash | ||
| npm install | ||
| npm run dev | ||
| # App available at http://localhost:3000 | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Test Cases and Results | ||
|
|
||
| The end-to-end test harness is in [`apps/test-orchestrate.js`](./apps/test-orchestrate.js). | ||
|
|
||
| ```bash | ||
| node apps/test-orchestrate.js | ||
| ``` | ||
|
|
||
| > **Credentials not required for deterministic checks.** Steps 1 and 2 (normalization correctness and path-parameter regression) run entirely locally — no Lamatic credentials are needed and no network calls are made. | ||
| > | ||
| > **Lamatic credentials required only for live integration.** Steps 3 A and B trigger the deployed Lamatic workflow and require all four `LAMATIC_*` environment variables to be set in `.env.local`. When credentials are absent the test harness detects this and skips the live workflow steps automatically, so the deterministic assertions still pass. | ||
|
|
||
| ### Test A — Additive (non-breaking) | ||
|
|
||
| **Input:** Base spec has `GET /users/{id}` returning `{ id, name, email }`, target spec adds optional response property `full_name: { type: "string" }`. | ||
|
|
||
| **Expected result:** | ||
| - `changesCount: 1` | ||
| - `breakingChangesCount: 0` | ||
| - `deploymentRisk: LOW` | ||
| - One non-breaking change: `FIELD_ADDED full_name` | ||
|
|
||
| ### Test B — Breaking (field removal + type change) | ||
|
|
||
| **Input:** V1 spec vs V2 spec that removes `email` and `name` from the response body, and changes the `id` path parameter type from `integer` to `string`. | ||
|
|
||
| **Expected result:** | ||
| - `FIELD_REMOVED email` | ||
| - `FIELD_REMOVED name` | ||
| - `TYPE_CHANGED id integer → string` | ||
| - `changesCount: 3` | ||
| - `breakingChangesCount: 3` | ||
| - `deploymentRisk: HIGH` | ||
| - `detailedImpact` — 3 grounded impact descriptions | ||
| - `migrationGuide` — 3 specific migration actions | ||
|
|
||
| --- | ||
|
|
||
| ## Lamatic Workflow / Configuration | ||
|
|
||
| - **Flow:** [`flows/analyze-schema-drift.ts`](./flows/analyze-schema-drift.ts) contains the checked-in Lamatic flow definition (trigger → LLM → response). | ||
| - **Prompt:** [`prompts/analyze-schema-drift_llm-node_system.md`](./prompts/analyze-schema-drift_llm-node_system.md) contains the LLM system prompt. | ||
| - **Model configuration:** [`model-configs/analyze-schema-drift_llm-node_generative-model-name.ts`](./model-configs/analyze-schema-drift_llm-node_generative-model-name.ts) contains the checked-in model configuration used by the kit (`gemini-2.5-flash`). | ||
| - **Constitution:** [`constitutions/default.md`](./constitutions/default.md) contains the safety and data handling guidelines referenced by the flow. | ||
| - The deployed flow is configured and tested in Lamatic Studio. | ||
| - The workflow receives deterministic schema-drift facts through `sampleInput` and uses the LLM to generate grounded impact analysis and migration guidance. | ||
| - **Kit config:** [`lamatic.config.ts`](./lamatic.config.ts) contains kit metadata and the required `LAMATIC_DRIFT_FLOW_ID`. | ||
|
|
||
| --- | ||
|
|
||
| ## Design Decisions and Tradeoffs | ||
|
|
||
| **Why `openapi-diff` instead of a pure LLM diff?** | ||
| `openapi-diff` gives deterministic, reproducible, structured output. The LLM layer only receives confirmed facts — it cannot contradict or fabricate changes. This is the key correctness guarantee. | ||
|
|
||
| **Why does `detectParameterTypeChanges` exist?** | ||
| `openapi-diff` does not consistently surface path-parameter type changes. `detectParameterTypeChanges()` is now part of the production deterministic normalization layer in [`apps/lib/sentinel.ts`](./apps/lib/sentinel.ts). It supplements `openapi-diff` by directly comparing path parameters between the two specs. This is why the production browser test correctly detects `id: integer → string` on `GET /users/{id}`. | ||
|
|
||
| **Why `Lamatic.executeFlow(flowId, payload)` via the Lamatic SDK?** | ||
| The integration uses the official `@lamatic/sdk` `executeFlow` API. The SDK manages flow execution, payload transmission, and polling internally, providing a robust, typed execution path directly against the deployed flow ID. | ||
|
|
||
| **Why is the LLM output merged with deterministic counts?** | ||
| `breakingCount`, `nonBreakingCount`, and dashboard `riskLevel` are derived deterministically from the change classification, independent of the LLM. This means the dashboard's risk badge and change counters are always correct even if the AI narrative fails or is degraded. | ||
|
|
||
| --- | ||
|
|
||
| ## Limitations | ||
|
|
||
| - **YAML spec support:** Both specs must be valid OpenAPI 3.0 JSON. YAML input is not currently parsed. | ||
| - **Lamatic dependency:** AI narrative synthesis requires a configured and deployed Lamatic flow. If the flow is unreachable, the API returns deterministic facts with a fallback `executiveSummary` string instead of failing. | ||
| - **Path parameter type changes:** Path parameter type changes are supplemented by a direct comparator because `openapi-diff` may not consistently surface them. | ||
| - **Single endpoint scope:** The current implementation treats the entire spec as a single analysis unit. It does not segment analysis per-endpoint for large multi-path specs. | ||
| - **No authentication on the API:** The `/api/analyze-drift` endpoint has no authentication. Suitable for local/internal use; add middleware before public deployment. | ||
|
|
||
| --- | ||
|
|
||
| Built on [Lamatic](https://lamatic.ai). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # API Schema Drift Sentinel | ||
|
|
||
| ## Overview | ||
|
|
||
| API Schema Drift Sentinel detects breaking changes between OpenAPI specifications and produces grounded migration guidance. | ||
|
|
||
| ## Purpose | ||
|
|
||
| The goal of this kit is to prevent breaking API drift by combining deterministic AST diffing with an AI reasoning layer. | ||
|
|
||
| ## Flows | ||
|
|
||
| ### 1. Analyze Schema Drift | ||
|
|
||
| - **Flow ID / Env key mapping:** `analyze-schema-drift` (configured via `LAMATIC_DRIFT_FLOW_ID`) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| LAMATIC_API_KEY=your_lamatic_api_key_here | ||
| LAMATIC_API_URL=https://api.lamatic.ai | ||
| LAMATIC_DRIFT_FLOW_ID=your_id | ||
| LAMATIC_PROJECT_ID=your_project_id_here |
28 changes: 28 additions & 0 deletions
28
kits/api-schema-drift-sentinel/apps/actions/orchestrate.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| "use server"; | ||
|
|
||
| import { runOpenApiDiff, normalizeDiff, triggerLamaticWorkflow } from '../lib/sentinel'; | ||
|
|
||
| export async function analyzeSchemaDrift( | ||
| oldSpecContent: string, | ||
| newSpecContent: string, | ||
| apiName = "Target API", | ||
| oldVersion = "1.0.0", | ||
| newVersion = "2.0.0" | ||
| ) { | ||
| try { | ||
| const rawDiff = await runOpenApiDiff(oldSpecContent, newSpecContent); | ||
| const normalizedChanges = normalizeDiff(rawDiff, oldSpecContent, newSpecContent); | ||
| const payload = { | ||
| apiName, | ||
| oldVersion, | ||
| newVersion, | ||
| changesCount: normalizedChanges.allChanges.length, | ||
| changes: normalizedChanges.allChanges | ||
| }; | ||
|
|
||
| const data = await triggerLamaticWorkflow(payload); | ||
| return { success: true, data }; | ||
| } catch (error: any) { | ||
| return { success: false, error: error.message }; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.