dsh-completion-guard 0.4.2 → 0.5.0

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as DEFAULT_HOST_LOCK, $t as SUPPORTED_EVIDENCE_ADAPTERS, A as decideTurnBoundary, At as bindingSatisfies, B as extractTextContent, Bt as extractArtifactPaths, C as createGitPrestateEnvelope, Ct as closingHint, D as revalidateGitPrestate, Dt as evidenceAvailabilityReason, E as parseGitCommandManifest, Et as renderRecoveryPacket, F as CAPTURE_V042_NOTICE, Ft as createProjection, G as isRunExecutable, Gt as canonicalRegistryBase, H as isDeterministicCheck, Ht as extractOperation, I as PROTOCOL_V3_NOTICE, It as rebindResponse, J as goalCompletionDenial, Jt as ACTION_MANIFEST_VERSION, K as parsePwshCommand, Kt as npmEscapedPackageName, L as deriveProjection, Lt as captureClause, M as isWholeTaskCompletionClaim, Mt as evidenceMatchesItem, N as latestAssistantText, Nt as isVerifyingCapability, O as verifiedLinearCommitReadback, Ot as itemDiagnosis, P as observeAssistantOutcome, Pt as currentContractDigest, Q as BASE_HOST_PACKAGES, Qt as STOP_PROTOCOL_VERSION, R as supersedeItem, Rt as captureItem, S as commitTreeSnapshotDigest, St as MIN_RECOVERY_CHAR_BUDGET, T as gitCommandMatchesTarget, Tt as recoveryDigest, U as withDurability, Ut as isInformationalMessage, V as extractToolSubject, Vt as extractMethod, W as canonicalArgvFromCommand, Wt as segmentClauses, X as ALPHA2_DSHMARKET_139_HOST_PACKAGES, Xt as SEMANTIC_ACTIONS, Y as hasCurrentCertificate, Yt as CERTIFICATE_VERSION, Z as ALPHA2_HOST_PACKAGES, Zt as STATEFUL_ACTIONS, _ as resolveInstalledHostLock, _t as effectuateBoundary, a as createProofManifest, an as semanticActionFromText, at as bindLiveGoalCapability, b as GIT_COMMAND_MANIFEST_IDS, bt as certifyCheckpoint, c as sessionQuery, cn as COMMAND_SURFACE_MANIFEST, ct as evaluateHostLock, d as hostLockContextFromComposedDump, dn as digestStrings, dt as RC1_HOST_PACKAGES, en as actionCompatible, et as EXPECTED_HOST_PACKAGES, f as hostLockRowsFromComposedDump, fn as normalizeClause, ft as ALPHA3_HOST_PACKAGES, g as resolveActiveProfileHostLock, gt as availableBoundaryQualifications, h as packageRowsFromPnpmLock, hn as sha256, ht as classifyUserInteraction, i as canonicalProjection, in as semanticActionFromCommand, it as bindExecutableIdentity, j as decideTurnStopping, jt as evidenceCoverage, k as classifyCompletionClaim, kt as relevantEvidence, l as validateProofManifest, ln as validateManifest, lt as evaluateToolSurfaceCapability, m as packageRowsFromActiveGraph, mn as sanitizeUrl, mt as segmentAuthorityBlocks, n as PROOF_PROTOCOL_VERSION, nn as requestedTargetAuthorizesMutation, nt as HOST_CAPABILITY_PACKAGE_GROUPS, o as proofDigest, on as validateActionManifest, ot as evaluateExternalWaitCapability, p as injectActiveProfileHostLock, pn as sanitizeClauseText, pt as authorityCaptureCounts, q as parseShellCommand, qt as ACTION_MANIFEST, r as bindProofToProjection, rn as requestedTargetMatchesResolved, rt as HOST_COHORTS, s as proofEvidenceConstraints, sn as validateActionTarget, st as evaluateHostCapability, t as PROOF_KINDS, tn as isStatefulAction, tt as GOAL_HOST_PACKAGES, u as HostProfileError, un as canonicalizePath, ut as selectHostCohort, v as verifyComposedHostLockDump, vt as isCurrentAcceptedBoundary, w as executeRevalidatedGitEffect, wt as openItems, x as commitIndexSnapshotDigest, xt as DEFAULT_RECOVERY_CHAR_BUDGET, y as snapshotSessionEvents, yt as qualifyBoundary, z as evidenceFromPersistedToolResult, zt as classifyClause } from "./domain-BXB_BPQg.js";
1
+ import { $ as isRunExecutable, $t as itemDiagnosis, A as revalidateGitPrestate, An as canonicalizePath, At as MIN_RECOVERY_CHAR_BUDGET, B as lifecyclePhase, Bt as createProjection, C as GIT_COMMAND_TEMPLATES, Cn as semanticActionFromText, Ct as segmentAuthorityBlocks, D as executeRevalidatedGitEffect, Dn as validateManifest, Dt as qualifyBoundary, E as createGitPrestateEnvelope, En as COMMAND_SURFACE_MANIFEST, Et as isCurrentAcceptedBoundary, F as isWholeTaskCompletionClaim, Fn as sha256, Ft as bindingSatisfies, G as deriveProjection, Gt as rebindAttemptKey, H as CAPTURE_V042_NOTICE, Ht as proposeRebind, I as latestAssistantText, It as evidenceCoverage, J as extractTextContent, Jt as CONFIRM_LINE_PATTERN, K as supersedeItem, Kt as rebindResponse, L as observeAssistantOutcome, Lt as evidenceMatchesItem, M as classifyCompletionClaim, Mn as normalizeClause, Mt as openItems, N as decideTurnBoundary, Nn as sanitizeClauseText, Nt as recoveryDigest, O as gitCommandMatchesTarget, On as classifyTaskIntent, Ot as certifyCheckpoint, P as decideTurnStopping, Pn as sanitizeUrl, Pt as renderRecoveryPacket, Q as canonicalArgvFromCommand, Qt as evidenceAvailabilityReason, R as FIRST_STEP_GUIDANCE, Rt as isVerifyingCapability, S as GIT_COMMAND_MANIFEST_IDS, Sn as semanticActionFromCommand, St as authorityCaptureCounts, T as commitTreeSnapshotDigest, Tn as validateActionTarget, Tt as effectuateBoundary, U as PROTOCOL_V3_NOTICE, Ut as proposeRebindOutcome, V as previewFirstStepInjection, Vt as confirmRebind, W as PROTOCOL_V4_NOTICE, Wt as proposeRebindV042, X as isDeterministicCheck, Xt as parseConfirmationMessage, Y as extractToolSubject, Yt as isFrozenV042RebindResponse, Z as withDurability, Zt as deriveItemDiagnosis, _ as readActiveHostGraph, _n as SUPPORTED_EVIDENCE_ADAPTERS, _t as evaluateHostLock, a as createProofManifest, an as extractMethod, at as ALPHA2_HOST_PACKAGES, b as verifyComposedHostLockDump, bn as requestedTargetAuthorizesMutation, bt as RC1_HOST_PACKAGES, c as sessionQuery, cn as segmentClauses, ct as EXPECTED_HOST_PACKAGES, d as hostLockContextFromComposedDump, dn as ACTION_MANIFEST, dt as HOST_COHORTS, en as relevantEvidence, et as parsePwshCommand, f as hostLockRowsFromComposedDump, fn as ACTION_MANIFEST_VERSION, ft as LEGACY_HOST_COHORTS, g as packageRowsFromPnpmLock, gn as STOP_PROTOCOL_VERSION, gt as evaluateHostCapability, h as packageRowsFromActiveGraph, hn as STATEFUL_ACTIONS, ht as evaluateExternalWaitCapability, i as canonicalProjection, in as extractArtifactPaths, it as ALPHA2_DSHMARKET_139_HOST_PACKAGES, j as verifiedLinearCommitReadback, jn as digestStrings, jt as closingHint, k as parseGitCommandManifest, kn as classifyUserInteraction, kt as DEFAULT_RECOVERY_CHAR_BUDGET, l as validateProofManifest, ln as canonicalRegistryBase, lt as GOAL_HOST_PACKAGES, m as inspectTargetHostGraph, mn as SEMANTIC_ACTIONS, mt as bindLiveGoalCapability, n as PROOF_PROTOCOL_VERSION, nn as captureItem, nt as goalCompletionDenial, o as proofDigest, on as extractOperation, ot as BASE_HOST_PACKAGES, p as injectActiveProfileHostLock, pn as CERTIFICATE_VERSION, pt as bindExecutableIdentity, q as evidenceFromPersistedToolResult, qt as replayRebindResult, r as bindProofToProjection, rn as classifyClause, rt as hasCurrentCertificate, s as proofEvidenceConstraints, sn as isInformationalMessage, st as DEFAULT_HOST_LOCK, t as PROOF_KINDS, tn as captureClause, tt as parseShellCommand, u as HostProfileError, un as npmEscapedPackageName, ut as HOST_CAPABILITY_PACKAGE_GROUPS, v as resolveActiveProfileHostLock, vn as actionCompatible, vt as evaluateToolSurfaceCapability, w as commitIndexSnapshotDigest, wn as validateActionManifest, wt as availableBoundaryQualifications, x as snapshotSessionEvents, xn as requestedTargetMatchesResolved, xt as ALPHA3_HOST_PACKAGES, y as resolveInstalledHostLock, yn as isStatefulAction, yt as selectHostCohort, z as claimedBatchHasRealRootInput, zt as currentContractDigest } from "./domain-Cx8vpxSj.js";
2
2
  import { defineTool } from "@deepseek-ai/dsh-tools";
3
3
  import { createHash } from "node:crypto";
4
4
  import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
@@ -812,6 +812,102 @@ function createBoundaryTool(getProjection, prepare, onRejected) {
812
812
  });
813
813
  }
814
814
 
815
+ //#endregion
816
+ //#region src/tools/prepare.ts
817
+ /**
818
+ * Thin READ-ONLY preparation surface (v0.5): before any stateful action it
819
+ * reports the supported command shape, the required resolution/effect/state
820
+ * evidence order, existing reusable references, and the exact missing fields.
821
+ * It never executes, installs, commits, pushes, restarts, or probes authority
822
+ * through side effects, and it never upgrades a default into user authority.
823
+ */
824
+ function createPrepareTool(options) {
825
+ return defineTool({
826
+ name: "context_guard_prepare",
827
+ description: "Read-only pre-action preparation: report the supported command shape, required resolution/effect/state evidence order, existing references, and exact missing target fields for one contract item. Performs no action and grants no authority.",
828
+ parameters: {
829
+ item_id: {
830
+ type: "string",
831
+ required: true
832
+ },
833
+ item_revision: { type: "number" },
834
+ semantic_action: { type: "string" },
835
+ requested_target: {
836
+ type: "object",
837
+ additionalProperties: true
838
+ },
839
+ planned_operation: { type: "string" }
840
+ },
841
+ output: {
842
+ schema: {
843
+ type: "object",
844
+ additionalProperties: true
845
+ },
846
+ render: (_args, value) => [{
847
+ type: "text",
848
+ text: JSON.stringify(value)
849
+ }]
850
+ },
851
+ async execute(rawArgs) {
852
+ const args = rawArgs;
853
+ const p = options.getProjection();
854
+ if (!p || !p.enabled || p.integrity !== "valid") return {
855
+ status: "unknown",
856
+ reason_code: "guard_unavailable"
857
+ };
858
+ const item = p.items.get(args.item_id ?? "");
859
+ if (!item) return {
860
+ status: "rejected",
861
+ reason_code: "item_not_found"
862
+ };
863
+ if (args.item_revision !== void 0 && item.revision !== args.item_revision) return {
864
+ status: "rejected",
865
+ reason_code: "item_revision_mismatch",
866
+ item_revision: item.revision
867
+ };
868
+ const diagnosis = deriveItemDiagnosis(p, item);
869
+ const plannedAction = args.semantic_action ?? item.semanticAction;
870
+ const manifestEntry = plannedAction ? ACTION_MANIFEST.actions[plannedAction] : void 0;
871
+ if (plannedAction && !manifestEntry) return {
872
+ status: "rejected",
873
+ reason_code: "unsupported_action"
874
+ };
875
+ const reusable = [...p.evidence.values()].filter((evidence) => relevantEvidence(p, item, evidence) && evidenceAvailabilityReason(evidence) === void 0).sort((a, b) => b.toolResultSeq - a.toolResultSeq).slice(0, 8).map((evidence) => ({
876
+ evidence_id: evidence.id,
877
+ role: evidence.evidenceRole,
878
+ semantic_action: evidence.semanticAction,
879
+ resolved_target: evidence.resolvedTarget,
880
+ tool_result_seq: evidence.toolResultSeq
881
+ }));
882
+ const requiredOrder = manifestEntry?.stateful ? [
883
+ "resolution (prestate facts from a trusted read)",
884
+ "effect (the exact planned change)",
885
+ "state (independent post-state readback)"
886
+ ] : ["state (matching durable evidence for the requested verification)"];
887
+ const missingTargetFields = manifestEntry?.stateful && plannedAction ? manifestEntry.resolvedTargetKeys.filter((key) => !(item.requestedTarget?.[key] !== void 0 || args.requested_target?.[key] !== void 0)) : [];
888
+ const capability = plannedAction && options.hostCapability ? options.hostCapability(plannedAction) : void 0;
889
+ return {
890
+ status: "prepared",
891
+ item: {
892
+ id: item.id,
893
+ revision: item.revision
894
+ },
895
+ diagnosis,
896
+ planned_action: plannedAction,
897
+ supported_command_shape: options.commandTemplate && plannedAction ? options.commandTemplate(plannedAction) ?? void 0 : void 0,
898
+ required_evidence_order: requiredOrder,
899
+ reusable_references: reusable,
900
+ missing_target_fields: missingTargetFields,
901
+ host_capability: capability ? {
902
+ status: capability.status,
903
+ reason_code: capability.reasonCode
904
+ } : void 0,
905
+ note: "Preparation performs no action. A default or guessed target is not user authority; explicit root instruction is required for missing target fields."
906
+ };
907
+ }
908
+ });
909
+ }
910
+
815
911
  //#endregion
816
912
  //#region src/tools/evidence.ts
817
913
  const execFileAsync = promisify(execFile);
@@ -900,7 +996,7 @@ function tuple(value) {
900
996
  function adapterId(action) {
901
997
  if (["create", "modify"].includes(action)) return "context-guard.artifact.v1";
902
998
  if (["install", "apply"].includes(action)) return "context-guard.package.v1";
903
- if (action === "restart") return "context-guard.service.v1";
999
+ if (action === "restart") return "context-guard.service.v2";
904
1000
  if (action === "publish") return "context-guard.registry.v1";
905
1001
  return "context-guard.git.v1";
906
1002
  }
@@ -913,7 +1009,7 @@ function unavailable(action, role, reason) {
913
1009
  resolved_target: {},
914
1010
  observed_state: {},
915
1011
  adapter_id: adapterId(action),
916
- adapter_version: PRODUCER_VERSION,
1012
+ adapter_version: action === "restart" ? "2.0.0" : PRODUCER_VERSION,
917
1013
  target_digest: "",
918
1014
  command_manifest_digest: ""
919
1015
  };
@@ -927,7 +1023,7 @@ function supported(action, role, resolved, observed = {}, commandManifest = {},
927
1023
  resolved_target: jsonTuple(resolved),
928
1024
  observed_state: jsonTuple(observed),
929
1025
  adapter_id: adapterId(action),
930
- adapter_version: PRODUCER_VERSION,
1026
+ adapter_version: action === "restart" ? "2.0.0" : PRODUCER_VERSION,
931
1027
  target_digest: digest(resolved),
932
1028
  command_manifest_digest: digest(commandManifest),
933
1029
  ...gitBinding ? { git_binding: gitBinding } : {},
@@ -972,7 +1068,7 @@ function findResolution(events, callId, action) {
972
1068
  }
973
1069
  if (event.type !== "tool/result") continue;
974
1070
  const meta = producerMeta(event);
975
- if (meta?.semanticAction !== action || meta.evidenceRole !== "resolution" || meta.adapterVersion !== PRODUCER_VERSION || !selector || !commandManifest) return void 0;
1071
+ if (meta?.semanticAction !== action || meta.evidenceRole !== "resolution" || meta.adapterVersion !== (action === "restart" ? "2.0.0" : PRODUCER_VERSION) || !selector || !commandManifest) return void 0;
976
1072
  const target = tuple(meta.resolvedTarget);
977
1073
  const rawBinding = record(meta.gitBinding);
978
1074
  const rawManifest = record(rawBinding?.manifest);
@@ -1155,13 +1251,13 @@ async function tgzIdentity(path) {
1155
1251
  integrity: `sha512-${createHash("sha512").update(bytes).digest("base64")}`
1156
1252
  };
1157
1253
  }
1158
- const DSHMARKET_VERSION = "1.36.0";
1159
- const DSHMARKET_INTEGRITY = "sha512-xX8CCoXdIALaxtLosj+5qGg8r1cykW2zo1AOPJcSQepg2r4Vd2K0NmERldDqfeyFV0pCuZsUoAPe1Q/BW7De/g==";
1160
1254
  const MARKET_SCHEMA = "dsh-market/update-api/v1";
1255
+ const MARKET_GENERATION_PREFIX = "dsh-market-instance/v1:";
1161
1256
  async function marketCapabilities(roots, signal) {
1162
- if (!roots.profile || !roots.marketOrigin) return void 0;
1257
+ if (!roots.profile || !roots.marketOrigin || !roots.verifyMarketInstance) return void 0;
1163
1258
  const installed = await profilePackage(roots.profile.path, "dshmarket");
1164
- if (installed?.version !== DSHMARKET_VERSION || installed.integrity !== DSHMARKET_INTEGRITY) return void 0;
1259
+ const binding = await roots.verifyMarketInstance(signal);
1260
+ if (!installed || !binding || binding.origin !== roots.marketOrigin || binding.profile !== roots.profile.name || binding.version !== installed.version || binding.integrity !== installed.integrity || !/^[0-9a-f]{64}$/.test(binding.loadedTreeSha256) || !binding.processIdentity || !binding.bootId) return void 0;
1165
1261
  const response = await (roots.fetcher ?? fetch)(`${roots.marketOrigin}/dsh-market/api/v1/capabilities`, {
1166
1262
  signal,
1167
1263
  headers: { accept: "application/json" },
@@ -1169,8 +1265,32 @@ async function marketCapabilities(roots, signal) {
1169
1265
  });
1170
1266
  if (!response.ok) return void 0;
1171
1267
  const value = record(await response.json());
1172
- if (value?.schema !== MARKET_SCHEMA || value.apiVersion !== 1 || value.marketVersion !== DSHMARKET_VERSION || value.profile !== roots.profile.name || typeof value.bootId !== "string") return void 0;
1173
- return value;
1268
+ if (value?.schema !== MARKET_SCHEMA || value.apiVersion !== 1 || value.marketVersion !== binding.version || value.profile !== binding.profile || value.bootId !== binding.bootId) return void 0;
1269
+ const features = record(value.features);
1270
+ const restart = record(value.restart);
1271
+ if (features?.restart !== true || restart?.supported !== true || restart.managedBy !== "market") return void 0;
1272
+ const provider = createHash("sha256").update(JSON.stringify([
1273
+ MARKET_SCHEMA,
1274
+ binding.origin,
1275
+ binding.profile,
1276
+ binding.version,
1277
+ binding.integrity,
1278
+ binding.loadedTreeSha256,
1279
+ "restart",
1280
+ true,
1281
+ "market"
1282
+ ])).digest("hex");
1283
+ const instance = createHash("sha256").update(JSON.stringify([binding.processIdentity, binding.bootId])).digest("hex");
1284
+ return {
1285
+ ...value,
1286
+ generation: `${MARKET_GENERATION_PREFIX}${provider}:${instance}`
1287
+ };
1288
+ }
1289
+ function sameMarketProvider(before, after) {
1290
+ const pattern = /^dsh-market-instance\/v1:([0-9a-f]{64}):[0-9a-f]{64}$/;
1291
+ const left = typeof before === "string" ? pattern.exec(before) : null;
1292
+ const right = typeof after === "string" ? pattern.exec(after) : null;
1293
+ return !!left && !!right && left[1] === right[1];
1174
1294
  }
1175
1295
  function importerLocator(text, packageId) {
1176
1296
  const escaped = packageId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -1200,6 +1320,7 @@ async function profilePackage(profilePath, packageId) {
1200
1320
  try {
1201
1321
  const lock = await readFile(resolve(profilePath, "pnpm-lock.yaml"), "utf8");
1202
1322
  const locator = importerLocator(lock, packageId);
1323
+ if (packageId === "dshmarket" && (!locator || locator.split("(", 1)[0] !== manifest.version)) return void 0;
1203
1324
  const integrity = locator ? lockIntegrity(lock, packageId, locator) : void 0;
1204
1325
  return integrity ? {
1205
1326
  version: manifest.version,
@@ -1354,7 +1475,8 @@ async function executeGuardAction(action, resolution, roots, signal, executableI
1354
1475
  if (action === "restart") {
1355
1476
  const capabilities = await marketCapabilities(roots, signal);
1356
1477
  if (!capabilities || !roots.marketOrigin || !resolutionCallId || !agent) return "unavailable";
1357
- if (capabilities.bootId !== target.pre_generation) return restartIntent(snapshotSessionEvents(agent.session), resolutionCallId, target) ? "completed" : "unavailable";
1478
+ if (!sameMarketProvider(target.pre_generation, capabilities.generation)) return "unavailable";
1479
+ if (capabilities.generation !== target.pre_generation) return restartIntent(snapshotSessionEvents(agent.session), resolutionCallId, target) ? "completed" : "unavailable";
1358
1480
  if (restartIntent(snapshotSessionEvents(agent.session), resolutionCallId, target)) return "handoff_pending";
1359
1481
  if (!roots.persistRestartIntent || !await roots.persistRestartIntent(agent, {
1360
1482
  resolutionCallId,
@@ -1629,7 +1751,7 @@ async function resolveTarget(action, selector, commandManifest, cwd, roots, sign
1629
1751
  if (features?.restart !== true || restart?.supported !== true || restart.managedBy !== "market") return void 0;
1630
1752
  return { target: {
1631
1753
  service_id: service,
1632
- pre_generation: String(capabilities?.bootId)
1754
+ pre_generation: String(capabilities?.generation)
1633
1755
  } };
1634
1756
  }
1635
1757
  if (action === "publish") {
@@ -1770,9 +1892,9 @@ async function readback(action, target, roots, signal, executableIdentity$1) {
1770
1892
  }
1771
1893
  if (action === "restart") {
1772
1894
  const capabilities = await marketCapabilities(roots, signal);
1773
- if (!capabilities || capabilities.bootId === target.pre_generation) return void 0;
1895
+ if (!capabilities || !sameMarketProvider(target.pre_generation, capabilities.generation) || capabilities.generation === target.pre_generation) return void 0;
1774
1896
  return {
1775
- new_generation: String(capabilities.bootId),
1897
+ new_generation: String(capabilities.generation),
1776
1898
  health: "healthy"
1777
1899
  };
1778
1900
  }
@@ -1850,6 +1972,7 @@ function normalizedRoots(options) {
1850
1972
  path: resolve(options.profile.path)
1851
1973
  } } : detectedProfile ? { profile: detectedProfile } : {},
1852
1974
  ...options.marketOrigin ? { marketOrigin: options.marketOrigin } : {},
1975
+ ...options.verifyMarketInstance ? { verifyMarketInstance: options.verifyMarketInstance } : {},
1853
1976
  ...options.fetcher ? { fetcher: options.fetcher } : {},
1854
1977
  ...options.commandRunner ? { commandRunner: options.commandRunner } : {},
1855
1978
  ...options.persistRestartIntent ? { persistRestartIntent: options.persistRestartIntent } : {},
@@ -2258,7 +2381,7 @@ function createEvidenceTool(options = {}) {
2258
2381
  const executableBinding = executable$1 ? await (roots.readExecutableIdentity ?? executableIdentity)(executable$1, exec.signal) : void 0;
2259
2382
  if (executable$1 && !executableBinding) return unavailable(action, role, "executable_identity_unavailable");
2260
2383
  const resolved = await resolveTarget(action, record(args.selector) ?? {}, record(args.command_manifest) ?? {}, cwdOf(agent), roots, exec.signal, executableBinding);
2261
- if (!resolved) return unavailable(action, role, "resolution_unavailable");
2384
+ if (!resolved) return unavailable(action, role, action === "restart" && !roots.verifyMarketInstance ? "market_instance_binding_unavailable" : "resolution_unavailable");
2262
2385
  const gitBinding = resolved.gitBinding ? JSON.parse(JSON.stringify(resolved.gitBinding)) : void 0;
2263
2386
  const commandManifest = record(args.command_manifest) ?? {};
2264
2387
  const expectedTransition = await expectedTransitionForResolution(action, resolved.target, commandManifest);
@@ -2288,7 +2411,7 @@ function createEvidenceTool(options = {}) {
2288
2411
  if (ownedEffect) {
2289
2412
  const events = snapshotSessionEvents(agent.session);
2290
2413
  const actionCall = actionCallMatches(events, args.effect_call_id, action, args.resolution_call_id, resolution.target);
2291
- if (!(action === "restart" ? actionCall && restartIntent(events, args.resolution_call_id, resolution.target) && (await marketCapabilities(roots, exec.signal))?.bootId !== resolution.target.pre_generation : actionCall && actionResultCompleted(events, args.effect_call_id))) return unavailable(action, role, "persisted_effect_mismatch");
2414
+ if (!(action === "restart" ? actionCall && restartIntent(events, args.resolution_call_id, resolution.target) && !!await readback("restart", resolution.target, roots, exec.signal) : actionCall && actionResultCompleted(events, args.effect_call_id))) return unavailable(action, role, "persisted_effect_mismatch");
2292
2415
  if (role === "effect") return supported(action, role, resolution.target, {}, resolution.commandManifest, void 0, currentExecutable);
2293
2416
  } else {
2294
2417
  const effect = findEffect(snapshotSessionEvents(agent.session), args.effect_call_id);
@@ -2384,7 +2507,7 @@ function createExternalOperationTool(read, capability) {
2384
2507
  function pendingCount(projection) {
2385
2508
  return [...projection.items.values()].filter((item) => item.status === "pending").length;
2386
2509
  }
2387
- function createContextGuardCommand(projectionFor, setEnabled, clearContract) {
2510
+ function createContextGuardCommand(projectionFor, setEnabled, clearContract, lifecycleFor) {
2388
2511
  return {
2389
2512
  name: "context-guard",
2390
2513
  description: "Enable, disable, clear, inspect, or diagnose Context Guard for this session.",
@@ -2422,12 +2545,26 @@ function createContextGuardCommand(projectionFor, setEnabled, clearContract) {
2422
2545
  text: "Usage: /context-guard on|off|clear|status|diagnose"
2423
2546
  };
2424
2547
  const passed = [...projection.items.values()].filter((item) => item.status === "passed").length;
2548
+ let certifiable_missing_evidence = 0;
2549
+ let unsupported = 0;
2550
+ for (const item of projection.items.values()) {
2551
+ if (item.status !== "pending") continue;
2552
+ const diagnosis = deriveItemDiagnosis(projection, item);
2553
+ if (diagnosis.repairability === "agent_repairable") certifiable_missing_evidence += 1;
2554
+ else if (diagnosis.certification === "unsupported") unsupported += 1;
2555
+ }
2425
2556
  const response = {
2426
2557
  enabled: projection.enabled,
2558
+ lifecycle: lifecycleFor?.(agent) ?? (projection.enabled ? "active" : "disabled"),
2427
2559
  epoch: projection.epoch,
2428
2560
  contract_revision: projection.contractRevision,
2429
2561
  pending: pendingCount(projection),
2430
2562
  passed,
2563
+ diagnosis: {
2564
+ certified: passed,
2565
+ certifiable_missing_evidence,
2566
+ unsupported
2567
+ },
2431
2568
  evidence: projection.evidence.size,
2432
2569
  integrity: projection.integrity,
2433
2570
  last_source_seq: projection.lastObservedSourceSeq
@@ -3242,6 +3379,9 @@ const Config = Schema.object({
3242
3379
  activation: Schema.string().default("opt-in"),
3243
3380
  hostLockPlatform: Schema.string(),
3244
3381
  hostLockProfile: Schema.string(),
3382
+ hostLockPolicy: Schema.string(),
3383
+ hostLockRuntimeRoot: Schema.string(),
3384
+ hostLockProfileRoot: Schema.string(),
3245
3385
  hostLockPackages: Schema.array(Schema.object({
3246
3386
  name: Schema.string().required(),
3247
3387
  version: Schema.string(),
@@ -3271,7 +3411,15 @@ function resolveConfig(config) {
3271
3411
  if (config.hostLockProfile !== void 0 && config.hostLockProfile !== "headless" && config.hostLockProfile !== "web") throw new TypeError("hostLockProfile must be \"headless\" or \"web\"");
3272
3412
  const hasHostRows = hostLockPackages !== void 0;
3273
3413
  if (hasHostRows !== (config.hostLockPlatform !== void 0) || hasHostRows !== (config.hostLockProfile !== void 0)) throw new TypeError("hostLockPackages, hostLockPlatform, and hostLockProfile must be injected together");
3414
+ for (const key of [
3415
+ "hostLockPolicy",
3416
+ "hostLockRuntimeRoot",
3417
+ "hostLockProfileRoot"
3418
+ ]) if (config[key] !== void 0 && (typeof config[key] !== "string" || !config[key])) throw new TypeError(`${key} must be a non-empty string`);
3274
3419
  return {
3420
+ ...typeof config.hostLockPolicy === "string" ? { hostLockPolicy: config.hostLockPolicy } : {},
3421
+ ...typeof config.hostLockRuntimeRoot === "string" ? { hostLockRuntimeRoot: config.hostLockRuntimeRoot } : {},
3422
+ ...typeof config.hostLockProfileRoot === "string" ? { hostLockProfileRoot: config.hostLockProfileRoot } : {},
3275
3423
  activation,
3276
3424
  ...hostLockPackages ? { hostLockPackages } : {},
3277
3425
  ...config.hostLockPlatform ? { hostLockPlatform: config.hostLockPlatform } : {},
@@ -3419,7 +3567,7 @@ function sessionHeaderForDigest(session) {
3419
3567
  delegationDepth: typeof raw.delegationDepth === "number" ? raw.delegationDepth : 0
3420
3568
  };
3421
3569
  }
3422
- function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalState) {
3570
+ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalState, refreshHostLock) {
3423
3571
  const projection = createProjection();
3424
3572
  const session = agent.session;
3425
3573
  let pendingRecovery = false;
@@ -3427,9 +3575,13 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3427
3575
  let observedEpoch = -1;
3428
3576
  let observedCompactionSeq = -1;
3429
3577
  let observedContractRevision = -1;
3578
+ let protocolV4Present = false;
3579
+ let realRootInputSeen = false;
3580
+ let lifecycle = "armed";
3430
3581
  const continuationAttempts = projection.continuationAttempts;
3431
3582
  const persistenceCorrectionAttempts = projection.persistenceCorrectionAttempts;
3432
3583
  const rebuild = () => {
3584
+ if (refreshHostLock) hostLock = refreshHostLock();
3433
3585
  const header = session.header;
3434
3586
  const priorRecoveryDigest = projection.lastRecoveryDigest;
3435
3587
  const derived = deriveProjection(snapshotSessionEvents(session), { activation: config.activation }, {
@@ -3450,6 +3602,12 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3450
3602
  projection.continuationAttempts = continuationAttempts;
3451
3603
  projection.persistenceCorrectionAttempts = persistenceCorrectionAttempts;
3452
3604
  projection.lastRecoveryDigest = priorRecoveryDigest;
3605
+ protocolV4Present = derived.protocolV4Present;
3606
+ realRootInputSeen = derived.realRootInputSeen;
3607
+ lifecycle = lifecyclePhase({
3608
+ enabled: projection.enabled,
3609
+ realInputSeen: realRootInputSeen
3610
+ });
3453
3611
  if (observedEpoch >= 0 && derived.projection.epoch > observedEpoch) {
3454
3612
  pendingRecovery = true;
3455
3613
  projection.lastRecoveryDigest = void 0;
@@ -3484,6 +3642,12 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3484
3642
  return {
3485
3643
  projection,
3486
3644
  session,
3645
+ get lifecycle() {
3646
+ return lifecycle;
3647
+ },
3648
+ get protocolV4Present() {
3649
+ return protocolV4Present;
3650
+ },
3487
3651
  sync,
3488
3652
  setEnabled,
3489
3653
  setDurability,
@@ -3491,6 +3655,41 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3491
3655
  consumeRecovery
3492
3656
  };
3493
3657
  }
3658
+ /** Delegated/subagent sessions never receive root-conversation injections. */
3659
+ function isDelegatedSession(session) {
3660
+ const header = session.header;
3661
+ return header?.origin === "subagent" || typeof header?.delegationDepth === "number" && header.delegationDepth > 0 || typeof header?.parentSession === "string";
3662
+ }
3663
+ /** Never reinterpret a legacy injected snapshot as freshly accepted core/v1. */
3664
+ function revalidateCoreLock(config, expected) {
3665
+ if (config.hostLockPolicy !== "dsh-core/v1" || !config.hostLockRuntimeRoot || !config.hostLockProfileRoot) return {
3666
+ ...expected,
3667
+ status: "unavailable",
3668
+ goalAvailable: false,
3669
+ reasonCode: "host_lock_migration_required"
3670
+ };
3671
+ try {
3672
+ const actual = evaluateHostLock(readActiveHostGraph(config.hostLockRuntimeRoot, config.hostLockProfileRoot), {
3673
+ platform: config.hostLockPlatform,
3674
+ profileKind: config.hostLockProfile
3675
+ });
3676
+ if (actual.status !== "supported") return actual;
3677
+ if (actual.digest !== expected.digest) return {
3678
+ ...actual,
3679
+ status: "unsupported",
3680
+ goalAvailable: false,
3681
+ reasonCode: "host_lock_installed_graph_drift"
3682
+ };
3683
+ return actual;
3684
+ } catch {
3685
+ return {
3686
+ ...expected,
3687
+ status: "unavailable",
3688
+ goalAvailable: false,
3689
+ reasonCode: "host_lock_missing"
3690
+ };
3691
+ }
3692
+ }
3494
3693
  function apply(ctx, rawConfig = {}) {
3495
3694
  const config = resolveConfig(rawConfig);
3496
3695
  const installedHostLock = evaluateHostLock(config.hostLockPackages ?? [], {
@@ -3504,17 +3703,20 @@ function apply(ctx, rawConfig = {}) {
3504
3703
  let runtime = runtimes.get(agent);
3505
3704
  if (!runtime) {
3506
3705
  const goals = optionalGoalService(ctx, agent);
3507
- const agentHostLock = bindLiveGoalCapability(installedHostLock, Boolean(goals) && hasPinnedUpdateGoalTool(agent));
3508
- runtime = createRuntime(agent, config, agentHostLock, goals ? () => goals.get(agent) : void 0);
3706
+ const refreshHostLock = () => {
3707
+ const current = bindLiveGoalCapability(revalidateCoreLock(config, installedHostLock), Boolean(goals) && hasPinnedUpdateGoalTool(agent));
3708
+ hostLocks.set(agent, current);
3709
+ return current;
3710
+ };
3711
+ const agentHostLock = refreshHostLock();
3712
+ runtime = createRuntime(agent, config, agentHostLock, goals ? () => goals.get(agent) : void 0, refreshHostLock);
3509
3713
  runtimes.set(agent, runtime);
3510
3714
  hostLocks.set(agent, agentHostLock);
3511
3715
  }
3512
3716
  return runtime;
3513
3717
  };
3514
- ctx.commands.register(createContextGuardCommand((agent) => ensure(agent).projection, (agent, enabled) => ensure(agent).setEnabled(enabled), (agent) => ensure(agent).sync()));
3718
+ ctx.commands.register(createContextGuardCommand((agent) => ensure(agent).projection, (agent, enabled) => ensure(agent).setEnabled(enabled), (agent) => ensure(agent).sync(), (agent) => ensure(agent).lifecycle));
3515
3719
  ctx.on("agent/session-start", ({ agent, source }) => {
3516
- ensureProtocolBoundary(agent);
3517
- ensureProtocolBoundary(agent, CAPTURE_V042_NOTICE);
3518
3720
  const runtime = ensure(agent);
3519
3721
  runtime.sync();
3520
3722
  if (source === "resume" || source === "compact") {
@@ -3581,6 +3783,17 @@ function apply(ctx, rawConfig = {}) {
3581
3783
  };
3582
3784
  agent.ctx.tools.register(createEvidenceTool(evidenceOptions));
3583
3785
  agent.ctx.tools.register(createActionTool(evidenceOptions));
3786
+ agent.ctx.tools.register(createPrepareTool({
3787
+ getProjection: () => runtime.projection,
3788
+ hostCapability: (action) => {
3789
+ const evaluation = createHostCapabilityEvaluator(hostLocks.get(agent) ?? installedHostLock)(action);
3790
+ return {
3791
+ status: evaluation.status,
3792
+ reasonCode: evaluation.reasonCode
3793
+ };
3794
+ },
3795
+ commandTemplate: (action) => GIT_COMMAND_TEMPLATES[action]
3796
+ }));
3584
3797
  agent.ctx.tools.register(createExternalOperationTool((id, toolAgent) => readExternalOperation(ctx, toolAgent, id), () => evaluateExternalWaitCapability(hostLocks.get(agent) ?? installedHostLock)));
3585
3798
  agent.ctx.tools.guard((exec) => goalCompletionDenial(runtime.projection, exec.name, exec.arguments));
3586
3799
  });
@@ -3590,12 +3803,33 @@ function apply(ctx, rawConfig = {}) {
3590
3803
  runtime.setDurability(durability);
3591
3804
  runtime.sync();
3592
3805
  const decision = await next();
3593
- if (decision.kind === "enter" && runtime.projection.enabled && runtime.consumeRecovery()) {
3594
- const recovery = renderRecoveryPacket(runtime.projection, { charBudget: 4e3 });
3806
+ if (decision.kind !== "enter") return decision;
3807
+ const injected = [];
3808
+ const delegated = isDelegatedSession(agent.session);
3809
+ let boundaryPending = !runtime.protocolV4Present;
3810
+ const firstStep = previewFirstStepInjection({
3811
+ activation: config.activation,
3812
+ enabled: runtime.projection.enabled,
3813
+ boundaryPresent: runtime.protocolV4Present,
3814
+ delegated
3815
+ }, claimedBatchHasRealRootInput(decision.messages));
3816
+ if (firstStep) {
3817
+ injected.push(pluginNoticeMessage(firstStep.boundary, "Context Guard recorded a replay version boundary"));
3818
+ if (runtime.lifecycle === "armed") injected.push(pluginNoticeMessage(firstStep.guidance, "Context Guard first-step protection guidance"));
3819
+ boundaryPending = false;
3820
+ }
3821
+ if (runtime.projection.enabled && runtime.consumeRecovery()) {
3822
+ const hasOpenWork = [...runtime.projection.items.values()].some((item) => item.status === "pending");
3823
+ const hasRejections = (runtime.projection.lastCheckpointRejections?.length ?? 0) > 0;
3824
+ const recovery = hasOpenWork || hasRejections ? renderRecoveryPacket(runtime.projection, { charBudget: 4e3 }) : void 0;
3595
3825
  const digest$2 = recovery ? recoveryDigest(recovery, runtime.projection) : void 0;
3596
3826
  if (recovery && digest$2 !== runtime.projection.lastRecoveryDigest) {
3597
3827
  runtime.projection.lastRecoveryDigest = digest$2;
3598
- decision.messages = [...decision.messages, createUserMessage({
3828
+ if (boundaryPending && !delegated) {
3829
+ injected.push(pluginNoticeMessage(PROTOCOL_V4_NOTICE, "Context Guard recorded a replay version boundary"));
3830
+ boundaryPending = false;
3831
+ }
3832
+ injected.push(createUserMessage({
3599
3833
  content: [{
3600
3834
  type: "text",
3601
3835
  text: `Open task requirements (recovered after compaction or resume):\n${recovery}`
@@ -3606,9 +3840,10 @@ function apply(ctx, rawConfig = {}) {
3606
3840
  form: "notice",
3607
3841
  summary: boundContextSummary("recovering open task requirements")
3608
3842
  }
3609
- })];
3843
+ }));
3610
3844
  }
3611
3845
  }
3846
+ if (injected.length > 0) decision.messages = [...injected, ...decision.messages];
3612
3847
  return decision;
3613
3848
  });
3614
3849
  ctx.on("agent/turn-stopping", async ({ agent }) => {
@@ -3650,26 +3885,20 @@ function optionalMarketOrigin(ctx, agent) {
3650
3885
  return `http://${service.host === "::1" ? "[::1]" : "127.0.0.1"}:${service.port}`;
3651
3886
  } catch {}
3652
3887
  }
3653
- function ensureProtocolBoundary(agent, notice = PROTOCOL_V3_NOTICE) {
3654
- if (snapshotSessionEvents(agent.session).some((event) => {
3655
- if (event.type !== "user/message" || !event.data || typeof event.data !== "object") return false;
3656
- const data = event.data;
3657
- const source = data.source && typeof data.source === "object" ? data.source : void 0;
3658
- const content = Array.isArray(data.content) ? data.content : [];
3659
- return (content.length === 1 && content[0] && typeof content[0] === "object" ? content[0].text : void 0) === notice && source?.kind === "plugin" && source.plugin === "context-guard" && source.form === "notice";
3660
- })) return;
3661
- agent.session.append.bind(agent.session)("user/message", createUserMessage({
3888
+ /** Build one host-approved plugin notice user/message for step-batch delivery. */
3889
+ function pluginNoticeMessage(text, summaryLabel) {
3890
+ return createUserMessage({
3662
3891
  content: [{
3663
3892
  type: "text",
3664
- text: notice
3893
+ text
3665
3894
  }],
3666
3895
  source: {
3667
3896
  kind: "plugin",
3668
3897
  plugin: "context-guard",
3669
3898
  form: "notice",
3670
- summary: boundContextSummary("Context Guard recorded a replay version boundary")
3899
+ summary: boundContextSummary(summaryLabel)
3671
3900
  }
3672
- }), { surfaceOp: "append" });
3901
+ });
3673
3902
  }
3674
3903
  function hasPinnedUpdateGoalTool(agent) {
3675
3904
  try {
@@ -3741,4 +3970,4 @@ function normalizeGoalState(value) {
3741
3970
  }
3742
3971
 
3743
3972
  //#endregion
3744
- export { ACTION_MANIFEST, ACTION_MANIFEST_VERSION, ALPHA2_DSHMARKET_139_HOST_PACKAGES, ALPHA2_HOST_PACKAGES, ALPHA3_HOST_PACKAGES, BASE_HOST_PACKAGES, CAPTURE_V042_NOTICE, CERTIFICATE_VERSION, COMMAND_SURFACE_MANIFEST, Config, DEFAULT_HOST_LOCK, DEFAULT_RECOVERY_CHAR_BUDGET, EXPECTED_HOST_PACKAGES, GIT_COMMAND_MANIFEST_IDS, GOAL_HOST_PACKAGES, HOST_CAPABILITY_PACKAGE_GROUPS, HOST_COHORTS, HostProfileError, MIN_RECOVERY_CHAR_BUDGET, PROOF_KINDS, PROOF_PROTOCOL_VERSION, PROTOCOL_V3_NOTICE, RC1_HOST_PACKAGES, SEMANTIC_ACTIONS, STATEFUL_ACTIONS, STOP_PROTOCOL_VERSION, SUPPORTED_EVIDENCE_ADAPTERS, actionCompatible, apply, authorityCaptureCounts, availableBoundaryQualifications, bindExecutableIdentity, bindLiveGoalCapability, bindProofToProjection, bindingSatisfies, canonicalArgvFromCommand, canonicalProjection, canonicalizePath, captureClause, captureItem, certifyCheckpoint, classifyClause, classifyCompletionClaim, classifyUserInteraction, closingHint, commitIndexSnapshotDigest, commitTreeSnapshotDigest, createGitPrestateEnvelope, createProjection, createProofManifest, currentContractDigest, decideTurnBoundary, decideTurnStopping, deriveProjection, digestStrings, effectuateBoundary, evaluateExternalWaitCapability, evaluateHostCapability, evaluateHostLock, evaluateToolSurfaceCapability, evidenceCoverage, evidenceFromPersistedToolResult, evidenceMatchesItem, executeRevalidatedGitEffect, extractArtifactPaths, extractMethod, extractOperation, extractTextContent, extractToolSubject, gitCommandMatchesTarget, goalCompletionDenial, hasCurrentCertificate, hostLockContextFromComposedDump, hostLockRowsFromComposedDump, inject, injectActiveProfileHostLock, isCurrentAcceptedBoundary, isDeterministicCheck, isInformationalMessage, isRunExecutable, isStatefulAction, isVerifyingCapability, isWholeTaskCompletionClaim, latestAssistantText, name, normalizeClause, observeAssistantOutcome, openItems, packageRowsFromActiveGraph, packageRowsFromPnpmLock, parseGitCommandManifest, parsePwshCommand, parseShellCommand, proofDigest, proofEvidenceConstraints, qualifyBoundary, recoveryDigest, renderRecoveryPacket, requestedTargetAuthorizesMutation, requestedTargetMatchesResolved, resolveActiveProfileHostLock, resolveInstalledHostLock, revalidateGitPrestate, sanitizeClauseText, sanitizeUrl, segmentAuthorityBlocks, segmentClauses, selectHostCohort, semanticActionFromCommand, semanticActionFromText, sessionQuery, sha256, snapshotSessionEvents, supersedeItem, validateActionManifest, validateActionTarget, validateManifest, validateProofManifest, verifiedLinearCommitReadback, verifyComposedHostLockDump, withDurability };
3973
+ export { ACTION_MANIFEST, ACTION_MANIFEST_VERSION, ALPHA2_DSHMARKET_139_HOST_PACKAGES, ALPHA2_HOST_PACKAGES, ALPHA3_HOST_PACKAGES, BASE_HOST_PACKAGES, CAPTURE_V042_NOTICE, CERTIFICATE_VERSION, COMMAND_SURFACE_MANIFEST, CONFIRM_LINE_PATTERN, Config, DEFAULT_HOST_LOCK, DEFAULT_RECOVERY_CHAR_BUDGET, EXPECTED_HOST_PACKAGES, FIRST_STEP_GUIDANCE, GIT_COMMAND_MANIFEST_IDS, GIT_COMMAND_TEMPLATES, GOAL_HOST_PACKAGES, HOST_CAPABILITY_PACKAGE_GROUPS, HOST_COHORTS, HostProfileError, LEGACY_HOST_COHORTS, MIN_RECOVERY_CHAR_BUDGET, PROOF_KINDS, PROOF_PROTOCOL_VERSION, PROTOCOL_V3_NOTICE, PROTOCOL_V4_NOTICE, RC1_HOST_PACKAGES, SEMANTIC_ACTIONS, STATEFUL_ACTIONS, STOP_PROTOCOL_VERSION, SUPPORTED_EVIDENCE_ADAPTERS, actionCompatible, apply, authorityCaptureCounts, availableBoundaryQualifications, bindExecutableIdentity, bindLiveGoalCapability, bindProofToProjection, bindingSatisfies, canonicalArgvFromCommand, canonicalProjection, canonicalizePath, captureClause, captureItem, certifyCheckpoint, claimedBatchHasRealRootInput, classifyClause, classifyCompletionClaim, classifyTaskIntent, classifyUserInteraction, closingHint, commitIndexSnapshotDigest, commitTreeSnapshotDigest, confirmRebind, createGitPrestateEnvelope, createProjection, createProofManifest, currentContractDigest, decideTurnBoundary, decideTurnStopping, deriveItemDiagnosis, deriveProjection, digestStrings, effectuateBoundary, evaluateExternalWaitCapability, evaluateHostCapability, evaluateHostLock, evaluateToolSurfaceCapability, evidenceAvailabilityReason, evidenceCoverage, evidenceFromPersistedToolResult, evidenceMatchesItem, executeRevalidatedGitEffect, extractArtifactPaths, extractMethod, extractOperation, extractTextContent, extractToolSubject, gitCommandMatchesTarget, goalCompletionDenial, hasCurrentCertificate, hostLockContextFromComposedDump, hostLockRowsFromComposedDump, inject, injectActiveProfileHostLock, inspectTargetHostGraph, isCurrentAcceptedBoundary, isDeterministicCheck, isFrozenV042RebindResponse, isInformationalMessage, isRunExecutable, isStatefulAction, isVerifyingCapability, isWholeTaskCompletionClaim, itemDiagnosis, latestAssistantText, lifecyclePhase, name, normalizeClause, observeAssistantOutcome, openItems, packageRowsFromActiveGraph, packageRowsFromPnpmLock, parseConfirmationMessage, parseGitCommandManifest, parsePwshCommand, parseShellCommand, previewFirstStepInjection, proofDigest, proofEvidenceConstraints, proposeRebind, proposeRebindOutcome, proposeRebindV042, qualifyBoundary, readActiveHostGraph, rebindAttemptKey, rebindResponse, recoveryDigest, relevantEvidence, renderRecoveryPacket, replayRebindResult, requestedTargetAuthorizesMutation, requestedTargetMatchesResolved, resolveActiveProfileHostLock, resolveInstalledHostLock, revalidateGitPrestate, sanitizeClauseText, sanitizeUrl, segmentAuthorityBlocks, segmentClauses, selectHostCohort, semanticActionFromCommand, semanticActionFromText, sessionQuery, sha256, snapshotSessionEvents, supersedeItem, validateActionManifest, validateActionTarget, validateManifest, validateProofManifest, verifiedLinearCommitReadback, verifyComposedHostLockDump, withDurability };
@@ -24,11 +24,31 @@ The effective plugin configuration and the DSH Session append-only log are the i
24
24
 
25
25
  The in-memory `GuardProjection` is a rebuildable cache: `deriveProjection` applies the effective activation configuration, replays the native log deterministically, recomputes every contract, evidence, and certificate, and flags `corrupt` when a recorded certificate no longer re-derives from the evidence in the log. The projection and its evidence are session-scoped: a new DSH session starts a new projection and cannot import, look up, or certify evidence IDs from another session. A completed workflow that needs a certificate must therefore produce its evidence and call `context_guard_checkpoint` in the same session. Under `always`, replay begins enabled; changing an existing profile from `opt-in` to `always` can therefore bring earlier persisted user messages into the derived contract. A recorded `/context-guard off` disables capture from that point until a later `on`. A recorded `/context-guard clear` supersedes every pending requirement and acceptance under a `CLEAR:<revision>` sentinel (prohibitions are retained) and bumps the contract revision, so a fresh empty-binding checkpoint can certify while the guard stays enabled. Captured contracts always carry a concrete subject/surface, so no unrelated evidence can close a requirement.
26
26
 
27
+ ## Session lifecycle: silent start, first-message activation
28
+
29
+ The runtime separates what it injects from what it certifies. An internal lifecycle phase — `armed` (protection enabled, waiting for the first real root input), `active` (real input entered a step), `disabled` (explicitly off) — expresses the startup strategy only; certification still depends solely on durable events, the current contract, and the evidence chain.
30
+
31
+ Session start (`agent/session-start`) registers tools and the runtime, reads history, and arms recovery for resume/compact. It appends nothing, so a fresh session stays blank (`seq === 0`) and a DSH preset can still be selected while the session is new. The rc.1 Web surface treats a session as blank until a `turn/start` (server preset lock) and falls back to `seq === 0` for cold list entries; Guard writes no message at T0, so both stay satisfiable.
32
+
33
+ At `agent/pre-step` the loop has CLAIMED this step's input but has not persisted it yet. A pure preview reads only the validated claim: under `always`, the first claimed batch that carries a real root user message (non-empty text, or any image/attachment part) injects the `Context Guard protocol boundary: v4.0.0` notice and a compact first-step guidance message ahead of the claimed messages, inside the same step batch. The formal projection never derives from the preview — the boundary and the user message become durable events when the loop appends the batch, and the contract is re-derived from them exactly as from any other log. `opt-in` never auto-injects; delegated/subagent sessions receive no boundary, guidance, or activation (their scope arrives through the parent's delegation prompt). Rejection, cancellation, or a filtered batch injects nothing and retries on the next legal input.
34
+
35
+ Recovery packets keep the existing content-dedup and ride the next entered step. The first 0.5 write into a pre-0.5 session carries the v4 boundary with it — that message is the explicit version cut: obligations and certificates recorded before it keep the frozen 0.4 rules (including exact-shape rebind replay), while later messages use the 0.5 confirmation grammar.
36
+
37
+ ## Unified diagnosis and preparation
38
+
39
+ `deriveItemDiagnosis` is the single pure judge shared by checkpoint, recovery, the rebind item query, `context_guard_prepare`, and `/context-guard status`. Per item it reports a task kind (`inquiry`/`action`/`constraint`/…), certification support (`unsupported`/`needs_target`/`needs_evidence`/`unavailable`/`supported`), repairability (agent-repairable, needs user input, unsupported, historical gap, none), the exact missing target fields and evidence facets, and one concrete next action with a resume condition and a stable attempt fingerprint. Inquiries stay captured obligations whose honest next step is to deliver the answer; they are never prescribed a rebind. An effect recorded without its resolution prestate is a historical gap: read back observed state, never re-execute to mint evidence.
40
+
41
+ `context_guard_prepare` is read-only. Before a stateful action it renders the supported command shape from the same audited parser the executor uses, the required resolution/effect/state order, reusable evidence references, the host capability verdict, and the exact missing target fields. It performs no action and never turns a guessed default into user authority.
42
+
43
+ A log-derived retry ledger keys each rejected rebind attempt by its stable inputs and outcome. An identical second attempt returns `unchanged` with the resume condition instead of a fresh rejection; new related evidence, a new root instruction, or a changed target produces a new key and re-opens evaluation.
44
+
27
45
  ## Synchronization
28
46
 
29
47
  The runtime rebuilds the projection from the log before each step. Before evidence is produced, the runtime awaits `ctx.sessions.flush(session)`; if no durability listener participated, evidence is marked `durability-unknown`, which fails closed.
30
48
 
31
- The v0.3 runtime accepts host identity only from `hostLockPackages` generated from the active runtime/profile graphs and bound to the active platform/profile kind. It does not infer identity from the nearest `pnpm-lock.yaml`: DSH core and profile plugins have separate locks, and the runtime lock can contain several historical versions. Missing, unreadable, duplicate, multi-version, or drifted identity disables the dependent capability. The audited values live in `manifests/supported-host.v1.json` as an exact host cohort registry: the whole graph must atomically match one audited cohort, mixed or unknown rows fail closed, and the selected cohort identity is part of every host lock digest, so switching cohorts invalidates certificate authority. `deriveProjection` receives this exact evaluation before it replays any checkpoint, so a certificate is never validated under a default lock and overwritten later; a changed digest retains historical evidence but makes current authority stale. Ordinary persisted `bash`/`pwsh` results are replayed only when the active platform's agent-loop and terminal group is exact; wrong-platform tool names fail closed. Ordinary `read`/`write`/`edit` results require the independent filesystem group, which pins the tool schemas and result/presentation contract together with the local/sandbox provider, observation policy, sandbox policy, and approval services. A filesystem-group failure disables only `create`/`modify` and filesystem-derived facts, not an independently valid terminal action.
49
+ The host lock uses `dsh-core/v1`: one exact 33-package DSH core, independent of optional market versions. The resolver walks all reachable runtime/profile dependencies, including those introduced by plugins, and checks installed manifests against lock identities. Missing, duplicated, mixed or changed core rows fail closed. Every replay rechecks the configured graph sources against the injected core identity before deriving certificate authority.
50
+
51
+ Core manifest version 2 and its policy/cohort values change the digest without changing the shared digest-v3 encoder. Legacy injected configuration requires explicit inspection and migration; historical records are never relabelled. Action capabilities are evaluated only after this core contract succeeds. Market restart additionally requires a trusted loaded-instance verifier. Current DSH supplies none, so restart is unavailable while unrelated guarded work remains supported. A requested restart stays pending. The version-2 service adapter distinguishes ordinary provider/instance drift from a persisted, target-bound restart handoff.
32
52
 
33
53
  Evidence is produced only from persisted `tool/call` + `tool/result` pairs. Guard never inserts context between a Code Mode sub-call and its durable result.
34
54
 
@@ -51,7 +71,11 @@ A legal plugin `user/message` notice marks the 0.4.2 capture boundary. Earlier e
51
71
 
52
72
  `context_guard_rebind` accepts `operation: propose|query|withdraw`, `item_id`, `proposal_id`, `clauses`, and optional `clarification_item_ids`. It has no model confirmation flag. A proposal's 1–8 clauses must concatenate to the complete original normalized text, and its serialized body must fit 8 KiB. Each clarification ID is either empty (keep the source clause) or names a later pending direct-root item that explicitly includes that clause and preserves captured identities and method constraints. The proposal records both root sources, item/revision identities, session, epoch, contract revision, candidate action/target/acceptance, and a digest. A GUI clause cannot be mapped to an installation-only result.
53
73
 
54
- The tool returns a candidate without mutating the projection. A matching durable tool result registers the proposal on replay. Only a complete canonical root message `确认重绑定 <proposal ID>` commits it. Confirmation rechecks the proposal against the current contract; changed, withdrawn, cross-session, or non-durable inputs keep the old item pending. Every child is constructed before the original is marked superseded. `supersededByItems` preserves the one-to-many relation, and `reboundFrom` preserves the proposal and confirmation source. A later root clarification keeps its own authority; partitioning alone cannot authorize a new mutation. Duplicate confirmation is idempotent.
74
+ The tool returns a candidate without mutating the projection. A matching durable tool result registers the proposal on replay. Replay validation is versioned: 0.5-era results match on structured semantic fields (status, reason code, proposal identity and digest) while display text may evolve; results carrying the frozen 0.4 response shapes validate byte-for-byte against the frozen 0.4 rules. Anything else is tampered or unknown and never replays.
75
+
76
+ A proposal that would keep every clause at `generic_run` when the original is already `generic_run` is refused as `no_certification_gain`: it would spend a user confirmation without improving certification. Partition mismatches return the bounded exact source (text, length, digest) instead of a generic error.
77
+
78
+ Since 0.5.0 a durable root message is one atomic confirmation transaction. The first non-empty top-level line may be the control line `确认重绑定 <proposal ID>`; the confirmation validates against the state BEFORE that message, and the remaining lines keep their own semantics — an explanation request stays conversational, a new task is captured like any other instruction, and an explicit reversal ("先不要确认") makes the whole message ambiguous and unapplied. A control string buried in a sentence, inside quotes, or inside a code block is malformed and never confirms; a matching control line that is not in first position, or two control lines, is ambiguous with no partial effect. Confirmation rechecks the proposal against the current contract; changed, withdrawn, cross-session, or non-durable inputs keep the old item pending, and a matching confirmation observed in a not-yet-durable log is reported as `not_durable` — distinguishable from one that was never sent. Every child is constructed before the original is marked superseded. `supersededByItems` preserves the one-to-many relation, and `reboundFrom` preserves the proposal and confirmation source. A later root clarification keeps its own authority; partitioning alone cannot authorize a new mutation. Duplicate confirmation is idempotent.
55
79
 
56
80
  No digest-v3 wire format or upstream fixture changes are needed. A replacement changes the item/status set and contract revision, invalidating the earlier certificate; evidence is never copied into a passed state. A fresh checkpoint must revalidate the exact action, target, executor, host lock, and transition. Evidence predating the authoritative root clause referenced by a replacement is rejected while retaining its historical ID.
57
81