dsh-completion-guard 0.5.2 → 0.6.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 parseShellCommand, $n as isStatefulAction, $t as isWholeTaskCompletionClaim, A as createGitPrestateEnvelope, An as extractMethod, At as RC1_HOST_PACKAGES, B as CAPTURE_V042_NOTICE, Bn as namedActions, Bt as renderRecoveryPacket, C as SESSION_EVENT_ENVELOPE_INVALID, Cn as evidenceAvailabilityReason, Ct as SUPPORTED_HOST_VERSIONS, D as GIT_COMMAND_TEMPLATES, Dn as captureItem, Dt as satisfiesSupportedHostRange, E as GIT_COMMAND_MANIFEST_IDS, En as captureClause, Et as parseHostVersion, F as verifiedLinearCommitReadback, Fn as interpretMessage, Ft as DEFAULT_RECOVERY_CHAR_BUDGET, G as evidenceFromPersistedToolResult, Gn as ACTION_MANIFEST, Gt as CONTROL_RECORD_PREFIX, H as PROTOCOL_V4_NOTICE, Hn as statefulActionsOfScope, Ht as evidenceCoverage, I as FIRST_STEP_GUIDANCE, In as isExecutableItem, It as MIN_RECOVERY_CHAR_BUDGET, J as isDeterministicCheck, Jn as SEMANTIC_ACTIONS, Jt as classifyCompletionClaim, K as extractTextContent, Kn as ACTION_MANIFEST_VERSION, Kt as NO_PROGRESS_RECORD_PREFIX, L as claimedBatchHasRealRootInput, Ln as isOpenObligation, Lt as closingHint, M as gitCommandMatchesTarget, Mn as isInformationalMessage, Mt as authorityCaptureCounts, N as parseGitCommandManifest, Nn as segmentClauses, Nt as segmentAuthorityBlocks, O as commitIndexSnapshotDigest, On as classifyClause, Ot as RC015_RC2_HOST_PACKAGES, P as revalidateGitPrestate, Pn as interpretClause, Pt as certifyCheckpoint, Q as parsePwshCommand, Qn as actionCompatible, Qt as isRootPauseRequest, R as lifecyclePhase, Rn as kindOfScope, Rt as openItems, S as SESSION_API_UNSUPPORTED, Sn as deriveItemDiagnosis, St as SUPPORTED_HOST_RANGE, T as snapshotSessionEvents, Tn as relevantEvidence, Tt as evaluateMinimumHostVersion, U as deriveProjection, Un as canonicalRegistryBase, Ut as evidenceMatchesItem, V as PROTOCOL_V3_NOTICE, Vn as semanticActionOfScope, Vt as bindingSatisfies, W as supersedeItem, Wn as npmEscapedPackageName, Wt as isVerifyingCapability, X as canonicalArgvFromCommand, Xn as STOP_PROTOCOL_VERSION, Xt as decideTurnStopping, Y as withDurability, Yn as STATEFUL_ACTIONS, Yt as decideTurnBoundary, Z as isRunExecutable, Zn as SUPPORTED_EVIDENCE_ADAPTERS, Zt as decisionBoundaryKey, _ as packageRowsFromPnpmLock, _n as rebindResponse, _t as evaluateToolSurfaceCapability, a as createProofManifest, an as hasCurrentCertificate, ar as validateActionTarget, at as BASE_HOST_PACKAGES, b as resolveInstalledHostLock, bn as isFrozenV042RebindResponse, bt as LATEST_SUPPORTED_HOST_VERSION, c as sessionQuery, cn as isCurrentAcceptedBoundary, cr as classifyTaskIntent, ct as GOAL_HOST_PACKAGES, d as combineHostPolicy, dn as createProjection, dr as digestStrings, dt as LEGACY_HOST_COHORTS, en as latestAssistantText, er as requestedTargetAuthorizesMutation, et as ACTIVE_HOST_COHORT_ID, f as hostLockContextFromComposedDump, fn as confirmRebind, fr as normalizeClause, ft as bindExecutableIdentity, g as packageRowsFromActiveGraph, gn as rebindAttemptKey, gt as evaluateHostLock, h as inspectTargetHostGraph, hn as proposeRebindV042, hr as sha256, ht as evaluateHostCapability, i as canonicalProjection, in as goalCompletionDenial, ir as validateActionManifest, it as ALPHA2_HOST_PACKAGES, j as executeRevalidatedGitEffect, jn as extractOperation, jt as ALPHA3_HOST_PACKAGES, k as commitTreeSnapshotDigest, kn as extractArtifactPaths, kt as RC015_HOST_PACKAGES, l as validateProofManifest, ln as qualifyBoundary, lr as classifyUserInteraction, lt as HOST_CAPABILITY_PACKAGE_GROUPS, m as injectActiveProfileHostLock, mn as proposeRebindOutcome, mr as sanitizeUrl, mt as evaluateExternalWaitCapability, n as PROOF_PROTOCOL_VERSION, nn as observeAssistantOutcome, nr as semanticActionFromCommand, nt as ACTIVE_HOST_LAUNCHER_VERSION, o as proofDigest, on as availableBoundaryQualifications, or as COMMAND_SURFACE_MANIFEST, ot as DEFAULT_HOST_LOCK, p as hostLockRowsFromComposedDump, pn as proposeRebind, pr as sanitizeClauseText, pt as bindLiveGoalCapability, q as extractToolSubject, qn as CERTIFICATE_VERSION, qt as NO_PROGRESS_TURNS_BEFORE_STOP, r as bindProofToProjection, rn as progressFingerprint, rr as semanticActionFromText, rt as ALPHA2_DSHMARKET_139_HOST_PACKAGES, s as proofEvidenceConstraints, sn as effectuateBoundary, sr as validateManifest, st as EXPECTED_HOST_PACKAGES, t as PROOF_KINDS, tn as latestRootInstruction, tr as requestedTargetMatchesResolved, tt as ACTIVE_HOST_COHORT_IDS, u as HostProfileError, un as currentContractDigest, ur as canonicalizePath, ut as HOST_COHORTS, v as readActiveHostGraph, vn as replayRebindResult, vt as hostVersionFromPackages, w as SessionApiError, wn as itemDiagnosis, wt as compareHostVersions, x as verifyComposedHostLockDump, xn as parseConfirmationMessage, xt as MIN_SUPPORTED_HOST_VERSION, y as resolveActiveProfileHostLock, yn as CONFIRM_LINE_PATTERN, yt as selectHostCohort, z as previewFirstStepInjection, zn as maskCodeSpans, zt as recoveryDigest } from "./domain-RuX9l07o.js";
1
+ import { $ as isDeterministicCheck, $n as deriveItemDiagnosis, $t as proofDigestV2, A as previewFirstStepInjection, An as isWholeTaskCompletionClaim, Ar as STOP_PROTOCOL_VERSION_V2, At as evaluateMinimumHostVersion, B as RELEASE_SETTLEMENT_PREFIX, Bn as qualifyBoundary, Br as validateActionManifest, Bt as PROOF_CAPABILITY_MATRIX, C as gitCommandMatchesTarget, Cn as NO_PROGRESS_RECORD_PREFIX, Cr as ACTION_MANIFEST_VERSION, Ct as hostVersionFromPackages, D as FIRST_STEP_GUIDANCE, Dn as decideTurnStopping, Dr as SEMANTIC_ACTIONS, Dt as SUPPORTED_HOST_RANGE, E as verifiedLinearCommitReadback, En as decideTurnBoundary, Er as CERTIFICATE_VERSION_V2, Et as MIN_SUPPORTED_HOST_VERSION, F as PROTOCOL_V5_NOTICE, Fn as goalCompletionDenial, Fr as requestedIdentityKey, Ft as RC1_HOST_PACKAGES, G as releaseContractFor, Gn as proposeRebindOutcome, Gr as classifyUserInteraction, Gt as PROOF_PROTOCOL_VERSION_V2, H as inFlightReservation, Hn as createProjection, Hr as COMMAND_SURFACE_MANIFEST, Ht as PROOF_KINDS_V2, I as deriveProjection, In as hasCurrentCertificate, Ir as requestedTargetAuthorizesMutation, It as ALPHA3_HOST_PACKAGES, J as reservationFor, Jn as rebindResponse, Jr as normalizeClause, Jt as canonicalProjection, K as releaseCoverage, Kn as proposeRebindV042, Kr as canonicalizePath, Kt as bindProofToProjection, L as RELEASE_OPERATIONS, Ln as availableBoundaryQualifications, Lr as requestedTargetMatchesResolved, Lt as authorityCaptureCounts, M as DEFAULT_DELEGATION_TOOL_NAMES, Mn as latestRootInstruction, Mr as actionCompatible, Mt as satisfiesSupportedHostRange, N as PROTOCOL_V3_NOTICE, Nn as observeAssistantOutcome, Nr as boundedArtifactChoiceMatches, Nt as RC015_RC2_HOST_PACKAGES, O as claimedBatchHasRealRootInput, On as decisionBoundaryKey, Or as STATEFUL_ACTIONS, Ot as SUPPORTED_HOST_VERSIONS, P as PROTOCOL_V4_NOTICE, Pn as progressFingerprint, Pr as isStatefulAction, Pt as RC015_HOST_PACKAGES, Q as extractToolSubject, Qn as parseConfirmationMessage, Qt as proofDigest, R as RELEASE_OPERATION_SURFACES, Rn as effectuateBoundary, Rr as semanticActionFromCommand, Rt as segmentAuthorityBlocks, S as executeRevalidatedGitEffect, Sn as CONTROL_RECORD_PREFIX, Sr as ACTION_MANIFEST, St as evaluateToolSurfaceCapability, T as revalidateGitPrestate, Tn as classifyCompletionClaim, Tr as CERTIFICATE_VERSION, Tt as LATEST_SUPPORTED_HOST_VERSION, U as normalizeReleaseContract, Un as confirmRebind, Ur as validateManifest, Ut as PROOF_MANIFEST_DOMAIN_V2, V as contractById, Vn as currentContractDigest, Vr as validateActionTarget, Vt as PROOF_KINDS, W as readbackSettlesContract, Wn as proposeRebind, Wr as classifyTaskIntent, Wt as PROOF_PROTOCOL_VERSION, X as evidenceFromPersistedToolResult, Xn as CONFIRM_LINE_PATTERN, Xr as sanitizeUrl, Xt as createProofManifestV2, Y as supersedeItem, Yn as replayRebindResult, Yr as sanitizeClauseText, Yt as createProofManifest, Z as extractTextContent, Zn as isFrozenV042RebindResponse, Zr as sha256, Zt as proofCapabilityReport, _ as GIT_COMMAND_MANIFEST_IDS, _n as renderRecoveryPacket, _r as namedActions, _t as bindExecutableIdentity, a as injectActiveProfileHostLock, an as scopeCoverageDigest, ar as classifyClause, at as ACTIVE_HOST_COHORT_ID, b as commitTreeSnapshotDigest, bn as evidenceMatchesItem, br as canonicalRegistryBase, bt as evaluateHostCapability, c as packageRowsFromPnpmLock, cn as validateProofManifest, cr as extractOperation, ct as ALPHA2_DSHMARKET_139_HOST_PACKAGES, d as resolveInstalledHostLock, dn as certificateClosure, dr as interpretClause, dt as DEFAULT_HOST_LOCK, en as proofEvidenceConstraints, er as evidenceAvailabilityReason, et as withDurability, f as verifyComposedHostLockDump, fn as DEFAULT_RECOVERY_CHAR_BUDGET, fr as interpretMessage, ft as EXPECTED_HOST_PACKAGES, g as snapshotSessionEvents, gn as recoveryDigest, gr as maskCodeSpans, gt as LEGACY_HOST_COHORTS, h as SessionApiError, hn as openItems, hr as kindOfScope, ht as HOST_COHORTS, i as hostLockRowsFromComposedDump, in as requiredSubjectsOf, ir as captureItem, it as parseShellCommand, j as CAPTURE_V042_NOTICE, jn as latestAssistantText, jr as SUPPORTED_EVIDENCE_ADAPTERS, jt as parseHostVersion, k as lifecyclePhase, kn as isRootPauseRequest, kr as STOP_PROTOCOL_VERSION, kt as compareHostVersions, l as readActiveHostGraph, ln as validateProofManifestV2, lr as isInformationalMessage, lt as ALPHA2_HOST_PACKAGES, m as SESSION_EVENT_ENVELOPE_INVALID, mn as closingHint, mr as isOpenObligation, mt as HOST_CAPABILITY_PACKAGE_GROUPS, n as combineHostPolicy, nn as proofOperationMatches, nr as relevantEvidence, nt as isRunExecutable, o as inspectTargetHostGraph, on as sessionQuery, or as extractArtifactPaths, ot as ACTIVE_HOST_COHORT_IDS, p as SESSION_API_UNSUPPORTED, pn as MIN_RECOVERY_CHAR_BUDGET, pr as isExecutableItem, pt as GOAL_HOST_PACKAGES, q as releasePreEffectDecision, qn as rebindAttemptKey, qr as digestStrings, qt as bindProofV2ToProjection, r as hostLockContextFromComposedDump, rn as proofV2Rejection, rr as captureClause, rt as parsePwshCommand, s as packageRowsFromActiveGraph, sn as sessionQueryV2, sr as extractMethod, st as ACTIVE_HOST_LAUNCHER_VERSION, t as HostProfileError, tn as proofHostSurfacesOf, tr as itemDiagnosis, tt as canonicalArgvFromCommand, u as resolveActiveProfileHostLock, un as certifiableOpenItems, ur as segmentClauses, ut as BASE_HOST_PACKAGES, v as GIT_COMMAND_TEMPLATES, vn as bindingSatisfies, vr as semanticActionOfScope, vt as bindLiveGoalCapability, w as parseGitCommandManifest, wn as NO_PROGRESS_TURNS_BEFORE_STOP, wr as BOUNDED_ARTIFACT_TYPES, wt as selectHostCohort, x as createGitPrestateEnvelope, xn as isVerifyingCapability, xr as npmEscapedPackageName, xt as evaluateHostLock, y as commitIndexSnapshotDigest, yn as evidenceCoverage, yr as statefulActionsOfScope, yt as evaluateExternalWaitCapability, z as RELEASE_RESERVATION_PREFIX, zn as isCurrentAcceptedBoundary, zr as semanticActionFromText, zt as certifyCheckpoint } from "./domain-COehX7MB.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";
@@ -166,6 +166,7 @@ function checkpointPage(p, query, full) {
166
166
  available_qualifications: []
167
167
  };
168
168
  if (full.certificate) output.certificate = full.certificate;
169
+ if (full.proof_state) output.proof_state = full.proof_state;
169
170
  const pagination = {
170
171
  snapshot: identity,
171
172
  scope: evidence_scope,
@@ -357,6 +358,10 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
357
358
  detail_id: { type: "string" },
358
359
  detail_offset: { type: "integer" },
359
360
  detail_snapshot: { type: "string" },
361
+ proof: {
362
+ type: "object",
363
+ additionalProperties: true
364
+ },
360
365
  bindings: {
361
366
  type: "array",
362
367
  required: true,
@@ -460,6 +465,25 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
460
465
  type: "object",
461
466
  additionalProperties: true
462
467
  },
468
+ proof_state: {
469
+ type: "object",
470
+ additionalProperties: false,
471
+ properties: {
472
+ status: {
473
+ type: "string",
474
+ enum: [
475
+ "absent",
476
+ "bound",
477
+ "rejected",
478
+ "invalid"
479
+ ]
480
+ },
481
+ reason_codes: {
482
+ type: "array",
483
+ items: { type: "string" }
484
+ }
485
+ }
486
+ },
463
487
  reason_code: { type: "string" },
464
488
  next_step: { type: "string" },
465
489
  detail_id: { type: "string" },
@@ -599,6 +623,8 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
599
623
  evidence_sha256: { type: "string" },
600
624
  binding_digest: { type: "string" },
601
625
  certification_digest: { type: "string" },
626
+ unit_id: { type: "string" },
627
+ unit_closure_digest: { type: "string" },
602
628
  goal_ref: { oneOf: [{
603
629
  type: "object",
604
630
  additionalProperties: false,
@@ -631,7 +657,7 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
631
657
  rejected_bindings: []
632
658
  };
633
659
  }
634
- const result = certifyCheckpoint(projection, args.bindings.map((binding) => ({
660
+ const bindings = args.bindings.map((binding) => ({
635
661
  itemId: binding.item_id,
636
662
  evidenceIds: binding.evidence_ids,
637
663
  ...binding.semantic_action ? { semanticAction: binding.semantic_action } : {},
@@ -654,7 +680,39 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
654
680
  resolvedTarget: closure.resolved_target,
655
681
  order: closure.order
656
682
  })) } : {}
657
- })), `C${projection.checkpoints.length + 1}`, false);
683
+ }));
684
+ let proofState = {
685
+ status: "absent",
686
+ reason_codes: []
687
+ };
688
+ if (args.proof !== void 0) {
689
+ const structural = validateProofManifestV2(args.proof);
690
+ if (structural.length) proofState = {
691
+ status: "invalid",
692
+ reason_codes: structural
693
+ };
694
+ else {
695
+ const binding = bindProofV2ToProjection(projection, args.proof);
696
+ proofState = binding.length ? {
697
+ status: "rejected",
698
+ reason_codes: binding
699
+ } : {
700
+ status: "bound",
701
+ reason_codes: []
702
+ };
703
+ }
704
+ }
705
+ const result = proofState.status === "rejected" || proofState.status === "invalid" ? {
706
+ status: "incomplete",
707
+ contractRevision: projection.contractRevision,
708
+ openItems: [],
709
+ rejectedBindings: []
710
+ } : certifyCheckpoint(projection, bindings, `C${projection.checkpoints.length + 1}`, false);
711
+ if (proofState.status === "rejected" || proofState.status === "invalid") for (const code of proofState.reason_codes) result.rejectedBindings.push({
712
+ itemId: "*",
713
+ reason: "the presented proof does not bind the current contract",
714
+ reasonCode: code
715
+ });
658
716
  if (!result.checkpoint) onRejected();
659
717
  const available_evidence = [...projection.evidence.values()].filter((evidence) => evidence.epoch === projection.epoch && (args.evidence_scope === "history" || [...projection.items.values()].some((item) => item.status === "pending" && relevantEvidence(projection, item, evidence)))).sort((a, b) => b.toolResultSeq - a.toolResultSeq || (a.id < b.id ? -1 : 1)).map((evidence) => ({
660
718
  id: evidence.id,
@@ -680,6 +738,7 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
680
738
  }));
681
739
  return checkpointPage(projection, args, {
682
740
  status: result.status,
741
+ proof_state: proofState,
683
742
  contract_revision: result.contractRevision,
684
743
  blocking_total: result.openItems.length,
685
744
  open_items: (args.item_ids?.length ? args.item_ids : result.openItems).map((id) => projection.items.get(id)).filter((item) => Boolean(item)).sort((a, b) => b.revision - a.revision || a.id.localeCompare(b.id)).map((item) => openItemForTool(projection, item)),
@@ -711,6 +770,10 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
711
770
  evidence_sha256: result.checkpoint.evidenceSha256,
712
771
  binding_digest: result.checkpoint.bindingDigest,
713
772
  certification_digest: result.checkpoint.certificationDigest,
773
+ ...result.checkpoint.unitId !== void 0 ? {
774
+ unit_id: result.checkpoint.unitId,
775
+ unit_closure_digest: result.checkpoint.unitClosureDigest
776
+ } : {},
714
777
  goal_ref: result.checkpoint.goalRef ?? null
715
778
  } } : {}
716
779
  });
@@ -848,24 +911,211 @@ function createBoundaryTool(getProjection, prepare, onRejected) {
848
911
  });
849
912
  }
850
913
 
914
+ //#endregion
915
+ //#region src/tools/action-preparation.ts
916
+ const GUARD_TOOL_STEPS = [
917
+ "Before execution: context_guard_evidence with evidence_role=resolution, selector and command_manifest. Keep the successful tool call ID and target_digest.",
918
+ "Execute once with context_guard_action, semantic_action, resolution_call_id, target_digest, contract_item_id and contract_item_revision.",
919
+ "Collect effect and state separately with context_guard_evidence, using the same resolution_call_id and the successful action call ID as effect_call_id.",
920
+ "Checkpoint the matching resolution/effect/state evidence IDs. Call IDs identify tool events; evidence IDs identify checkpoint facts.",
921
+ "If the action already ran without resolution, report the historical evidence gap and read back state. Do not repeat a mutation to create missing prestate evidence."
922
+ ];
923
+ const NATIVE_WRITE_STEPS = [
924
+ "Before execution: context_guard_evidence with evidence_role=resolution, selector and command_manifest. Keep the successful tool call ID and target_digest.",
925
+ "Execute the planned effect once with the native write/edit tool named by command_manifest.planned_tool and exactly the planned_arguments.",
926
+ "Collect effect and state separately with context_guard_evidence, using the same resolution_call_id and the successful write/edit call ID as effect_call_id.",
927
+ "Checkpoint the matching resolution/effect/state evidence IDs. Call IDs identify tool events; evidence IDs identify checkpoint facts.",
928
+ "If the action already ran without resolution, report the historical evidence gap and read back state. Do not repeat a mutation to create missing prestate evidence."
929
+ ];
930
+ /**
931
+ * The unified descriptor for one stateful action. Git actions keep their
932
+ * historical selector/manifest/step shapes; the other six gain the same
933
+ * complete recipe instead of Git-only coverage.
934
+ */
935
+ function actionPreparation(action) {
936
+ switch (action) {
937
+ case "install":
938
+ case "apply": return {
939
+ action,
940
+ selector_fields: ["package_id", "profile"],
941
+ optional_selector_fields: ["version"],
942
+ command_manifest_fields: ["manifest_id", "tgz_path"],
943
+ command_manifest_ids: [action === "install" ? "dsh.plugin_add_tgz.install.v1" : "dsh.plugin_add_tgz.apply.v1"],
944
+ planned_tools: ["context_guard_action"],
945
+ planned_argument_fields: [],
946
+ producer_fields: ["version", "integrity_digest"],
947
+ readback_fields: [
948
+ "package_id",
949
+ "version",
950
+ "integrity_digest",
951
+ "profile"
952
+ ],
953
+ execution_surface: "context_guard_action",
954
+ steps: GUARD_TOOL_STEPS
955
+ };
956
+ case "create": return {
957
+ action,
958
+ selector_fields: ["artifact_id"],
959
+ optional_selector_fields: [],
960
+ command_manifest_fields: ["planned_tool", "planned_arguments"],
961
+ command_manifest_ids: ["artifact.create.v1"],
962
+ planned_tools: ["write", "write_file"],
963
+ planned_argument_fields: ["file_path", "content"],
964
+ producer_fields: [
965
+ "scope",
966
+ "pre_digest",
967
+ "change_set_digest"
968
+ ],
969
+ readback_fields: ["post_digest"],
970
+ execution_surface: "native_write_edit",
971
+ steps: NATIVE_WRITE_STEPS
972
+ };
973
+ case "modify": return {
974
+ action,
975
+ selector_fields: ["artifact_id"],
976
+ optional_selector_fields: [],
977
+ command_manifest_fields: ["planned_tool", "planned_arguments"],
978
+ command_manifest_ids: ["artifact.modify.v1"],
979
+ planned_tools: ["edit", "edit_file"],
980
+ planned_argument_fields: [
981
+ "file_path",
982
+ "old_string",
983
+ "new_string"
984
+ ],
985
+ producer_fields: [
986
+ "scope",
987
+ "pre_digest",
988
+ "change_set_digest"
989
+ ],
990
+ readback_fields: ["post_digest"],
991
+ execution_surface: "native_write_edit",
992
+ steps: NATIVE_WRITE_STEPS
993
+ };
994
+ case "restart": return {
995
+ action,
996
+ selector_fields: ["service_id"],
997
+ optional_selector_fields: [],
998
+ command_manifest_fields: ["manifest_id"],
999
+ command_manifest_ids: ["dshmarket.restart.v1"],
1000
+ planned_tools: ["context_guard_action"],
1001
+ planned_argument_fields: [],
1002
+ producer_fields: ["pre_generation"],
1003
+ readback_fields: ["new_generation", "health"],
1004
+ execution_surface: "context_guard_action",
1005
+ steps: GUARD_TOOL_STEPS
1006
+ };
1007
+ case "publish": return {
1008
+ action,
1009
+ selector_fields: [
1010
+ "artifact_id",
1011
+ "version",
1012
+ "registry"
1013
+ ],
1014
+ optional_selector_fields: [],
1015
+ command_manifest_fields: ["manifest_id", "tgz_path"],
1016
+ command_manifest_ids: ["npm.publish_tgz.v1"],
1017
+ planned_tools: ["context_guard_action"],
1018
+ planned_argument_fields: [],
1019
+ producer_fields: ["integrity_digest"],
1020
+ readback_fields: [
1021
+ "artifact_id",
1022
+ "version",
1023
+ "registry",
1024
+ "integrity_digest"
1025
+ ],
1026
+ execution_surface: "context_guard_action",
1027
+ steps: GUARD_TOOL_STEPS
1028
+ };
1029
+ case "commit": return {
1030
+ action,
1031
+ selector_fields: ["repository", "branch"],
1032
+ optional_selector_fields: [],
1033
+ command_manifest_fields: ["planned_tool", "planned_arguments"],
1034
+ command_manifest_ids: ["git.commit_index_tree.v2"],
1035
+ planned_tools: ["bash", "pwsh"],
1036
+ planned_argument_fields: ["command", "workdir"],
1037
+ producer_fields: ["pre_head_oid", "change_set_digest"],
1038
+ readback_fields: ["post_head_oid", "pre_head_oid"],
1039
+ execution_surface: "context_guard_action",
1040
+ steps: GUARD_TOOL_STEPS
1041
+ };
1042
+ case "push": return {
1043
+ action,
1044
+ selector_fields: [
1045
+ "repository",
1046
+ "remote",
1047
+ "refspec"
1048
+ ],
1049
+ optional_selector_fields: [],
1050
+ command_manifest_fields: ["planned_tool", "planned_arguments"],
1051
+ command_manifest_ids: ["git.push_explicit_refs.v2"],
1052
+ planned_tools: ["bash", "pwsh"],
1053
+ planned_argument_fields: ["command", "workdir"],
1054
+ producer_fields: ["local_oid"],
1055
+ readback_fields: ["remote_oid"],
1056
+ execution_surface: "context_guard_action",
1057
+ steps: GUARD_TOOL_STEPS
1058
+ };
1059
+ case "pull": return {
1060
+ action,
1061
+ selector_fields: [
1062
+ "repository",
1063
+ "remote",
1064
+ "refspec"
1065
+ ],
1066
+ optional_selector_fields: [],
1067
+ command_manifest_fields: ["planned_tool", "planned_arguments"],
1068
+ command_manifest_ids: ["git.pull_ff_only_explicit.v2"],
1069
+ planned_tools: ["bash", "pwsh"],
1070
+ planned_argument_fields: ["command", "workdir"],
1071
+ producer_fields: [
1072
+ "upstream_oid",
1073
+ "pre_head_oid",
1074
+ "pull_mode"
1075
+ ],
1076
+ readback_fields: ["post_head_oid", "tracking_ref_oid"],
1077
+ execution_surface: "context_guard_action",
1078
+ steps: GUARD_TOOL_STEPS
1079
+ };
1080
+ case "fetch": return {
1081
+ action,
1082
+ selector_fields: [
1083
+ "repository",
1084
+ "remote",
1085
+ "refspec"
1086
+ ],
1087
+ optional_selector_fields: [],
1088
+ command_manifest_fields: ["planned_tool", "planned_arguments"],
1089
+ command_manifest_ids: ["git.fetch_tracking_explicit.v2"],
1090
+ planned_tools: ["bash", "pwsh"],
1091
+ planned_argument_fields: ["command", "workdir"],
1092
+ producer_fields: ["upstream_oid", "pre_head_oid"],
1093
+ readback_fields: ["tracking_ref_oid", "post_head_oid"],
1094
+ execution_surface: "context_guard_action",
1095
+ steps: GUARD_TOOL_STEPS
1096
+ };
1097
+ }
1098
+ }
1099
+
851
1100
  //#endregion
852
1101
  //#region src/tools/prepare.ts
1102
+ /** Discovery pages stay bounded like checkpoint pages. */
1103
+ const DISCOVERY_ITEM_LIMIT = 8;
853
1104
  /**
854
- * Thin READ-ONLY preparation surface (v0.5): before any stateful action it
1105
+ * Thin READ-ONLY preparation surface (v0.5/0.6): before any stateful action it
855
1106
  * reports the supported command shape, the required resolution/effect/state
856
1107
  * evidence order, existing reusable references, and the exact missing fields.
857
1108
  * It never executes, installs, commits, pushes, restarts, or probes authority
858
1109
  * through side effects, and it never upgrades a default into user authority.
1110
+ * Without an `item_id` it returns the bounded current-item discovery list, so
1111
+ * the first step of a session can find the right ID instead of guessing one.
859
1112
  */
860
1113
  function createPrepareTool(options) {
861
1114
  return defineTool({
862
1115
  name: "context_guard_prepare",
863
- 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.",
1116
+ 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. Omit item_id to list current open items with their IDs. Performs no action and grants no authority.",
864
1117
  parameters: {
865
- item_id: {
866
- type: "string",
867
- required: true
868
- },
1118
+ item_id: { type: "string" },
869
1119
  item_revision: { type: "number" },
870
1120
  semantic_action: { type: "string" },
871
1121
  requested_target: {
@@ -886,11 +1136,46 @@ function createPrepareTool(options) {
886
1136
  },
887
1137
  async execute(rawArgs) {
888
1138
  const args = rawArgs;
1139
+ if (options.refreshProjection) {
1140
+ let durable = false;
1141
+ try {
1142
+ durable = await options.refreshProjection() === true;
1143
+ } catch {
1144
+ durable = false;
1145
+ }
1146
+ if (!durable) return {
1147
+ status: "unknown",
1148
+ reason_code: "projection_durability_unavailable"
1149
+ };
1150
+ }
889
1151
  const p = options.getProjection();
890
1152
  if (!p || !p.enabled || p.integrity !== "valid") return {
891
1153
  status: "unknown",
892
1154
  reason_code: "guard_unavailable"
893
1155
  };
1156
+ if (args.item_id === void 0) {
1157
+ const open = [...p.items.values()].filter((item$1) => item$1.status === "pending").sort((a, b) => a.revision - b.revision || a.id.localeCompare(b.id));
1158
+ return {
1159
+ status: "prepared",
1160
+ mode: "discovery",
1161
+ durability: p.durabilityWatermark,
1162
+ contract_revision: p.contractRevision,
1163
+ total_open: open.length,
1164
+ items: open.slice(0, DISCOVERY_ITEM_LIMIT).map((item$1) => {
1165
+ const diagnosis$1 = deriveItemDiagnosis(p, item$1);
1166
+ return {
1167
+ id: item$1.id,
1168
+ revision: item$1.revision,
1169
+ kind: item$1.kind,
1170
+ ...item$1.taskKind !== void 0 ? { task_kind: item$1.taskKind } : {},
1171
+ semantic_action: item$1.semanticAction ?? "generic_run",
1172
+ reason_code: diagnosis$1.reason_code,
1173
+ text: item$1.normalizedText
1174
+ };
1175
+ }),
1176
+ note: "Re-run with one item_id to prepare that item. Preparation performs no action."
1177
+ };
1178
+ }
894
1179
  const item = p.items.get(args.item_id ?? "");
895
1180
  if (!item) return {
896
1181
  status: "rejected",
@@ -920,7 +1205,8 @@ function createPrepareTool(options) {
920
1205
  "effect (the exact planned change)",
921
1206
  "state (independent post-state readback)"
922
1207
  ] : ["state (matching durable evidence for the requested verification)"];
923
- const missingTargetFields = manifestEntry?.stateful && plannedAction ? manifestEntry.resolvedTargetKeys.filter((key) => !(item.requestedTarget?.[key] !== void 0 || args.requested_target?.[key] !== void 0)) : [];
1208
+ const recipe = plannedAction && isStatefulAction(plannedAction) ? actionPreparation(plannedAction) : void 0;
1209
+ const missingTargetFields = recipe ? recipe.selector_fields.filter((key) => !(item.requestedTarget?.[key] !== void 0 || args.requested_target?.[key] !== void 0)) : [];
924
1210
  const capability = plannedAction && options.hostCapability ? options.hostCapability(plannedAction) : void 0;
925
1211
  const commandShape = options.commandTemplate && plannedAction ? options.commandTemplate(plannedAction) : void 0;
926
1212
  return {
@@ -937,7 +1223,19 @@ function createPrepareTool(options) {
937
1223
  missing_target_fields: missingTargetFields,
938
1224
  ...capability ? { host_capability: {
939
1225
  status: capability.status,
940
- reason_code: capability.reasonCode
1226
+ ...capability.reasonCode !== void 0 ? { reason_code: capability.reasonCode } : {}
1227
+ } } : {},
1228
+ ...recipe ? { evidence_input_contract: {
1229
+ selector_fields: recipe.selector_fields,
1230
+ optional_selector_fields: recipe.optional_selector_fields,
1231
+ command_manifest_fields: recipe.command_manifest_fields,
1232
+ command_manifest_ids: recipe.command_manifest_ids,
1233
+ planned_tools: recipe.planned_tools,
1234
+ planned_argument_fields: recipe.planned_argument_fields,
1235
+ producer_fields: recipe.producer_fields,
1236
+ readback_fields: recipe.readback_fields,
1237
+ execution_surface: recipe.execution_surface,
1238
+ steps: recipe.steps
941
1239
  } } : {},
942
1240
  note: "Preparation performs no action. A default or guessed target is not user authority; explicit root instruction is required for missing target fields."
943
1241
  };
@@ -1037,12 +1335,14 @@ function adapterId(action) {
1037
1335
  if (action === "publish") return "context-guard.registry.v1";
1038
1336
  return "context-guard.git.v1";
1039
1337
  }
1040
- function unavailable(action, role, reason) {
1338
+ function unavailable(action, role, reason, missingFields, nextStep) {
1041
1339
  return {
1042
1340
  status: "unavailable",
1043
1341
  reason_code: reason,
1044
1342
  semantic_action: action,
1045
1343
  evidence_role: role,
1344
+ ...missingFields?.length ? { missing_fields: missingFields } : {},
1345
+ ...nextStep ? { next_step: nextStep } : {},
1046
1346
  resolved_target: {},
1047
1347
  observed_state: {},
1048
1348
  adapter_id: adapterId(action),
@@ -1282,12 +1582,63 @@ async function tgzIdentity(path) {
1282
1582
  offset = bodyStart + Math.ceil(size$1 / 512) * 512;
1283
1583
  }
1284
1584
  if (typeof manifest?.name !== "string" || typeof manifest.version !== "string") return void 0;
1585
+ const gitHead = typeof manifest.gitHead === "string" && /^[0-9a-f]{40}$/.test(manifest.gitHead) ? manifest.gitHead : void 0;
1586
+ const repositoryField = manifest.repository;
1587
+ const repository = typeof repositoryField === "string" ? repositoryField : record(repositoryField) && typeof record(repositoryField)?.url === "string" ? String(record(repositoryField).url) : void 0;
1285
1588
  return {
1286
1589
  name: manifest.name,
1287
1590
  version: manifest.version,
1288
- integrity: `sha512-${createHash("sha512").update(bytes).digest("base64")}`
1591
+ integrity: `sha512-${createHash("sha512").update(bytes).digest("base64")}`,
1592
+ sha256: createHash("sha256").update(bytes).digest("hex"),
1593
+ ...gitHead ? { gitHead } : {},
1594
+ ...repository ? { repository } : {}
1595
+ };
1596
+ }
1597
+ /**
1598
+ * Read the candidate identity of a publish from TRUSTED PRODUCERS only: the
1599
+ * exact tgz bytes (SHA-256 and npm SRI), the manifest the packer embedded
1600
+ * (package name, version, gitHead, repository) and, when the contract names a
1601
+ * ref, the local repository's own answer for that ref. Nothing here comes from
1602
+ * a model argument, so a caller cannot assert an identity the artifact does
1603
+ * not have.
1604
+ */
1605
+ async function observePublishCandidate(resolution, roots, signal, cwd) {
1606
+ const tgzPath = requireString(resolution.commandManifest, "tgz_path");
1607
+ if (!tgzPath) return void 0;
1608
+ const identity = await tgzIdentity(tgzPath);
1609
+ if (!identity) return void 0;
1610
+ const registry = typeof resolution.target.registry === "string" ? resolution.target.registry : void 0;
1611
+ return {
1612
+ packageId: identity.name,
1613
+ version: identity.version,
1614
+ artifactSha256: identity.sha256,
1615
+ artifactSri: identity.integrity,
1616
+ ...identity.gitHead ? { fullSha40: identity.gitHead } : {},
1617
+ ...identity.repository ? { repository: identity.repository } : {},
1618
+ ...registry ? { registry } : {}
1289
1619
  };
1290
1620
  }
1621
+ /**
1622
+ * Resolve one ref to its commit with the audited git executable. Exported so
1623
+ * the runtime can resolve the ref an ADOPTED CONTRACT names, which is the only
1624
+ * closed, reachable path for that identity: the action tool's manifest schema
1625
+ * has no field for it, and a model-supplied ref would not be authority.
1626
+ */
1627
+ async function resolveAuditedRef(cwd, ref, roots, signal) {
1628
+ if (!cwd || !/^refs\/[A-Za-z0-9._/-]+$/.test(ref)) return void 0;
1629
+ try {
1630
+ const identity = await (roots.readExecutableIdentity ?? executableIdentity)("git", signal);
1631
+ if (!identity || identity.executable !== "git") return void 0;
1632
+ const resolved = (await git(cwd, [
1633
+ "rev-parse",
1634
+ "--verify",
1635
+ `${ref}^{commit}`
1636
+ ], signal, identity.realpath)).trim().toLowerCase();
1637
+ return /^[0-9a-f]{40}$/.test(resolved) ? resolved : void 0;
1638
+ } catch {
1639
+ return;
1640
+ }
1641
+ }
1291
1642
  const MARKET_SCHEMA = "dsh-market/update-api/v1";
1292
1643
  const MARKET_GENERATION_PREFIX = "dsh-market-instance/v1:";
1293
1644
  async function marketCapabilities(roots, signal) {
@@ -2017,7 +2368,9 @@ function normalizedRoots(options) {
2017
2368
  ...options.authorizeMutation ? { authorizeMutation: options.authorizeMutation } : {},
2018
2369
  ...options.prepareMutation ? { prepareMutation: options.prepareMutation } : {},
2019
2370
  ...options.readExecutableIdentity ? { readExecutableIdentity: options.readExecutableIdentity } : {},
2020
- ...options.allowLoopbackHttpRegistry ? { allowLoopbackHttpRegistry: true } : {}
2371
+ ...options.allowLoopbackHttpRegistry ? { allowLoopbackHttpRegistry: true } : {},
2372
+ ...options.releaseGate ? { releaseGate: options.releaseGate } : {},
2373
+ ...options.releaseSettle ? { releaseSettle: options.releaseSettle } : {}
2021
2374
  };
2022
2375
  }
2023
2376
  function createActionTool(options = {}) {
@@ -2186,14 +2539,70 @@ function createActionTool(options = {}) {
2186
2539
  ...identity
2187
2540
  };
2188
2541
  }
2542
+ const releaseOperation = action === "publish" ? "npm_publish" : void 0;
2543
+ let releaseGranted = false;
2544
+ let releaseContractId;
2545
+ if (releaseOperation && roots.releaseGate) {
2546
+ const observed = await observePublishCandidate(resolution, roots, exec.signal, cwdOf(agent));
2547
+ if (!observed) return {
2548
+ status: "unavailable",
2549
+ reason_code: "release_candidate_unobservable",
2550
+ ...identity
2551
+ };
2552
+ let decision;
2553
+ try {
2554
+ decision = await roots.releaseGate({
2555
+ agent,
2556
+ operation: releaseOperation,
2557
+ callId: args.resolution_call_id,
2558
+ resolvedTarget: resolution.target,
2559
+ observed
2560
+ });
2561
+ } catch {
2562
+ decision = void 0;
2563
+ }
2564
+ if (decision === void 0) return {
2565
+ status: "unavailable",
2566
+ reason_code: "release_gate_unavailable",
2567
+ ...identity
2568
+ };
2569
+ if (decision.status !== "granted") return {
2570
+ status: "unavailable",
2571
+ reason_code: decision.reasonCode,
2572
+ ...identity
2573
+ };
2574
+ releaseGranted = true;
2575
+ releaseContractId = decision.contractId;
2576
+ }
2189
2577
  try {
2190
2578
  const status = await executeGuardAction(action, resolution, roots, exec.signal, currentExecutable, args.resolution_call_id, agent);
2579
+ if (releaseOperation && releaseGranted && roots.releaseSettle) {
2580
+ const readback$1 = status === "completed" ? await publishReadback(resolution.target, roots, exec.signal) : void 0;
2581
+ await roots.releaseSettle({
2582
+ agent,
2583
+ operation: releaseOperation,
2584
+ callId: args.resolution_call_id,
2585
+ ...releaseContractId !== void 0 ? { contractId: releaseContractId } : {},
2586
+ effect: status === "completed" ? "completed" : status === "unavailable" ? "not_effected" : "unknown",
2587
+ readback: readback$1 === void 0 ? "unavailable" : {
2588
+ kind: "npm_integrity",
2589
+ identity: readback$1
2590
+ }
2591
+ });
2592
+ }
2191
2593
  return {
2192
2594
  status,
2193
2595
  reason_code: status === "completed" ? "action_completed" : status === "handoff_pending" ? "restart_handoff_pending" : "action_execution_failed",
2194
2596
  ...identity
2195
2597
  };
2196
2598
  } catch {
2599
+ if (releaseOperation && releaseGranted && roots.releaseSettle) await roots.releaseSettle({
2600
+ agent,
2601
+ operation: releaseOperation,
2602
+ callId: args.resolution_call_id,
2603
+ effect: "unknown",
2604
+ readback: "unavailable"
2605
+ });
2197
2606
  return {
2198
2607
  status: "unavailable",
2199
2608
  reason_code: "action_execution_failed",
@@ -2203,6 +2612,23 @@ function createActionTool(options = {}) {
2203
2612
  }
2204
2613
  });
2205
2614
  }
2615
+ /**
2616
+ * The trusted npm readback identity for a completed publish: the registry's
2617
+ * own `dist.integrity` for the exact package version. `undefined` means no
2618
+ * producer is reachable, which keeps the release attempt `unconfirmed` instead
2619
+ * of claiming a verified release.
2620
+ */
2621
+ async function publishReadback(target, roots, signal) {
2622
+ const registry = typeof target.registry === "string" ? target.registry : void 0;
2623
+ const artifact = typeof target.artifact_id === "string" ? target.artifact_id : void 0;
2624
+ const version = typeof target.version === "string" ? target.version : void 0;
2625
+ if (!registry || !artifact || !version) return void 0;
2626
+ try {
2627
+ return await registryIntegrity(registry, artifact, version, roots, signal);
2628
+ } catch {
2629
+ return;
2630
+ }
2631
+ }
2206
2632
  function createEvidenceTool(options = {}) {
2207
2633
  const roots = normalizedRoots(options);
2208
2634
  return defineTool({
@@ -2292,6 +2718,11 @@ function createEvidenceTool(options = {}) {
2292
2718
  type: "string",
2293
2719
  required: true
2294
2720
  },
2721
+ missing_fields: {
2722
+ type: "array",
2723
+ items: { type: "string" }
2724
+ },
2725
+ next_step: { type: "string" },
2295
2726
  evidence_role: {
2296
2727
  type: "string",
2297
2728
  required: true,
@@ -2414,6 +2845,27 @@ function createEvidenceTool(options = {}) {
2414
2845
  if (!agent) return unavailable(action, role, "producer_agent_unavailable");
2415
2846
  try {
2416
2847
  if (role === "resolution") {
2848
+ const recipe = actionPreparation(action);
2849
+ {
2850
+ const manifest = record(args.command_manifest);
2851
+ const plannedArgs = record(manifest?.planned_arguments);
2852
+ const selector = record(args.selector);
2853
+ const needsManifestId = [
2854
+ "install",
2855
+ "apply",
2856
+ "publish",
2857
+ "restart"
2858
+ ].includes(action);
2859
+ const needsPlannedEffect = !needsManifestId;
2860
+ const missing = [
2861
+ ...recipe.selector_fields.filter((key) => !requireString(selector ?? {}, key)).map((key) => `selector.${key}`),
2862
+ ...needsManifestId && !requireString(manifest ?? {}, "manifest_id") ? ["command_manifest.manifest_id"] : [],
2863
+ ...needsManifestId && recipe.command_manifest_fields.includes("tgz_path") && !requireString(manifest ?? {}, "tgz_path") ? ["command_manifest.tgz_path"] : [],
2864
+ ...needsPlannedEffect && !requireString(manifest ?? {}, "planned_tool") ? ["command_manifest.planned_tool"] : [],
2865
+ ...needsPlannedEffect ? recipe.planned_argument_fields.filter((key) => !requireString(plannedArgs ?? {}, key)).map((key) => `command_manifest.planned_arguments.${key}`) : []
2866
+ ];
2867
+ if (missing.length) return unavailable(action, role, "resolution_input_missing", missing, "Supply the listed resolution inputs before execution. Use context_guard_prepare for the exact input contract of this action. A manifest_id alone does not describe a planned tool call.");
2868
+ }
2417
2869
  const executable$1 = executableFor(action);
2418
2870
  const executableBinding = executable$1 ? await (roots.readExecutableIdentity ?? executableIdentity)(executable$1, exec.signal) : void 0;
2419
2871
  if (executable$1 && !executableBinding) return unavailable(action, role, "executable_identity_unavailable");
@@ -2425,7 +2877,7 @@ function createEvidenceTool(options = {}) {
2425
2877
  if (!expectedTransition) return unavailable(action, role, "expected_transition_unavailable");
2426
2878
  return supported(action, role, resolved.target, {}, commandManifest, gitBinding, executableBinding, expectedTransition);
2427
2879
  }
2428
- if (!args.resolution_call_id) return unavailable(action, role, "producer_reference_missing");
2880
+ if (!args.resolution_call_id) return unavailable(action, role, "producer_reference_missing", ["resolution_call_id", ...!args.effect_call_id ? ["effect_call_id"] : []], "Use the successful pre-action resolution call ID and matching action call ID. Selectors and evidence IDs cannot replace call IDs. If execution already happened without resolution, report the historical gap; do not repeat the mutation.");
2429
2881
  const resolution = findResolution(snapshotSessionEvents(agent.session), args.resolution_call_id, action);
2430
2882
  if (!resolution) return unavailable(action, role, "persisted_effect_mismatch");
2431
2883
  const executable = executableFor(action);
@@ -2444,7 +2896,7 @@ function createEvidenceTool(options = {}) {
2444
2896
  "pull",
2445
2897
  "fetch"
2446
2898
  ].includes(action);
2447
- if (!args.effect_call_id) return unavailable(action, role, "producer_reference_missing");
2899
+ if (!args.effect_call_id) return unavailable(action, role, "producer_reference_missing", ["effect_call_id"], "Supply the successful matching effect call ID. Git/package/service effects must come from context_guard_action; do not repeat an already completed mutation to manufacture missing evidence.");
2448
2900
  if (ownedEffect) {
2449
2901
  const events = snapshotSessionEvents(agent.session);
2450
2902
  const actionCall = actionCallMatches(events, args.effect_call_id, action, args.resolution_call_id, resolution.target);
@@ -2464,6 +2916,135 @@ function createEvidenceTool(options = {}) {
2464
2916
  });
2465
2917
  }
2466
2918
 
2919
+ //#endregion
2920
+ //#region src/tools/release.ts
2921
+ const RELEASE_TOOL = "context_guard_release";
2922
+ function createReleaseTool(options) {
2923
+ return defineTool({
2924
+ name: RELEASE_TOOL,
2925
+ description: "Read the explicit release state, or reconcile a release attempt whose effect was unknown by reading back the external identity. This tool never re-sends a release.",
2926
+ parameters: {
2927
+ operation: {
2928
+ type: "string",
2929
+ required: true,
2930
+ enum: ["status", "reconcile"]
2931
+ },
2932
+ contract_id: { type: "string" },
2933
+ resolution_call_id: { type: "string" }
2934
+ },
2935
+ output: {
2936
+ schema: {
2937
+ type: "object",
2938
+ additionalProperties: true
2939
+ },
2940
+ render: (_args, value) => [{
2941
+ type: "text",
2942
+ text: JSON.stringify(value)
2943
+ }]
2944
+ },
2945
+ async execute(rawArgs, exec) {
2946
+ const args = rawArgs;
2947
+ const projection = options.getProjection();
2948
+ if (!projection) return {
2949
+ status: "unknown",
2950
+ reason_code: "guard_unavailable"
2951
+ };
2952
+ if (args.operation === "status") return {
2953
+ status: "available",
2954
+ policy: projection.policy,
2955
+ state_damaged: projection.releaseStateDamaged,
2956
+ coverage_surface: Object.keys(RELEASE_OPERATION_SURFACES).map((operation$1) => ({
2957
+ operation: operation$1,
2958
+ ...RELEASE_OPERATION_SURFACES[operation$1]
2959
+ })),
2960
+ contracts: projection.releaseContracts.map((contract$1) => ({
2961
+ contract_id: contract$1.contractId,
2962
+ revoked_at_seq: contract$1.revokedAtSeq ?? null,
2963
+ adopted_at_revision: contract$1.adoptedAtRevision,
2964
+ operations: releaseCoverage(contract$1),
2965
+ candidate: contract$1.candidate
2966
+ })),
2967
+ reservations: projection.releaseReservations.map(({ contractId: contractId$1, operation: operation$1, callId: callId$1, startedAtSeq, observedArtifactSri }) => ({
2968
+ contract_id: contractId$1,
2969
+ operation: operation$1,
2970
+ resolution_call_id: callId$1,
2971
+ started_at_seq: startedAtSeq,
2972
+ observed_artifact_sri: observedArtifactSri ?? null,
2973
+ in_flight: inFlightReservation(projection, contractId$1, operation$1)?.callId === callId$1
2974
+ })),
2975
+ settlements: projection.releaseSettlements.map(({ contractId: contractId$1, operation: operation$1, callId: callId$1, outcome: outcome$1, readback: readback$2, settledAtSeq }) => ({
2976
+ contract_id: contractId$1,
2977
+ operation: operation$1,
2978
+ resolution_call_id: callId$1,
2979
+ outcome: outcome$1,
2980
+ readback: readback$2,
2981
+ settled_at_seq: settledAtSeq
2982
+ })),
2983
+ diagnostics: projection.releaseDiagnostics
2984
+ };
2985
+ if (args.operation !== "reconcile") return {
2986
+ status: "rejected",
2987
+ reason_code: "release_subcommand_unknown"
2988
+ };
2989
+ const contractId = args.contract_id ?? "";
2990
+ const callId = args.resolution_call_id ?? "";
2991
+ const contract = contractById(projection, contractId);
2992
+ if (!contract) return {
2993
+ status: "rejected",
2994
+ reason_code: "release_contract_unknown"
2995
+ };
2996
+ const reservation = reservationFor(projection, contractId, callId);
2997
+ if (!reservation) return {
2998
+ status: "rejected",
2999
+ reason_code: "release_reservation_unknown"
3000
+ };
3001
+ const operation = reservation.operation;
3002
+ const settled = projection.releaseSettlements.find((entry) => entry.contractId === contractId && entry.operation === operation && entry.callId === callId && (entry.outcome === "settled" || entry.outcome === "not_effected"));
3003
+ if (settled) return {
3004
+ status: "already_resolved",
3005
+ outcome: settled.outcome,
3006
+ reason_code: "release_already_resolved"
3007
+ };
3008
+ const registry = contract.candidate.registry;
3009
+ const packageId = contract.candidate.packageId;
3010
+ const version = contract.candidate.version;
3011
+ if (!registry || !packageId || !version) return {
3012
+ status: "unavailable",
3013
+ reason_code: "release_readback_identity_incomplete"
3014
+ };
3015
+ const identity = await registryIntegrity(registry, packageId, version, options, exec.signal);
3016
+ if (!identity) return {
3017
+ status: "unavailable",
3018
+ reason_code: "release_readback_unavailable"
3019
+ };
3020
+ const readback$1 = {
3021
+ kind: "npm_integrity",
3022
+ identity
3023
+ };
3024
+ const verdict = readbackSettlesContract(contract, readback$1, reservation.observedArtifactSri);
3025
+ const outcome = verdict === "settled" ? "settled" : "unknown";
3026
+ if (!await options.persistSettlement({
3027
+ agent: exec.agent,
3028
+ contractId,
3029
+ operation,
3030
+ callId,
3031
+ readback: readback$1,
3032
+ outcome
3033
+ })) return {
3034
+ status: "unknown",
3035
+ reason_code: "release_settlement_not_durable"
3036
+ };
3037
+ return {
3038
+ status: verdict === "settled" ? "settled" : verdict === "mismatch" ? "mismatch" : "unconfirmed",
3039
+ outcome,
3040
+ readback: readback$1,
3041
+ reason_code: verdict === "settled" ? "release_reconciled" : verdict === "mismatch" ? "release_readback_identity_mismatch" : "release_readback_identity_unavailable",
3042
+ note: "Reconciliation never re-sends the release operation."
3043
+ };
3044
+ }
3045
+ });
3046
+ }
3047
+
2467
3048
  //#endregion
2468
3049
  //#region src/tools/external-operation.ts
2469
3050
  function createExternalOperationTool(read, capability) {
@@ -2539,17 +3120,168 @@ function createExternalOperationTool(read, capability) {
2539
3120
  });
2540
3121
  }
2541
3122
 
3123
+ //#endregion
3124
+ //#region src/domain/migration.ts
3125
+ /** The frozen digest domains: a migration never rewrites any of them. */
3126
+ const PRESERVED_DIGEST_DOMAINS = [
3127
+ "ccg.sessionRefDigest.v3",
3128
+ "ccg.hostLockDigest.v3",
3129
+ "ccg.evidenceFact.v3",
3130
+ "ccg.evidenceSha256.v3",
3131
+ "ccg.predParams.v3",
3132
+ "ccg.binding.v3",
3133
+ "ccg.bindingDigest.v3",
3134
+ "ccg.certificationDigest.v3",
3135
+ "ccg.locator.v1",
3136
+ "ccg.proofManifest.v1"
3137
+ ];
3138
+ function migrationReport(projection) {
3139
+ const v5 = projection.boundaryProtocol === 5;
3140
+ const legacyItemIds = certifiableOpenItems(projection).filter((item) => item.unitId === void 0).map((item) => item.id);
3141
+ const closure = v5 ? certificateClosure(projection) : { itemIds: [] };
3142
+ const reasonCodes = [];
3143
+ if (!v5) reasonCodes.push("legacy_session_keeps_v4_contract");
3144
+ if (v5 && legacyItemIds.length > 0) reasonCodes.push("pre_v5_obligations_retained_in_closure");
3145
+ return {
3146
+ ruleMode: v5 ? "v5" : "legacy-v4",
3147
+ certificateVersion: v5 ? CERTIFICATE_VERSION_V2 : CERTIFICATE_VERSION,
3148
+ stopProtocolVersion: v5 ? STOP_PROTOCOL_VERSION_V2 : STOP_PROTOCOL_VERSION,
3149
+ unitClosure: v5,
3150
+ ...v5 && projection.currentUnitId !== void 0 ? { currentUnitId: projection.currentUnitId } : {},
3151
+ legacyItemIds,
3152
+ unitClosureItemIds: [...closure.itemIds],
3153
+ preservedDigestDomains: [...PRESERVED_DIGEST_DOMAINS],
3154
+ historyOnlyCertificateVersions: v5 ? [CERTIFICATE_VERSION] : [],
3155
+ rollbackRequiresStateSnapshot: v5,
3156
+ rollbackInstruction: v5 ? "Restore the 0.5.x state snapshot before starting the older binary. Replaying a v5 log with 0.5.x fails closed with certificate_replay_mismatch; never migrate new-schema data down by hand." : "No rollback action is required for this session: it has not written a v5 boundary.",
3157
+ reasonCodes
3158
+ };
3159
+ }
3160
+
2542
3161
  //#endregion
2543
3162
  //#region src/commands/context-guard.ts
2544
3163
  function pendingCount(projection) {
2545
3164
  return [...projection.items.values()].filter((item) => item.status === "pending").length;
2546
3165
  }
3166
+ /**
3167
+ * The release surface. Adoption is deliberately NOT performed here: the
3168
+ * derivation adopts a contract only from the durable root `command/run` for
3169
+ * `/context-guard release adopt <json>`, so the command handler's job is to
3170
+ * report the resulting state (and to explain what is not protectable), never
3171
+ * to grant authority itself.
3172
+ */
3173
+ function releaseResponse(projection, rawInput) {
3174
+ const rest = rawInput.trim().slice(7).trim();
3175
+ const [verb] = rest.split(/\s+/, 1);
3176
+ if (verb === "adopt") {
3177
+ const payload = rest.slice(5).trim();
3178
+ let parsed;
3179
+ try {
3180
+ parsed = JSON.parse(payload);
3181
+ } catch {
3182
+ return {
3183
+ kind: "error",
3184
+ text: "Context Guard release adopt: the contract must be one JSON object."
3185
+ };
3186
+ }
3187
+ const normalized = normalizeReleaseContract(parsed, {
3188
+ seq: -1,
3189
+ digest: ""
3190
+ });
3191
+ if (!normalized.contract) return {
3192
+ kind: "error",
3193
+ text: `Context Guard release adopt rejected: ${normalized.errors.join(", ")}`
3194
+ };
3195
+ const contract = normalized.contract;
3196
+ const adopted = projection.releaseContracts.some((entry) => entry.contractId === contract.contractId) ? projection.releaseContracts.find((entry) => entry.contractId === contract.contractId) : contract;
3197
+ return {
3198
+ kind: "success",
3199
+ text: JSON.stringify({
3200
+ status: "adopted",
3201
+ contract_id: adopted.contractId,
3202
+ candidate: adopted.candidate,
3203
+ operations: releaseCoverage(adopted),
3204
+ readiness_refs: adopted.readinessRefs,
3205
+ closure_cert_ref: adopted.closureCertRef ?? null,
3206
+ expires_at_epoch_ms: adopted.expiresAtEpochMs ?? null,
3207
+ note: "The contract becomes authoritative from the durable root command that carried it. It grants no authority in this process."
3208
+ })
3209
+ };
3210
+ }
3211
+ if (verb === "revoke") {
3212
+ const contractId = rest.slice(6).trim();
3213
+ if (!contractId) return {
3214
+ kind: "error",
3215
+ text: "Usage: /context-guard release revoke <contract_id>"
3216
+ };
3217
+ const contract = projection.releaseContracts.find((entry) => entry.contractId === contractId);
3218
+ if (!contract) return {
3219
+ kind: "error",
3220
+ text: `Context Guard release revoke: unknown contract ${contractId}`
3221
+ };
3222
+ return {
3223
+ kind: "success",
3224
+ text: JSON.stringify({
3225
+ status: contract.revokedAtSeq === void 0 ? "revoking" : "revoked",
3226
+ contract_id: contractId,
3227
+ revoked_at_seq: contract.revokedAtSeq ?? null,
3228
+ in_flight: projection.releaseReservations.filter((reservation) => reservation.contractId === contractId && !projection.releaseSettlements.some((settlement) => settlement.callId === reservation.callId && (settlement.outcome === "settled" || settlement.outcome === "not_effected"))).map(({ operation, callId }) => ({
3229
+ operation,
3230
+ call_id: callId
3231
+ })),
3232
+ note: "Revocation is recorded durably and keeps the audit trail. It denies the next effect; an operation already in flight still needs its trusted readback to be reconciled."
3233
+ })
3234
+ };
3235
+ }
3236
+ if (verb !== "" && verb !== "status") return {
3237
+ kind: "error",
3238
+ text: "Usage: /context-guard release status | release adopt <json contract> | release revoke <contract_id>. Adoption and revocation are recorded from this command itself; they never grant authority in-process."
3239
+ };
3240
+ const contracts = projection.releaseContracts.map((contract) => ({
3241
+ contract_id: contract.contractId,
3242
+ adopted_at_seq: contract.adoptedBy.seq,
3243
+ revoked_at_seq: contract.revokedAtSeq ?? null,
3244
+ candidate: contract.candidate,
3245
+ readiness_refs: contract.readinessRefs,
3246
+ closure_cert_ref: contract.closureCertRef ?? null,
3247
+ operations: releaseCoverage(contract),
3248
+ expires_at_epoch_ms: contract.expiresAtEpochMs ?? null,
3249
+ consumed_operations: projection.releaseSettlements.filter((settlement) => settlement.contractId === contract.contractId && settlement.outcome === "settled").map((settlement) => settlement.operation),
3250
+ in_flight: projection.releaseReservations.filter((reservation) => reservation.contractId === contract.contractId && !projection.releaseSettlements.some((settlement) => settlement.callId === reservation.callId && (settlement.outcome === "settled" || settlement.outcome === "not_effected"))).map(({ operation, callId, startedAtSeq }) => ({
3251
+ operation,
3252
+ call_id: callId,
3253
+ started_at_seq: startedAtSeq
3254
+ })),
3255
+ settlements: projection.releaseSettlements.filter((settlement) => settlement.contractId === contract.contractId).map(({ operation, callId, outcome, readback: readback$1, settledAtSeq }) => ({
3256
+ operation,
3257
+ call_id: callId,
3258
+ outcome,
3259
+ readback: readback$1,
3260
+ settled_at_seq: settledAtSeq
3261
+ }))
3262
+ }));
3263
+ return {
3264
+ kind: "success",
3265
+ text: JSON.stringify({
3266
+ profile_applicable: projection.policy === "release" || contracts.length > 0,
3267
+ policy: projection.policy,
3268
+ state_damaged: projection.releaseStateDamaged,
3269
+ adopted_contracts: contracts,
3270
+ coverage_surface: RELEASE_OPERATIONS.map((operation) => ({
3271
+ operation,
3272
+ ...RELEASE_OPERATION_SURFACES[operation]
3273
+ })),
3274
+ diagnostics: projection.releaseDiagnostics,
3275
+ note: "A release is never implicit: only an explicit root adoption creates a contract, and only operations with a Guard execution surface can be protected."
3276
+ })
3277
+ };
3278
+ }
2547
3279
  function createContextGuardCommand(projectionFor, setEnabled, clearContract, lifecycleFor) {
2548
3280
  return {
2549
3281
  name: "context-guard",
2550
- description: "Enable, disable, clear, inspect, or diagnose Context Guard for this session.",
3282
+ description: "Enable, disable, clear, inspect, diagnose, or manage the explicit release contract for this session.",
2551
3283
  recordInput: true,
2552
- input: { hint: "on|off|clear|status|diagnose" },
3284
+ input: { hint: "on|off|clear|status|diagnose|migration|release status|release adopt <json>|release revoke <id>" },
2553
3285
  handler: ({ agent, rawInput }) => {
2554
3286
  const projection = projectionFor(agent);
2555
3287
  const [subcommand] = rawInput.trim().split(/\s+/, 1);
@@ -2577,22 +3309,31 @@ function createContextGuardCommand(projectionFor, setEnabled, clearContract, lif
2577
3309
  text: `Context Guard contract cleared: ${before - after} requirement/acceptance item(s) superseded; ${after} pending remain (prohibitions retained).`
2578
3310
  };
2579
3311
  }
3312
+ if (resolved === "release") return releaseResponse(projection, rawInput);
3313
+ if (resolved === "migration") return {
3314
+ kind: "success",
3315
+ text: JSON.stringify(migrationReport(projection))
3316
+ };
2580
3317
  if (resolved !== "status" && resolved !== "diagnose") return {
2581
3318
  kind: "error",
2582
- text: "Usage: /context-guard on|off|clear|status|diagnose"
3319
+ text: "Usage: /context-guard on|off|clear|status|diagnose|migration|release status|release adopt <json>|release revoke <id>"
2583
3320
  };
2584
3321
  const passed = [...projection.items.values()].filter((item) => item.status === "passed").length;
2585
3322
  let certifiable_missing_evidence = 0;
2586
3323
  let unsupported = 0;
3324
+ const reason_classes = {};
2587
3325
  for (const item of projection.items.values()) {
2588
3326
  if (item.status !== "pending") continue;
2589
3327
  const diagnosis = deriveItemDiagnosis(projection, item);
3328
+ reason_classes[diagnosis.reason_class] = (reason_classes[diagnosis.reason_class] ?? 0) + 1;
2590
3329
  if (diagnosis.repairability === "agent_repairable") certifiable_missing_evidence += 1;
2591
3330
  else if (diagnosis.certification === "unsupported") unsupported += 1;
2592
3331
  }
3332
+ const migration = migrationReport(projection);
2593
3333
  const response = {
2594
3334
  enabled: projection.enabled,
2595
3335
  lifecycle: lifecycleFor?.(agent) ?? (projection.enabled ? "active" : "disabled"),
3336
+ policy: projection.policy,
2596
3337
  epoch: projection.epoch,
2597
3338
  contract_revision: projection.contractRevision,
2598
3339
  pending: pendingCount(projection),
@@ -2600,11 +3341,25 @@ function createContextGuardCommand(projectionFor, setEnabled, clearContract, lif
2600
3341
  diagnosis: {
2601
3342
  certified: passed,
2602
3343
  certifiable_missing_evidence,
2603
- unsupported
3344
+ unsupported,
3345
+ reason_classes
2604
3346
  },
2605
3347
  evidence: projection.evidence.size,
2606
3348
  integrity: projection.integrity,
2607
- last_source_seq: projection.lastObservedSourceSeq
3349
+ last_source_seq: projection.lastObservedSourceSeq,
3350
+ migration: {
3351
+ rule_mode: migration.ruleMode,
3352
+ certificate_version: migration.certificateVersion,
3353
+ unit_closure: migration.unitClosure,
3354
+ legacy_open_items: migration.legacyItemIds.length
3355
+ },
3356
+ release: {
3357
+ policy: projection.policy,
3358
+ state_damaged: projection.releaseStateDamaged,
3359
+ adopted_contracts: projection.releaseContracts.length,
3360
+ in_flight: projection.releaseReservations.filter((reservation) => !projection.releaseSettlements.some((settlement) => settlement.callId === reservation.callId && settlement.outcome === "settled")).length,
3361
+ applicable: projection.policy === "release" || projection.releaseContracts.length > 0
3362
+ }
2608
3363
  };
2609
3364
  return {
2610
3365
  kind: "success",
@@ -3414,6 +4169,7 @@ defineMethod("transform", [
3414
4169
  //#region src/config.ts
3415
4170
  const Config = Schema.object({
3416
4171
  activation: Schema.string().default("opt-in"),
4172
+ policy: Schema.string().default("standard"),
3417
4173
  hostLockPlatform: Schema.string(),
3418
4174
  hostLockProfile: Schema.string(),
3419
4175
  hostLockPolicy: Schema.string(),
@@ -3428,6 +4184,8 @@ const Config = Schema.object({
3428
4184
  function resolveConfig(config) {
3429
4185
  const activation = config.activation ?? "opt-in";
3430
4186
  if (activation !== "opt-in" && activation !== "always") throw new TypeError(`activation must be "opt-in" or "always", received ${JSON.stringify(activation)}`);
4187
+ const policy = config.policy ?? "standard";
4188
+ if (policy !== "standard" && policy !== "strict" && policy !== "release") throw new TypeError(`policy must be "standard", "strict", or "release", received ${JSON.stringify(policy)}`);
3431
4189
  let hostLockPackages;
3432
4190
  if (config.hostLockPackages !== void 0) {
3433
4191
  if (!Array.isArray(config.hostLockPackages)) throw new TypeError("hostLockPackages must be an array");
@@ -3458,6 +4216,7 @@ function resolveConfig(config) {
3458
4216
  ...typeof config.hostLockRuntimeRoot === "string" ? { hostLockRuntimeRoot: config.hostLockRuntimeRoot } : {},
3459
4217
  ...typeof config.hostLockProfileRoot === "string" ? { hostLockProfileRoot: config.hostLockProfileRoot } : {},
3460
4218
  activation,
4219
+ policy,
3461
4220
  ...hostLockPackages ? { hostLockPackages } : {},
3462
4221
  ...config.hostLockPlatform ? { hostLockPlatform: config.hostLockPlatform } : {},
3463
4222
  ...config.hostLockProfile ? { hostLockProfile: config.hostLockProfile } : {}
@@ -3534,7 +4293,12 @@ function authorizeMutationFromProjection(projection, request) {
3534
4293
  status: "denied",
3535
4294
  reasonCode: "mutation_target_clarification_required"
3536
4295
  };
3537
- if (!requestedTargetAuthorizesMutation(request.action, item.requestedTarget, request.resolvedTarget)) return {
4296
+ const boundedAuthorized = (scope) => requestedTargetAuthorizesMutation(request.action, {
4297
+ ...item.requestedTarget,
4298
+ scope
4299
+ }, request.resolvedTarget);
4300
+ const selectionAuthorized = projection.trustedSelections.some((selection) => selection.kind === "directory" && typeof item.requestedTarget?.artifact_type === "string" && item.unitId !== void 0 && boundedAuthorized(selection.selected));
4301
+ if (!requestedTargetAuthorizesMutation(request.action, item.requestedTarget, request.resolvedTarget) && !selectionAuthorized) return {
3538
4302
  status: "denied",
3539
4303
  reasonCode: "mutation_requested_target_mismatch"
3540
4304
  };
@@ -3810,10 +4574,12 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3810
4574
  const session = agent.session;
3811
4575
  let pendingRecovery = false;
3812
4576
  let durabilityConfirmed = false;
4577
+ let durabilityWatermark = "unknown";
3813
4578
  let observedEpoch = -1;
3814
4579
  let observedCompactionSeq = -1;
3815
4580
  let observedContractRevision = -1;
3816
4581
  let protocolV4Present = false;
4582
+ let protocolV5Present = false;
3817
4583
  let realRootInputSeen = false;
3818
4584
  let lifecycle = "armed";
3819
4585
  const continuationAttempts = projection.continuationAttempts;
@@ -3832,7 +4598,10 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3832
4598
  if (!projection.integrityViolations.includes(code)) projection.integrityViolations.push(code);
3833
4599
  return;
3834
4600
  }
3835
- const derived = deriveProjection(events, { activation: config.activation }, {
4601
+ const derived = deriveProjection(events, {
4602
+ activation: config.activation,
4603
+ policy: config.policy
4604
+ }, {
3836
4605
  cwd: typeof header?.cwd === "string" ? header.cwd : "",
3837
4606
  sessionHeader
3838
4607
  }, durabilityConfirmed, hostLock);
@@ -3854,7 +4623,9 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3854
4623
  projection.continuationAttempts = continuationAttempts;
3855
4624
  projection.persistenceCorrectionAttempts = persistenceCorrectionAttempts;
3856
4625
  projection.lastRecoveryDigest = priorRecoveryDigest;
4626
+ projection.durabilityWatermark = durabilityWatermark;
3857
4627
  protocolV4Present = derived.protocolV4Present;
4628
+ protocolV5Present = derived.boundaryV5;
3858
4629
  realRootInputSeen = derived.realRootInputSeen;
3859
4630
  lifecycle = lifecyclePhase({
3860
4631
  enabled: projection.enabled,
@@ -3881,6 +4652,8 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3881
4652
  };
3882
4653
  const setDurability = (confirmed) => {
3883
4654
  durabilityConfirmed = confirmed;
4655
+ durabilityWatermark = confirmed ? "confirmed" : "failed";
4656
+ projection.durabilityWatermark = durabilityWatermark;
3884
4657
  };
3885
4658
  const markRecoveryNeeded = () => {
3886
4659
  pendingRecovery = true;
@@ -3900,6 +4673,9 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3900
4673
  get protocolV4Present() {
3901
4674
  return protocolV4Present;
3902
4675
  },
4676
+ get protocolV5Present() {
4677
+ return protocolV5Present;
4678
+ },
3903
4679
  sync,
3904
4680
  setEnabled,
3905
4681
  setDurability,
@@ -3907,6 +4683,15 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3907
4683
  consumeRecovery
3908
4684
  };
3909
4685
  }
4686
+ /** The durable session working directory, when the V3 header carries one. */
4687
+ function sessionCwd(session) {
4688
+ const header = session.header;
4689
+ return typeof header?.cwd === "string" && header.cwd.length > 0 ? header.cwd : void 0;
4690
+ }
4691
+ /** A bounded signal for a short trusted readback performed by the gate. */
4692
+ function requestSignal() {
4693
+ return AbortSignal.timeout(5e3);
4694
+ }
3910
4695
  /** Delegated/subagent sessions never receive root-conversation injections. */
3911
4696
  function isDelegatedSession(session) {
3912
4697
  const header = session.header;
@@ -3942,9 +4727,9 @@ function revalidateCoreLock(config, expected) {
3942
4727
  };
3943
4728
  }
3944
4729
  }
3945
- function apply(ctx, rawConfig = {}) {
4730
+ function apply(ctx, rawConfig = {}, seams = {}) {
3946
4731
  const config = resolveConfig(rawConfig);
3947
- const installedHostLock = evaluateHostLock(config.hostLockPackages ?? [], {
4732
+ const installedHostLock = seams.hostLock ?? evaluateHostLock(config.hostLockPackages ?? [], {
3948
4733
  platform: config.hostLockPlatform,
3949
4734
  profileKind: config.hostLockProfile
3950
4735
  });
@@ -3956,7 +4741,7 @@ function apply(ctx, rawConfig = {}) {
3956
4741
  if (!runtime) {
3957
4742
  const goals = optionalGoalService(ctx, agent);
3958
4743
  const refreshHostLock = () => {
3959
- const current = bindLiveGoalCapability(revalidateCoreLock(config, installedHostLock), Boolean(goals) && hasPinnedUpdateGoalTool(agent));
4744
+ const current = bindLiveGoalCapability(seams.hostLock ?? revalidateCoreLock(config, installedHostLock), Boolean(goals) && hasPinnedUpdateGoalTool(agent));
3960
4745
  hostLocks.set(agent, current);
3961
4746
  return current;
3962
4747
  };
@@ -3995,8 +4780,41 @@ function apply(ctx, rawConfig = {}) {
3995
4780
  runtime.sync();
3996
4781
  return durable;
3997
4782
  }, () => runtime.markRecoveryNeeded()));
4783
+ /**
4784
+ * Persist one C10 release record through the plugin-notice channel and make
4785
+ * it durable before returning. A record that cannot be flushed is reported
4786
+ * as a failure: an unflushed reservation is not an in-flight operation, and
4787
+ * an unflushed settlement would let a consumed ticket look unused.
4788
+ */
4789
+ const persistReleaseRecord = async (toolAgent, prefix, payload) => {
4790
+ const target = toolAgent.session;
4791
+ target.append.bind(target)("user/message", createUserMessage({
4792
+ content: [{
4793
+ type: "text",
4794
+ text: `${prefix}${JSON.stringify(payload)}`
4795
+ }],
4796
+ source: {
4797
+ kind: "plugin",
4798
+ plugin: "context-guard",
4799
+ form: "notice",
4800
+ summary: boundContextSummary("recording an explicit release record")
4801
+ }
4802
+ }), { surfaceOp: "append" });
4803
+ let durable = false;
4804
+ try {
4805
+ durable = await ctx.sessions.flush(target);
4806
+ } catch {
4807
+ durable = false;
4808
+ }
4809
+ runtime.setDurability(durable);
4810
+ runtime.sync();
4811
+ return durable;
4812
+ };
3998
4813
  const evidenceOptions = {
3999
4814
  hostCapability: createHostCapabilityEvaluator(hostLocks.get(agent) ?? installedHostLock),
4815
+ ...seams.commandRunner ? { commandRunner: seams.commandRunner } : {},
4816
+ ...seams.fetcher ? { fetcher: seams.fetcher } : {},
4817
+ ...seams.allowLoopbackHttpRegistry ? { allowLoopbackHttpRegistry: true } : {},
4000
4818
  prepareMutation: async (toolAgent) => {
4001
4819
  if (toolAgent.session !== agent.session) return false;
4002
4820
  const durable = await ctx.sessions.flush(agent.session);
@@ -4009,6 +4827,63 @@ function apply(ctx, rawConfig = {}) {
4009
4827
  return authorizeMutationFromProjection(runtime.projection, request);
4010
4828
  },
4011
4829
  marketOrigin: optionalMarketOrigin(ctx, agent),
4830
+ releaseGate: async (request) => {
4831
+ runtime.sync();
4832
+ const projection = runtime.projection;
4833
+ if (!(projection.policy === "release" || projection.releaseContracts.length > 0)) return {
4834
+ status: "granted",
4835
+ reasonCode: "release_profile_not_adopted"
4836
+ };
4837
+ const observed = { ...request.observed };
4838
+ const declaredRef = releaseContractFor(projection, request.operation)?.candidate.ref;
4839
+ if (declaredRef !== void 0 && observed.ref === void 0) {
4840
+ const refSha = await resolveAuditedRef(sessionCwd(runtime.session), declaredRef, evidenceOptions, requestSignal());
4841
+ if (refSha !== void 0) {
4842
+ observed.ref = declaredRef;
4843
+ observed.refSha = refSha;
4844
+ }
4845
+ }
4846
+ const decision = releasePreEffectDecision(projection, {
4847
+ operation: request.operation,
4848
+ observed,
4849
+ resolvedTarget: request.resolvedTarget,
4850
+ nowEpochMs: Date.now()
4851
+ });
4852
+ if (decision.status !== "granted" || decision.contractId === void 0) return {
4853
+ status: "denied",
4854
+ reasonCode: decision.reasonCode
4855
+ };
4856
+ return await persistReleaseRecord(agent, RELEASE_RESERVATION_PREFIX, {
4857
+ contractId: decision.contractId,
4858
+ operation: request.operation,
4859
+ callId: request.callId,
4860
+ startedAtSeq: 0,
4861
+ status: "in_flight",
4862
+ ...observed.artifactSri !== void 0 ? { observedArtifactSri: observed.artifactSri } : {}
4863
+ }) ? {
4864
+ status: "granted",
4865
+ reasonCode: decision.reasonCode,
4866
+ contractId: decision.contractId
4867
+ } : {
4868
+ status: "denied",
4869
+ reasonCode: "release_reservation_not_durable"
4870
+ };
4871
+ },
4872
+ releaseSettle: async (request) => {
4873
+ const contractId = request.contractId ?? releaseContractFor(runtime.projection, request.operation)?.contractId ?? "unknown";
4874
+ const contract = runtime.projection.releaseContracts.find((entry) => entry.contractId === contractId);
4875
+ const reservation = reservationFor(runtime.projection, contractId, request.callId);
4876
+ let outcome = request.effect === "not_effected" ? "not_effected" : "unknown";
4877
+ if (request.effect === "completed" && contract) outcome = readbackSettlesContract(contract, request.readback, reservation?.observedArtifactSri) === "settled" ? "settled" : "unknown";
4878
+ await persistReleaseRecord(agent, RELEASE_SETTLEMENT_PREFIX, {
4879
+ contractId,
4880
+ operation: request.operation,
4881
+ callId: request.callId,
4882
+ settledAtSeq: 0,
4883
+ readback: request.readback,
4884
+ outcome
4885
+ });
4886
+ },
4012
4887
  persistRestartIntent: async (toolAgent, intent) => {
4013
4888
  const session = toolAgent.session;
4014
4889
  session.append.bind(session)("user/message", createUserMessage({
@@ -4044,7 +4919,26 @@ function apply(ctx, rawConfig = {}) {
4044
4919
  reasonCode: evaluation.reasonCode
4045
4920
  };
4046
4921
  },
4047
- commandTemplate: (action) => GIT_COMMAND_TEMPLATES[action]
4922
+ commandTemplate: (action) => GIT_COMMAND_TEMPLATES[action],
4923
+ refreshProjection: async () => {
4924
+ const durable = await ctx.sessions.flush(agent.session);
4925
+ runtime.setDurability(durable);
4926
+ runtime.sync();
4927
+ return durable;
4928
+ }
4929
+ }));
4930
+ agent.ctx.tools.register(createReleaseTool({
4931
+ getProjection: () => runtime.projection,
4932
+ fetcher: evidenceOptions.fetcher,
4933
+ ...evidenceOptions.allowLoopbackHttpRegistry ? { allowLoopbackHttpRegistry: true } : {},
4934
+ persistSettlement: async (request) => persistReleaseRecord(request.agent, RELEASE_SETTLEMENT_PREFIX, {
4935
+ contractId: request.contractId,
4936
+ operation: request.operation,
4937
+ callId: request.callId,
4938
+ settledAtSeq: 0,
4939
+ readback: request.readback,
4940
+ outcome: request.outcome
4941
+ })
4048
4942
  }));
4049
4943
  agent.ctx.tools.register(createExternalOperationTool((id, toolAgent) => readExternalOperation(ctx, toolAgent, id), () => evaluateExternalWaitCapability(hostLocks.get(agent) ?? installedHostLock)));
4050
4944
  agent.ctx.tools.guard((exec) => goalCompletionDenial(runtime.projection, exec.name, exec.arguments));
@@ -4058,10 +4952,11 @@ function apply(ctx, rawConfig = {}) {
4058
4952
  if (decision.kind !== "enter") return decision;
4059
4953
  const injected = [];
4060
4954
  const delegated = isDelegatedSession(agent.session);
4061
- let boundaryPending = !runtime.protocolV4Present;
4955
+ let boundaryPending = !runtime.protocolV5Present;
4062
4956
  const firstStep = previewFirstStepInjection({
4063
4957
  activation: config.activation,
4064
4958
  enabled: runtime.projection.enabled,
4959
+ boundaryV5Present: runtime.protocolV5Present,
4065
4960
  boundaryPresent: runtime.protocolV4Present,
4066
4961
  delegated
4067
4962
  }, claimedBatchHasRealRootInput(decision.messages));
@@ -4078,7 +4973,7 @@ function apply(ctx, rawConfig = {}) {
4078
4973
  if (recovery && digest$2 !== runtime.projection.lastRecoveryDigest) {
4079
4974
  runtime.projection.lastRecoveryDigest = digest$2;
4080
4975
  if (boundaryPending && !delegated) {
4081
- injected.push(pluginNoticeMessage(PROTOCOL_V4_NOTICE, "Context Guard recorded a replay version boundary"));
4976
+ injected.push(pluginNoticeMessage(PROTOCOL_V5_NOTICE, "Context Guard recorded a replay version boundary"));
4082
4977
  boundaryPending = false;
4083
4978
  }
4084
4979
  injected.push(createUserMessage({
@@ -4222,4 +5117,4 @@ function normalizeGoalState(value) {
4222
5117
  }
4223
5118
 
4224
5119
  //#endregion
4225
- export { ACTION_MANIFEST, ACTION_MANIFEST_VERSION, ACTIVE_HOST_COHORT_ID, ACTIVE_HOST_COHORT_IDS, ACTIVE_HOST_LAUNCHER_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, CONTROL_RECORD_PREFIX, 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, LATEST_SUPPORTED_HOST_VERSION, LEGACY_HOST_COHORTS, MIN_RECOVERY_CHAR_BUDGET, MIN_SUPPORTED_HOST_VERSION, NO_PROGRESS_RECORD_PREFIX, NO_PROGRESS_TURNS_BEFORE_STOP, PROOF_KINDS, PROOF_PROTOCOL_VERSION, PROTOCOL_V3_NOTICE, PROTOCOL_V4_NOTICE, RC015_HOST_PACKAGES, RC015_RC2_HOST_PACKAGES, RC1_HOST_PACKAGES, SEMANTIC_ACTIONS, SESSION_API_UNSUPPORTED, SESSION_EVENT_ENVELOPE_INVALID, STATEFUL_ACTIONS, STOP_PROTOCOL_VERSION, SUPPORTED_EVIDENCE_ADAPTERS, SUPPORTED_HOST_RANGE, SUPPORTED_HOST_VERSIONS, SessionApiError, actionCompatible, apply, authorityCaptureCounts, availableBoundaryQualifications, bindExecutableIdentity, bindLiveGoalCapability, bindProofToProjection, bindingSatisfies, canonicalArgvFromCommand, canonicalProjection, canonicalizePath, captureClause, captureItem, certifyCheckpoint, claimedBatchHasRealRootInput, classifyClause, classifyCompletionClaim, classifyTaskIntent, classifyUserInteraction, closingHint, combineHostPolicy, commitIndexSnapshotDigest, commitTreeSnapshotDigest, compareHostVersions, confirmRebind, createGitPrestateEnvelope, createProjection, createProofManifest, currentContractDigest, decideTurnBoundary, decideTurnStopping, decisionBoundaryKey, deriveItemDiagnosis, deriveProjection, digestStrings, effectuateBoundary, evaluateExternalWaitCapability, evaluateHostCapability, evaluateHostLock, evaluateMinimumHostVersion, evaluateToolSurfaceCapability, evidenceAvailabilityReason, evidenceCoverage, evidenceFromPersistedToolResult, evidenceMatchesItem, executeRevalidatedGitEffect, extractArtifactPaths, extractMethod, extractOperation, extractTextContent, extractToolSubject, gitCommandMatchesTarget, goalCompletionDenial, hasCurrentCertificate, hostLockContextFromComposedDump, hostLockRowsFromComposedDump, hostVersionFromPackages, inject, injectActiveProfileHostLock, inspectTargetHostGraph, interpretClause, interpretMessage, isCurrentAcceptedBoundary, isDeterministicCheck, isExecutableItem, isFrozenV042RebindResponse, isInformationalMessage, isOpenObligation, isRootPauseRequest, isRunExecutable, isStatefulAction, isVerifyingCapability, isWholeTaskCompletionClaim, itemDiagnosis, kindOfScope, latestAssistantText, latestRootInstruction, lifecyclePhase, maskCodeSpans, name, namedActions, normalizeClause, observeAssistantOutcome, openItems, packageRowsFromActiveGraph, packageRowsFromPnpmLock, parseConfirmationMessage, parseGitCommandManifest, parseHostVersion, parsePwshCommand, parseShellCommand, previewFirstStepInjection, progressFingerprint, proofDigest, proofEvidenceConstraints, proposeRebind, proposeRebindOutcome, proposeRebindV042, qualifyBoundary, readActiveHostGraph, rebindAttemptKey, rebindResponse, recoveryDigest, relevantEvidence, renderRecoveryPacket, replayRebindResult, requestedTargetAuthorizesMutation, requestedTargetMatchesResolved, resolveActiveProfileHostLock, resolveInstalledHostLock, revalidateGitPrestate, sanitizeClauseText, sanitizeUrl, satisfiesSupportedHostRange, segmentAuthorityBlocks, segmentClauses, selectHostCohort, semanticActionFromCommand, semanticActionFromText, semanticActionOfScope, sessionQuery, sha256, snapshotSessionEvents, statefulActionsOfScope, supersedeItem, validateActionManifest, validateActionTarget, validateManifest, validateProofManifest, verifiedLinearCommitReadback, verifyComposedHostLockDump, withDurability };
5120
+ export { ACTION_MANIFEST, ACTION_MANIFEST_VERSION, ACTIVE_HOST_COHORT_ID, ACTIVE_HOST_COHORT_IDS, ACTIVE_HOST_LAUNCHER_VERSION, ALPHA2_DSHMARKET_139_HOST_PACKAGES, ALPHA2_HOST_PACKAGES, ALPHA3_HOST_PACKAGES, BASE_HOST_PACKAGES, BOUNDED_ARTIFACT_TYPES, CAPTURE_V042_NOTICE, CERTIFICATE_VERSION, CERTIFICATE_VERSION_V2, COMMAND_SURFACE_MANIFEST, CONFIRM_LINE_PATTERN, CONTROL_RECORD_PREFIX, Config, DEFAULT_DELEGATION_TOOL_NAMES, 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, LATEST_SUPPORTED_HOST_VERSION, LEGACY_HOST_COHORTS, MIN_RECOVERY_CHAR_BUDGET, MIN_SUPPORTED_HOST_VERSION, NO_PROGRESS_RECORD_PREFIX, NO_PROGRESS_TURNS_BEFORE_STOP, PROOF_CAPABILITY_MATRIX, PROOF_KINDS, PROOF_KINDS_V2, PROOF_MANIFEST_DOMAIN_V2, PROOF_PROTOCOL_VERSION, PROOF_PROTOCOL_VERSION_V2, PROTOCOL_V3_NOTICE, PROTOCOL_V4_NOTICE, PROTOCOL_V5_NOTICE, RC015_HOST_PACKAGES, RC015_RC2_HOST_PACKAGES, RC1_HOST_PACKAGES, SEMANTIC_ACTIONS, SESSION_API_UNSUPPORTED, SESSION_EVENT_ENVELOPE_INVALID, STATEFUL_ACTIONS, STOP_PROTOCOL_VERSION, STOP_PROTOCOL_VERSION_V2, SUPPORTED_EVIDENCE_ADAPTERS, SUPPORTED_HOST_RANGE, SUPPORTED_HOST_VERSIONS, SessionApiError, actionCompatible, apply, authorityCaptureCounts, availableBoundaryQualifications, bindExecutableIdentity, bindLiveGoalCapability, bindProofToProjection, bindProofV2ToProjection, bindingSatisfies, boundedArtifactChoiceMatches, canonicalArgvFromCommand, canonicalProjection, canonicalizePath, captureClause, captureItem, certifyCheckpoint, claimedBatchHasRealRootInput, classifyClause, classifyCompletionClaim, classifyTaskIntent, classifyUserInteraction, closingHint, combineHostPolicy, commitIndexSnapshotDigest, commitTreeSnapshotDigest, compareHostVersions, confirmRebind, createGitPrestateEnvelope, createProjection, createProofManifest, createProofManifestV2, currentContractDigest, decideTurnBoundary, decideTurnStopping, decisionBoundaryKey, deriveItemDiagnosis, deriveProjection, digestStrings, effectuateBoundary, evaluateExternalWaitCapability, evaluateHostCapability, evaluateHostLock, evaluateMinimumHostVersion, evaluateToolSurfaceCapability, evidenceAvailabilityReason, evidenceCoverage, evidenceFromPersistedToolResult, evidenceMatchesItem, executeRevalidatedGitEffect, extractArtifactPaths, extractMethod, extractOperation, extractTextContent, extractToolSubject, gitCommandMatchesTarget, goalCompletionDenial, hasCurrentCertificate, hostLockContextFromComposedDump, hostLockRowsFromComposedDump, hostVersionFromPackages, inject, injectActiveProfileHostLock, inspectTargetHostGraph, interpretClause, interpretMessage, isCurrentAcceptedBoundary, isDeterministicCheck, isExecutableItem, isFrozenV042RebindResponse, isInformationalMessage, isOpenObligation, isRootPauseRequest, isRunExecutable, isStatefulAction, isVerifyingCapability, isWholeTaskCompletionClaim, itemDiagnosis, kindOfScope, latestAssistantText, latestRootInstruction, lifecyclePhase, maskCodeSpans, name, namedActions, normalizeClause, observeAssistantOutcome, openItems, packageRowsFromActiveGraph, packageRowsFromPnpmLock, parseConfirmationMessage, parseGitCommandManifest, parseHostVersion, parsePwshCommand, parseShellCommand, previewFirstStepInjection, progressFingerprint, proofCapabilityReport, proofDigest, proofDigestV2, proofEvidenceConstraints, proofHostSurfacesOf, proofOperationMatches, proofV2Rejection, proposeRebind, proposeRebindOutcome, proposeRebindV042, qualifyBoundary, readActiveHostGraph, rebindAttemptKey, rebindResponse, recoveryDigest, relevantEvidence, renderRecoveryPacket, replayRebindResult, requestedIdentityKey, requestedTargetAuthorizesMutation, requestedTargetMatchesResolved, requiredSubjectsOf, resolveActiveProfileHostLock, resolveInstalledHostLock, revalidateGitPrestate, sanitizeClauseText, sanitizeUrl, satisfiesSupportedHostRange, scopeCoverageDigest, segmentAuthorityBlocks, segmentClauses, selectHostCohort, semanticActionFromCommand, semanticActionFromText, semanticActionOfScope, sessionQuery, sessionQueryV2, sha256, snapshotSessionEvents, statefulActionsOfScope, supersedeItem, validateActionManifest, validateActionTarget, validateManifest, validateProofManifest, validateProofManifestV2, verifiedLinearCommitReadback, verifyComposedHostLockDump, withDurability };