Measured while inventorying the DESTRUCTIVE_CHANGE 409's wire faces for #10886. Separate defect from that card, filed rather than fixed in its PR.
What happens
duplicatePackage copies each source row through saveMetaItem (packages/metadata-protocol/src/protocol.ts, currently around :16807) and passes no force. When the copy's target name already holds an object — the ordinary duplicate-again workflow, where the target namespace already carries the renamed object from an earlier duplicate — saveMetaItem's Phase 3a-destructive gate refuses it, and clientFacingFailureText quotes the refusal verbatim onto failed[].error.
Measured through the real protocol (source package com.acme.crm holding crm_task with one field, target namespace crm2 already holding crm2_task with four):
POST /packages/com.acme.crm/duplicate -> 200
{
"success": false, "copiedCount": 0, "failedCount": 1,
"targetPackageId": "com.acme.crm2",
"failed": [{
"type": "object", "name": "crm_task",
"error": "[destructive_change] object/crm2_task would drop or transform existing data: Field 'b' removed — existing data in this column will become inaccessible.; … — re-submit with ?force=true to proceed."
}]
}
Why the last clause is wrong here
The remedy is correct prose for the door the message was written for — PUT /api/v1/meta/:type/:name?force=true, where force is a real query parameter the route reads and threads. It is not correct on this one:
- the duplicate route (
packages/runtime/src/domains/packages.ts, currently around :832) accepts only targetPackageId, targetName, targetNamespace, organizationId and actor — there is no force to set, in the query string or the body;
duplicatePackage's own request type has no force field, and its internal saveMetaItem call hard-codes its absence.
So a caller who does what the sentence says gets the identical refusal. The real remedies on this face are different ones (pick a target namespace that is free, or reconcile the colliding object first), and none of them is stated.
Scope note
This is the message-side twin of #10886's conclusion, not a duplicate of it. #10886 measured that this same failed[].error is the SOLE carrier of the destructive findings — it threads no issues — and therefore declined to trim the message. That verdict is about the per-field prose. This card is about the remedy clause, which is unactionable specifically here regardless of whether the prose stays.
Related: #10888 covers declaring a structured channel on this same failed[] element.
Measured on origin/main at 490879ad0f.
Measured while inventorying the DESTRUCTIVE_CHANGE 409's wire faces for #10886. Separate defect from that card, filed rather than fixed in its PR.
What happens
duplicatePackagecopies each source row throughsaveMetaItem(packages/metadata-protocol/src/protocol.ts, currently around:16807) and passes noforce. When the copy's target name already holds an object — the ordinary duplicate-again workflow, where the target namespace already carries the renamed object from an earlier duplicate —saveMetaItem's Phase 3a-destructive gate refuses it, andclientFacingFailureTextquotes the refusal verbatim ontofailed[].error.Measured through the real protocol (source package
com.acme.crmholdingcrm_taskwith one field, target namespacecrm2already holdingcrm2_taskwith four):Why the last clause is wrong here
The remedy is correct prose for the door the message was written for —
PUT /api/v1/meta/:type/:name?force=true, whereforceis a real query parameter the route reads and threads. It is not correct on this one:packages/runtime/src/domains/packages.ts, currently around:832) accepts onlytargetPackageId,targetName,targetNamespace,organizationIdandactor— there is noforceto set, in the query string or the body;duplicatePackage's own request type has noforcefield, and its internalsaveMetaItemcall hard-codes its absence.So a caller who does what the sentence says gets the identical refusal. The real remedies on this face are different ones (pick a target namespace that is free, or reconcile the colliding object first), and none of them is stated.
Scope note
This is the message-side twin of #10886's conclusion, not a duplicate of it. #10886 measured that this same
failed[].erroris the SOLE carrier of the destructive findings — it threads noissues— and therefore declined to trim the message. That verdict is about the per-field prose. This card is about the remedy clause, which is unactionable specifically here regardless of whether the prose stays.Related: #10888 covers declaring a structured channel on this same
failed[]element.Measured on
origin/mainat490879ad0f.