Refactor/edge apps - Complete POST appinstance and DELETE /appinstances/{appInstanceId} async lifecycle
Complete the POST /appinstances and DELETE /appinstances/{appInstanceId} async lifecycle Context A spec-compliance review against architecture/oeg/ found that POST /appinstances accepted requests and published to the DataBus, but skipped nearly every oeg_db write the flow doc requires, and the event.srm.operation.completed consumer was a stub that only logged incoming events — meaning no async operation ever became observable, retryable, or terminal. DELETE /appinstances/{appInstanceId} had the same gap in reverse: no persistence on the request side, and (once the completion consumer became real) no way to distinguish a terminate completion from a deploy completion.
This MR closes both gaps end-to-end, plus a couple of pre-existing tooling issues found along the way.
POST /appinstances:
- Resolve appId against app_registrations before accepting the request; 400 INVALID_ARGUMENT for an unregistered app (matches the vendored spec's documented response codes for this operation no 404 is listed).
- Wire Operation/AppInstance/CallbackRegistration repositories into the service and DI (previously only AppRegistrationRepository was connected, despite the other repos/adapters already existing).
- Persist a PENDING operations row and an instantiating app_instances row before publishing command.srm.service.deploy, per the flow doc's write-before-publish requirement.
- Idempotency-Key header support (a platform extension, not in the vendored CAMARA spec): a repeated key returns the existing AppInstanceInfo reflecting current instance status via a live oeg_db read, not a stale echo instead of creating a duplicate operation or republishing.
- Persist callback_registrations from subscriptionRequest; sinkCredential is stored only as a secret:// reference, never the raw value (no secret-resolution mechanism exists yet, so nothing beyond the reference can be done with it today).
UC7 completion path (event.srm.operation.completed) The consumer previously did nothing but log. Now:
- EdgeApplicationManagementService.handle_completed updates the operations row (status/result/error/completed_at) and the corresponding app_instances row(s) (ready/failed), including a fallback via operation_id for the case where a total failure carries an empty instances[].
- NatsOperationConsumer is now a thin deserialize-and-delegate adapter instead of doing the DB work itself; main.py opens a session per message since there's no HTTP request to hang one off (mirrors get_session's commit/rollback shape by hand).
- Webhook delivery: a new CallbackDeliveryPort + HttpCallbackClient (deliberately separate from SRMClientPort a per-subscription external URL, not the fixed internal SRM peer) deliver one CloudEvent per updated instance when an active callback_registrations row exists, recording the outcome in callback_deliveries. Delivery failures are caught and recorded, never propagated, so they can't roll back the operations/app_instances updates already made in the same transaction. No Authorization header is sent — the vendored spec's security requirement ([{}, notificationsBearerAuth: []]) makes unauthenticated delivery valid, not just a stopgap for the missing secret-resolution mechanism.
DELETE /appinstances/{appInstanceId}
- Look up the app_instances row first (404 if unknown a documented response code for this operation); write a PENDING operations row (operation_type=TERMINATE) and set app_instances.state=TERMINATING, before publishing command.srm.service.terminate.
- Fixed a bug this surfaced in the completion handler: it previously mapped every completed instance to ready, regardless of what kind of operation produced the completion so a successful termination would have been flipped back to ready instead of removed. handle_completed now branches on operation.operation_type: a TERMINATE completion deletes the app_instances row (no terminal "terminated" state exists in the domain model to mark it with instead); a failed termination still maps to failed, since the instance isn't necessarily gone.