Skip to main content

App Works — OpenAPI fragments

Status: Draft · Created: 2026-09-17 · Program: App Works Closes: the fragment half of SK-07 Companion documents: ../README.md (why fragments exist, the superset rule, the generation pipeline) · ../agent-surfaces.md (the x-mcp values) · ../../CONTRACTS.md §4 (the normative route table)


1. The convention​

  • One fragment per API-owning epic, named apw-<nn>.openapi.yaml — apw-01.openapi.yaml, apw-12.openapi.yaml, … There is no fragment for an epic that owns no route (APW-13).
  • OpenAPI 3.0 (openapi: 3.0.3), not 3.1 — the version the platform actually serves. The generator never calls setOpenAPIVersion, so @nestjs/swagger 11's default applies: the emitted document's openapi field is 3.0.0 (@nestjs/swagger/dist/swagger-module.js:29; docs/api/index.md:24 states "OpenAPI 3.0 JSON specification"). A fragment must be a subset of that document.
  • Nullability uses the 3.0 spelling: nullable: true. This is not cosmetic. The MCP converter reads the 3.0 form (apps/mcp/src/openapi-tools/schema-converter.service.ts:20,71 — /** OpenAPI 3.0 spelling of "may be null" … */ / if (schema.nullable === true)), whereas 3.1 spells it type: [x, 'null'] — which the converter ignores. A 3.1 fragment would therefore encode nullability in a form the pipeline silently drops.
  • A fragment is never the source of truth. The generated apps/api/openapi.json is (../README.md §1–§2). A fragment that disagrees with the generated document is a finding, not a specification change.
  • A fragment lists only what the plan writes down. Where a plan names a route but does not write its request or response shape, the route is listed in §2 below as "route named at file:line, schema to be derived" and no operation is written. Guessing a field name would make the guess the contract.
  • Every operation carries x-source (the file:line/section the operation was derived from) and x-mcp (the agent-surface decision: not-exposed, or the tool name and its hints). x-mcp is defined in ../agent-surfaces.md §3.
  • No info.version beyond a pointer. The platform serves one unversioned document titled Ever Works API at version 1.0 (apps/api/src/openapi/openapi-document.config.ts:12,16); a fragment's info block names its epic and its phase, nothing more.
  • No servers. The fragments are contract shapes, not deployments; an installation's origins are configuration (../../CONFIGURATION.md §4).
  • No secrets, no hostnames. Every example value is a placeholder, an RFC 2606 host or a synthetic digest — the public-repository rule (README §7 rule 10).

2. Per-epic status​

FragmentEpicStatusWhat is groundedWhat is not
apw-01.openapi.yamlAPW-01writtenPOST /api/works/app-source/inspect — request fields with their validators, the 200 semantics, 400 codes app_works_disabled / invalid_url, 429; the POST /api/works extensions (kind, repositoryUrl, repositoryMode, targetOwner, blueprintId, appEnv), the 200 response envelope and the documented 400/409/503 codes; the delete_stored_data + confirm_slug additions to POST /api/works/:id/delete with 422 confirmation_mismatch and 400 for a link + delete_data_repositorythe full AppSourceInspectResponseDto and AppSourceInspectRequestDto property list — the plan writes the fields but not every type, so the fragment marks the response additionalProperties: true and cites the DTO
apw-02.openapi.yamlAPW-02not written—GET /api/works/:id/upstream, POST /api/works/:id/upstream/sync (202), POST /api/works/:id/upstream/readiness/retry (202) — route named at ../../APW-02-fork-lifecycle/plan.md §4.1; schema to be derived
apw-03.openapi.yamlAPW-03not written—the catalog reads and the app-spec/license routes — route named at ../../APW-03-app-spec-and-catalog/plan.md §4.1; AppsCatalogListResponse exists as a name but its properties are in the web components, not the API section; schema to be derived
apw-04.openapi.yamlAPW-04not written—provision / provisioning / cancel / blueprint-suggestion / the two admin routes — route named at ../../APW-04-app-provisioner/plan.md §4; schema to be derived
apw-05.openapi.yamlAPW-05not written—the builds routes and PUT /api/works/:id/builds/pull-token — route named at ../../APW-05-builds/plan.md §5; AppBuildSummary/AppBuildDetail are written out in the plan §3.2, so this fragment is the cheapest next one to write
apw-06.openapi.yamlAPW-06not written—app-status / app-jobs / smoke / rollback / lifecycle / logs / target / deletion-preview plus the kind-app branches on the existing deploy routes — route named at ../../APW-06-app-runtime/plan.md §9.1; AppStatusSnapshot and AppRenderInput are named in ../../CONTRACTS.md §3 but not written field-by-field; schema to be derived
apw-07.openapi.yamlAPW-07not written—app-env, app-dependencies and PUT /api/deploy/works/:id/runtime-env — route named at ../../APW-07-app-env-and-dependencies/plan.md §5; AppEnvEntryView is written out in plan §3.3, so this fragment is also cheap
apw-08.openapi.yamlAPW-08not written—evolve, delivery, cost and the goals/missions field additions — route named at ../../APW-08-evolve-loop/plan.md §4; TaskDeliveryView/TaskCostView are written out in plan §3.4; schema to be derived for the controller DTOs
apw-09.openapi.yamlAPW-09not written—the upstream-pull-request family — route named at ../../APW-09-upstream-pull-requests/plan.md §5; that plan writes the list envelope as { data: UpstreamPullRequestView[], meta: { total } }. ({ entries, summary } is APW-07's GET /api/works/:id/app-env envelope, not APW-09's — corrected 2026-09-17.) Properties are otherwise to be derived
apw-10.openapi.yamlAPW-10not written—the operator family api/admin/apps-tier/* and the two owner reads — the plan's §6.1 table names the routes; CONTRACTS §4 abbreviates the family with *, so the fragment cannot be written until §6.1's members are treated as normative
apw-11.openapi.yamlAPW-11not written—GET /api/me/apps, PUT /api/me/apps/preferences, GET /api/app-launcher/platforms — route named at ../../APW-11-app-launcher/plan.md §4; AppLauncherListResponse is written out in plan §3.3, so this fragment is cheap
apw-12.openapi.yamlAPW-12writtenmethod, path, auth class, throttle and request → response for all 13 /api/auth/ever-id/* routes plus the everId addition to GET /api/auth/providers, and the shared code-keyed error responsesthe exact TypeScript type of EverIdCallbackOutcome's variants beyond the four outcome discriminators (written in plan §3.5, so they are groundable — the fragment marks the union without re-deriving it); TokenResponse and IdentityProviderCheck are existing/named types, cited not redefined

Fragments are written in the order the plan documents make them cheap. APW-05 §3.2, APW-07 §3.3, APW-08 §3.4 and APW-11 §3.3 each write their response types out in full, so their fragments need no new decisions — they are the next four to add.


3. How a fragment is checked​

  1. Lint — parse and dereference every fragment with @apidevtools/swagger-parser, already a dependency of apps/mcp (apps/mcp/package.json:25). No new dependency.
  2. Superset — apps/api/src/openapi/__tests__/app-works-contract.spec.ts (APW-13 P0) loads every fragment and the generated apps/api/openapi.json, and asserts, per operation:
    • the path + method exist;
    • every property the fragment marks required is required in the generated document;
    • every status code the fragment lists is declared in the generated document;
    • every x-mcp value in the fragment matches the whitelist decision in ../agent-surfaces.md — not-exposed means no whitelist entry, a tool name means exactly that entry.
  3. Regenerate before comparing — pnpm --filter ever-works-api build && pnpm --filter ever-works-api generate:openapi (the generated file is git-ignored; see ../README.md §1).

A fragment is stale the moment the generated document gains a required property the fragment does not list only if the fragment also lists that operation's request body — which is why the two written fragments mark the parts they do not fully know with additionalProperties: true and an x-source note rather than claiming a closed shape.