Measured while implementing #14095 (the insert door's DUPLICATE_RECORD envelope). This is a metadata-protocol consequence of that change, outside that card's declared file surface, filed rather than ridden on its PR. It must be routed before or with #14095: two pins in packages/runtime go red without it.
What moved, and why it is the designed behaviour rather than an accident
Both sinks decide disclosure by a POSITIVE test — "would the boundary that serves this throw call it a client refusal?", answered by resolveThrownHttpError:
clientFacingRowFailureText (packages/metadata-protocol/src/protocol.ts): if (status >= 400 && status < 500) return err.message — otherwise the generic The of this record failed. The reason is in the server log.
quotableSeedFailureDetail / declaresSeedClientRefusal (packages/metadata-protocol/src/seed-loader.ts): the same shape, otherwise WITHHELD_WRITE_REASON.
The insert door's envelope declares status: 409, so a duplicate row is no longer in the withheld population. That is the sink's own stated remedy — its docblock says the undeclared case is withheld deliberately and "the remedy is at the producer … declaring is cheaper than the workaround, which is the direction that makes authored code hard to get wrong". So the DISCLOSURE half looks correct and is arguably the point.
Measured, on real drivers through the real doors:
batch row before: { code: 'INTERNAL_ERROR', message: 'The create of this record failed. The reason is in the server log.' }
batch row after : { code: 'DUPLICATE_RECORD', httpStatus: 409,
message: "Duplicate record refused on 'bd_note': a unique constraint on 'email' already holds this value. No record was written." }
seed row before: 'Failed to write dt_acct record #1 (name=second): the data engine rejected the write; the reason is in the server log'
seed row after : "Failed to write dt_acct record #1 (name=second): Duplicate record refused on 'dt_acct': a unique constraint on 'email' already holds this value. No record was written."
⭐ No leak is introduced. The envelope's message carries no statement, no bound values and no dialect text — the driver's error stays whole on cause. Every leak assertion in both suites (not.toContain('insert into'), 'dup@example.com', 'UNIQUE constraint failed', 'SQLITE_CONSTRAINT') still holds against the new sentence.
⚠️ The half that is a real regression, not a pin rename
seedFailureLogLine builds the OPERATOR line from err.message alone:
function seedFailureLogLine(payloadMessage: string, err: unknown): string {
const cause = seedFailureCause(err); // err.message — never `err.cause`
return payloadMessage.includes(cause)
? `[SeedLoader] ${payloadMessage}`
: `[SeedLoader] ${payloadMessage} ${seedCauseLabel(err)}: ${cause}`;
}
With the envelope, cause is the PLATFORM sentence and payloadMessage already contains it, so the line collapses to [SeedLoader] Failed to write … and the driver's own text — UNIQUE constraint failed: dt_acct.email, or MySQL's index name — reaches neither the response nor the log. seed-loader-driver-text-real-driver.integration.test.ts asserts exactly that operator half (logged contains UNIQUE constraint failed and Cause (withheld from the seed response)), which is why it is a pin worth keeping rather than re-baselining.
⚠️ Derivation note: the payload strings above are measured; this log-line consequence is read off the source branch plus the measured payload, not off a captured log line. Confirm it in place when taking the card.
The insert door met the same problem one layer down and fixed it the same way it should be fixed here: the engine now logs e instanceof DuplicateRecordError ? e.cause : e, because the platform logger serializes only message and stack. seedFailureCause wants the equivalent — prefer the driver's own sentence off cause when the caught error carries one.
What the work is
packages/metadata-protocol/src/seed-loader.ts — seedFailureCause (and seedCauseLabel's question with it) should reach through cause so the operator keeps the driver's sentence when a platform envelope replaced it. The equivalent question is worth asking of the batch-row sink's console.warn half.
packages/runtime/src/batch-row-driver-text-real-driver.integration.test.ts and packages/runtime/src/seed-loader-driver-text-real-driver.integration.test.ts — retriage the two sentence pins onto the declared-refusal population, keeping every leak assertion and the operator half intact. ⛔ Not a re-baseline: the leak assertions are what those files exist for and none of them moved.
The decision, if the disclosure reading is disputed
The alternative is to keep both rows withheld even for a declared 4xx — which would mean the sinks' positive-list rule is wrong, or that the insert envelope should not declare a status. Neither looks right: the ruling on #14095 requires the status (it is what makes REST answer 409 instead of a sanitised 500), and withholding a producer-authored sentence that discloses nothing is the "declared ≠ enforced" shape pointed the other way. Recorded here so the maintainer can rule if they read it differently.
Triage housekeeping (R+99): the Blocked-by: #14095 line has been removed from this body — #14095 closed as completed on 2026-09-02T06:52Z via PR #14405, so the line was inert. It is the fourth exhausted #14095 line found; the other three (#14389, #14390, #14419) were stripped in R+98 and this card was not on that list. See the triage comment below for the grading and for a correction to the seedFailureCause reading above.
Generated by Claude Code
Measured while implementing #14095 (the insert door's
DUPLICATE_RECORDenvelope). This is ametadata-protocolconsequence of that change, outside that card's declared file surface, filed rather than ridden on its PR. It must be routed before or with #14095: two pins inpackages/runtimego red without it.What moved, and why it is the designed behaviour rather than an accident
Both sinks decide disclosure by a POSITIVE test — "would the boundary that serves this throw call it a client refusal?", answered by
resolveThrownHttpError:clientFacingRowFailureText(packages/metadata-protocol/src/protocol.ts):if (status >= 400 && status < 500) return err.message— otherwise the genericThe of this record failed. The reason is in the server log.quotableSeedFailureDetail/declaresSeedClientRefusal(packages/metadata-protocol/src/seed-loader.ts): the same shape, otherwiseWITHHELD_WRITE_REASON.The insert door's envelope declares
status: 409, so a duplicate row is no longer in the withheld population. That is the sink's own stated remedy — its docblock says the undeclared case is withheld deliberately and "the remedy is at the producer … declaring is cheaper than the workaround, which is the direction that makes authored code hard to get wrong". So the DISCLOSURE half looks correct and is arguably the point.Measured, on real drivers through the real doors:
⭐ No leak is introduced. The envelope's message carries no statement, no bound values and no dialect text — the driver's error stays whole on
cause. Every leak assertion in both suites (not.toContain('insert into'),'dup@example.com','UNIQUE constraint failed','SQLITE_CONSTRAINT') still holds against the new sentence.seedFailureLogLinebuilds the OPERATOR line fromerr.messagealone:With the envelope,
causeis the PLATFORM sentence andpayloadMessagealready contains it, so the line collapses to[SeedLoader] Failed to write …and the driver's own text —UNIQUE constraint failed: dt_acct.email, or MySQL's index name — reaches neither the response nor the log.seed-loader-driver-text-real-driver.integration.test.tsasserts exactly that operator half (loggedcontainsUNIQUE constraint failedandCause (withheld from the seed response)), which is why it is a pin worth keeping rather than re-baselining.The insert door met the same problem one layer down and fixed it the same way it should be fixed here: the engine now logs
e instanceof DuplicateRecordError ? e.cause : e, because the platform logger serializes onlymessageandstack.seedFailureCausewants the equivalent — prefer the driver's own sentence offcausewhen the caught error carries one.What the work is
packages/metadata-protocol/src/seed-loader.ts—seedFailureCause(andseedCauseLabel's question with it) should reach throughcauseso the operator keeps the driver's sentence when a platform envelope replaced it. The equivalent question is worth asking of the batch-row sink'sconsole.warnhalf.packages/runtime/src/batch-row-driver-text-real-driver.integration.test.tsandpackages/runtime/src/seed-loader-driver-text-real-driver.integration.test.ts— retriage the two sentence pins onto the declared-refusal population, keeping every leak assertion and the operator half intact. ⛔ Not a re-baseline: the leak assertions are what those files exist for and none of them moved.The decision, if the disclosure reading is disputed
The alternative is to keep both rows withheld even for a declared 4xx — which would mean the sinks' positive-list rule is wrong, or that the insert envelope should not declare a
status. Neither looks right: the ruling on #14095 requires the status (it is what makes REST answer 409 instead of a sanitised 500), and withholding a producer-authored sentence that discloses nothing is the "declared ≠ enforced" shape pointed the other way. Recorded here so the maintainer can rule if they read it differently.Triage housekeeping (R+99): the
Blocked-by: #14095line has been removed from this body — #14095 closed as completed on 2026-09-02T06:52Z via PR #14405, so the line was inert. It is the fourth exhausted#14095line found; the other three (#14389, #14390, #14419) were stripped in R+98 and this card was not on that list. See the triage comment below for the grading and for a correction to theseedFailureCausereading above.Generated by Claude Code