Skip to content

DSH Forge Foundation Contracts

English | 中文

This reference owns stable configuration and public desktop service facts. Distribution responsibilities and lifecycle design are in ../design/dsh-forge.md; source, recovery, and release maintenance boundaries are in ../engineering/foundation-boundaries.md.

Distribution and Profile

distribution.yml is the only source of distribution identity. It must declare schema, id, name, packageScope, applicationId, version, defaultProfile, and one or more platform/architecture targets. Existing channel, metadataUrl, and trustRoot fields apply only to update metadata with a trust root; full-package OTA does not read them from distribution.yml.

profiles/<name>/profile.yml declares a fixed runtime version set and ordered bundles. The top-level plugins field is rejected. @dsh-forge/desktop-layer is injected by the launcher for one generation and must not appear in bundles. A sibling cordis.patch.yml is the final profile override.

profile:resolve writes generated profile files, a profile lockfile, resolved-manifest.json, sbom.input.json, license notices, and a configuration dump under artifacts/<distribution>/<profile>/<input-digest>/. These files are resolution evidence. After any source, bundle, version, or build-script authorization change, run profile:resolve and profile:verify again.

Full-package OTA

The root package.json field dshForgeBuild must be a positive safe integer. The packaging script copies it into the application's package.json; maintainers must increment it when rebuilding the same SemVer. Packaged apps read the fixed https://github.com/ptonlix/dsh-forge/releases/latest/download/version.json. Its JSON must contain exactly windows, macos, and ubuntu, each with exact SemVer version, positive safe integer build, and HTTPS url ending in .exe, .dmg, and .AppImage respectively. An update is available only when the remote version is greater, or the versions match and the remote build is greater.

Windows and macOS use their respective entries. Linux releases only the Ubuntu AppImage, and uses its entry only on Ubuntu 22.04+ with a writable absolute regular APPIMAGE; other distributions and launch modes do not check or run OTA. Nothing downloads before confirmation. A confirmed package is written only to a controlled user-data staging directory, then an apps/desktop/platform helper runs after Electron exits.

The private upgrade status projection reports receivedBytes, totalBytes, and percent only while downloading. The Settings page renders a determinate progress bar and percentage when the response supplies a valid Content-Length; otherwise it renders an indeterminate progress bar with the received bytes and does not invent a percentage. It never receives the update URL, staging path, command, or restart token.

For Windows, a controlled staging cmd.exe runner waits for Electron to exit so that NSIS does not retain a handle on the executable being replaced. It starts the updated executable explicitly after a zero-exit installer; the macOS helper invokes the updated bundle with open -n, and the Ubuntu helper starts the replacement AppImage. A restart succeeds only after the new process has completed its Host, loopback, window, and renderer readiness sequence and has written a one-time controlled receipt with the helper's random token. No receipt leaves the staged package for diagnosis; macOS and Ubuntu restore the old application where replacement has already started. The new Windows application performs best-effort staging cleanup after its receipt. After a valid macOS receipt, DMG detachment, backup removal, and staging cleanup are best-effort and cannot turn a completed restart into a failure.

The manifest and full packages have no digest, signature, or trust-root verification. HTTPS, user confirmation, and macOS system signing/notarization do not make this a generally auditable update trust channel; the release workflow uploads all three platform packages to fixed asset names in the same GitHub Release, and the publisher must still verify that the manifest version/build matches the release tag.

After a generation is ready it checks silently; when the state is available, the Settings trigger shows a non-blocking orange "new version available" badge. The badge does not open Settings automatically, show a confirmation dialog, or download anything. The user opens the "Upgrade management" page and clicks "Upgrade now", after which the main process rechecks and shows native confirmation. The trigger uses the existing Remote status projection and never exposes update URLs, staging paths, or commands. Once the Settings panel is open, the "Upgrade management" navigation item shows the same badge so the user can identify the destination. That navigation item also uses a distinct refresh/update icon instead of the generic Settings gear, while retaining the shell's state colors and click behavior.

Before a release, run pnpm run release:prepare -- 0.2.0. The command validates and synchronizes the root distribution.yml version, root package.json version, and dshForgeBuild: a higher target version resets the build to 1, while rebuilding the same version increments the build automatically. Mismatched version sources, invalid input, or lower versions fail before writing; the command does not create a tag or publish a Release. After preparation, commit the changes and create a matching annotated tag (for example, v0.2.0); CI continues to verify that the tag matches distribution.yml.

Public Import

@dsh-forge/desktop-services is the only public desktop import. It augments Cordis Context with desktopProfiles, desktopPnpm, and desktopServices; consumers call assertDesktopServicesProtocol() before using the services. The current protocol is 1.

The local provider, Electron runtime, launcher paths, profile directories, and raw pnpm arguments are internal. Third-party bundles must not import @dsh-forge/desktop-services-local or assume that a normal Web, headless, or test composition has a desktop layer.

desktopProfiles

desktopProfiles.current is an immutable name snapshot for one generation. snapshot() returns a deep-frozen profile snapshot, and list() returns readonly profile summaries with bundle, compatibility, selectable, and diagnostic facts. select(name) persists a pending target and restarts the generation; it cannot replace a running Loader tree.

The same concurrent target shares one operation. A different concurrent target fails without replacing the persisted pending target. Any retained service reference rejects after generation disposal and cannot affect a new generation. The packaged application does not expose profile selection through its UI.

desktopPnpm

desktopPnpm.run() accepts only these discriminated commands:

CommandRequired fieldsMeaning
inspectquery: 'list' or query: 'why'Reads profile dependencies; why requires packageName.
reconcilenoneSynchronizes the profile lockfile without lifecycle scripts.
removepackageNameRemoves one exact package through the provider.

DesktopPnpmOperation exposes stdout, stderr, a done promise, and idempotent cancel(). done settles only after the managed process tree, reconcile, source validation, health check, receipt, or recovery reaches a final state. One generation permits one operation; an already-cancelled signal, a busy lease, or a closed generation fails before spawning a process.

install(request) accepts only a catalog-confirmed immutable ConfirmedPluginInstall. Registry installs bind registry, tarball, and integrity; Git installs bind a complete commit; workspace entries are display-only and cannot trigger dynamic installation. The provider rebinds the request to the current catalog and compares package name, exact version, source, and integrity before committing a receipt.

Trust and Manifests

The descriptor and catalog use executionMode: 'trusted-in-process'. Review, authorization, and enforcement facts are separate; enforcement: unavailable means the public API is not a Node or Electron security boundary.

resolved-manifest.json records the profile runtime, bundles, sources, integrity, licenses, scripts, allowBuilds, the actual platform dependency closure, and input digest. The input digest covers cross-platform source inputs and the normalized YAML semantics of the root lockfile; platform-selected optional native packages remain evidence rather than digest identity. runtime-manifest.json adds Electron, Node, pnpm, native-addon, built-target, declared-target, and signing facts. Neither manifest proves author trustworthiness, license accuracy, a valid signature, or safety of executed code.

Verification

sh
pnpm run profile:resolve -- dsh-forge-official
pnpm run profile:verify -- dsh-forge-official
pnpm run catalog:verify
pnpm run test:desktop-services-consumer

The consumer-facing package guide is ../../packages/desktop-services/README.md. It provides a compilable example and package-specific maintenance commands.