diff --git a/.changeset/30326-install-result-protocol-check-skipped.md b/.changeset/30326-install-result-protocol-check-skipped.md new file mode 100644 index 00000000000..76bed3f0ee4 --- /dev/null +++ b/.changeset/30326-install-result-protocol-check-skipped.md @@ -0,0 +1,13 @@ +--- +'@object-ui/app-shell': patch +'@object-ui/i18n': patch +--- + +feat(console): the install result says when a package was installed without a protocol compatibility check + +Clause-②: yes (widening) +Metadata-protocol: none + +- **What the administrator now reads.** `POST /api/v1/marketplace/install-local` answers `data.protocolCheckSkipped` when its protocol handshake admitted the package without checking it: the package declares no `engines.protocol` (`no-range`), or a range the runtime cannot read (`unparsed-range`). The three Console surfaces that show an install result now draw it as a caution line under the success sentence: the package page's Install result, the catalog's organization install message, and Setup → Packages → Import. The line names the package, the platform protocol it was installed under and, for `unparsed-range`, the range it declared, in the reader's locale. A `reason` this Console does not know shows the server's own `message`. An install whose range was checked shows nothing new. +- **Types.** `LocalManagedInstallResult` and `LocalUnmanagedInstallResult` gain the optional `protocolCheckSkipped`. +- **New strings.** `@object-ui/i18n` gains `marketplace.install.protocolCheckSkipped.noRange` and `.unparsedRange` in all 10 locales. diff --git a/ui/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx b/ui/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx index 83e5abf5fd6..877027030aa 100644 --- a/ui/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx +++ b/ui/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx @@ -82,6 +82,8 @@ import { type CloudInstallationInfo, } from './marketplaceApi.js'; import { useDependencyMissingText } from './dependencyMissing.js'; +import { useProtocolCheckSkippedText } from './protocolCheckSkipped.js'; +import { ProtocolCheckSkippedNote } from './ProtocolCheckSkippedNote.js'; import { getCloudBase, getRuntimeConfig, isMarketplaceEnabled } from '../../runtime-config.js'; import { emitMetadataRefresh } from '../../assistant/assistantBus.js'; import { useMetadata } from '../../providers/MetadataProvider.js'; @@ -148,6 +150,7 @@ export function MarketplacePackagePage() { const { isAdmin, isResolved } = useWorkspaceAdminStatus(); const { t, language } = useObjectTranslation(); const dependencyMissingText = useDependencyMissingText(); + const protocolCheckSkippedText = useProtocolCheckSkippedText(); // Dates and numbers on this surface read the display locale; a bare // `toLocale*()` call used the MACHINE's locale (objectui#9909). const displayLocale = useDisplayLocale(); @@ -267,6 +270,9 @@ export function MarketplacePackagePage() { message: string; unmanaged?: { strippedLocks: LocalInstallStrippedLock[] }; credentials?: { packageId: string; note?: string }; + // objectstack#30326 — the install answer's `protocolCheckSkipped`, drawn: + // installed, but its platform protocol range was not checked. + protocolNote?: string; } | null>(null); // ADR-0131 D6 — the install modes the package's latest manifest declares, // read from the manifest install-local judges, and the operator's pick among @@ -601,6 +607,10 @@ export function MarketplacePackagePage() { try { const result = await installLocal({ packageId, ...(installMode ? { installMode } : {}) }); const name = data?.package ? localizePackage(data.package as any, language).displayName : result.manifestId; + // objectstack#30326 — every success answer can say the package was + // admitted with its protocol range unchecked; each note below carries it. + const protocolNote = protocolCheckSkippedText(result) ?? undefined; + const withProtocolNote = protocolNote ? { protocolNote } : {}; // An unmanaged copy (ADR-0131 D6): the package's metadata is this // runtime's own from now on, in the base the answer names; the answer // lists every protection lock the copy dropped, with the package's reason. @@ -614,6 +624,7 @@ export function MarketplacePackagePage() { ok: true, message: t('marketplace.install.unmanagedSuccess', { version: result.version, name, base: result.targetPackageId }), unmanaged: { strippedLocks: result.strippedLocks }, + ...withProtocolNote, }); emitMetadataRefresh(); return; @@ -629,8 +640,8 @@ export function MarketplacePackagePage() { // and the page says when the app will appear. if (!result.hotLoaded) { setLocalResult(ignoredCopy - ? { ok: false, message: t('marketplace.install.unmanagedIgnored', { name }) } - : { ok: true, message: t('marketplace.install.localLoadsAtRestart', { version: result.version, name }) }); + ? { ok: false, message: t('marketplace.install.unmanagedIgnored', { name }), ...withProtocolNote } + : { ok: true, message: t('marketplace.install.localLoadsAtRestart', { version: result.version, name }), ...withProtocolNote }); return; } try { @@ -639,11 +650,12 @@ export function MarketplacePackagePage() { // Non-fatal: the install succeeded; the app list will refresh on next navigation. } setLocalResult(ignoredCopy - ? { ok: false, message: t('marketplace.install.unmanagedIgnored', { name }) } + ? { ok: false, message: t('marketplace.install.unmanagedIgnored', { name }), ...withProtocolNote } : { ok: true, message: t('marketplace.install.localSuccess', { version: result.version, name }), credentials: { packageId: result.manifestId, note: result.credentialNote }, + ...withProtocolNote, }); emitMetadataRefresh(); } catch (e: any) { @@ -996,6 +1008,7 @@ export function MarketplacePackagePage() { {localResult.ok ?