Skip to content

POST /packages/:id/duplicate reports "re-submit with ?force=true to proceed" on a door that accepts no force — the remedy names a mechanism unavailable on that face #11015

Description

@os-elon

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.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions