Skip to content

fix(metadata-protocol): the metadata write refusal stops depending on deployment topology (#8184) - #8353

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-8184-scoped-kernel-package-door
Aug 13, 2026
Merged

fix(metadata-protocol): the metadata write refusal stops depending on deployment topology (#8184)#8353
os-zhuang merged 2 commits into
mainfrom
claude/issue-8184-scoped-kernel-package-door

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#8184

PUT /api/v1/meta/object/showcase_task?package=READONLY_PKG answered two different machine-readable codes for one condition, selected by the kernel's environmentId — a row-scoping key, not a topology declaration:

kernelanswer
host-config / CLI lightweight assembler (environmentId undefined — the flagship showcase, self-hosted servers)403 ITEM_LOCKED, lockSource: 'package'
project / cloud per-environment kernel (environmentId set)403 NOT_OVERRIDABLE — the package was never read

saveMetaItem carries its own artifact-backed refusal behind if (this.environmentId !== undefined), and it threw before SysMetadataRepository.assertAllowed — the topology-independent package door (#7682, then #8146's hatch ruling) — ever ran. A client that learned to handle ITEM_LOCKED on a self-hosted deployment never saw it on a cloud one, and an operator reading NOT_OVERRIDABLE was 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_OVERRIDABLE before #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 isWritablePackage predicate and throws the repository's own emitterreadOnlyBaseOverrideError is 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 the NOT_OVERRIDABLE-everywhere problem started.

That is the one edit outside protocol.ts: readOnlyBaseOverrideError drops private and gains an @internal note naming the second call site. No behaviour of the repository door changes.

Same limb ordering, because the ordering is the rule

  • Below every registry limb. The branch is guarded by !overlayAllowed, so an allowOrgOverride type 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.
  • Above the hatch limb.isOverlayAllowed folds OS_METADATA_WRITABLE in, 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 why hatchOpen is passed as a literal false here: 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_OVERRIDABLE verbatim; 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-only needed no limb: the branch is guarded by artifactBacked, so the intent is always override-artifact. The create side is the ADR-0070 D1 gate further down saveMetaItem, already topology-independent and already answering 422 WRITABLE_PACKAGE_REQUIRED on 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 the hatchOpen remedy selection; and the preservation pins (ADR-0005 view overlay 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

…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
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 8:03am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/metadata-protocol)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/metadata-protocol)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/metadata-protocol)

1 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/v9.mdx(via @objectstack/metadata-protocol)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 13, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 13, 2026 08:19
@os-zhuang
os-zhuang added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 003feaeAug 13, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8184-scoped-kernel-package-door branch August 13, 2026 08:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-zhuang@claude