@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 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.1) — 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.
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: 551302cb33397c2c0e363d86de2a9fd66d52185a
6
- packages/ankh-guard/README.md: 7bd89a16642978a8ded951d2248b2a51887c294c
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.1)——门禁通过独立的 `preflight-runner` 运行(从在线 checkout 解析已发布的 `@deepseek-ai/dsh-app-boot` 等),不再需要 fork 补丁。
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`)。
@@ -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
- await createProfileResolutionGeneration({ installAnchor: anchor, profile: composed, home: resolvedHome });
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. Awaited, so
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
- await createRuntimeResolution({ installAnchor: anchor, profile: composed });
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 { patches, rows, profileDir: composed.dir };
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.1",
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.1 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.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.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: