Skip to content

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

@tonydspaniard

Description

@tonydspaniard

Part of #160. Depends on #161 (spec emitter).

Goal

Add bin/altair openapi:import \xe2\x80\x94 a Symfony Console command that reads an OpenAPI 3.1 document, writes Altair YAML specs via the emitter (#161), and optionally chains into spec:scaffold to produce a runnable project. Emits a JSON receipt for agents and records a mutation event + journal entry so the operation is observable and reversible.

Why

The emitter (#161) is the engine. This issue is the headline: "one command, OpenAPI \xe2\x86\x92 working project." Without it, the library piece sits unused; with it, the porting-from-an-existing-API workflow becomes a single command and the tokens-to-ship benchmark gains a credible import-then-scaffold variant.

CLI surface

bin/altair openapi:import openapi.yaml                       # \xe2\x86\x92 ./api/, specs only
bin/altair openapi:import openapi.yaml --out=specs/
bin/altair openapi:import openapi.yaml --scaffold            # chain into spec:scaffold
bin/altair openapi:import openapi.yaml --scaffold --persistence=cycle
bin/altair openapi:import openapi.yaml --scaffold --queue=redis
bin/altair openapi:import openapi.yaml --dry-run             # report, write nothing
bin/altair openapi:import openapi.yaml --format=json         # structured receipt
bin/altair openapi:import openapi.yaml --force               # overwrite existing files

Receipt shape (--format=json)

{
  \"ok\": true,
  \"input\": \"openapi.yaml\",
  \"specs_written\": [
    \"api/posts/create.yaml\",
    \"api/posts/get.yaml\",
    \"api/posts/list.yaml\",
    \"api/posts/update.yaml\",
    \"api/posts/delete.yaml\"
  ],
  \"scaffolded\": true,
  \"scaffold_journal_id\": \"01J5...\",
  \"event_id\": \"01J5...\",
  \"unmapped\": [],
  \"warnings\": []
}

Failures use the same envelope with ok: false, a non-empty unmapped[], and a non-zero exit code. The receipt is byte-stable for the same input \xe2\x80\x94 golden-file-safe for agent acceptance.

Behavior

  • Resolves Altair\Scaffold\Spec\Emitter\Emitter from the container
  • Records an openapi.import event via Altair\Events\Contracts\RecorderInterface (see .altair/events.jsonl)
  • When --scaffold is set: writes a Altair\Scaffold\Journal\Journal entry covering both the imported specs and the downstream scaffolded files, so journal:rewind undoes the whole import in one step
  • When --scaffold --persistence=cycle is set: infers an entity name from each operation's response schema and writes a persistence: block into the emitted YAML before scaffolding
  • When --scaffold --queue=<transport> is set: emits a queue: block for operations that carry the x-altair-queue extension (extension handling lands in the extensions issue; this command just respects the block if it's present)
  • Existing files are never clobbered without --force; conflicts are reported in the receipt

Acceptance criteria

  • openapi:import petstore.yaml produces a directory of spec files that pass spec:lint
  • openapi:import petstore.yaml --scaffold produces a project that boots and serves at least one operation end-to-end
  • --dry-run writes no files and reports a clean plan
  • --format=json receipt is byte-stable for the same input (golden test)
  • Existing files are not clobbered without --force; conflicts surface in the receipt
  • One openapi.import event lands in .altair/events.jsonl per run
  • When --scaffold is set, a single journal entry covers the whole operation; journal:rewind undoes both the imported specs and the scaffolded files
  • Unit + integration tests; integration runs openapi:import --scaffold against a Petstore fixture and asserts files, events, journal, and scaffolded artifacts
  • Docs page lands at docs/openapi/import.md and follows the per-package template
  • One benchmark task variant added to benchmarks/ exercising the import-then-scaffold path

Out of scope

Notes

Honour the project convention: the journal entry + event must be written after the operation succeeds, never speculatively. If --scaffold is requested and the downstream scaffold fails, the imported specs are rolled back (or, if --force was passed and rollback would clobber a hand-edited file, the receipt explains what wasn't undone).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions