…r with its posture-naming reason on every install, upgrade and uninstall gesture
objectstack W2 (objectstack-ai/objectstack#22935) refuses, below the
platform-operator rung under `group` / `isolated`, an install or upgrade of a
package that carries security catalog items and the uninstall of such a
package, at install-local and at `DELETE /api/v1/packages/:id`, with a `403`
whose message names the posture. Measured with real renders fed those exact
envelopes: the package page's Install and Reinstall (the console's upgrade on
an install-local runtime), its local Uninstall, Installed Apps' Uninstall, the
catalog page's organization install, and the Packages page's Delete app and
Import each already show the door's sentence, so these are pins only.
Part of objectui#7611 (item 2 of release record 6158162134).
Claude-Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z
Co-authored-by: Claude <noreply@anthropic.com>
Part of #7611
Clause-②: no
This PR covers item 2 of the card's release record (comment 6158162134) and nothing else: package install, upgrade and uninstall under a wall, in the console. The card stays open for items 1, 3 and 4. The seat's claim is comment 6158502886.
Result
The console already shows the walled refusal's reason on every path that can receive it. The release record called this "not measured", so I measured it first. Each install, upgrade and uninstall gesture shows the door's posture-naming sentence verbatim. The PR is pins only. No source file changes, and the changeset has empty frontmatter.
The refusal's wire shape, read at objectstack
4806b18aeaobjectstack W2 (objectstack-ai/objectstack#22935, merge
4806b18aea, carried by@objectstack/*18.0.0-next.0) refuses two things below the platform-operator rung undergroup/isolated: an install or upgrade of a package that carries security catalog items, and the uninstall of such a package.install-local (
@objectstack/cloud-connection,refuseWalledCatalogPackage) refuses on two requests:POST /api/v1/marketplace/install-local, for an install and also an upgrade, with VERBInstalling;DELETE /api/v1/marketplace/install-local/MANIFEST_ID, for an uninstall, with VERBUninstalling.DELETE /api/v1/packages/:id(@objectstack/runtime,refuseWalledCatalogPackageDelete) answers through the dispatcher'sdeps.error(message, 403), sobuildApiErrorderives the code from the status (standardErrorCodeForHttpStatus):VERDICT is the sentence from
walledCatalogWriteVerdict(@objectstack/metadata-core). It is written for the first catalog type the package declares, checked in this order: position, permission set, capability. For example:No posture field. Neither envelope carries one. The posture appears only in the message. An upgrade goes to the same door as an install: the door checks both the incoming and the installed manifest, and answers with the
Installingsentence.The console's paths, measured
Each case renders the real component. A stubbed
fetchanswers a realResponsecarrying the exact envelope above, with status textForbidden. Each case runs under bothgroupandisolated.marketplaceApiis not mocked.installLocal)POST install-localPOST install-local: no version is named, so the door installslatestover the installed oneuninstallLocal)DELETE install-local/MANIFEST_IDuninstallLocal)installLocalwithversionId: 'latest')POST install-localapiJson)DELETE /api/v1/packages/:id(PERMISSION_DENIED)apiJson, manifest inline)POST install-local(FORBIDDEN)None of the five marketplace cases shows
Forbidden,HTTP_403or a bareFORBIDDEN. Neither Packages page case showsForbiddenor this page's own fallback,Request failed (403). The marketplace pins assert that each gesture's success sentence is absent, and that each request left the browser with the expected method, path and body.The
DELETE /api/v1/packages/:idpath (Zone 2, item 3). The console calls it from one place:PackageDetailSheet's Delete app on the Packages page (views/metadata-admin). That page also has a second door into install-local: Import, which posts the file's manifest inline. Both sit outside the claimedconsole/marketplace/surface. Both are measured here in one new test file beside the page, and no source there changes.Wording (Zone 2, item 4): verbatim, the existing convention. Three reasons:
doInstallLocalalready shows the install-mode refusals as sent ("each message names its own remedy, and the page has nothing to add to it"). Both uninstall callers showe.message. The Packages page shows the envelope throughreadEnvelopeFailureText.manage_metadatadoes not admit it, and what they can do instead.403 FORBIDDEN, with the same door's missing-capability refusal, which endsrequires the `manage_metadata` capability.. TheDELETE /api/v1/packages/:idrefusal carries only the status-derivedPERMISSION_DENIED. So the console could tell a walled refusal apart only by parsing the server's prose. That is a second reading of the refusal, and the server's own sentence already does the job.Tests
All runs are at head
ef8ab4ae4.pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/console/marketplace/ packages/app-shell/src/views/metadata-admin/PackagesPage.walledCatalogRefusal-7611.test.tsx→Test Files 34 passed (34),Tests 229 passed (229). That is the 32 existing marketplace suites, plus the new marketplace pin (11 tests) and the new Packages page pin (4 tests).pnpm --filter @object-ui/app-shell type-checkechoestsc --noEmit && tsc -p tsconfig.test.jsonand exits 0.tsc -p tsconfig.test.json --listFileslists both new test files.turbo run build --filter=@object-ui/app-shell^...: exit 0. All 29 packages were cache hits.--print-configon a new file resolves 119 rules, so the file is linted, and the changeset.mdmatches nofilesglob. Second,--format jsonreports 2 files, 0 errors and 0 warnings, both from the repo root with--no-inline-configand frompackages/app-shell, the way itslintscript runs. Third, untouched files are unaffected: the resolved config hasparserOptions.projectandprojectServiceunset, so linting is not type-aware, and no rule undereslint-rules/reads the filesystem. The diff adds two files and edits none, so it cannot change any other file's lint result.node scripts/check-changeset-presence.mjs(the changeset has empty frontmatter, which this gate accepts as a complete answer),pnpm check:changeset-claims,pnpm check:pending-changeset-literals,pnpm check:new-line-citations(0 new citations),pnpm check:control-bytes,pnpm check:test-path-roots,pnpm check:vi-mock-specifiers. Also derived from the workflows and run:check-changeset-fixed,check-changeset-no-major,check-changeset-overwrite,check-vi-mock-inherit,check-vi-mock-override-shape,check-lint-coverage,check-type-check-coverage, andpnpm check:unreferenced-sources/esm-specifiers/metadata-write-doors/handler-key-reads/self-import/i18n-keys/phantom-deps.pnpm check:sdui-registration-pins. It exits 2 because there is no console build to read. It reads bundle-time registrations, and this diff ships no code.Ablation (one leg, on the committed tree): run with objectstack's
scripts/ablation-replace.mjsin wrap mode. It replacedreadApiError'spayload?.error?.message // the declared envelopewithundefined. The anchor count went from 1 to 0, and the blob from4fd74da91c1cto922b64be38e3. The marketplace pin then ran atTests 10 failed | 1 passed (11), which is the direction predicted beforehand. Each failure wasUnable to find an element with the text: Installing "com.example.crm" is refused: …, with the page showing the bareFORBIDDEN: the exact shape of a dropped reason. The one pass is the harness CONTROL, which shows a bare form either way. The restore was proven: the blob after restore equals HEAD (4fd74da91c1c),git diff HEADis empty, and the marker count is 0.Acceptance notes
These are observations only. None is filed, and none has a carrier.
uninstallLocalthrows anErrorwith neithercodenorstatus, whileinstallLocalcarries both. Both of its callers show onlye.message, so nothing user-visible depends on it today.details.catalogItems. The console does not show them. The sentence already says the package carries security catalog items, so listing the items would be a feature request, not part of this item.Spec Main Shape Gateis red onmainand on every PR (advisory, owned by objectui#12325).OAuthConsentPage.public-client-12301is a known timing flake (objectui#12344).Files:
packages/app-shell/src/console/marketplace/__tests__/walledCatalogPackageRefusal-7611.test.tsx,packages/app-shell/src/views/metadata-admin/PackagesPage.walledCatalogRefusal-7611.test.tsx,.changeset/7611-walled-package-refusal.md.Implemented by the os-dev agent dispatched by the
domain:ui#1seat, sessionhttps://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z.Generated by Claude Code