Part of #171. Depends on #174 (spec block).
Goal
Activate the x-altair-idempotency OpenAPI extension end-to-end. The key + JSON Schema already landed in #163; this issue closes the loop now that the corresponding idempotency: spec block exists (#174):
spec:emit-openapi writes x-altair-idempotency when the spec carries the block
openapi:import reads it back and emits the spec block
openapi:roundtrip (#164) gains the extension to its compared set so a regression that drops it fails the gate
Why
Until this lands, an openapi:import of a document with x-altair-idempotency produces a spec without an idempotency: block — meaning the imported endpoint has no idempotency protection even though the source said it should. Worse, the round-trip succeeds silently, because the round-trip gate (#164) deliberately does not compare reserved-but-inactive extensions today.
Once this issue ships, the round-trip is honest end-to-end: a doc that claims x-altair-idempotency produces a spec that claims idempotency:, which scaffolds an Action that actually enforces it.
Changes
Forward (spec:emit-openapi)
Altair\Scaffold\Emitter\OpenApiEmitter::renderAltairExtensions():
if ($spec->idempotency !== null) {
$extensions['x-altair-idempotency'] = [
'ttl' => $spec->idempotency->ttl,
'scope' => $spec->idempotency->scope,
];
// 'mode' is server-side concern; not round-tripped.
}
Reverse (openapi:import)
Altair\Scaffold\Spec\Emitter\OperationMapper::map():
$idempotency = $this->extensionMap($operation, 'x-altair-idempotency');
if ($idempotency !== null && isset($idempotency['ttl']) && is_string($idempotency['ttl'])) {
$spec['idempotency'] = [
'ttl' => $idempotency['ttl'],
'scope' => isset($idempotency['scope']) && is_string($idempotency['scope']) ? $idempotency['scope'] : 'tenant',
'mode' => 'optional', // safe default — source didn't carry it
];
}
Round-trip gate (openapi:roundtrip)
Altair\Scaffold\Cli\OpenApiRoundtripRunner grows x-altair-idempotency in:
- the projection's compared field set
- the per-extension drift check loop
Acceptance criteria
Out of scope
Notes
This is the smallest of the sub-issues but the most load-bearing for the "agent can rely on the spec" property. The drift-gate hookup is what prevents a future refactor from silently breaking idempotency preservation on round-trip.
Part of #171. Depends on #174 (spec block).
Goal
Activate the
x-altair-idempotencyOpenAPI extension end-to-end. The key + JSON Schema already landed in #163; this issue closes the loop now that the correspondingidempotency:spec block exists (#174):spec:emit-openapiwritesx-altair-idempotencywhen the spec carries the blockopenapi:importreads it back and emits the spec blockopenapi:roundtrip(#164) gains the extension to its compared set so a regression that drops it fails the gateWhy
Until this lands, an
openapi:importof a document withx-altair-idempotencyproduces a spec without anidempotency:block — meaning the imported endpoint has no idempotency protection even though the source said it should. Worse, the round-trip succeeds silently, because the round-trip gate (#164) deliberately does not compare reserved-but-inactive extensions today.Once this issue ships, the round-trip is honest end-to-end: a doc that claims
x-altair-idempotencyproduces a spec that claimsidempotency:, which scaffolds an Action that actually enforces it.Changes
Forward (
spec:emit-openapi)Altair\Scaffold\Emitter\OpenApiEmitter::renderAltairExtensions():Reverse (
openapi:import)Altair\Scaffold\Spec\Emitter\OperationMapper::map():Round-trip gate (
openapi:roundtrip)Altair\Scaffold\Cli\OpenApiRoundtripRunnergrowsx-altair-idempotencyin:Acceptance criteria
idempotency: { ttl: 24h }produces an OpenAPI fragment containingx-altair-idempotency: { ttl: 24h, scope: tenant }x-altair-idempotency: { ttl: 24h }imports into a spec with the equivalentidempotency:blockopenapi:roundtripon a fixture withx-altair-idempotencyreports cleanopenapi:roundtrip --checkto exit 1 with akind: extension_driftentry locatingx-altair-idempotencyx-altair-idempotencyout of the "carried through" row into the "round-trips" rowx-altair-idempotencyto the compared-set listingOut of scope
x-altair-webhookactivation — follows the webhook framework epicNotes
This is the smallest of the sub-issues but the most load-bearing for the "agent can rely on the spec" property. The drift-gate hookup is what prevents a future refactor from silently breaking idempotency preservation on round-trip.