feat(oabctl): programmatic delete API with explicit target contract#1415
feat(oabctl): programmatic delete API with explicit target contract#1415chaodu-agent wants to merge 9 commits into
Conversation
Designs the delete contract deferred by the lib-split review, symmetric
with apply_manifests:
- delete_services(&SdkConfig, &[DeleteTarget], &DeleteOptions)
-> Result<DeleteReport, DeleteError>
- DeleteOptions requires an explicit cluster and shares apply's bucket
resolution chain (explicit -> OAB_CONTROL_PLANE_BUCKET -> account) so
delete always cleans the bucket apply wrote to; never reads CLI config
- Contract guards before any AWS call: non-empty target set, non-empty
cluster, non-blank namespace/name (regression-tested without AWS)
- Typed DeleteError {Validation, Target, Teardown} preserving the report
of targets completed before a failure; teardown remains resumable
- Best-effort ingress skips surface in DeletedService.warnings; S3
cleanup failures still fail the target (safe to retry)
- delete.rs output now routes through the shared progress gate: CLI
behavior unchanged, programmatic calls are silent
- CLI paths (delete <name>, delete -f) unchanged
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- needless_borrow in apply's registry-detach wait - allow too_many_arguments on validate_checkpoint / prepare_identity / ingress::delete_exact — these deliberately thread the full deployment identity (partition/account/region/cluster/bucket) as separate explicit parameters; bundling them would obscure the F1 ownership contract - box PreparedIdentity::Exact payload (large_enum_variant) No behavior change; 90 tests + doc-test pass, release build clean.
… refactor/oabctl-delete-api # Conflicts: # operator/src/apply.rs # operator/src/delete.rs
- apply: redundant_guards — match empty failures with a slice pattern - ingress: cloud_map_service_id_from_arn became test-only after the exact-identity merge (all production paths use the boundary-pinned parser); mark it #[cfg(test)] with a note instead of carrying dead code Verified with the CI toolchain (rustc 1.97.1): clippy -D warnings clean, 96 tests + doc-test pass.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
chaodu-agent
left a comment
There was a problem hiding this comment.
Important
CHANGES REQUESTED
Consolidated review: #1415 (comment)
GitHub event: COMMENT — self-review delivery only; this is not an approval.
| ))); | ||
| } | ||
| for target in targets { | ||
| if target.namespace.trim().is_empty() || target.name.trim().is_empty() { |
There was a problem hiding this comment.
🔴 F1 — Make the target-to-resource identity injective
Blank-only validation allows distinct targets such as prod/team-bot and prod-team/bot to derive the same ECS, API Gateway, and Cloud Map names. On the first delete, prepare_identity can therefore resolve the other logical target’s live ECS service and mint a fully valid exact-resource checkpoint for the wrong pair.
Requested change: use a reversible/injective physical identity (or immutable logical-identity metadata verified before checkpoint creation) and add a regression proving distinct namespace/name pairs cannot collide.
| .await? { | ||
| PreparedIdentity::Exact(boxed) => { | ||
| let (checkpoint, state) = *boxed; | ||
| delete_checkpointed_ecs(&ecs, &checkpoint, state).await?; |
There was a problem hiding this comment.
🔴 F2 — Fence post-ECS cleanup against concurrent apply
Once this call observes the old incarnation gone, the code immediately deletes checkpointed API/Cloud Map resources and name-keyed S3 state. apply does not read delete-checkpoints and can reuse those dependency IDs or write a new manifest in this window, after which this delete removes the recreated deployment’s resources/state.
Requested change: introduce a target-scoped generation/lease/tombstone protocol shared by apply and delete, with conditional S3 mutation, and test an apply interleaving after ECS drain completion.
|
Caution This PR has been waiting on the author for more than 2 days (labeled @chaodu-agent — You must add a new comment on this PR to remove the |
This comment has been minimized.
This comment has been minimized.
chaodu-agent
left a comment
There was a problem hiding this comment.
Important
CHANGES REQUESTED
Consolidated review: #1415 (comment)
Contract freeze: RC-1 at 1bd61e825e3f2e1308bde3be6294e10b0e3bead6; SHA-256 a2377a772fc9ba7f4856cea6af2cc6f6596dba919f12256295fd65516de3c674.
GitHub event: COMMENT — self-review delivery only; this is not an approval.
| ))); | ||
| } | ||
| for target in targets { | ||
| if target.namespace.trim().is_empty() || target.name.trim().is_empty() { |
There was a problem hiding this comment.
🔴 F1 — Make logical target identity injective
Validation accepts both (prod, team-bot) and (prod-team, bot), although both derive oab-prod-team-bot; an initial lookup can therefore checkpoint and delete the other logical target. This violates RC-1 AC-2 before exact checkpoint validation can help.
Requested change: use one injective mapping shared by apply/delete, or reject every ambiguous component shape before AWS calls. A batch-only duplicate check is insufficient because separate calls must also be safe. Add the colliding-pair regression.
| &checkpoint.region, | ||
| ) | ||
| .await?; | ||
| cleanup_s3(&s3, bucket, namespace, name).await?; |
There was a problem hiding this comment.
🟡 F2 — Remove stale apply ingress-teardown state on successful delete
This success path removes the manifest/artifacts and delete checkpoint, but not ingress-teardown-checkpoints/{namespace}/{name}.json. If a prior apply was interrupted after writing that key, delete can report success while the stale key deterministically blocks re-apply.
Requested change: after exact dependent cleanup succeeds, remove the target apply-side teardown checkpoint without weakening retry safety, and test interrupted ingress teardown → delete → re-apply eligibility.
| Ok(()) | ||
| } | ||
|
|
||
| /// Tear down OAB services programmatically, without reading CLI home |
There was a problem hiding this comment.
🟡 F3 — Document the required same-target serialization boundary
RC-1 accepts the lack of an internal apply/delete fence only when callers serialize the full account/Region/bucket/cluster/namespace/name target. The public Rustdocs and user docs do not state this mandatory safety precondition.
Requested change: document the serialization and recovery rule for both apply_manifests and delete_services, while retaining the already-documented no-orphan behavior.
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn delete_services_rejects_empty_target_set() { |
There was a problem hiding this comment.
🟡 F4 — Add the contract-required regression evidence
The current tests cover fail-closed identity responses and checkpoint boundaries, but not logical-name collision prevention or the public partial-report promise when a later target fails.
Requested change: add a colliding-pair test plus a two-target failure test proving the first completion remains in DeleteError.completed and processing stops deterministically; include the F2 checkpoint cleanup sequence.
This comment has been minimized.
This comment has been minimized.
chaodu-agent
left a comment
There was a problem hiding this comment.
Important
CHANGES REQUESTED
Consolidated review: #1415 (comment)
GitHub event: COMMENT — self-review delivery only; this is not an approval.
| target.name | ||
| ))); | ||
| } | ||
| if target.namespace.contains('-') { |
There was a problem hiding this comment.
🔴 F1 — Apply and delete still operate over different logical identity domains
This guard rejects hyphenated namespaces only for programmatic delete, while manifest/apply validation still accepts them and CLI delete bypasses this function. Therefore prod/team-bot remains an accepted delete alias for a deployment created as prod-team/bot; both derive oab-prod-team-bot, and their logical checkpoint keys differ.
Requested change: enforce one shared injective identity/ownership rule before AWS calls across manifest/fleet apply and every programmatic and CLI delete entry point, with an explicit legacy migration policy and a cross-entry-point regression.
Apply and delete previously validated logical identity independently: the hyphen-free namespace guard existed only in programmatic delete, so apply could still create 'prod-team/bot' while 'prod/team-bot' remained a valid delete target — both resolving to the physical name 'oab-prod-team-bot' (round-3 review finding F1). A new shared module, operator/src/identity.rs, now owns the rule: - Domain rule: validate_injective_identity (non-empty components, hyphen-free namespace) is enforced before any AWS call by manifest validation (CLI apply, fleet expansion, apply_manifests) and by delete_services. - Ownership rule: ensure_exclusive_physical_identity probes the exact control-plane keys (manifest, delete checkpoint, ingress-teardown checkpoint) of every colliding alias pair before mutation, in apply_ecs and in the shared delete choke point run_with_bucket (CLI + programmatic). Contested physical identities fail closed naming the colliding pair. - All physical name and control-plane key derivations (ECS/Cloud Map names, manifest/checkpoint keys, scale, ingress) route through the shared module so probe and mutation cannot drift. - Manifest schema namespace pattern tightened to ^[a-z0-9]+$. BREAKING CHANGE / legacy policy: manifests with hyphenated namespaces are rejected at apply with a migration message. Existing hyphenated-namespace deployments stay deletable via 'oabctl delete' (warning + ownership check); migrate by deleting and re-creating under a hyphen-free namespace. Tests: identity module unit tests plus cross-entry-point regressions in manifest, apply, and delete proving colliding logical pairs cannot overwrite, checkpoint, or delete one another. cargo clippy --all-targets -- -D warnings and cargo test (113 + 1 doctest) pass.
This comment has been minimized.
This comment has been minimized.
chaodu-agent
left a comment
There was a problem hiding this comment.
Note
LGTM ✅ — The exact requested head closes the prior identity-collision and teardown-safety findings; no new in-scope blockers were found.
Consolidated review: #1415 (comment)
GitHub event: COMMENT — self-review delivery only; this is not an approval.
|
Note LGTM ✅ — The round-3 blocker is closed: apply and every delete entry point now share one injective identity rule plus a pre-mutation control-plane ownership check, with an explicit legacy-namespace policy. What This PR DoesAdds a public in-process How It WorksEach target resolves to one live ECS service. Before mutation, delete stores a durable S3 checkpoint (caller boundary, bucket, canonical cluster/service ARNs, ECS New in this revision, a shared
Findings
Finding Details🟢 F1 (round-3) — Shared injective identity/ownership rule, verified per entry pointIndependent exact-SHA audit of
ℹ️ Residual notes (within RC-1's accepted risks, documented)
Baseline Check
VerifiedOn the exact reviewed head
Previous Review and External FeedbackRound-3 consolidated review
✅ Addressed in Prior real-AWS verification
✅ Still valid for the canonical path; this revision adds only pre-mutation validation/probing before that path, plus read-only S3 probes that fail closed. Automated stale-contributor noticeOperational only; superseded by current PR activity. No other external reviewer findings were present on the reviewed head. What's Good (🟢)
5️⃣ Three Reasons We Might Not Need This PR
|
chaodu-agent
left a comment
There was a problem hiding this comment.
Note
LGTM ✅ — Round-3 F1 (non-injective apply/delete identity) is closed on head be006ec by a shared identity module enforced at every apply and delete entry point, with an explicit legacy-namespace policy and cross-entry-point regressions.
Consolidated review: #1415 (comment)
Summary
Designs and implements the programmatic delete contract that the #1404 review intentionally deferred ("until their programmatic contracts are designed independently"). This PR is stacked on
refactor/oabctl-lib.The API is symmetric with
apply_manifests:DeleteOptionsrequires an explicit cluster; the library path never reads~/.oabctl/config.toml.OAB_CONTROL_PLANE_BUCKET→oab-control-plane-{account}.DeleteError { Validation | Target | Teardown }preserves theDeleteReportfor targets completed before a failure.Review Contract
Contract revision: RC-1
Goal
Provide a public, in-process Rust API that safely deletes one or more explicitly identified OAB ECS deployments without reading CLI home configuration. A successful result must mean that the checkpointed ECS incarnation and its exact ingress dependencies are gone and the target's control-plane manifest/artifacts are removed. Ambiguous ownership or incomplete teardown must fail closed with a structured, retryable error rather than report false success or delete another deployment.
Non-goals
apply_manifestsanddelete_servicescalls for the same logical target; callers must serialize same-target mutations as described under Accepted Residual Risks.apply_manifestsAPI, changing existing CLI UX, or introducing a general distributed workflow engine.Accepted Residual Risks
(AWS account, Region, control-plane bucket, cluster, namespace, name)tuple. The intended NEST control-plane caller uses a per-target single-flight/lock. If this precondition is violated, recovery is to stop concurrent writers, inspect the retained checkpoint, re-apply the intended desired state if apply should win, or retry delete if delete should win. A shared generation fence remains a follow-up.Acceptance Criteria
delete_services,DeleteTarget,DeleteOptions,DeletedService,DeleteReport,DeleteError, andDeleteErrorKindwithout changing the existing CLI command surface.(namespace, name)pairs must not resolve to the same ECS/API/Cloud Map identity.~/.oabctl/config.toml, and resolves the control-plane bucket using the same explicit option → environment → caller-account chain as apply.createdAtincarnation, and exact registry/API identifiers when ingress exists.DeleteReportcontains only fully completed targets.DeleteErroridentifies validation/target/teardown phase, preserves prior completed targets, identifies the failed target for teardown failures, and retains the checkpoint whenever retryable cleanup remains.cargo testandcargo clippy --all-targets -- -D warningspass for the operator crate on the reviewed head.Follow-ups
CLI
No intended behavior change:
oabctl delete <name>andoabctl delete -f <path>retain config-file cluster resolution, printed progress, and aggregate failure reporting.Motivation
NEST's manager needs
/deletefor tenant deprovisioning. Webhook removal and Telegram-side disclosure remain in the control plane, while ECS, ingress, and S3 teardown reuse this API in-process, matching how the manager already consumesapply_manifests.Validation Evidence
54bd8f28eda19c3bd209789be89c44740d3a55f1.Review Contractvalidation andoperatorCI passed; the operator job completedcargo check,cargo clippy,cargo test, andcargo buildsuccessfully.1bd61e8..54bd8f2found all round-2 findings resolved with no new in-scope blocker.