Summary
Make the asynchronous Service deletion lifecycle explicit and machine-readable across axern service delete, service get, and service list.
The current control-plane model already persists ServiceDeletionStatus, and an exact service get <id> can retrieve a deleted Service. The CLI output layer, however, drops deletion_status; the delete command always prints Service deleted, even though deletion is asynchronous; and the list help does not explain that its default view excludes terminal Services or that --status deleted is the audit lookup path.
This made a successful HK acceptance look like a failed deletion check: service list and agent list no longer showed the Service, while service get <id> showed status=deleted and a deletion-completed latest event. Automation had to infer lifecycle completion from the event because the persisted deletion phase was absent from CLI JSON.
Current behavior
axern service delete <id> calls the asynchronous delete API once, then unconditionally prints Service deleted: <id>.
axern service get <id> -o json uses a CLI-specific ServiceJSON projection that omits the proto's Service.deletion_status field.
axern service list defaults to the active/non-terminal view.
axern service list --status deleted already retrieves deleted audit records, but this is not discoverable from the command description.
- Exact
service get <id> remains available after deletion, which is the desired audit behavior until admin service purge is explicitly invoked.
Relevant code:
apps/cli/internal/commands/service/command.go
apps/cli/internal/output/service_json.go
sdk/proto/axern/control/service/v1/service_types.proto
Long-term direction
Keep deletion asynchronous and keep terminal Services hidden from the default list. Make those semantics explicit rather than adding a second list mechanism or a compatibility output path.
- Add a typed deletion lifecycle projection to CLI Service JSON, derived directly from
ServiceDeletionStatus.
- Include deletion phase, volume disposition, claim IDs, message, and completion timestamp when present.
- Change the immediate human-readable delete result to state that deletion was requested unless the returned Service is already complete.
- Add
--wait and --wait-timeout to service delete, using the persisted deletion phase as the completion contract rather than the latest event text.
- Document in
service list --help that the default view excludes terminal records and that --status deleted retrieves them.
- Keep
service get <id> as the exact audit lookup and admin service purge <id> as the only permanent record removal path.
Acceptance criteria
service get <id> -o json exposes a stable deletion_status object whenever the control plane returns one.
service delete <id> no longer reports an asynchronous request as unconditionally completed.
service delete <id> --wait --wait-timeout <duration> returns successfully only after SERVICE_DELETION_PHASE_COMPLETE; timeout and lifecycle errors return non-zero.
service list --help describes the terminal-record default and gives --status deleted as the retrieval path.
- Table and JSON output distinguish
deleting from deletion-complete without parsing event messages.
- Tests cover request-only deletion, waited completion, timeout, JSON projection, and explicit deleted-status listing.
- Public API, persisted lifecycle semantics, default list behavior, and purge authorization remain unchanged.
Non-goals
Summary
Make the asynchronous Service deletion lifecycle explicit and machine-readable across
axern service delete,service get, andservice list.The current control-plane model already persists
ServiceDeletionStatus, and an exactservice get <id>can retrieve a deleted Service. The CLI output layer, however, dropsdeletion_status; the delete command always printsService deleted, even though deletion is asynchronous; and the list help does not explain that its default view excludes terminal Services or that--status deletedis the audit lookup path.This made a successful HK acceptance look like a failed deletion check:
service listandagent listno longer showed the Service, whileservice get <id>showedstatus=deletedand adeletion-completedlatest event. Automation had to infer lifecycle completion from the event because the persisted deletion phase was absent from CLI JSON.Current behavior
axern service delete <id>calls the asynchronous delete API once, then unconditionally printsService deleted: <id>.axern service get <id> -o jsonuses a CLI-specificServiceJSONprojection that omits the proto'sService.deletion_statusfield.axern service listdefaults to the active/non-terminal view.axern service list --status deletedalready retrieves deleted audit records, but this is not discoverable from the command description.service get <id>remains available after deletion, which is the desired audit behavior untiladmin service purgeis explicitly invoked.Relevant code:
apps/cli/internal/commands/service/command.goapps/cli/internal/output/service_json.gosdk/proto/axern/control/service/v1/service_types.protoLong-term direction
Keep deletion asynchronous and keep terminal Services hidden from the default list. Make those semantics explicit rather than adding a second list mechanism or a compatibility output path.
ServiceDeletionStatus.--waitand--wait-timeouttoservice delete, using the persisted deletion phase as the completion contract rather than the latest event text.service list --helpthat the default view excludes terminal records and that--status deletedretrieves them.service get <id>as the exact audit lookup andadmin service purge <id>as the only permanent record removal path.Acceptance criteria
service get <id> -o jsonexposes a stabledeletion_statusobject whenever the control plane returns one.service delete <id>no longer reports an asynchronous request as unconditionally completed.service delete <id> --wait --wait-timeout <duration>returns successfully only afterSERVICE_DELETION_PHASE_COMPLETE; timeout and lifecycle errors return non-zero.service list --helpdescribes the terminal-record default and gives--status deletedas the retrieval path.deletingfrom deletion-complete without parsing event messages.Non-goals
--include-deleted; the existing repeatable--statusfilter is sufficient.latest_event.typeas the canonical deletion state; events remain diagnostic/audit data whileServiceDeletionStatusis the lifecycle contract.