@khorsheed/dsh-ankh-guard 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.en.md +1 -1
- package/README.i18n.yaml +2 -2
- package/README.md +1 -1
- package/lib/preflight-runner.js +30 -6
- package/lib/types/preflight-runner.d.ts +20 -0
- package/lib/types/preflight-runner.js +71 -7
- package/package.json +3 -3
- package/skills/dsh-self-restart-guard/SKILL.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.3.2(2026-09-27)
|
|
4
|
+
|
|
5
|
+
适配宿主 0.1.7-rc.2 线:verifiedHost 前移至 0.1.7-rc.2(3080 生产实证线随宿主基线切到 rc.2);rc.1→rc.2 对本包无破坏性变更(逐类清点见 [Agent Note](../../.agents/notes/implemented/architecture/2026-09-27-host-017-rc2-breaking-changes.md)),全量构建+测试双绿。
|
|
6
|
+
|
|
7
|
+
- **修复 tarball profile 上 preflight 误报 FAIL**:runner 把 compose 算出的 runtime resolution(0.1.7 的 `resolution` / 0.1.6 的 `generation`)算完即丢,boot prepare 从不挂载——tarball profile 的 node_modules 没有任何 `@deepseek-ai/*` 条目,原生解析全灭(实测 177 个条目 failed to import,真实启动完全干净)。现在 composePreflightPatches 保留并按两线键名返回 pluginPackagesConfig,prepare 按 runProfile 顺序挂载(profileContext → 启动环境 → PluginPackages → provideCmdline);提供 profileContext 的线追加 dry-run 覆写 `{ id: 'hmr', disabled: true }` 守住 no-HMR 契约。判定契约(0/1/3)与诊断强度不变
|
|
8
|
+
- **skill 防踩坑**:`dsh-self-restart-guard` 的取证步骤写明「证据命令作用域到改动所在仓库」——凭证绑定 harness 检出的 git HEAD;家族外改动(tarball 进 profile 的插件)由部署驱动器(`pnpm deploy:3080`)在自己的绿色门禁里记录。对 harness 全量套件手跑 `record --run` 会先清空既有有效凭证再撞上本机无关红(~11 分钟、561 个与本改动无关的失败),跑完门禁零证据
|
|
9
|
+
|
|
3
10
|
## 0.3.1(2026-09-26)
|
|
4
11
|
|
|
5
12
|
适配宿主 rc.1 线并实证 0.1.5/0.1.7 双线可用(0.1.5-rc.1 全量 boot 实证,2026-09-25;0.1.7-rc.1 为 3080 生产验证线)。
|
package/README.en.md
CHANGED
|
@@ -198,7 +198,7 @@ None.
|
|
|
198
198
|
|
|
199
199
|
- npm release line (`@deepseek-ai/dsh@0.1.5-rc.1`): supported — 0.1.5-rc.1 full-line boot-verified (42 packages including capture, 2026-09-25) — with two designed degrades. The composition-preflight gate runs through the standalone `preflight-runner` (composing through the published `@deepseek-ai/dsh-app-boot` primitives with a drift tripwire, since 0.1.5-rc.1 still does not export `composeProfile`) wherever a dsh app layout resolves — `--harness-root`, the durable launch spec, `DSH_HARNESS`, or the default checkout. On a pure npm deployment with no harness checkout the gate reports a notice and proceeds instead. The original-tab bridge feature-probes the optional WebServer/connection authentication seams; hosts without token auth naturally take the existing-cookie path. Cold reads (the parked probe and the preset derivation) ride the 0.1.5 handle-based sessionPersistence (`open(id, 'read')` → `read` → `close`; the one-shot `inspect` is gone). Every other capability is intact on the npm line. minHost stays 0.1.5-rc.1 — older hosts stay on the previous release line.
|
|
200
200
|
- Historical verification: a live npm-host 0.1.1-rc.2 → 0.1.2-alpha.4 isolated cutover passed (transition preflight removed a v3 whole-unit projection cache with an old-schema record from a home copy, live apply quarantined the old file, and target completed Token URL → 303 → cookie 200, the ownership stability window, and canary at zero retries; the old file remained byte-exact in the cutover directory. An untransitioned control over the same home failed on the missing Alpha.4 record fields, demonstrating that acceptance covered the real schema break).
|
|
201
|
-
- source line (deepseek-harness master, fork or upstream): ✅ (verifiedHost: 0.1.7-rc.
|
|
201
|
+
- source line (deepseek-harness master, fork or upstream): ✅ (verifiedHost: 0.1.7-rc.2) — the gate runs through the standalone `preflight-runner` (resolves the published `@deepseek-ai/dsh-app-boot` etc. from the live checkout), so no fork patch is required.
|
|
202
202
|
- Dual-line evidence: the 0.1.5 boot passes end-to-end through three compat layers — the [preset-registry dual-name probe](../../.agents/notes/implemented/bug-fix/2026-09-25-preset-registry-dual-name-probe.md), [dual-shape typert codecs](../../.agents/notes/implemented/bug-fix/2026-09-25-typert-codec-dual-shape.md), and [typert faces carrying zod@4](../../.agents/notes/implemented/bug-fix/2026-09-25-typert-faces-carry-zod-v4.md).
|
|
203
203
|
|
|
204
204
|
**Version line mapping**: the first release after 0.2.0 supports host `0.1.5-rc.1` and later; hosts on `0.1.2-rc.1` stay on `0.2.0`, hosts on `0.1.0-rc.6` ~ `0.1.1-rc.2` stay on the 0.1.x release line (last release `0.1.1`).
|
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
3
|
# editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/ankh-guard/README.en.md
|
|
5
|
-
packages/ankh-guard/README.en.md:
|
|
6
|
-
packages/ankh-guard/README.md:
|
|
5
|
+
packages/ankh-guard/README.en.md: 1503f247dbba22fb64d0d5988225a06ee2e49c9d
|
|
6
|
+
packages/ankh-guard/README.md: 90693789e64935abff5fb1d7fbbf5d4807c19a99
|
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ dsh-ankh-guard restart \
|
|
|
197
197
|
|
|
198
198
|
- npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):支持——0.1.5-rc.1 全量 boot 实证通过(42 包含 capture,2026-09-25)——带两处设计内降级。composition-preflight 门禁通过独立的 `preflight-runner` 运行(0.1.5-rc.1 仍未导出 `composeProfile`,runner 改经已发布的 `@deepseek-ai/dsh-app-boot` 原语组装,带漂移绊线测试),只要能解析到 dsh app 布局——`--harness-root`、耐久 launch spec、`DSH_HARNESS` 或默认检出路径——就完整运行。没有 harness 检出的纯 npm 部署下门禁退化为提示后放行。原标签页桥会探测可选 WebServer/connection 认证 seam,不使用 token 认证的宿主自然走现有 Cookie 路径;冷读(停靠探测与 preset 推导)走 0.1.5 的 handle 制 sessionPersistence(`open(id, 'read')` → `read` → `close`,一次性 `inspect` 已移除)。其余能力在 npm 线上完整。minHost 保持 0.1.5-rc.1,旧宿主请停留在旧发布线。
|
|
199
199
|
- 历史验证:npm host 的 0.1.1-rc.2 → 0.1.2-alpha.4 隔离切换已通过(transition preflight 在 home 副本上移开带旧 schema record 的 v3 whole-unit projection cache,live apply 隔离旧文件,target 以零重试完成 Token URL → 303 → Cookie 200、ownership 稳定窗口与 canary;旧文件逐字节保留在 cutover 目录。相同 home 的无 transition 对照因缺少 Alpha.4 record 字段而拒绝,证明验收覆盖了真实 schema 断裂面)。
|
|
200
|
-
- 源码线(deepseek-harness master,fork 或上游):✅(verifiedHost: 0.1.7-rc.
|
|
200
|
+
- 源码线(deepseek-harness master,fork 或上游):✅(verifiedHost: 0.1.7-rc.2)——门禁通过独立的 `preflight-runner` 运行(从在线 checkout 解析已发布的 `@deepseek-ai/dsh-app-boot` 等),不再需要 fork 补丁。
|
|
201
201
|
- 双线证据:0.1.5 的 boot 经三层兼容修复端到端通过——[preset-registry 双名探测](../../.agents/notes/implemented/bug-fix/2026-09-25-preset-registry-dual-name-probe.md)、[typert codec 双形状](../../.agents/notes/implemented/bug-fix/2026-09-25-typert-codec-dual-shape.md)、[face 自带 zod@4](../../.agents/notes/implemented/bug-fix/2026-09-25-typert-faces-carry-zod-v4.md)。
|
|
202
202
|
|
|
203
203
|
**版本线对照**:0.2.0 之后的首个发布起支持宿主 `0.1.5-rc.1` 及以后;宿主 `0.1.2-rc.1` 请停留在 `0.2.0`,宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 请停留在 0.1.x 发布线(末版 `0.1.1`)。
|
package/lib/preflight-runner.js
CHANGED
|
@@ -159,22 +159,34 @@ async function composePreflightPatches(profile, patchFiles, root, home, binding
|
|
|
159
159
|
if (hostLine === "rc") healProfilesModuleFallback(anchor, resolvedHome);
|
|
160
160
|
const composed = loadProfile(NAME, profile, anchor, resolvedHome, { userLayer: true });
|
|
161
161
|
writeFileSync(join(composed.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG);
|
|
162
|
+
let pluginPackagesConfig;
|
|
162
163
|
if (hostLine === "0.1.2") await healProfilesModuleFallback({
|
|
163
164
|
installAnchor: anchor,
|
|
164
165
|
profile: composed,
|
|
165
166
|
home: resolvedHome
|
|
166
167
|
});
|
|
167
|
-
else if (hostLine === "0.1.6") await createProfileResolutionGeneration({
|
|
168
|
+
else if (hostLine === "0.1.6") pluginPackagesConfig = { generation: await createProfileResolutionGeneration({
|
|
168
169
|
installAnchor: anchor,
|
|
169
170
|
profile: composed,
|
|
170
171
|
home: resolvedHome
|
|
171
|
-
});
|
|
172
|
-
else if (hostLine === "0.1.7") await createRuntimeResolution({
|
|
172
|
+
}) };
|
|
173
|
+
else if (hostLine === "0.1.7") pluginPackagesConfig = { resolution: await createRuntimeResolution({
|
|
173
174
|
installAnchor: anchor,
|
|
174
175
|
profile: composed
|
|
175
|
-
});
|
|
176
|
+
}) };
|
|
176
177
|
const homePatches = loadOptionalPatches(NAME, join(resolvedHome, HOME_PATCH_FILENAME)) ?? [];
|
|
177
178
|
const overlays = patchFiles.flatMap((file) => loadOverlayPatches(NAME, resolve(file)));
|
|
179
|
+
const profileContext = hostLine === "0.1.6" || hostLine === "0.1.7" ? {
|
|
180
|
+
name: profile,
|
|
181
|
+
dir: composed.dir,
|
|
182
|
+
patchPath: composed.patchPath,
|
|
183
|
+
installAnchor: anchor,
|
|
184
|
+
startedBundles: composed.layers.map((layer) => layer.packageName),
|
|
185
|
+
cwd: process.cwd(),
|
|
186
|
+
home: resolvedHome,
|
|
187
|
+
overlays,
|
|
188
|
+
telemetryDisabledEnv: process.env.DSH_TELEMETRY_DISABLED
|
|
189
|
+
} : void 0;
|
|
178
190
|
const bundlePatches = composed.layers.flatMap((layer) => layer.patches);
|
|
179
191
|
const patches = [
|
|
180
192
|
...bundlePatches,
|
|
@@ -208,6 +220,10 @@ async function composePreflightPatches(profile, patchFiles, root, home, binding
|
|
|
208
220
|
openBrowser: false
|
|
209
221
|
}
|
|
210
222
|
});
|
|
223
|
+
if (profileContext !== void 0 && rows.has("hmr")) composedOverlays.push({
|
|
224
|
+
id: "hmr",
|
|
225
|
+
disabled: true
|
|
226
|
+
});
|
|
211
227
|
if (rows.has("ankh-guard")) composedOverlays.push({
|
|
212
228
|
id: "ankh-guard",
|
|
213
229
|
config: {
|
|
@@ -224,7 +240,9 @@ async function composePreflightPatches(profile, patchFiles, root, home, binding
|
|
|
224
240
|
return {
|
|
225
241
|
patches,
|
|
226
242
|
rows,
|
|
227
|
-
profileDir: composed.dir
|
|
243
|
+
profileDir: composed.dir,
|
|
244
|
+
...profileContext === void 0 ? {} : { profileContext },
|
|
245
|
+
...pluginPackagesConfig === void 0 ? {} : { pluginPackagesConfig }
|
|
228
246
|
};
|
|
229
247
|
}
|
|
230
248
|
/**
|
|
@@ -302,6 +320,7 @@ async function runPreflight(profile, patchFiles = [], root = resolveHarnessRoot(
|
|
|
302
320
|
const loadLayeredEnv = appBoot.loadLayeredEnv;
|
|
303
321
|
const launchEnvironmentKey = launchEnvironment.DSH_LAUNCH_ENVIRONMENT_KEY;
|
|
304
322
|
const provideCmdline = cmdline.provideCmdline;
|
|
323
|
+
const PluginPackages = appBoot.PluginPackages;
|
|
305
324
|
let environment;
|
|
306
325
|
try {
|
|
307
326
|
environment = loadLayeredEnv(NAME);
|
|
@@ -322,8 +341,13 @@ async function runPreflight(profile, patchFiles = [], root = resolveHarnessRoot(
|
|
|
322
341
|
});
|
|
323
342
|
const rootConfig = join(composed.profileDir, PROFILE_ROOT_FILENAME);
|
|
324
343
|
const appReady = createAppReadyStub();
|
|
325
|
-
const ctx = await boot(NAME, rootConfig, structuredClone(patches), (hostCtx) => {
|
|
344
|
+
const ctx = await boot(NAME, rootConfig, structuredClone(patches), async (hostCtx) => {
|
|
345
|
+
if (composed.profileContext !== void 0) hostCtx.provide?.("profileContext", composed.profileContext);
|
|
326
346
|
hostCtx.provide?.(launchEnvironmentKey, environment);
|
|
347
|
+
if (composed.pluginPackagesConfig !== void 0) {
|
|
348
|
+
if (PluginPackages === void 0 || hostCtx.plugin === void 0) throw new Error("host line requires a PluginPackages mount but the loaded app-boot does not export PluginPackages");
|
|
349
|
+
await hostCtx.plugin(PluginPackages, composed.pluginPackagesConfig);
|
|
350
|
+
}
|
|
327
351
|
provideCmdline(hostCtx, {
|
|
328
352
|
args: [],
|
|
329
353
|
exit: () => {},
|
|
@@ -52,6 +52,26 @@ export interface PreflightComposition {
|
|
|
52
52
|
}>;
|
|
53
53
|
/** The profile directory (the include root's anchor). */
|
|
54
54
|
profileDir: string;
|
|
55
|
+
/**
|
|
56
|
+
* The launcher-owned profileContext service value (0.1.6+ lines), built
|
|
57
|
+
* field-for-field as runProfile builds it. Undefined on the heal-based
|
|
58
|
+
* lines, whose launcher provides no such service. The boot prepare must
|
|
59
|
+
* provide it before the tree mounts: the 0.1.7 settings service injects
|
|
60
|
+
* profileContext, so without it every settings-dependent apply never runs.
|
|
61
|
+
*/
|
|
62
|
+
profileContext?: Record<string, unknown>;
|
|
63
|
+
/**
|
|
64
|
+
* The PluginPackages mount config the boot prepare must apply, mirroring
|
|
65
|
+
* the launcher: `{ generation }` on the 0.1.6 line, `{ resolution }` on the
|
|
66
|
+
* 0.1.7 line. Undefined on the heal-based lines (rc, 0.1.2), where the heal
|
|
67
|
+
* materializes real fallback links and native Node resolution carries the
|
|
68
|
+
* tree — the launcher mounts no PluginPackages there either. Without this
|
|
69
|
+
* mount the in-memory resolution is computed and discarded, so on a tarball
|
|
70
|
+
* profile (whose own node_modules holds no `@deepseek-ai/*` entries) every
|
|
71
|
+
* entry import fails natively while the real boot of the same profile is
|
|
72
|
+
* clean.
|
|
73
|
+
*/
|
|
74
|
+
pluginPackagesConfig?: Record<string, unknown>;
|
|
55
75
|
}
|
|
56
76
|
/**
|
|
57
77
|
* Compose one profile's full patch stack through the launcher's layering —
|
|
@@ -200,6 +200,7 @@ export async function composePreflightPatches(profile, patchFiles, root, home, b
|
|
|
200
200
|
// the dry-run would otherwise fail on exactly the tree a first boot
|
|
201
201
|
// composes fine.
|
|
202
202
|
writeFileSync(join(composed.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG);
|
|
203
|
+
let pluginPackagesConfig;
|
|
203
204
|
if (hostLine === '0.1.2') {
|
|
204
205
|
await healProfilesModuleFallback({ installAnchor: anchor, profile: composed, home: resolvedHome });
|
|
205
206
|
}
|
|
@@ -208,19 +209,48 @@ export async function composePreflightPatches(profile, patchFiles, root, home, b
|
|
|
208
209
|
// compute the generation AFTER the profile load and root-config rewrite,
|
|
209
210
|
// materializing nothing. Awaited, so a resolution-graph failure rejects
|
|
210
211
|
// the compose instead of escaping as an unhandled rejection. The explicit
|
|
211
|
-
// home keeps the recorded profilesDir on the deployment under check.
|
|
212
|
-
|
|
212
|
+
// home keeps the recorded profilesDir on the deployment under check. The
|
|
213
|
+
// generation is kept: the launcher hands it to the boot's PluginPackages
|
|
214
|
+
// mount (`{ generation }`), and so must the dry-run — computing it and
|
|
215
|
+
// dropping it leaves profile-tree imports to native Node resolution,
|
|
216
|
+
// which finds nothing in a tarball profile's node_modules.
|
|
217
|
+
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, profile: composed, home: resolvedHome });
|
|
218
|
+
pluginPackagesConfig = { generation };
|
|
213
219
|
}
|
|
214
220
|
else if (hostLine === '0.1.7') {
|
|
215
221
|
// The 0.1.7 launcher compose (apps/cli composeProfile): the resolution is
|
|
216
222
|
// computed in memory right after the profile load and root-config rewrite
|
|
217
|
-
// — the older lines' fallback projections are gone for good
|
|
223
|
+
// — the older lines' fallback projections are gone for good, so this
|
|
224
|
+
// interception is the ONLY way profile-tree imports resolve. Awaited, so
|
|
218
225
|
// a resolution-graph failure rejects the compose instead of escaping as
|
|
219
|
-
// an unhandled rejection.
|
|
220
|
-
|
|
226
|
+
// an unhandled rejection. The resolution is kept for the boot's
|
|
227
|
+
// PluginPackages mount (`{ resolution }`), exactly as the launcher's
|
|
228
|
+
// runProfile hands it over; discarding it is the tarball-profile false
|
|
229
|
+
// FAIL (every official entry reports "failed to import" on a profile
|
|
230
|
+
// whose real boot is clean).
|
|
231
|
+
const resolution = await createRuntimeResolution({ installAnchor: anchor, profile: composed });
|
|
232
|
+
pluginPackagesConfig = { resolution };
|
|
221
233
|
}
|
|
222
234
|
const homePatches = loadOptionalPatches(NAME, join(resolvedHome, HOME_PATCH_FILENAME)) ?? [];
|
|
223
235
|
const overlays = patchFiles.flatMap(file => loadOverlayPatches(NAME, resolve(file)));
|
|
236
|
+
// The launcher provides a data-only profileContext service before the tree
|
|
237
|
+
// mounts (since 0.1.6-alpha.2; the heal-based lines had no such service).
|
|
238
|
+
// The 0.1.7 settings service injects it, so a dry-run without it leaves
|
|
239
|
+
// `settings` — and everything injecting it — pending: applies the contract
|
|
240
|
+
// promises to exercise never run, on a profile the real boot runs clean.
|
|
241
|
+
const profileContext = hostLine === '0.1.6' || hostLine === '0.1.7'
|
|
242
|
+
? {
|
|
243
|
+
name: profile,
|
|
244
|
+
dir: composed.dir,
|
|
245
|
+
patchPath: composed.patchPath,
|
|
246
|
+
installAnchor: anchor,
|
|
247
|
+
startedBundles: composed.layers.map(layer => layer.packageName),
|
|
248
|
+
cwd: process.cwd(),
|
|
249
|
+
home: resolvedHome,
|
|
250
|
+
overlays,
|
|
251
|
+
telemetryDisabledEnv: process.env.DSH_TELEMETRY_DISABLED,
|
|
252
|
+
}
|
|
253
|
+
: undefined;
|
|
224
254
|
const bundlePatches = composed.layers.flatMap(layer => layer.patches);
|
|
225
255
|
const patches = [...bundlePatches, ...composed.patches, ...homePatches, ...overlays];
|
|
226
256
|
const rows = new Map();
|
|
@@ -258,6 +288,15 @@ export async function composePreflightPatches(profile, patchFiles, root, home, b
|
|
|
258
288
|
},
|
|
259
289
|
});
|
|
260
290
|
}
|
|
291
|
+
if (profileContext !== undefined && rows.has('hmr')) {
|
|
292
|
+
// Providing profileContext satisfies the hmr row's disable expression
|
|
293
|
+
// (`!ctx.get('profileContext')`) on the runtime-resolution lines. A
|
|
294
|
+
// dry-run is one-shot — no HMR, no user-patch watchers: the boot's own
|
|
295
|
+
// tree write-back would queue a config refresh on hmr's operations queue,
|
|
296
|
+
// and dispose then awaits a queue that never drains (observed: preflight
|
|
297
|
+
// hung past boot and the process exited 13 on an unsettled await).
|
|
298
|
+
composedOverlays.push({ id: 'hmr', disabled: true });
|
|
299
|
+
}
|
|
261
300
|
if (rows.has('ankh-guard')) {
|
|
262
301
|
// The guard plugin writes state at apply (the instance-launch record,
|
|
263
302
|
// snapshots). A dry-run is NOT the real instance — isolate its state to a
|
|
@@ -279,7 +318,13 @@ export async function composePreflightPatches(profile, patchFiles, root, home, b
|
|
|
279
318
|
if (telemetryPatch !== undefined)
|
|
280
319
|
composedOverlays.push(telemetryPatch);
|
|
281
320
|
patches.push(...composedOverlays);
|
|
282
|
-
return {
|
|
321
|
+
return {
|
|
322
|
+
patches,
|
|
323
|
+
rows,
|
|
324
|
+
profileDir: composed.dir,
|
|
325
|
+
...(profileContext === undefined ? {} : { profileContext }),
|
|
326
|
+
...(pluginPackagesConfig === undefined ? {} : { pluginPackagesConfig }),
|
|
327
|
+
};
|
|
283
328
|
}
|
|
284
329
|
/**
|
|
285
330
|
* The launcher's readiness signal, mirrored: 0.1.2's runProfile provides an
|
|
@@ -363,6 +408,7 @@ export async function runPreflight(profile, patchFiles = [], root = resolveHarne
|
|
|
363
408
|
const loadLayeredEnv = appBoot.loadLayeredEnv;
|
|
364
409
|
const launchEnvironmentKey = launchEnvironment.DSH_LAUNCH_ENVIRONMENT_KEY;
|
|
365
410
|
const provideCmdline = cmdline.provideCmdline;
|
|
411
|
+
const PluginPackages = appBoot.PluginPackages;
|
|
366
412
|
let environment;
|
|
367
413
|
try {
|
|
368
414
|
environment = loadLayeredEnv(NAME);
|
|
@@ -389,8 +435,26 @@ export async function runPreflight(profile, patchFiles = [], root = resolveHarne
|
|
|
389
435
|
const appReady = createAppReadyStub();
|
|
390
436
|
// Cloned for the same insert-aliasing reason the launcher documents: boot
|
|
391
437
|
// application mutates rows by reference.
|
|
392
|
-
const ctx = await boot(NAME, rootConfig, structuredClone(patches), (hostCtx) => {
|
|
438
|
+
const ctx = await boot(NAME, rootConfig, structuredClone(patches), async (hostCtx) => {
|
|
439
|
+
// Mirror runProfile's prepare order: profileContext, launch environment,
|
|
440
|
+
// PluginPackages, cmdline — all before boot() mounts the root include.
|
|
441
|
+
if (composed.profileContext !== undefined)
|
|
442
|
+
hostCtx.provide?.('profileContext', composed.profileContext);
|
|
393
443
|
hostCtx.provide?.(launchEnvironmentKey, environment);
|
|
444
|
+
// On the runtime-resolution lines the composed resolution/generation
|
|
445
|
+
// must be mounted in-process through PluginPackages BEFORE the config
|
|
446
|
+
// tree mounts — boot() awaits prepare before the root include, so every
|
|
447
|
+
// entry import resolves through the interception. Skipping the mount is
|
|
448
|
+
// not a neutral shortcut: without it Node resolves profile-tree imports
|
|
449
|
+
// natively, a tarball profile's node_modules holds no official packages,
|
|
450
|
+
// and the dry-run reports a wall of "failed to import" on a tree the
|
|
451
|
+
// real launcher boots clean.
|
|
452
|
+
if (composed.pluginPackagesConfig !== undefined) {
|
|
453
|
+
if (PluginPackages === undefined || hostCtx.plugin === undefined) {
|
|
454
|
+
throw new Error('host line requires a PluginPackages mount but the loaded app-boot does not export PluginPackages');
|
|
455
|
+
}
|
|
456
|
+
await hostCtx.plugin(PluginPackages, composed.pluginPackagesConfig);
|
|
457
|
+
}
|
|
394
458
|
provideCmdline(hostCtx, { args: [], exit: () => { }, ready: appReady.service });
|
|
395
459
|
});
|
|
396
460
|
// The launcher commits readiness once boot and host setup settle; a
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khorsheed/dsh-ankh-guard",
|
|
3
3
|
"description": "Hard gate for self-modification restarts: a green-build credential bound to the git HEAD, checked before any restart of the running instance",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -90,8 +90,8 @@
|
|
|
90
90
|
},
|
|
91
91
|
"compat": {
|
|
92
92
|
"minHost": "0.1.5-rc.1",
|
|
93
|
-
"notes": "0.1.5-rc.1 full-line boot-verified 2026-09-25 (42 packages including capture) through the three compat layers: preset-registry dual-name probe, dual-shape typert codecs, and typert faces carrying zod@4; 0.1.7-rc.
|
|
94
|
-
"verifiedHost": "0.1.7-rc.
|
|
93
|
+
"notes": "0.1.5-rc.1 full-line boot-verified 2026-09-25 (42 packages including capture) through the three compat layers: preset-registry dual-name probe, dual-shape typert codecs, and typert faces carrying zod@4; 0.1.7-rc.2 is the 3080 production-verified line; composition-preflight runs via the standalone preflight-runner and degrades to a notice when no live harness checkout resolves; original-tab browser handoff feature-probes optional WebServer/connection auth seams; reversible state quarantine was exercised in a live 0.1.1-rc.2 to 0.1.2-alpha.4 cutover",
|
|
94
|
+
"verifiedHost": "0.1.7-rc.2"
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -52,6 +52,8 @@ $GUARD verify --repo <repo> --state-dir "$DSH_HOME/state"
|
|
|
52
52
|
|
|
53
53
|
For a classified pure same-launch restart, run only the `verify` line first. If it reports either a fresh green credential or `proven deployment valid`, continue without rerunning the expensive command. The reusable proof exists only after this guard version has observed a complete watchdog restart and canary; `last-good-boot.json` by itself, an older guard state, or a matching HEAD without the runtime fingerprint does not qualify. `schedule-exit` recomputes the fingerprint and pins the selected evidence SHA into the short-lived restart marker, and the successor watchdog revalidates it before accepting canary. A refusal is not bypassable: run the full evidence command above.
|
|
54
54
|
|
|
55
|
+
Scope the evidence command to the change being proved, and run it against the repo that change lives in. When the change lives OUTSIDE the credential repo — plugin packages shipped to a profile as tarballs, say — the credential repo's monorepo-wide suite proves nothing about the change and may carry unrelated red (a 35k-test suite with hundreds of pre-existing failures in experimental packages takes ~11 minutes to fail and records nothing); that is the deployment driver's job (e.g. `pnpm deploy:3080` records trust-command evidence from its own green gate — see below). Hand `record --run` is for changes inside the credential repo itself, scoped to the suites the change touches whenever that is honest. And because `record` clears the old credential and the reusable proof before running, a failed evidence command leaves the gate with zero valid evidence — `verify` first, record only what you must.
|
|
56
|
+
|
|
55
57
|
`--trust-command --command "..."` is reserved for an external orchestrator that already observed the command's real exit status (for example, the repository's deployment driver). It is not an agent shortcut.
|
|
56
58
|
|
|
57
59
|
4. **Composition preflight** — `restart`, `schedule-exit`, and `reconfigure` each run this gate internally exactly once and refuse before stopping the healthy host. For an ordinary same-launch restart, call the stop-capable verb directly; use the standalone verb only to diagnose an already-observed failure, never as a speculative duplicate immediately before that verb:
|