Skip to content

Commit fda61e4

Browse files
claude[bot]os-zhuangclaude
authored
fix(metadata-protocol): audit the batch package publish in Phase 2, outside the transaction (#8400) (#8605)
* fix(metadata-protocol): audit the batch package publish in Phase 2, outside the transaction `publishPackageDrafts` (Studio's "publish whole app") wrote no `sys_metadata_audit` rows at all — neither the allowed-outcome `publish` rows nor a `denied` row for a refusal. It calls `promoteDraftForPublish` directly, so #7748's row in `publishMetaItem` never ran for it. Both outcomes are written in Phase 2, after the ADR-0067 D2 transaction has closed: the allowed rows off `promoted[]`, the `denied` row from the rollback `catch`. Inside the transaction the refusal's own row would roll back with the batch it records, which is the defect #7748 closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH * test(metadata-protocol): mark the audit-vocabulary literals; add changeset `check:error-code-casing` correctly flags `code: 'batch_aborted'` in a code position. Opted out per site with the gate's documented `adr0112-ok: D6b` marker rather than exempting the whole file, so the rest stays guarded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH * test(metadata-protocol): pin BOTH audit rows to the draft's own org scope The existing fixtures used ORG for the draft and for the publishing session, so an audit row keyed on either would pass. These two differ: an env-wide draft (organization_id IS NULL) published, and refused, by a non-null-org caller — the #3115 shape listDrafts surfaces via its $or. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH --------- Co-authored-by: os-zhuang <jack@objectstack.ai> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 0a5dc29 commit fda61e4

4 files changed

Lines changed: 831 additions & 4 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
'@objectstack/metadata-core': patch
4+
---
5+
6+
fix(metadata-protocol): `publishPackageDrafts` now writes the audit rows a batch publish always owed
7+
8+
Studio's "publish whole app" (`POST /packages/:id/publish-drafts`) promoted every
9+
draft in a package and wrote **no `sys_metadata_audit` rows at all** — neither the
10+
allowed-outcome `publish` rows nor a `denied` row for a refusal. The route calls
11+
`promoteDraftForPublish` directly rather than `publishMetaItem`, so the row added
12+
for the single-item routes never ran for it: a batch that published twenty
13+
artifacts left the compliance trail exactly as empty as a batch nobody ran.
14+
15+
Both outcomes are now recorded, and **where** they are recorded is the fix:
16+
17+
- **allowed** — one `publish` / `allowed` row per promoted item, written in Phase 2
18+
off `promoted[]`, with `source: 'protocol.publishPackageDrafts'` so the trail
19+
distinguishes "publish whole app" from a single-item publish. The row is keyed on
20+
the scope the draft was promoted in, not the request's active org, because
21+
env-wide drafts are promoted env-wide.
22+
- **denied** — one `publish` / `denied` row with `code: 'batch_aborted'` when the
23+
batch rolls back, written from the rollback handler, **outside** the
24+
`engine.transaction()`. Written inside it, the refusal's own row would roll back
25+
with the batch it records — leaving nothing behind about a refused publish, which
26+
is the defect the single-item audit rows exist to close.
27+
28+
The causal reason rides in `note`, which is served by `GET /api/v1/meta/:type/:name/audit`
29+
and therefore carries the client-facing text rather than raw driver output.

packages/metadata-core/src/objects/sys-metadata-audit.object.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,17 @@ export const SysMetadataAuditObject = ObjectSchema.create({
123123
* - on `allowed`: `'ok'`
124124
* - on `denied`: `'not_overridable'` | `'not_creatable'` |
125125
* `'item_locked'` | `'invalid_metadata'` | `'destructive_change'` |
126-
* `'metadata_conflict'`
126+
* `'metadata_conflict'` | `'batch_aborted'`
127127
* - on `forced`: `'lock_override'` (Phase 3)
128128
*
129+
* `batch_aborted` (#8400) is the batch publish's own refusal value:
130+
* `publishPackageDrafts` promotes a whole package inside ONE transaction,
131+
* so its refusal is "the batch rolled back and nothing landed" rather than
132+
* any one item's verdict, and the causal code rides in `note`. It is ONE
133+
* fixed value on purpose — lower-casing whatever `error.code` happened to
134+
* abort the batch would turn this closed set into an open one that grows
135+
* silently with the error catalog.
136+
*
129137
* Deliberately lowercase, and deliberately NOT the `error.code` vocabulary
130138
* (ADR-0112) even though the denial values are spelled the same as the
131139
* codes the protocol throws. This column is persisted audit history: rows

0 commit comments

Comments
 (0)