feat(scaffold): bin/altair openapi:import \xe2\x80\x94 OpenAPI 3.1 \xe2\x86\x92 runnable Univeros project (#162)#168
Merged
Conversation
…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.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
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 intospec:scaffoldto 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
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
JournalEntry::openApiImportfactory) covers both the imported specs and the chained scaffold outputs.journal:rewindon that single id undoes the whole import.EventKind::OpenapiImportrecords one mutation event per run.receipt.rolled_backand the run exits 1.Journal/RecorderInterfacethey're no-ops andjournal_id/event_idstay 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 segmententity.fields= synthesisedidUUID primary + one field per request-body input mapped to a Cycle column typerepository=<appNs>\\<Resource>\\<Resource>RepositoryItem 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)
x-altair-*extensions \xe2\x80\x94 x-altair-* OpenAPI extensions \xe2\x80\x94 round-trippable persistence, queue, webhook, idempotency #163 (the--queueflag is accepted v1 and surfaces a forward-compat warning so adding extension support later isn't a breaking change).Files
src/Altair/Scaffold/Cli/{OpenApiImportCommand,OpenApiImportRunner,OpenApiImportOptions,ImportReceipt,PersistenceInferrer}.phpEventKind::OpenapiImport,JournalEntry::openApiImport(),EmittedFileKind::Specdocs/openapi/import.mdbenchmarks/tokens-to-ship/task-import.md+fixtures/posts.openapi.yamltests/Scaffold/Cli/*andtests/Scaffold/Journal/JournalEntryTest.phpTest plan
composer cs\xe2\x80\x94 greencomposer stan\xe2\x80\x94 greencomposer rector(full tree, no cache) \xe2\x80\x94 greencomposer test\xe2\x80\x94 6204 tests (+29 new), 0 new failures (5 pre-existing environmental errors: MongoDB ext +tsc/mypynot on local PATH)bin/altair manifest:generate\xe2\x80\x94 clean (only.agent/packages/scaffold.mdadds the new classes)users-api.yamland on the new Posts benchmark fixture \xe2\x80\x94 deterministic, idempotent (--forceoverwrite path verified, skip-without---forcepath verified)