Skip to content

feat(scaffold): bin/altair openapi:import \xe2\x80\x94 OpenAPI 3.1 \xe2\x86\x92 runnable Univeros project (#162)#168

Merged
tonydspaniard merged 1 commit into
masterfrom
feat/162-openapi-import-cli
May 30, 2026
Merged

feat(scaffold): bin/altair openapi:import \xe2\x80\x94 OpenAPI 3.1 \xe2\x86\x92 runnable Univeros project (#162)#168
tonydspaniard merged 1 commit into
masterfrom
feat/162-openapi-import-cli

Conversation

@tonydspaniard
Copy link
Copy Markdown
Member

Closes #162. Part of #160. Builds on #161.

Summary

Wires #161's spec emitter into a CLI command that reads an OpenAPI 3.1 document, writes Altair YAML specs, and (with --scaffold) chains into spec:scaffold to produce a runnable project.

End-to-end on the users-api fixture: 2 operations \xe2\x86\x92 17 files (specs + Actions + Inputs + Responders + domain stubs + tests + OpenAPI fragments + routes + entity + repository + migration when --persistence=cycle).

CLI surface

bin/altair openapi:import openapi.yaml                    # \xe2\x86\x92 ./api/
bin/altair openapi:import openapi.yaml --scaffold         # full pipeline
bin/altair openapi:import openapi.yaml --scaffold --persistence=cycle
bin/altair openapi:import openapi.yaml --dry-run --format=json
bin/altair openapi:import openapi.yaml --out=specs/ --force

JSON receipt (agent-facing contract, byte-stable under NullRecorder):

{
  \"ok\": true,
  \"input\": \"openapi.yaml\",
  \"specs_written\": [\"api/users/create.yaml\", \"api/users/get.yaml\"],
  \"scaffolded\": true,
  \"scaffold_files\": [\"app/Http/Actions/CreateUserAction.php\", \"...\"],
  \"rolled_back\": [],
  \"unmapped\": [],
  \"warnings\": [],
  \"journal_id\": \"20260530T120000Z-abc12345\",
  \"event_id\": \"01J5F8R5Z9...\",
  \"error\": null
}

Reversibility

  • One combined journal entry (new JournalEntry::openApiImport factory) covers both the imported specs and the chained scaffold outputs. journal:rewind on that single id undoes the whole import.
  • New EventKind::OpenapiImport records one mutation event per run.
  • Scaffold failure after a successful import: best-effort rollback of just-written specs; the paths surface in receipt.rolled_back and the run exits 1.
  • Both integrations stay optional \xe2\x80\x94 absent Journal / RecorderInterface they're no-ops and journal_id/event_id stay null (which keeps the receipt golden-file-safe).

Persistence inference (--persistence=cycle)

Triggers on POST-to-collection endpoints only (the conceptual "create" of a resource). Emits a persistence: block with:

  • entity.class = <appNs>\\<Resource>\\<Resource>
  • entity.table = the resource path segment
  • entity.fields = synthesised id UUID primary + one field per request-body input mapped to a Cycle column type
  • repository = <appNs>\\<Resource>\\<Resource>Repository

Item endpoints (GET/PUT/DELETE on {id}) do not re-declare the entity \xe2\x80\x94 they reference it through their typed response bodies, matching the hand-authored convention.

Out of scope (per #160)

Files

Area Files
Command + runner src/Altair/Scaffold/Cli/{OpenApiImportCommand,OpenApiImportRunner,OpenApiImportOptions,ImportReceipt,PersistenceInferrer}.php
Hooks EventKind::OpenapiImport, JournalEntry::openApiImport(), EmittedFileKind::Spec
Docs docs/openapi/import.md
Benchmark benchmarks/tokens-to-ship/task-import.md + fixtures/posts.openapi.yaml
Tests 29 across tests/Scaffold/Cli/* and tests/Scaffold/Journal/JournalEntryTest.php

Test plan

  • composer cs \xe2\x80\x94 green
  • composer stan \xe2\x80\x94 green
  • composer rector (full tree, no cache) \xe2\x80\x94 green
  • composer test \xe2\x80\x94 6204 tests (+29 new), 0 new failures (5 pre-existing environmental errors: MongoDB ext + tsc/mypy not on local PATH)
  • bin/altair manifest:generate \xe2\x80\x94 clean (only .agent/packages/scaffold.md adds the new classes)
  • Real-CLI smoke: import + scaffold + persistence=cycle on users-api.yaml and on the new Posts benchmark fixture \xe2\x80\x94 deterministic, idempotent (--force overwrite path verified, skip-without---force path verified)

…iveros project (#162)

Wires #161's spec emitter into a CLI command that reads an OpenAPI 3.1
document, writes Altair YAML specs, and (with --scaffold) chains into
spec:scaffold to produce a runnable project.

CLI surface
- bin/altair openapi:import <document>
- --out=<dir>         output directory for specs (default ./api)
- --scaffold          chain into spec:scaffold after writing specs
- --persistence=cycle inject persistence: block on POST-collection endpoints
- --queue=<transport> accepted for forward compatibility; inert until #163
- --dry-run           plan only, write nothing
- --force             overwrite existing files
- --format=json|human deterministic JSON receipt for agents
- --root=<path>       override project root

Receipt is byte-stable for the same input under NullRecorder; journal_id
and event_id stay null unless a Journal / RecorderInterface is bound,
which keeps golden-file CI gates safe.

Reversibility
- One combined journal entry (new JournalEntry::openApiImport factory)
  covers BOTH the imported specs and the chained scaffold outputs, so
  journal:rewind undoes the whole import in one step.
- New EventKind::OpenapiImport records a single mutation event per run
  with the consolidated file count.
- On scaffold failure: best-effort rollback of imported specs; the
  removed paths surface in receipt.rolled_back and the run exits 1.

Smoke-tested on tests/Scaffold/Sdk/Fixtures/users-api.yaml and the new
Posts benchmark fixture:
- 2-operation Users doc → 17 files (specs + actions + inputs +
  responders + domain stubs + tests + OpenAPI fragments + routes +
  entity + repository + migration when --persistence=cycle).
- 5-operation Posts doc → 38 files.

Documentation
- docs/openapi/import.md — full reference for the command, flags,
  receipt shape, persistence inference, reversibility model, and
  known limitations.
- benchmarks/tokens-to-ship/task-import.md + posts.openapi.yaml —
  separately-reported variant of the tokens-to-ship benchmark that
  measures the cost of import-then-scaffold against the hand-build
  baseline (the prose-spec variant remains task.md).

Tests
- 29 new tests across PersistenceInferrer (7), ImportReceipt (3),
  OpenApiImportRunner (10), OpenApiImportScaffold (2), and
  JournalEntry::openApiImport (1, added to existing JournalEntryTest).
- Full pipeline tested end-to-end against a tmp sandbox.

Out of scope (per #160)
- x-altair-* extensions for round-trippable persistence/queue — #163
- openapi:roundtrip drift gate — #164

Part of #160. Closes #162.
@tonydspaniard tonydspaniard merged commit 15e4b89 into master May 30, 2026
4 checks passed
@tonydspaniard tonydspaniard deleted the feat/162-openapi-import-cli branch May 30, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI: bin/altair openapi:import (with --scaffold, journal + events hookup)

1 participant