mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
## Why Owner deletion admission stops at `submitted`. Manual inventory and approval still stand between owner intent and the existing deletion executor, preventing self-serve deletion. ## What The existing `buzz-admin deletions drain` process prepares and automatically approves operator-attested owner requests. Operator-origin requests retain manual approval. ## How The drain prioritizes approved work, then claims one owner submission with the existing generation lease. It freezes inventory and records digest-bound `owner_automatic` approval in one transaction before entering the unchanged executor. Before approval, the transaction rechecks archived state and current ownership. It locks the deletion advisory fence, community, owner memberships, and request in that order. A regression test pins privileged abort during a live preparation lease. What got simpler: one drain still owns preparation and execution. There is no second worker, queue, lifecycle, or retry authority. The rebased branch contains only this feature and its review fixes. ## Risk Automatic approval permits irreversible deletion without a human grace period. Operator attestation is not a cryptographic owner signature. Provenance, locked authority checks, generation fences, and digest-bound approval constrain the operation. This remains work in progress. Broad test failures and incomplete mobile checks are disclosed below; publication is not a merge-ready verdict. ## Testing Published head: `e059ae9056b447ca3b5d0c2893da86550ef16f5c`. All seven commit trees match tested source `5605f27eaece1e809f746cc1839b55c7ceef8d80`; only attribution metadata changed. Source-SHA results below are not new-head execution receipts. At source commit `5605f27eaece1e809f746cc1839b55c7ceef8d80`, isolated PostgreSQL tests showed stale archive and ownership fixtures fail closed after the fix. Before the fix both reached automatic approval. Removing abort's generation increment or stage transition made the new live-lease regression fail; restored production passed. `just test` failed in two ACP native-git fixtures and one agent body-timeout test. The failing source files match the pinned base, but there is no exact-base execution comparison proving those failures unrelated. `just test-unit` also failed in the ACP fixtures, leaving 18 tests unrun. `just ci` stopped in Flutter tooling package resolution; separately run non-mobile lanes do not constitute a full `just ci` pass. No live tenant deletion or human testing was performed. A dedicated populated `0052` → `0053` approval-backfill fixture remains a coverage gap. Final-head hosted CI and security review are required. ## Bigger picture Rebased directly onto main `12670bd0f037c66a682272bb81c46c3f254fad74`; prerequisite #7818/#7827 are already merged. Main owns `0052_channel_artifacts`, so this feature's automatic-approval migration is now `0053`. Client/KGoose and quota work are separate PRs and are not part of this diff. Generated with Codex --------- Signed-off-by: Codex <noreply@openai.com> Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
158 lines
8.0 KiB
Markdown
158 lines
8.0 KiB
Markdown
# Community Deletion Operator Job
|
|
|
|
Buzz executes whole-community deletion through the typed, one-shot
|
|
`/usr/local/bin/buzz-admin deletions drain` command. The Helm chart can schedule
|
|
that command as a Kubernetes CronJob; it does not call relay HTTP and it does
|
|
not add another queue or retry service.
|
|
|
|
Postgres remains the handoff and source of truth. A run gives already-approved
|
|
work priority. When none is ready, it may claim an operator-attested
|
|
owner-origin request at `submitted`, build the existing bounded inventory, and
|
|
atomically freeze that inventory with a digest-bound `owner_automatic`
|
|
approval. The same lease then enters the unchanged executor and resumes from
|
|
durable checkpoints.
|
|
Operator-origin requests never auto-progress. `concurrencyPolicy: Forbid` prevents scheduled
|
|
pod overlap, `backoffLimit: 0` prevents Kubernetes Job retries, and the deletion
|
|
store remains authoritative when a pod exits, reaches its deadline, or is
|
|
replaced.
|
|
|
|
## Enablement
|
|
|
|
The CronJob is disabled by default. Production deployments should use an
|
|
existing Secret and a dedicated, pre-created service account:
|
|
|
|
```yaml
|
|
secrets:
|
|
existingSecret: buzz-operator-secrets
|
|
|
|
operatorJobs:
|
|
deletionDrain:
|
|
enabled: true
|
|
schedule: "*/5 * * * *"
|
|
activeDeadlineSeconds: 3600
|
|
terminationGracePeriodSeconds: 30
|
|
serviceAccountName: buzz-deletion-drain
|
|
podLabels:
|
|
tags.datadoghq.com/service: buzz-deletion-drain
|
|
podAnnotations:
|
|
sidecar.istio.io/inject: "false"
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
cpu: "1"
|
|
memory: 1Gi
|
|
```
|
|
|
|
Set `s3.endpoint`, `s3.bucket`, `s3.region`, and `s3.addressingStyle` in chart
|
|
values. The selected Secret must contain `DATABASE_URL` and `REDIS_URL`; it may
|
|
contain `BUZZ_S3_ACCESS_KEY` and `BUZZ_S3_SECRET_KEY` when the object store uses
|
|
static credentials. The pod receives only those connection values and the four
|
|
non-secret S3 settings. It does not receive `BUZZ_RELAY_PRIVATE_KEY`,
|
|
`BUZZ_GIT_HOOK_HMAC_SECRET`, `RELAY_URL`, or the full Secret through `envFrom`.
|
|
The pod also disables service-account token automounting and Kubernetes service
|
|
link environment injection because the executor does not call the Kubernetes
|
|
API or discover cluster Services. This disables the ordinary Kubernetes API
|
|
token mount, not credentials injected by a platform workload-identity
|
|
mechanism.
|
|
|
|
Leaving `operatorJobs.deletionDrain.serviceAccountName` empty falls back to the
|
|
relay's own service account, including cloud IAM attached through the platform's
|
|
workload-identity mechanism. For example, the EKS IRSA admission webhook can
|
|
inject its projected web-identity token and AWS environment variables despite
|
|
`automountServiceAccountToken: false`; other platforms provide their own
|
|
identity mechanism. The drain pod therefore inherits the relay's cloud role by
|
|
default. Create a dedicated service account with only the object-store
|
|
permissions listed below and name it explicitly if you want the executor's IAM
|
|
blast radius to be smaller than the relay's.
|
|
|
|
The S3 principal needs the relay's normal object permissions plus bucket-level
|
|
`s3:ListBucketVersions` and object-level `s3:DeleteObjectVersion` for every
|
|
tenant-owned prefix. This also applies to never-versioned buckets because S3
|
|
reports their objects with the `null` version id.
|
|
|
|
## Runbook
|
|
|
|
1. Confirm database migrations are current. For operator-origin requests,
|
|
confirm explicit inventory and operator approval with
|
|
`buzz-admin deletions inspect <request-id>`. For owner-origin requests,
|
|
expect the drain to record `approval_origin: owner_automatic`; `approved_by`
|
|
is the immutable mediating operator, not the owner.
|
|
2. Confirm the selected Secret contains the required keys and the S3 principal
|
|
has version-list and exact-version delete permissions.
|
|
3. Enable the CronJob and inspect its rendered command and environment before
|
|
rollout.
|
|
4. Locate the rendered CronJob by label rather than by guessing its name:
|
|
|
|
```sh
|
|
kubectl get cronjob -n <namespace> \
|
|
-l app.kubernetes.io/component=deletion-drain,app.kubernetes.io/instance=<release>
|
|
```
|
|
|
|
The name is `<bounded-fullname>-deletion-drain`. The chart truncates only the
|
|
fullname portion when needed so the suffix remains stable within Kubernetes'
|
|
52-character CronJob name limit. `buzz.fullname` collapses to the release
|
|
name when the release name already contains the chart name, so
|
|
`helm install buzz ...` renders `buzz-deletion-drain`, not
|
|
`buzz-buzz-deletion-drain`.
|
|
5. Start one staffed manual run with
|
|
`kubectl create job --from=cronjob/<cronjob-name> <job-name>`.
|
|
6. Follow pod logs and re-run `buzz-admin deletions inspect <request-id>` to
|
|
verify lease, checkpoint, retry, blocked, and terminal state.
|
|
7. If a run fails or times out, fix the recorded dependency or permission
|
|
failure. Do not add Kubernetes retries: the next scheduled drain consults the
|
|
durable retry/checkpoint state and resumes only when the store allows it.
|
|
7. Use `buzz-admin deletions abort` as privileged recovery while a request is
|
|
still at `submitted` or `inventoried` when safe preparation cannot continue.
|
|
Abort preserves the archived community and immutable request evidence. An
|
|
operator may also `unblock` a remediated preparation failure.
|
|
|
|
## Deadlines, termination, and the retry budget
|
|
|
|
`activeDeadlineSeconds` is a Kubernetes-side limit, and the deletion store does
|
|
not learn why a pod went away. Two consequences matter when reading state:
|
|
|
|
- A `DeadlineExceeded` Job is not recorded as a deletion retry. Shutdown
|
|
releases the claim without recording one, so `retry_count` and `blocked_at`
|
|
do not advance. Only the object-store drain checkpoints per manifest chunk
|
|
and resumes mid-stage; every other stage restarts from its beginning on the
|
|
next run. A deadline that keeps landing inside one of those non-resumable
|
|
stages therefore repeats indefinitely: each run increments `attempts` and
|
|
burns the window again while the retry budget never moves and the request is
|
|
never blocked.
|
|
- Diagnose this from both sides. `buzz-admin deletions inspect <request-id>`
|
|
shows a rising `attempts` with a flat `retry_count` and no `last_error`;
|
|
Kubernetes holds the reason. The chart labels the CronJob and the drain pods,
|
|
but not the generated Jobs, so find the attempts with
|
|
`kubectl get pods -n <namespace> -l app.kubernetes.io/component=deletion-drain`
|
|
and read the condition with `kubectl describe job <job-name>`.
|
|
|
|
Size `activeDeadlineSeconds` for the longest single stage this community will
|
|
run, not for the average run. To recover, either raise the deadline and let the
|
|
schedule pick the request back up, or take one staffed run with
|
|
`buzz-admin deletions run <request-id>` outside the CronJob's deadline.
|
|
|
|
`terminationGracePeriodSeconds` is a best-effort window, not a guarantee. The
|
|
drain command handles `SIGTERM` and releases its lease cleanly when it wins the
|
|
race, but a pod that is still working when the grace period expires is
|
|
`SIGKILL`ed with the lease still held. Nothing is lost: the durable lease simply
|
|
expires (60s by default, heartbeated every 10s) and the next run reclaims the
|
|
request with a fresh lease generation, which fences any straggler write from the
|
|
killed process. Expect up to roughly a lease duration of delay before the
|
|
request is runnable again; do not raise the grace period expecting a clean
|
|
handoff.
|
|
|
|
Owner self-serve relay admission still records only a `submitted` row and does
|
|
no inventory, approval, S3 work, or execution synchronously. A successful drain
|
|
has no human approval step or cooling-off period: operator-attested owner intent
|
|
is prepared automatically under privileged policy and becomes immediately
|
|
eligible for execution. Transient preparation failures use the existing retry
|
|
schedule; permanent or exhausted failures block durably. Owner-facing
|
|
admission has no cancellation endpoint.
|
|
|
|
The chart has no existing PrometheusRule or provider-neutral CronJob alert
|
|
integration. Operators must alert on failed/missed Jobs and long-running active
|
|
Jobs in their deployment platform. Adding a chart-native alert abstraction is
|
|
debt, not part of this job contract.
|