Skip to content

publishPackageDrafts writes no audit rows — Studio's "publish whole app" leaves the audit trail empty #8400

Description

@os-zhuang

What

publishPackageDrafts (behind Studio's "publish whole app", POST /packages/:id/publish-drafts) promotes every draft in a package and writes no sys_metadata_audit rows at all — neither the allowed-outcome publish rows nor the denied row for a refusal.

Found while implementing #7748, which fixed the same gap on the single-item routes (publishMetaItem, rollbackMetaItem, and all four 409 METADATA_CONFLICT sites). Not fixed there, deliberately — see below.

Why it was left out of #7748

publishPackageDrafts calls promoteDraftForPublish directly (not publishMetaItem), and it does so inside one engine.transaction() — the ADR-0067 D2 "a commit cannot half-land" invariant. That makes the batch case a genuinely different contract from the three sites #7748 touched:

So the correct placement for the batch path is Phase 2 (after the transaction commits), driven off the promoted[] array — a different edit from #7748's, with its own test, rather than something to smuggle into that card.

Repro sketch

  1. Stage two or more drafts in one package.
  2. POST /packages/:id/publish-drafts — answers 200, drafts go active.
  3. GET /api/v1/meta/:type/:name/audit for any published item — no publish row.

(Compare: after #7748, the same item published one-at-a-time does get its row.)

Notes for whoever takes this

  • The sys_metadata_audit schema already declares publish as an operation option — no schema work needed.
  • ⛔ Beware the vacuity trap draft-publish-lifecycle: the metadata audit trail records only save — publish, rollback and the 409 conflict denial never write a row #7748 documented: most multi-table fake engines in this repo open insert with if (table === 'sys_metadata_audit') return { id: 'audit_skip' };, which makes every audit assertion pass for the wrong reason. packages/metadata-protocol/src/protocol.lifecycle-audit-rows.test.ts has a harness that genuinely persists audit rows and separates "attempted" from "landed" — reuse its shape.
  • Best-effort semantics (ADR-0010 §3.6) mean a failed audit write is swallowed, so "row missing" and "write failed" are indistinguishable to a caller unless the test observes the attempt separately.

Backlink: #7748 (single-item routes, fixed).

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions