Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .docs/2026-07-19-public-evidence-product-slice-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Public Evidence Product Slice Plan

## Objective

Use current public evidence from Reddit, X, and GitHub to select, build, validate, and ship one working OpenAPI painkiller before customer interviews.

## Constraints

- Build one complete vertical slice, not an editor platform.
- Use public complaints, issue discussions, and active repositories as directional evidence. Do not treat engagement as willingness to pay.
- Keep uploads local to the browser unless persistence is required by the selected workflow.
- Do not add authentication, billing, workspaces, collaboration, or speculative infrastructure.
- Preserve OpenAPI semantics and report uncertainty honestly.

## Execution

1. Collect recent pain signals from Reddit and X plus recurring issues and workflows from GitHub.
2. Rank pains by frequency, urgency, buyer relevance, implementation cost, and fit with the current positioning.
3. Write the selected feature specification, validation contract, and task ledger before implementation.
4. Build the smallest end-to-end workflow on a feature branch.
5. Test valid, invalid, breaking, non-breaking, no-JavaScript, mobile, privacy, and accessibility behavior as applicable.
6. Deploy a preview, inspect it in a browser, open a pull request, pass CI, and ship only the validated slice.

## Decision Rule

Prefer a workflow that produces an immediate decision from two real OpenAPI documents. Reject ideas that require accounts, persistent customer data, third-party integrations, or broad editing before value is visible.
20 changes: 20 additions & 0 deletions .docs/2026-07-19-public-openapi-pain-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Public OpenAPI Pain Evidence

## Decision

Ship a browser-local OpenAPI change check. A user selects a baseline and revised JSON or YAML contract, then receives a concise report of high-confidence breaking changes.

## Evidence

- A June 2026 FastAPI discussion describes accidental response-field changes becoming more likely as AI tooling increases development pace. The recommended control is comparing generated OpenAPI contracts in CI: https://www.reddit.com/r/FastAPI/comments/1tvfwxr/prevent_unintentional_breaking_api_changes_in/
- A February 2026 discussion reports repeated review work caused by inconsistent OpenAPI documents and SDK generator failures: https://www.reddit.com/r/node/comments/1r76obi/i_was_tired_of_fixing_inconsistent_openapi_specs/
- A January 2026 discussion repeatedly identifies synchronization between implementation and OpenAPI as the central requirement: https://www.reddit.com/r/node/comments/1q5imqi/openapi_static_or_dynamic/
- GitHub documents removed operations, renamed parameters or response fields, newly required parameters, type changes, enum removals, validation changes, and authentication changes as consumer-breaking categories: https://docs.github.com/en/rest/about-the-rest-api/breaking-changes
- The active oasdiff issue backlog shows that exhaustive semantic comparison is difficult, including OpenAPI 3.1 composition, multi-type schemas, formats, defaults, and semantic wording: https://github.com/oasdiff/oasdiff/issues
- Current oasdiff documentation separates definite errors, potential warnings, and informational changes, supporting a severity-based decision surface: https://github.com/oasdiff/oasdiff/blob/main/docs/BREAKING-CHANGES.md

X search produced weak direct pain evidence. It showed OpenAPI being used as agent tooling input, but did not produce a stronger buyer signal than Reddit and GitHub. Do not inflate that channel's contribution.

## Scope Choice

Start with high-confidence structural breaks that can be explained deterministically. Do not claim exhaustive compatibility analysis. Keep contracts in memory and avoid accounts, persistence, remote URLs, GitHub installation, or CI integration.
37 changes: 37 additions & 0 deletions .tasks/2026-07-19-TSK-8-contract-change-check-TASKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# TSK-8: Contract Change Check

**Overall status:** IN_PROGRESS

## TG-001: Evidence and contract

- **Owner:** Main agent
- **Status:** DONE
- **Acceptance:** Public evidence, scope, privacy boundary, supported checks, limitations, and validation contract are recorded.
- **Evidence:** `.docs/2026-07-19-public-openapi-pain-evidence.md` and the approved feature specification.

## TG-002: Comparison core

- **Owner:** Main agent
- **Status:** DONE
- **Dependencies:** TG-001
- **Acceptance:** JSON and YAML parsing plus supported change classification pass focused unit tests without network access.
- **Validation:** `bun run test`
- **Evidence:** Seven test files and 18 unit tests pass, including focused parser and comparison coverage.

## TG-003: Browser workflow

- **Owner:** Main agent
- **Status:** DONE
- **Dependencies:** TG-002
- **Acceptance:** `/compare` delivers local file selection, samples, decision-oriented results, privacy disclosure, responsive layout, and accessible states.
- **Validation:** `bun run test:e2e`
- **Evidence:** Six browser tests pass across the marketing and comparison routes, including accessibility, narrow viewport, malformed input, oversized input, and no-JavaScript coverage.

## TG-004: Release validation

- **Owner:** Main agent
- **Status:** IN_PROGRESS
- **Dependencies:** TG-003
- **Acceptance:** Full checks, preview deployment, browser inspection, pull request, CI, and rollback record pass.
- **Validation:** `bun run check`
- **Evidence:** The local full gate passes. Preview version `c4b254a2-b80a-445c-baff-822e5cba705f` serves `/compare` with the new route and assets. Pull request and CI remain.
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"@tanstack/solid-start": "1.168.30",
"drizzle-orm": "0.45.2",
"solid-js": "1.9.14",
"valibot": "1.4.2"
"valibot": "1.4.2",
"yaml": "2.9.0"
},
"devDependencies": {
"@axe-core/playwright": "4.12.1",
Expand Down
53 changes: 53 additions & 0 deletions specs/features/2026-07-19-contract-change-check/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Contract Change Check

**Status:** APPROVED
**Owner:** Main agent
**Roadmap:** Public-evidence product slice before Phase 2

## Outcome

A visitor can compare two local OpenAPI 3.x JSON or YAML documents and understand whether the revision contains a supported high-confidence breaking change before sharing any contract with a server.

## Requirements

1. Provide a dedicated `/compare` route linked from the marketing page.
2. Accept one baseline file and one revised file, each limited to 2 MB and `.json`, `.yaml`, or `.yml`.
3. Parse files entirely in the browser. Do not transmit, persist, log, or analyze contract contents server-side.
4. Require an OpenAPI 3.x root version and a paths object in both documents.
5. Detect and explain these high-confidence changes:
- operation removed
- required request parameter added
- successful response removed
- schema or property type changed
- response property removed
- request property made required
- supported security requirement removed from an operation
6. Group results into breaking changes and review notes, with method, path, location, and plain-language impact.
7. Show a clear no-supported-breaking-changes result without claiming full compatibility.
8. Provide sample contracts so the workflow is useful without user data.
9. Work with keyboard navigation, mobile layouts, reduced motion, and without client-side JavaScript for the explanatory shell. Comparison requires JavaScript and must say so.

## Non-Goals

- Full OpenAPI validation or exhaustive compatibility analysis
- External `$ref` retrieval or dereferencing
- Swagger 2.0, AsyncAPI, callbacks, webhooks, links, discriminator analysis, or JSON Schema composition semantics
- Accounts, saved comparisons, GitHub integration, CI configuration, billing, or AI-generated judgments

## Privacy and Failure Behavior

- File contents remain in browser memory and are cleared on reload.
- Reject unsupported extensions, files over 2 MB, malformed JSON or YAML, missing OpenAPI 3.x versions, and missing paths with actionable messages.
- Do not fetch external references. Report unresolved `$ref` coverage as a review note.
- Never render specification text as HTML.

## Validation

- Unit tests cover every supported breaking-change category and direction.
- Browser tests cover sample comparison, malformed input, privacy copy, keyboard use, mobile layout, accessibility, and the no-JavaScript shell.
- The production bundle contains no server upload path for contract contents.
- `bun run check` passes and the Cloudflare preview is inspected before merge.

## Rollout

Deploy to preview first. Roll back by reverting the route, feature slice, marketing link, and YAML dependency. No data migration is required.
109 changes: 109 additions & 0 deletions src/core/openapi/compare.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { describe, expect, it } from 'vitest'

import { compareOpenApi } from './compare'

const operation = {
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: { id: { type: 'string' }, dueDate: { type: 'string' } },
},
},
},
},
},
}

function document(nextOperation: Record<string, unknown> = operation) {
return { openapi: '3.1.0', paths: { '/invoices/{id}': { get: nextOperation } } }
}

describe('compareOpenApi', () => {
it('reports the supported high-confidence breaking changes', () => {
const revision = document({
parameters: [{ in: 'query', name: 'expand', required: true, schema: { type: 'string' } }],
security: [{ oauth: [] }],
requestBody: {
content: {
'application/json': {
schema: {
type: 'object',
required: ['email'],
properties: { email: { type: 'string' } },
},
},
},
},
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: { type: 'object', properties: { id: { type: 'number' } } },
},
},
},
},
})
const base = document({
...operation,
security: [{ apiKey: [] }, { oauth: [] }],
requestBody: {
content: {
'application/json': {
schema: {
type: 'object',
properties: { email: { type: 'string' } },
},
},
},
},
})

expect(compareOpenApi(base, revision).breaking.map(({ code }) => code)).toEqual([
'required-parameter-added',
'response-property-removed',
'schema-type-changed',
'request-property-required',
'security-scheme-removed',
])
})

it('reports removed operations and successful responses', () => {
expect(compareOpenApi(document(), { openapi: '3.1.0', paths: {} }).breaking[0]?.code).toBe(
'operation-removed',
)
expect(
compareOpenApi(document(), document({ responses: { '404': { description: 'Missing' } } }))
.breaking[0]?.code,
).toBe('success-response-removed')
})

it('reports removed response properties', () => {
const result = compareOpenApi(
document(),
document({
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: { type: 'object', properties: { id: { type: 'string' } } },
},
},
},
},
}),
)

expect(result.breaking.map(({ code }) => code)).toContain('response-property-removed')
})

it('does not claim a breaking change for identical contracts', () => {
expect(compareOpenApi(document(), document())).toEqual({ breaking: [], review: [] })
})
})
Loading