Removing a queue is part of the feature
P6X4's consolidation was not complete when traffic stopped; topics, consumers, dead letters, credentials, alerts, and recovery paths also had to disappear.
I marked P6X4's notification migration complete when the new in-process module handled every scenario.
The old queue still existed.
Nothing published to it during ordinary tests. Its consumer deployment was scaled to zero. The dashboard was quiet. I treated the remaining infrastructure as cleanup and moved on.
Two weeks later, a scheduled compatibility job used an old client and published a message. The broker retained it, the dead-letter policy moved it after repeated expiry, and an alert opened against a service I believed no longer existed.
The code path was gone. The system path was not.
Absence of traffic was not absence of capability
A queue can remain consequential while idle. It has an address publishers may know, a policy for retaining data, credentials that grant access, consumers that can be restarted, and alerts that still claim an owner.
P6X4's service removals initially focused on source code and routing. I expanded the definition of a feature migration:
A boundary is removed only when no supported publisher can address it, no runtime can consume it, no retained message requires it, no credential authorizes it, and no operational control assumes it exists.
That definition turned deletion into a first-class deliverable.
It also changed planning. A proposal that “replaces one queue call with a function” needed to budget for contract inventory, message retention, draining, recovery, credential revocation, topology verification, and documentation. The deletion work was not optional polish after the interesting architecture.
The first obstacle was knowing what existed.
Broker configuration, deployment manifests, application code, infrastructure definitions, dashboards, and runbooks each described part of the topology. None was complete. Some publishers constructed topic names dynamically. One local tool held a credential outside the main deployment specification.
I generated a checked-in topology snapshot with:
- Topic or queue identity.
- Contract names and versions.
- Declared publishers.
- Consumer groups and entry points.
- Retry and dead-letter policy.
- Retention and sensitivity class.
- Credentials permitted to publish or consume.
- Dashboards, alerts, and runbooks.
- Source authority and recovery behavior.
- Planned retirement date, when applicable.
Static analysis found message-client calls and role manifests. Infrastructure definitions supplied broker resources and policies. Runtime startup reported active subscriptions and release identities into a temporary lab registry. The generator compared the views and failed on unowned differences.
The inventory was not a universal discovery system. Dynamic names required explicit declarations, and anything outside the repository could still escape it. For P6X4's controlled environment, it replaced several incompatible lists with one review surface.
Stopping publishers came before stopping consumers
Scaling a consumer down first allowed new messages to accumulate unseen. Deleting a topic first could lose accepted work. I used a staged retirement.
First, every supported publisher moved to the new path or became incapable of issuing the obsolete command. A compatibility facade returned a clear retired-contract error instead of silently forwarding forever. Publisher credentials lost permission for the old destination.
Second, I recorded the final publish sequence and time. The consumer remained active until it had processed or classified every message through that boundary.
Third, the consumer entered drain mode. It claimed no work beyond the final sequence, completed current leases, and produced a terminal retirement report.
Only then did I remove the subscription and destination.
The order made new publication a detectable violation. If a message appeared after the final sequence, retirement stopped and the inventory showed which identity had published it.
Retained messages needed semantic disposition
“Drain the queue” sounded simple until I inspected the messages.
Some commands remained valid and could complete. Some referred to document revisions superseded during migration. Some had already produced an effect but lacked a consumption receipt. Several dead letters used an older contract the current consumer no longer understood.
I classified each retained item:
- Complete: process through the supported handler and record the result.
- Superseded: close the command with a durable reason and no effect.
- Duplicate: link it to an existing stable intent or receipt.
- Unknown outcome: query evidence or move to explicit recovery; do not retry blindly.
- Unsupported contract: use a retained handler or a reviewed migration.
- Corrupt or unauthenticated: quarantine with evidence.
Deleting the queue did not authorize throwing away ambiguity. The retirement report linked every original identity to a terminal state or a live recovery record.
This reused Z29C's strongest lesson: transport state is not workflow state. An empty queue can coexist with missing outcomes if messages were acknowledged carelessly.
The dead-letter destination contained old payloads, error details, and headers copied from failed deliveries. It also retained document identifiers longer than the main command stream.
I had thought of it as debugging infrastructure. Its data lifecycle was part of the product boundary.
Before deletion, I exported only records still needed for recovery into P6X4's structured recovery ledger. The ledger stored the minimum evidence, a sensitivity classification, retention rule, and reference to the original workflow. Raw payloads with no remaining purpose expired under their declared policy.
I tested that a deleted document did not remain reconstructable from abandoned dead-letter payloads. Q2F8's inventory rules applied to operational debris as well as primary tables.
Once every retained item had a disposition, the dead-letter queue could be removed. Keeping it indefinitely “just in case” would have preserved both sensitive data and a false recovery path.
Retry policy was attached to the old topology
The consumer deployment contained exponential backoff and maximum attempts. The broker subscription also had a redelivery policy. The handler performed one adapter retry internally. Together they could produce more attempts than any one configuration suggested.
Moving notification into P6X4 required an explicit new retry policy based on stable effect identity and adapter capability. I did not copy old numbers mechanically.
During retirement, I disabled internal retries in the compatibility consumer and let one durable policy decide. Otherwise, a draining message could create additional effects just as I tried to establish its terminal state.
The topology inventory displayed effective attempt layers rather than listing each in isolation. Removing a queue meant removing its implicit retry behavior from the system, not only its storage resource.
After routes and consumers disappeared, three identities could still publish to the topic: the retired service, a migration tool, and an overly broad general application role.
I revoked all three grants before deleting the destination. The new module used no broker permission for its local call. If a compatibility publisher tried to run, authorization failed immediately and logged the obsolete identity.
The general role exposed a larger issue. It could publish to every P6X4 topic because early setup favored convenience. I replaced it with per-role permissions derived from runtime manifests. Removing one destination then produced a meaningful assertion: no active identity had unexplained messaging authority.
Secret deletion followed access revocation. Build and deployment definitions stopped injecting broker configuration into roles that no longer needed it. A scan of release manifests checked that the old destination and credentials did not appear.
Unused permission is still a system capability. Retirement closed it.
Dashboards and alerts could preserve a false model
The old service dashboard grouped notification latency, consumer restarts, dead-letter depth, and broker errors. After migration, parts of that information still mattered but its ownership changed.
I did not delete the dashboard blindly. I mapped every question:
- Notification intents without terminal effects moved to P6X4's workflow view.
- Adapter latency moved to the delivery capability.
- Broker consumer health disappeared because the consumer disappeared.
- Dead-letter depth became structured recovery age.
- Release identity moved to the shared artifact view.
Alerts followed the questions. Any alert whose only remediation was “restart the old consumer” was removed. New alerts pointed to a supported recovery action and current runbook.
Leaving obsolete monitoring would have made a future incident harder: the interface would invite action against a mechanism no longer authoritative.
The notification runbook contained queue names, commands, dashboards, and a warning about duplicate sends. I marked it retired and linked the replacement recovery path.
More importantly, I added a link checker that understood internal operational references. Topic names, runtime roles, and dashboard identifiers used by a runbook had to exist in the topology snapshot unless the document was explicitly historical. A deleted queue therefore broke stale current instructions.
I preserved a short migration record explaining why the path disappeared and where its responsibilities moved. I did not keep an executable command for reviving the old consumer.
Documentation deletion is easy to postpone because it causes no immediate runtime error. Making references testable turned it into part of the same change.
Local fixtures were part of the topology
One test helper still started the retired consumer and published old messages. It passed because it built an isolated miniature of architecture that no longer existed.
I inventoried development and test fixtures alongside deployed resources. Contract tests for historical messages remained only where P6X4 promised to consume retained versions. End-to-end scenarios changed to the new local module or effect protocol. Seed scripts stopped creating old topics.
This prevented a subtle reversal: new work copied patterns from old tests because they were easier to discover than the current design.
Deleting a fixture was not erasing history. The migration decision record and version-control history remained. The active test suite described the architecture I intended to maintain.
The retirement scenario intentionally kept one old script dormant until after publisher permissions were removed. When it woke, the publish failed with an explicit authorization error and its identity appeared in the topology violation log.
I then tried a worse case: a client that cached a short-lived credential before revocation. The broker policy rejected the destination after a retirement epoch even if the credential was otherwise valid. The old topic accepted no post-fence messages.
Another scenario restored a backup containing the deleted consumer configuration. Deployment validation compared it with the current topology generation and refused to start the obsolete role.
These drills made removal resilient to stale state, not just the clean present.
Billing and quotas were architecture evidence
Even in a small lab, resources had measurable cost: retained messages consumed storage; dormant deployments reserved configuration and image history; metrics created series; backup policies copied unused databases.
I added these resources to the deletion checklist rather than claiming a dramatic savings number. After retirement, the broker namespace no longer listed the topic, monitoring no longer collected its metrics, backup inventory no longer referenced its store, and preview environments stopped provisioning it.
The absence was independently observable.
Cost reporting helped because it listed artifacts code search missed. A quiet resource with a recurring charge or quota allocation was a clue that the boundary still existed somewhere.
Deleting infrastructure is less reversible than routing traffic away. I staged destructive steps after the new path passed its rollback window.
Before that point, the consumer was stopped and credentials restricted, but retained definitions could be restored under an explicit migration procedure. After every old message had a disposition and the new module had handled failure scenarios, the broker resource and secrets were deleted.
Rollback after deletion meant restoring the new P6X4 capability or applying a forward fix. It did not mean silently recreating the old queue from memory.
The decision record named that boundary. This prevented fear of deletion from keeping a shadow architecture indefinitely.
A topology test made absence durable
The final check compared the desired topology snapshot with infrastructure, runtime manifests, and static message references. It also maintained a denylist of retired identifiers. Reintroducing notification.requested.v1 or the old queue name failed with a link to the retirement record.
The denylist was intentionally small and dated. It protected against accidental resurrection during the consolidation period, not every name ever used. After old clients and restore points expired, the rule could become historical documentation.
I added an end-to-end scenario that created a document, rendered it, delivered it through a stub, and observed the notification effect without provisioning the retired topic. A green result demonstrated not only that the new path worked but that the old resource was unnecessary.
The notification feature had been functionally migrated weeks before it was architecturally migrated.
Only after the final pass had these statements become true:
- No supported publisher could address the old command.
- No retained message lacked a semantic outcome.
- No runtime could restart the old consumer.
- No credential permitted the old capability.
- No sensitive payload remained without purpose.
- No dashboard or runbook recommended an obsolete action.
- No fixture taught the old topology as current.
- No preview or backup recreated it silently.
The queue disappeared from code, infrastructure, operations, and recovery.
That work did not produce a new interface. It made the smaller architecture real.
Removing a queue is not cleanup after the feature. When the feature is architectural subtraction, removal is the feature.
Restore procedures could resurrect retired infrastructure
The backup inventory contained deployment configuration captured before the migration. Restoring the application database was safe; restoring the complete environment snapshot could recreate the notification topic, consumer identity, and broad broker grants.
I versioned topology separately from data recovery. A restore loaded durable domain and workflow records into infrastructure generated from the current approved topology. Historical deployment definitions were evidence, not executable authority. The restore tool rejected runtime roles and destinations marked retired after the backup's creation.
For retained messages, the recovery ledger—not a recreated dead-letter queue—held the supported disposition. A restore test started from a pre-retirement data snapshot and current infrastructure, ran migrations, and confirmed that notification intents resumed through P6X4's new effect path.
This distinguished recovering user-meaningful state from reviving every mechanism that once carried it.
P6X4 controlled nearly every publisher, but I treated compatibility scripts as if they were external clients. The old message contract received a retirement date, last accepted contract version, and a replacement command path.
During the transition, a small adapter translated supported old requests into new stable intents and returned a receipt. It rejected fields whose old semantics could not be preserved. The adapter produced usage records by client identity so I could see when the last caller stopped.
After the deadline, the broker policy denied publication. The adapter code remained only through the rollback window and then disappeared. I did not leave a translation layer indefinitely because it was cheap to run; it preserved the old contract's conceptual weight and test burden.
Even inside one personal project, treating a boundary as a public promise improved retirement discipline.
Ownership had to move before alerts disappeared
An old alert can be noisy, but deleting it too early removes evidence during the risky window. I mapped each alert to a new observable question before turning the old one off.
For example, queue depth became oldest notification intent without a terminal receipt. Consumer error rate became classified effect failures by adapter. Redelivery count became duplicate intent observations. The new measures followed the work rather than the transport and remained meaningful after the broker path vanished.
I ran both sets during one synthetic comparison period. Differences exposed a bug: messages classified as superseded disappeared from the old success/error dashboard but remained correctly visible as terminal workflow outcomes in P6X4.
Only when every actionable old signal had a current home did I delete the monitoring surface.
Deletion deserved code review quality
The final removal patch deleted more lines than the functional migration added. It touched infrastructure, permissions, tests, documentation, dashboards, retention, and restore fixtures. I reviewed it by consequence rather than skimming because “only deletion” sounded safe.
The checklist asked whether any removed artifact was the sole evidence for an unresolved outcome, whether another runtime still referenced it indirectly, whether backup retention changed, and whether the new topology had a tested recovery action.
Subtraction can be dangerous when it removes the last copy of meaning. It is valuable when it removes mechanisms after meaning has moved somewhere deliberate.
That distinction made deletion an engineering review, not a ceremonial sweep.