What
publishPackageDrafts has a pre-flight gate that refuses the whole batch before anything
is promoted — currently the ADR-0028 namespace-prefix rule on object drafts:
if (preflightViolations.length > 0) {
return {
success: false,
publishedCount: 0,
failedCount: preflightViolations.length,
published: [],
failed: preflightViolations,
};
}
That return is above the engine.transaction(), so it is reached by neither the
allowed-outcome rows nor the rollback handler's denied row. A publish refused here writes
nothing to sys_metadata_audit at all: in the trail, an author whose package was rejected
for a bad object name is indistinguishable from an author who never pressed Publish.
Why #8400 did not cover it
#8400's ruling placed the batch's audit rows in Phase 2, after the transaction commits,
driven off promoted[], and its denied row in the rollback handler. Both live below this
early return. The card named the rollback refusal specifically, so the pre-flight branch was
left alone rather than folded in silently — it is a separate refusal class with its own
shape (N violations, no causal item, nothing ever attempted).
Shape question for whoever takes this
The pre-flight refusal is not "one causal item aborted the batch" — it is a list of
violations, each naming a different item. So it is genuinely a different row shape from
batch_aborted:
- one
denied row per violation, keyed on its own (type, name), or - one row for the batch, which then has no honest
(type, name) to key on.
Option 1 matches what the audit table is for (per-item decisions, and the per-item history
tab reads (type, name, occurred_at)); option 2 needs a synthetic identity in a compliance
ledger, which #8400 deliberately avoided for the unattributable case.
Backlink: #8400, #7748.
Generated by Claude Code
What
publishPackageDraftshas a pre-flight gate that refuses the whole batch before anythingis promoted — currently the ADR-0028 namespace-prefix rule on object drafts:
That
returnis above theengine.transaction(), so it is reached by neither theallowed-outcome rows nor the rollback handler's
deniedrow. A publish refused here writesnothing to
sys_metadata_auditat all: in the trail, an author whose package was rejectedfor a bad object name is indistinguishable from an author who never pressed Publish.
Why #8400 did not cover it
#8400's ruling placed the batch's audit rows in Phase 2, after the transaction commits,
driven off
promoted[], and itsdeniedrow in the rollback handler. Both live below thisearly return. The card named the rollback refusal specifically, so the pre-flight branch was
left alone rather than folded in silently — it is a separate refusal class with its own
shape (N violations, no causal item, nothing ever attempted).
Shape question for whoever takes this
The pre-flight refusal is not "one causal item aborted the batch" — it is a list of
violations, each naming a different item. So it is genuinely a different row shape from
batch_aborted:deniedrow per violation, keyed on its own(type, name), or(type, name)to key on.Option 1 matches what the audit table is for (per-item decisions, and the per-item history
tab reads
(type, name, occurred_at)); option 2 needs a synthetic identity in a complianceledger, which #8400 deliberately avoided for the unattributable case.
Backlink: #8400, #7748.
Generated by Claude Code