Uh oh!
There was an error while loading. Please reload this page.
fix(metadata-protocol): the metadata write refusal stops depending on deployment topology (#8184) - #8353
Conversation
…e door (#8184) `saveMetaItem`'s artifact-backed refusal sits behind `environmentId !== undefined` and threw before `SysMetadataRepository.assertAllowed` ever ran, so one request answered `403 ITEM_LOCKED` on a host-config kernel and the undiscriminated `403 NOT_OVERRIDABLE` on a project/cloud per-env one. The branch now consults the same `isWritablePackage` predicate and throws the repository's OWN emitter — one condition, one vocabulary, on every topology. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8184
PUT /api/v1/meta/object/showcase_task?package=READONLY_PKGanswered two different machine-readable codes for one condition, selected by the kernel'senvironmentId— a row-scoping key, not a topology declaration:environmentIdundefined — the flagship showcase, self-hosted servers)403 ITEM_LOCKED,lockSource: 'package'environmentIdset)403 NOT_OVERRIDABLE— the package was never readsaveMetaItemcarries its own artifact-backed refusal behindif (this.environmentId !== undefined), and it threw beforeSysMetadataRepository.assertAllowed— the topology-independent package door (#7682, then #8146's hatch ruling) — ever ran. A client that learned to handleITEM_LOCKEDon a self-hosted deployment never saw it on a cloud one, and an operator readingNOT_OVERRIDABLEwas told the type had no overlay channel when the real obstacle was the read-only base they had named.Not a regression: that branch answered
NOT_OVERRIDABLEbefore #8185 and #8320 too. Those cards made the divergence visible by fixing the other half. This is the last open link of #7682 → #8146 → #8184.Mirrored, not re-invented
The scoped branch now consults the same
isWritablePackagepredicate and throws the repository's own emitter —readOnlyBaseOverrideErroris called, not copied — so code, status,lockSource,packageIdand the sentence are byte-identical on both topologies, and neither door can drift when the other moves. Two independently-authored refusals for one condition is how theNOT_OVERRIDABLE-everywhere problem started.That is the one edit outside
protocol.ts:readOnlyBaseOverrideErrordropsprivateand gains an@internalnote naming the second call site. No behaviour of the repository door changes.Same limb ordering, because the ordering is the rule
!overlayAllowed, so anallowOrgOverridetype never reaches the door. An ADR-0005 org overlay of a code-shipped item always names the read-only package it customizes; a door one limb higher would close the overlay model outright. Pinned.isOverlayAllowedfoldsOS_METADATA_WRITABLEin, so an open hatch takes the write past this branch to the repository door, which applies the same rule with its own hatch-aware remedy — the refusal never prescribes the step the caller already took. That is also whyhatchOpenis passed as a literalfalsehere: reaching the line proves the hatch is closed, and a recomputed value would be dead code dressed as a decision. Both directions pinned.Narrow, exactly as the repository is
Only a write that names a read-only base is re-coded. A package-less write keeps
NOT_OVERRIDABLEverbatim; a package-less hatch write still lands env-wide bound to no package, and bound to the org and to no package under an org kernel. The broad reading — refusing a hatch write that names no read-only base — would retire the hatch's only documented use and remains undelegated: a maintainer decision plus a docs/ADR change, not something to arrive at from here.runtime-onlyneeded no limb: the branch is guarded byartifactBacked, so the intent is alwaysoverride-artifact. The create side is the ADR-0070 D1 gate further downsaveMetaItem, already topology-independent and already answering422 WRITABLE_PACKAGE_REQUIREDon every kernel.Tests
A new final block in
sys-metadata-repository.package-writability.test.ts— deliberately beside the host-config block, because the comparison is the card. Eleven cases: the two kernels compared against each other (code, status,lockSource, and the message string) rather than against a literal, so a future change that moves one and not the other cannot pass; the writable-base and package-less cases keeping the type-door code; both directions of thehatchOpenremedy selection; and the preservation pins (ADR-0005viewoverlay of a read-only package, package-less hatch write env-wide and under an org kernel, hatch write naming a writable base).That file's final docblock previously stated this topology as an uncovered boundary ("do not read a green run as evidence that the scoped kernel refuses too"). It is re-pointed rather than deleted, so the next reader sees the boundary closed instead of a docblock asserting a gap that no longer exists.
Reverse-verified with the direction predicted first: revert
protocol.ts, keep the suite ⇒ exactly the 2 discrimination cases go red and all 9 preservation/boundary cases stay green (2 failed | 28 passed). Restored out of the commit, byte-identical.Generated by Claude Code
Generated by Claude Code