Skip to content

Commit fbefd17

Browse files
committed
fix(metadata-protocol): the write refusal reports the package door, not only the type's flags (#7682)
`PUT /api/v1/meta/object/showcase_task` answered `403 NOT_OVERRIDABLE` identically whether `?package=` named a read-only or a writable package: `SysMetadataRepository.assertAllowed` discriminated on the metadata TYPE's registry flags and never read the base the caller named. `ITEM_LOCKED` and `WRITABLE_PACKAGE_REQUIRED` are both registered to this package in the error-code ledger and neither was ever emitted on this path. The refusal branch now reads the named base through the shared `isWritablePackage` predicate (imported from `package-writability.ts`, the same one `saveMetaItem`'s ADR-0070 D1 gate and the `/packages` lifecycle gate use): * `override-artifact` into a read-only base -> 403 ITEM_LOCKED, carrying ADR-0010's reserved `lockSource: 'package'` and the package id. "Pick a writable base" would be a false prescription here, so the code names the lock instead. * `runtime-only` into a read-only base -> 422 WRITABLE_PACKAGE_REQUIRED, the same code, status and prescription `saveMetaItem` already emits for exactly this condition, now also stated at the single persistence route. No ALLOW decision moves: every allow limb returns before this point, so an ADR-0005 overlay (which names the read-only package it customizes by construction) and the documented `OS_METADATA_WRITABLE` hatch behave exactly as before -- both pinned, the hatch because #8146 must be free to move it deliberately. A write naming no base keeps the previous codes verbatim, and the DELETE verb is untouched (#6960). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmES43BMDg4bPrxTdi5q7t
1 parent 6ceffe0 commit fbefd17

3 files changed

Lines changed: 575 additions & 2 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
fix(metadata-protocol): the metadata write refusal reports the package door — `ITEM_LOCKED` / `WRITABLE_PACKAGE_REQUIRED` are emitted where they apply (#7682)
6+
7+
`PUT /api/v1/meta/object/showcase_task` answered `403 NOT_OVERRIDABLE`
8+
("'object' is not allowOrgOverride in the registry") — the **same** code, status
9+
and sentence whether `?package=` pointed at a **read-only** package or a
10+
**writable** one. The refusal discriminated on the metadata TYPE's registry
11+
flags and never read the base the caller named, so the two codes the error-code
12+
ledger registers to this package for the package-writability condition —
13+
`ITEM_LOCKED` and `WRITABLE_PACKAGE_REQUIRED` — were never emitted on this path
14+
at all. Declared, not enforced.
15+
16+
`SysMetadataRepository.assertAllowed` now reads the named base through the
17+
shared `isWritablePackage` predicate (the same one `saveMetaItem`'s ADR-0070 D1
18+
gate and the `/packages` lifecycle gate use — imported, not re-spelled), and a
19+
refused write that named a read-only base says so:
20+
21+
- **`override-artifact`** (an artifact backs the name, and it ships from a
22+
package the deployment provides) → `403 ITEM_LOCKED`, carrying
23+
`lockSource: 'package'` — ADR-0010's own reserved value for a lock the package
24+
layer asserts — plus the package id. `WRITABLE_PACKAGE_REQUIRED` would be the
25+
wrong prescription here: switching bases cannot help, because the artifact is
26+
code-shipped wherever the caller points. This is the server-side counterpart
27+
of the "Read-only" badge Studio already renders.
28+
- **`runtime-only`** (no artifact under this name — a NEW item authored into a
29+
read-only base) → `422 WRITABLE_PACKAGE_REQUIRED` with the package id, the
30+
same code, status and prescription `saveMetaItem` already emits for exactly
31+
this condition. One vocabulary, now stated at the single persistence route as
32+
well, so callers that do not pass through that gate cannot skip it.
33+
34+
**No allow decision moves.** Every write that succeeded before still succeeds:
35+
this is the code selection inside the refusal branch, not a new gate. That
36+
distinction is load-bearing rather than cautious — an ADR-0005 org overlay names
37+
the read-only package it customizes *by construction*, so a package door that
38+
refused would close the overlay model itself. In particular the documented
39+
`OS_METADATA_WRITABLE` hatch is untouched: an env-hatch write into a read-only
40+
package (`OS_METADATA_WRITABLE=permission` + a `permission` set belonging to a
41+
read-only package) still succeeds, and is now pinned by a test — whether that
42+
hatch or Studio's badge is the correct semantics is a separate maintainer
43+
decision (#8146) and must move deliberately, not as a side effect of this fix.
44+
Writes that name no base keep the previous `NOT_OVERRIDABLE` / `NOT_CREATABLE`
45+
codes verbatim, and the DELETE verb is unchanged (#6960 moved that side on
46+
purpose; `DeleteOptions` names no package).
47+
48+
Reachability, stated so it is not mistaken for more than it is: this refusal is
49+
what answers on the host-config topology (`environmentId` undefined — the CLI's
50+
lightweight assembler, i.e. the flagship showcase and self-hosted servers shaped
51+
like it), which is the topology the defect was measured on. On a scoped kernel
52+
`saveMetaItem` refuses earlier, in `protocol.ts`, still with the undiscriminated
53+
`NOT_OVERRIDABLE`; that second refusal point is filed separately.

0 commit comments

Comments
 (0)