Source-level environment and credential tests prove the individual loaders, but they do not prove that the published launcher runs them before Loader evaluates a shipped profile.
Start the built dsh binary with the shipped base bundle and a test-only LLM probe. Put the endpoint in $DSH_HOME/.env, put the bearer token only in $DSH_HOME/.credentials.yaml, remove inherited DeepSeek overrides, and assert the mock request received both without leaking the token. This covers launch order, profile composition, the adapter, and the credential seam without a real API.
The profile rework left references to the old entry modes behind. Renames
the user patch-layer API and its spec file (watchPersonalPatches ->
watchUserPatches, personal-config.spec.ts -> user-patches.spec.ts) and
retargets the prose that still named `config.yaml`, `--config`, raw-config
mode, and surface overlays: repository-plugin and mcp-memory READMEs, the
credentials-local anchor into app-boot, vendor manifest items 12-13, the
vendored include/hmr comments, and install.sh.
Restores the boot-failure guard the rework dropped with raw mode: the
built-bin case now boots `--profile web --patch <invalid>` and asserts the
settled diagnostic and exit 1, so the HMR initial-scan deadlock stays
covered; its orphaned raw fixture is renamed and the unused one deleted.
The superseded personal-config Agent Note and its superseding profile note
are now cross-linked.
A profile manifest and a bundle manifest are different kinds and shared one
flat `dsh` section: `dsh.plugins` listed bundles (not plugins) and `dsh.patch`
declared a bundle's layer. Each kind now names its role — a bundle declares
`dsh.bundle.patch`, a profile declares `dsh.profile.bundles` — so a
package.json states which role it plays and the list name matches its contents.
`DEFAULT_PROFILE_PLUGINS` becomes `DEFAULT_PROFILE_BUNDLES`, and
`DshManifestSection` splits into `DshBundleManifest`/`DshProfileManifest`.
Pre-release: no compatibility shim; turtle-ui moved with it (bd5ff10).
- Relative path specs (., ../plugin, file:/link: forms) anchor to the
invoking directory before forwarding: pnpm's cwd is the profile dir, so a
bare 'add .' from a plugin checkout used to self-link the profile (exit 0,
nothing installed). Bare paths stay bare and prefixed specs keep their
prefix, preserving pnpm's link-vs-copy semantics.
- dsh.plugins reconciles against the INSTALLED state on every successful
pnpm run, not the dependency diff: an update whose new version gains
dsh.patch activates the layer; a version that drops it (or a removal)
deactivates it. Template bundles are never touched.
- A failed pnpm run now names the profile directory, and a git-spec failure
explains pnpm >=10's prepare-script block with a pointer at the profile's
pnpm-workspace.yaml allowBuilds (turtle-ui's prepare-based git install is
the reference consumer); reference README documents all three.
The old $DSH_HOME/config.yaml personal overlay returns under the profile
scheme's filename: machine-local preferences that apply to every profile,
loaded after the profile's own cordis.patch.yml (so the home layer outranks
it) and before --patch overlays and flag patches. Both user layers are
hot-reloaded on long-lived surfaces and shown in --dump-config with their
own provenance labels; the built-bin e2e covers the home layer landing live.
- Clone patch lists per generation (boot + composeLive): the include pushes
insert rows by reference and mutates them in place, so a reused object
baked user overrides into bundle rows and removal could not revert; the
built-bin hot-reload e2e now asserts an override AND its removal reverting.
- The headless runner awaits Loader settlement before prompting (its inject
gate covers only apiProxy/httpServer) and abandons cleanly when the tree
died during the wait.
- healProfilesModuleFallback walks the app's full dependency+peer closure:
out-of-tree plugins import seam packages (dsh-compact, dsh-subprocess, ...)
that only implementations reach, and peers are how seams are declared.
- Profile init writes pnpm-workspace.yaml (nodeLinker: hoisted), not .npmrc
— pnpm >=10 reads settings from the workspace manifest.
- Web dumps reject boot-only flags instead of printing a tree that differs
from the same invocation's boot; --port validates at the flag;
--dump-default-config no longer parses the (possibly broken) user layer;
trustedHosts flag derivation merges over the composed value instead of
replacing it; web-runtime gains surfaceContext (headless disables the GUI
prompt/bash-vars the old -p never mounted); 'node_modules' is a reserved
profile name; plugin-warning names the recovery step; client AGENTS.md
registration surfaces point at the web-app bundle.
- Ship session-reference/tmux-context/tool-ask-user as app dependencies for
terminal front-door patch layers (turtle-ui), same stance as mcp-client.
A custom profile that mounts no telemetry-otel row exports nothing, so
DSH_TELEMETRY_DISABLED must not fail its boot (CI exports the switch
globally, which broke the lifecycle-fixture profile). The web-app dist
resolution test accepts the fail-loud unbuilt outcome the CI coverage lane
sees before any build, and the headless spec covers the idle-anchor and
pre-start skip branches under the per-file gate.
dsh --profile <name> replaces the fixed entry modes: --config and -p are
removed, --patch adds overlays over the composed profile, a positional task
selects one-shot mode (requires the headless-runner row), and dsh web stays as
the alias for --profile web carrying the Web flag family as patches. dsh
plugin --profile <name> forwards verbatim to pnpm in the profile directory,
initializes on first use, and reconciles the dsh.plugins layer list after
add/remove (patch-less packages warn and stay plain dependencies). Config
dumps and the keyless web e2e scaffold compose the same bundle layers over the
same empty root as the boot.
Master's transactional loader made the invalid-provider PTY case regress:
the HMR main watcher's initial scan refreshed the include mid-initial-apply,
the concurrent group updates stranded the include fiber, and once serialized
the failing apply's rollback deadlocked on HMR's refresh drain — dsh exited
13 with no diagnostic and the terminal stranded, the exact symptom this
branch fixes. Serialize every include child-tree mutation through one queue
and pass ignoreInitial to the HMR main watcher; the failing boot now settles
through boot()'s labelled rejection with the tree disposed and exit 1. The
PTY case asserts the settled diagnostic; the fail-loud release remains the
guard for rejections boot cannot see.
Conflicts: apps/cli/src/tui.ts (keep the release install over master's comment
rewording), packages/ui/app-boot/README* (master's new installFailLoud row
wording plus this branch's release and timeout rows).