dsh-completion-guard 0.5.3 → 0.6.1

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 extractToolSubject, $n as isFrozenV042RebindResponse, $r as sha256, $t as proofDigest, A as lifecyclePhase, An as decisionBoundaryKey, Ar as STATEFUL_ACTIONS, At as compareHostVersions, B as RELEASE_RESERVATION_PREFIX, Bn as effectuateBoundary, Br as semanticActionFromCommand, Bt as certifyCheckpoint, C as gitCommandMatchesTarget, Cn as isVerifyingCapability, Cr as npmEscapedPackageName, Ct as evaluateToolSurfaceCapability, D as FIRST_STEP_GUIDANCE, Dn as classifyCompletionClaim, Dr as CERTIFICATE_VERSION, Dt as MIN_SUPPORTED_HOST_VERSION, E as verifiedLinearCommitReadback, En as NO_PROGRESS_TURNS_BEFORE_STOP, Er as BOUNDED_ARTIFACT_TYPES, Et as LATEST_SUPPORTED_HOST_VERSION, F as PROTOCOL_V4_NOTICE, Fn as observeAssistantOutcome, Fr as boundedArtifactChoiceMatches, Ft as RC015_HOST_PACKAGES, G as readbackSettlesContract, Gn as confirmRebind, Gr as validateManifest, Gt as PROOF_PROTOCOL_VERSION, H as contractById, Hn as qualifyBoundary, Hr as validateActionManifest, Ht as PROOF_KINDS, I as PROTOCOL_V5_NOTICE, In as progressFingerprint, Ir as isStatefulAction, It as RC1_HOST_PACKAGES, J as releasePreEffectDecision, Jn as proposeRebindV042, Jr as canonicalizePath, Jt as bindProofV2ToProjection, K as releaseContractFor, Kn as proposeRebind, Kr as classifyTaskIntent, Kt as PROOF_PROTOCOL_VERSION_V2, L as deriveProjection, Ln as goalCompletionDenial, Lr as requestedIdentityKey, Lt as ALPHA3_HOST_PACKAGES, M as CAPTURE_V042_NOTICE, Mn as isWholeTaskCompletionClaim, Mr as STOP_PROTOCOL_VERSION_V2, Mt as parseHostVersion, N as DEFAULT_DELEGATION_TOOL_NAMES, Nn as latestAssistantText, Nr as SUPPORTED_EVIDENCE_ADAPTERS, Nt as satisfiesSupportedHostRange, O as claimedBatchHasRealRootInput, On as decideTurnBoundary, Or as CERTIFICATE_VERSION_V2, Ot as SUPPORTED_HOST_RANGE, P as PROTOCOL_V3_NOTICE, Pn as latestRootInstruction, Pr as actionCompatible, Pt as RC015_RC2_HOST_PACKAGES, Q as extractTextContent, Qn as CONFIRM_LINE_PATTERN, Qr as sanitizeUrl, Qt as proofCapabilityReport, R as RELEASE_OPERATIONS, Rn as hasCurrentCertificate, Rr as requestedTargetAuthorizesMutation, Rt as authorityCaptureCounts, S as executeRevalidatedGitEffect, Sn as evidenceMatchesItem, Sr as canonicalRegistryBase, St as evaluateHostLock, T as revalidateGitPrestate, Tn as NO_PROGRESS_RECORD_PREFIX, Tr as ACTION_MANIFEST_VERSION, Tt as selectHostCohort, U as inFlightReservation, Un as currentContractDigest, Ur as validateActionTarget, Ut as PROOF_KINDS_V2, V as RELEASE_SETTLEMENT_PREFIX, Vn as isCurrentAcceptedBoundary, Vr as semanticActionFromText, Vt as PROOF_CAPABILITY_MATRIX, W as normalizeReleaseContract, Wn as createProjection, Wr as COMMAND_SURFACE_MANIFEST, Wt as PROOF_MANIFEST_DOMAIN_V2, X as supersedeItem, Xn as rebindResponse, Xr as normalizeClause, Xt as createProofManifest, Y as reservationFor, Yn as rebindAttemptKey, Yr as digestStrings, Yt as canonicalProjection, Z as evidenceFromPersistedToolResult, Zn as replayRebindResult, Zr as sanitizeClauseText, Zt as createProofManifestV2, _ as GIT_COMMAND_MANIFEST_IDS, _n as openItems, _r as kindOfScope, _t as LEGACY_HOST_COHORTS, a as injectActiveProfileHostLock, an as requiredSubjectsOf, ar as captureClause, at as parseShellCommand, b as commitTreeSnapshotDigest, bn as bindingSatisfies, br as semanticActionOfScope, bt as evaluateExternalWaitCapability, c as packageRowsFromPnpmLock, cn as sessionQueryV2, cr as extractArtifactPaths, ct as ACTIVE_HOST_LAUNCHER_VERSION, d as resolveInstalledHostLock, dn as certifiableOpenItems, dr as isInformationalMessage, dt as BASE_HOST_PACKAGES, en as proofDigestV2, er as parseConfirmationMessage, et as isDeterministicCheck, f as verifyComposedHostLockDump, fn as certificateClosure, fr as segmentClauses, ft as DEFAULT_HOST_LOCK, g as snapshotSessionEvents, gn as closingHint, gr as isOpenObligation, gt as HOST_COHORTS, h as SessionApiError, hn as MIN_RECOVERY_CHAR_BUDGET, hr as isExecutableItem, ht as HOST_CAPABILITY_PACKAGE_GROUPS, i as hostLockRowsFromComposedDump, in as proofV2Rejection, ir as relevantEvidence, it as parsePwshCommand, j as previewFirstStepInjection, jn as isRootPauseRequest, jr as STOP_PROTOCOL_VERSION, jt as evaluateMinimumHostVersion, k as firstStepGuidance, kn as decideTurnStopping, kr as SEMANTIC_ACTIONS, kt as SUPPORTED_HOST_VERSIONS, l as readActiveHostGraph, ln as validateProofManifest, lr as extractMethod, lt as ALPHA2_DSHMARKET_139_HOST_PACKAGES, m as SESSION_EVENT_ENVELOPE_INVALID, mn as DEFAULT_RECOVERY_CHAR_BUDGET, mr as interpretMessage, mt as GOAL_HOST_PACKAGES, n as combineHostPolicy, nn as proofHostSurfacesOf, nr as evidenceAvailabilityReason, nt as canonicalArgvFromCommand, o as inspectTargetHostGraph, on as scopeCoverageDigest, or as captureItem, ot as ACTIVE_HOST_COHORT_ID, p as SESSION_API_UNSUPPORTED, pn as unitDescendantIds, pr as interpretClause, pt as EXPECTED_HOST_PACKAGES, q as releaseCoverage, qn as proposeRebindOutcome, qr as classifyUserInteraction, qt as bindProofToProjection, r as hostLockContextFromComposedDump, rn as proofOperationMatches, rr as itemDiagnosis, rt as isRunExecutable, s as packageRowsFromActiveGraph, sn as sessionQuery, sr as classifyClause, st as ACTIVE_HOST_COHORT_IDS, t as HostProfileError, tn as proofEvidenceConstraints, tr as deriveItemDiagnosis, tt as withDurability, u as resolveActiveProfileHostLock, un as validateProofManifestV2, ur as extractOperation, ut as ALPHA2_HOST_PACKAGES, v as GIT_COMMAND_TEMPLATES, vn as recoveryDigest, vr as maskCodeSpans, vt as bindExecutableIdentity, w as parseGitCommandManifest, wn as CONTROL_RECORD_PREFIX, wr as ACTION_MANIFEST, wt as hostVersionFromPackages, x as createGitPrestateEnvelope, xn as evidenceCoverage, xr as statefulActionsOfScope, xt as evaluateHostCapability, y as commitIndexSnapshotDigest, yn as renderRecoveryPacket, yr as namedActions, yt as bindLiveGoalCapability, z as RELEASE_OPERATION_SURFACES, zn as availableBoundaryQualifications, zr as requestedTargetMatchesResolved, zt as segmentAuthorityBlocks } from "./domain-DKr8sLZZ.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
  });
@@ -849,59 +912,234 @@ function createBoundaryTool(getProjection, prepare, onRejected) {
849
912
  }
850
913
 
851
914
  //#endregion
852
- //#region src/tools/git-preparation.ts
853
- /** Input recipes are guidance only; live resolution remains authoritative. */
854
- function gitPreparation(action) {
855
- if (![
856
- "commit",
857
- "push",
858
- "fetch",
859
- "pull"
860
- ].includes(action)) return void 0;
861
- return {
862
- selector_fields: action === "commit" ? ["repository", "branch"] : [
863
- "repository",
864
- "remote",
865
- "refspec"
866
- ],
867
- command_manifest_fields: ["planned_tool", "planned_arguments"],
868
- planned_tools: ["bash", "pwsh"],
869
- planned_argument_fields: ["command", "workdir"],
870
- steps: [
871
- "Before execution: context_guard_evidence with evidence_role=resolution, selector and command_manifest. Keep the successful tool call ID and target_digest.",
872
- "Execute once with context_guard_action, semantic_action, resolution_call_id, target_digest, contract_item_id and contract_item_revision.",
873
- "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.",
874
- "Checkpoint the matching resolution/effect/state evidence IDs. Call IDs identify tool events; evidence IDs identify checkpoint facts.",
875
- "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."
876
- ]
877
- };
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
+ }
878
1098
  }
879
1099
 
880
1100
  //#endregion
881
1101
  //#region src/tools/prepare.ts
1102
+ /** Discovery pages stay bounded like checkpoint pages. */
1103
+ const DISCOVERY_ITEM_LIMIT = 8;
1104
+ const encodeCursor = (cursor) => Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
882
1105
  /**
883
- * Thin READ-ONLY preparation surface (v0.5): before any stateful action it
1106
+ * The items discovery lists for the CURRENT work: under a v5 boundary that is
1107
+ * the current unit's closure plus its required descendants (the same scope a
1108
+ * v2 certificate answers for, prohibitions included because a standing
1109
+ * constraint is never finished work) plus every pre-v5 obligation, which keeps
1110
+ * their birth rules and must never be silently dropped. A legacy session lists
1111
+ * the whole session. Switched-away sibling units keep their own history and
1112
+ * are reached by item ID, not re-listed here (0.6.1, W060-03).
1113
+ */
1114
+ function discoveryItemIds(p) {
1115
+ const pending = [...p.items.values()].filter((item) => item.status === "pending").sort((a, b) => a.revision - b.revision || a.id.localeCompare(b.id));
1116
+ if (p.boundaryProtocol !== 5) return pending.map((item) => item.id);
1117
+ const closureUnits = new Set([...p.currentUnitId !== void 0 ? [p.currentUnitId, ...unitDescendantIds(p, p.currentUnitId)] : []]);
1118
+ return pending.filter((item) => item.unitId === void 0 || item.unitId !== void 0 && closureUnits.has(item.unitId)).map((item) => item.id);
1119
+ }
1120
+ /**
1121
+ * Thin READ-ONLY preparation surface (v0.5/0.6): before any stateful action it
884
1122
  * reports the supported command shape, the required resolution/effect/state
885
1123
  * evidence order, existing reusable references, and the exact missing fields.
886
1124
  * It never executes, installs, commits, pushes, restarts, or probes authority
887
1125
  * through side effects, and it never upgrades a default into user authority.
1126
+ * Without an `item_id` it returns the bounded current-item discovery list, so
1127
+ * the first step of a session can find the right ID instead of guessing one.
888
1128
  */
889
1129
  function createPrepareTool(options) {
890
1130
  return defineTool({
891
1131
  name: "context_guard_prepare",
892
- 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.",
1132
+ description: "Read-only pre-action preparation: report the supported command shape, required evidence order, existing references, and exact missing target fields for one contract item. Omit item_id to list current open items with their IDs; a stateful change needs resolution/effect/state, a read-only verification needs one matching fact. Pagination follows page_cursor (bound to the contract revision) and semantic_action filters the listing. Performs no action and grants no authority.",
893
1133
  parameters: {
894
- item_id: {
895
- type: "string",
896
- required: true
897
- },
1134
+ item_id: { type: "string" },
898
1135
  item_revision: { type: "number" },
899
1136
  semantic_action: { type: "string" },
900
1137
  requested_target: {
901
1138
  type: "object",
902
1139
  additionalProperties: true
903
1140
  },
904
- planned_operation: { type: "string" }
1141
+ planned_operation: { type: "string" },
1142
+ page_cursor: { type: "string" }
905
1143
  },
906
1144
  output: {
907
1145
  schema: {
@@ -915,11 +1153,83 @@ function createPrepareTool(options) {
915
1153
  },
916
1154
  async execute(rawArgs) {
917
1155
  const args = rawArgs;
1156
+ if (options.refreshProjection) {
1157
+ let durable = false;
1158
+ try {
1159
+ durable = await options.refreshProjection() === true;
1160
+ } catch {
1161
+ durable = false;
1162
+ }
1163
+ if (!durable) return {
1164
+ status: "unknown",
1165
+ reason_code: "projection_durability_unavailable"
1166
+ };
1167
+ }
918
1168
  const p = options.getProjection();
919
1169
  if (!p || !p.enabled || p.integrity !== "valid") return {
920
1170
  status: "unknown",
921
1171
  reason_code: "guard_unavailable"
922
1172
  };
1173
+ if (args.item_id === void 0) {
1174
+ const filter = args.semantic_action ?? null;
1175
+ const invalidCursor = (reason_code, note) => ({
1176
+ status: "rejected",
1177
+ reason_code,
1178
+ mode: "discovery",
1179
+ contract_revision: p.contractRevision,
1180
+ ...note !== void 0 ? { note } : { note: "Re-run discovery without page_cursor to list from the first page." }
1181
+ });
1182
+ let startAfter;
1183
+ if (args.page_cursor !== void 0) {
1184
+ let parsed;
1185
+ try {
1186
+ if (args.page_cursor.length <= 1024) {
1187
+ const value = JSON.parse(Buffer.from(args.page_cursor, "base64url").toString("utf8"));
1188
+ if (value?.v === 1 && Number.isSafeInteger(value.r) && (value.f === null || typeof value.f === "string") && Array.isArray(value.k) && Number.isSafeInteger(value.k[0]) && typeof value.k[1] === "string") parsed = value;
1189
+ }
1190
+ } catch {
1191
+ parsed = void 0;
1192
+ }
1193
+ if (!parsed) return invalidCursor("discovery_cursor_malformed");
1194
+ if (parsed.r !== p.contractRevision) return invalidCursor("discovery_cursor_stale", `The contract changed (cursor revision ${parsed.r}, current ${p.contractRevision}). Re-run discovery without page_cursor; items are never skipped by a stale page.`);
1195
+ if ((parsed.f ?? null) !== (filter ?? null)) return invalidCursor("discovery_cursor_filter_mismatch");
1196
+ startAfter = parsed.k;
1197
+ }
1198
+ const eligible = discoveryItemIds(p).map((id) => p.items.get(id)).filter((item$1) => filter === null || (item$1.semanticAction ?? "generic_run") === filter);
1199
+ const startIndex = startAfter === void 0 ? 0 : eligible.findIndex((item$1) => item$1.revision === startAfter[0] && item$1.id === startAfter[1]) + 1;
1200
+ if (startAfter !== void 0 && startIndex <= 0) return invalidCursor("discovery_cursor_stale", "The cursor names an item no longer in the current listing. Re-run discovery without page_cursor.");
1201
+ const page = eligible.slice(startIndex, startIndex + DISCOVERY_ITEM_LIMIT);
1202
+ const hasMore = startIndex + page.length < eligible.length;
1203
+ return {
1204
+ status: "prepared",
1205
+ mode: "discovery",
1206
+ durability: p.durabilityWatermark,
1207
+ contract_revision: p.contractRevision,
1208
+ total_open: eligible.length,
1209
+ listed: page.length,
1210
+ has_more: hasMore,
1211
+ items: page.map((item$1) => {
1212
+ const diagnosis$1 = deriveItemDiagnosis(p, item$1);
1213
+ return {
1214
+ id: item$1.id,
1215
+ revision: item$1.revision,
1216
+ kind: item$1.kind,
1217
+ ...item$1.taskKind !== void 0 ? { task_kind: item$1.taskKind } : {},
1218
+ semantic_action: item$1.semanticAction ?? "generic_run",
1219
+ reason_code: diagnosis$1.reason_code,
1220
+ text: item$1.normalizedText
1221
+ };
1222
+ }),
1223
+ ...hasMore ? { next_cursor: encodeCursor({
1224
+ v: 1,
1225
+ r: p.contractRevision,
1226
+ f: filter,
1227
+ k: [page[page.length - 1].revision, page[page.length - 1].id]
1228
+ }) } : {},
1229
+ ...filter !== null ? { filtered_by: { semantic_action: filter } } : {},
1230
+ note: "Re-run with one item_id to prepare that item, or pass page_cursor for the next page. Preparation performs no action."
1231
+ };
1232
+ }
923
1233
  const item = p.items.get(args.item_id ?? "");
924
1234
  if (!item) return {
925
1235
  status: "rejected",
@@ -944,13 +1254,13 @@ function createPrepareTool(options) {
944
1254
  resolved_target: evidence.resolvedTarget,
945
1255
  tool_result_seq: evidence.toolResultSeq
946
1256
  }));
947
- const requiredOrder = manifestEntry?.stateful ? [
1257
+ const requiredOrder = plannedAction && isStatefulAction(plannedAction) ? [
948
1258
  "resolution (prestate facts from a trusted read)",
949
1259
  "effect (the exact planned change)",
950
1260
  "state (independent post-state readback)"
951
- ] : ["state (matching durable evidence for the requested verification)"];
952
- const recipe = plannedAction ? gitPreparation(plannedAction) : void 0;
953
- const missingTargetFields = manifestEntry?.stateful && plannedAction ? (recipe?.selector_fields ?? manifestEntry.resolvedTargetKeys).filter((key) => !(item.requestedTarget?.[key] !== void 0 || args.requested_target?.[key] !== void 0)) : [];
1261
+ ] : ["effect (one matching durable verification fact)"];
1262
+ const recipe = plannedAction && isStatefulAction(plannedAction) ? actionPreparation(plannedAction) : void 0;
1263
+ const missingTargetFields = recipe ? recipe.selector_fields.filter((key) => !(item.requestedTarget?.[key] !== void 0 || args.requested_target?.[key] !== void 0)) : [];
954
1264
  const capability = plannedAction && options.hostCapability ? options.hostCapability(plannedAction) : void 0;
955
1265
  const commandShape = options.commandTemplate && plannedAction ? options.commandTemplate(plannedAction) : void 0;
956
1266
  return {
@@ -969,13 +1279,163 @@ function createPrepareTool(options) {
969
1279
  status: capability.status,
970
1280
  ...capability.reasonCode !== void 0 ? { reason_code: capability.reasonCode } : {}
971
1281
  } } : {},
972
- ...recipe ? { evidence_input_contract: recipe } : {},
1282
+ ...recipe ? { evidence_input_contract: {
1283
+ selector_fields: recipe.selector_fields,
1284
+ optional_selector_fields: recipe.optional_selector_fields,
1285
+ command_manifest_fields: recipe.command_manifest_fields,
1286
+ command_manifest_ids: recipe.command_manifest_ids,
1287
+ planned_tools: recipe.planned_tools,
1288
+ planned_argument_fields: recipe.planned_argument_fields,
1289
+ producer_fields: recipe.producer_fields,
1290
+ readback_fields: recipe.readback_fields,
1291
+ execution_surface: recipe.execution_surface,
1292
+ steps: recipe.steps
1293
+ } } : {},
973
1294
  note: "Preparation performs no action. A default or guessed target is not user authority; explicit root instruction is required for missing target fields."
974
1295
  };
975
1296
  }
976
1297
  });
977
1298
  }
978
1299
 
1300
+ //#endregion
1301
+ //#region src/tools/interpret.ts
1302
+ function parsePartition(args, extent) {
1303
+ const read = (key) => {
1304
+ const raw = args[key];
1305
+ if (raw === void 0) return void 0;
1306
+ if (!Array.isArray(raw)) return void 0;
1307
+ const spans = [];
1308
+ for (const entry of raw) {
1309
+ const record$1 = entry;
1310
+ const start = record$1?.start;
1311
+ const end = record$1?.end;
1312
+ if (typeof start !== "number" || !Number.isSafeInteger(start) || typeof end !== "number" || !Number.isSafeInteger(end) || start >= end) return void 0;
1313
+ spans.push({
1314
+ start,
1315
+ end
1316
+ });
1317
+ }
1318
+ return spans;
1319
+ };
1320
+ const information = read("information_spans");
1321
+ const unknown = read("unknown_spans");
1322
+ if (information === void 0 || unknown === void 0) return { error: "interpretation_partition_required" };
1323
+ if (information.length === 0) return { error: "interpretation_partition_required" };
1324
+ const all = [...information, ...unknown];
1325
+ for (const span of all) if (span.start < extent.start || span.end > extent.end) return { error: "interpretation_span_outside_input" };
1326
+ const ordered = [...all].sort((left, right) => left.start - right.start || left.end - right.end);
1327
+ for (let index = 1; index < ordered.length; index += 1) if (ordered[index].start < ordered[index - 1].end) return { error: "interpretation_spans_overlap" };
1328
+ return {
1329
+ information,
1330
+ unknown
1331
+ };
1332
+ }
1333
+ /** The byte extent that covers every source span of one item. */
1334
+ function itemExtent(spans) {
1335
+ return {
1336
+ start: Math.min(...spans.map((span) => span.start)),
1337
+ end: Math.max(...spans.map((span) => span.end))
1338
+ };
1339
+ }
1340
+ function createInterpretTool(options) {
1341
+ return defineTool({
1342
+ name: "context_guard_interpret",
1343
+ description: "Record the interpretation of one unresolved obligation: pass the item ID from context_guard_prepare plus the partition of its input spans you read as information (information_spans) and the sub-spans you could not resolve (unknown_spans); for an attached asset, the partition is unnecessary. Contract bookkeeping only — performs no action, grants no authority. Information sub-spans close when this turn's host-confirmed final answer is delivered; unknown sub-spans remain pending obligations. Correctness is never certified.",
1344
+ parameters: {
1345
+ item_id: {
1346
+ type: "string",
1347
+ required: true
1348
+ },
1349
+ information_spans: {
1350
+ type: "array",
1351
+ items: {
1352
+ type: "object",
1353
+ additionalProperties: true
1354
+ }
1355
+ },
1356
+ unknown_spans: {
1357
+ type: "array",
1358
+ items: {
1359
+ type: "object",
1360
+ additionalProperties: true
1361
+ }
1362
+ }
1363
+ },
1364
+ output: {
1365
+ schema: {
1366
+ type: "object",
1367
+ additionalProperties: true
1368
+ },
1369
+ render: (_args, value) => [{
1370
+ type: "text",
1371
+ text: JSON.stringify(value)
1372
+ }]
1373
+ },
1374
+ async execute(rawArgs) {
1375
+ const args = rawArgs;
1376
+ const reject = (reason_code) => ({
1377
+ status: "rejected",
1378
+ reason_code
1379
+ });
1380
+ if (options.refreshProjection) {
1381
+ let durable = false;
1382
+ try {
1383
+ durable = await options.refreshProjection() === true;
1384
+ } catch {
1385
+ durable = false;
1386
+ }
1387
+ if (!durable) return {
1388
+ status: "unknown",
1389
+ reason_code: "projection_durability_unavailable"
1390
+ };
1391
+ }
1392
+ const p = options.getProjection();
1393
+ if (!p || !p.enabled || p.integrity !== "valid") return reject("guard_unavailable");
1394
+ const itemId = typeof args.item_id === "string" ? args.item_id.trim() : "";
1395
+ if (!itemId) return reject("item_not_found");
1396
+ const item = p.items.get(itemId);
1397
+ if (!item) return reject("item_not_found");
1398
+ if (item.status !== "pending") return reject("item_not_pending");
1399
+ if (!item.asset && item.authorityDisposition !== "unresolved") return reject("not_interpretable");
1400
+ if (item.asset) return {
1401
+ status: "recorded",
1402
+ item_id: item.id,
1403
+ item_revision: item.revision,
1404
+ kind: "asset",
1405
+ asset: {
1406
+ message_seq: item.asset.messageSeq,
1407
+ part_index: item.asset.partIndex,
1408
+ media_sha256: item.asset.mediaSha256
1409
+ },
1410
+ note: "Interpretation recorded for this asset obligation. It closes when the host-confirmed final answer of this interpreting turn is delivered; this record never certifies the interpretation's correctness and never authorizes or proves any execution."
1411
+ };
1412
+ if (!item.spans || item.spans.length === 0) return reject("item_spans_unavailable");
1413
+ const partition = parsePartition(args, itemExtent(item.spans));
1414
+ if ("error" in partition) return reject(partition.error);
1415
+ return {
1416
+ status: "recorded",
1417
+ item_id: item.id,
1418
+ item_revision: item.revision,
1419
+ kind: "clause",
1420
+ spans: item.spans.map((span) => ({
1421
+ part_index: span.partIndex,
1422
+ start: span.start,
1423
+ end: span.end
1424
+ })),
1425
+ information_spans: partition.information.map((span) => ({
1426
+ start: span.start,
1427
+ end: span.end
1428
+ })),
1429
+ unknown_spans: partition.unknown.map((span) => ({
1430
+ start: span.start,
1431
+ end: span.end
1432
+ })),
1433
+ note: "Interpretation partition recorded for this unresolved clause: information sub-spans close when the host-confirmed final answer of this interpreting turn is delivered; every unknown sub-span remains a pending obligation and must still be resolved or performed. This record never certifies correctness and never authorizes any execution."
1434
+ };
1435
+ }
1436
+ });
1437
+ }
1438
+
979
1439
  //#endregion
980
1440
  //#region src/tools/evidence.ts
981
1441
  const execFileAsync = promisify(execFile);
@@ -1315,12 +1775,63 @@ async function tgzIdentity(path) {
1315
1775
  offset = bodyStart + Math.ceil(size$1 / 512) * 512;
1316
1776
  }
1317
1777
  if (typeof manifest?.name !== "string" || typeof manifest.version !== "string") return void 0;
1778
+ const gitHead = typeof manifest.gitHead === "string" && /^[0-9a-f]{40}$/.test(manifest.gitHead) ? manifest.gitHead : void 0;
1779
+ const repositoryField = manifest.repository;
1780
+ const repository = typeof repositoryField === "string" ? repositoryField : record(repositoryField) && typeof record(repositoryField)?.url === "string" ? String(record(repositoryField).url) : void 0;
1318
1781
  return {
1319
1782
  name: manifest.name,
1320
1783
  version: manifest.version,
1321
- integrity: `sha512-${createHash("sha512").update(bytes).digest("base64")}`
1784
+ integrity: `sha512-${createHash("sha512").update(bytes).digest("base64")}`,
1785
+ sha256: createHash("sha256").update(bytes).digest("hex"),
1786
+ ...gitHead ? { gitHead } : {},
1787
+ ...repository ? { repository } : {}
1788
+ };
1789
+ }
1790
+ /**
1791
+ * Read the candidate identity of a publish from TRUSTED PRODUCERS only: the
1792
+ * exact tgz bytes (SHA-256 and npm SRI), the manifest the packer embedded
1793
+ * (package name, version, gitHead, repository) and, when the contract names a
1794
+ * ref, the local repository's own answer for that ref. Nothing here comes from
1795
+ * a model argument, so a caller cannot assert an identity the artifact does
1796
+ * not have.
1797
+ */
1798
+ async function observePublishCandidate(resolution, roots, signal, cwd) {
1799
+ const tgzPath = requireString(resolution.commandManifest, "tgz_path");
1800
+ if (!tgzPath) return void 0;
1801
+ const identity = await tgzIdentity(tgzPath);
1802
+ if (!identity) return void 0;
1803
+ const registry = typeof resolution.target.registry === "string" ? resolution.target.registry : void 0;
1804
+ return {
1805
+ packageId: identity.name,
1806
+ version: identity.version,
1807
+ artifactSha256: identity.sha256,
1808
+ artifactSri: identity.integrity,
1809
+ ...identity.gitHead ? { fullSha40: identity.gitHead } : {},
1810
+ ...identity.repository ? { repository: identity.repository } : {},
1811
+ ...registry ? { registry } : {}
1322
1812
  };
1323
1813
  }
1814
+ /**
1815
+ * Resolve one ref to its commit with the audited git executable. Exported so
1816
+ * the runtime can resolve the ref an ADOPTED CONTRACT names, which is the only
1817
+ * closed, reachable path for that identity: the action tool's manifest schema
1818
+ * has no field for it, and a model-supplied ref would not be authority.
1819
+ */
1820
+ async function resolveAuditedRef(cwd, ref, roots, signal) {
1821
+ if (!cwd || !/^refs\/[A-Za-z0-9._/-]+$/.test(ref)) return void 0;
1822
+ try {
1823
+ const identity = await (roots.readExecutableIdentity ?? executableIdentity)("git", signal);
1824
+ if (!identity || identity.executable !== "git") return void 0;
1825
+ const resolved = (await git(cwd, [
1826
+ "rev-parse",
1827
+ "--verify",
1828
+ `${ref}^{commit}`
1829
+ ], signal, identity.realpath)).trim().toLowerCase();
1830
+ return /^[0-9a-f]{40}$/.test(resolved) ? resolved : void 0;
1831
+ } catch {
1832
+ return;
1833
+ }
1834
+ }
1324
1835
  const MARKET_SCHEMA = "dsh-market/update-api/v1";
1325
1836
  const MARKET_GENERATION_PREFIX = "dsh-market-instance/v1:";
1326
1837
  async function marketCapabilities(roots, signal) {
@@ -1518,7 +2029,7 @@ async function executeGuardAction(action, resolution, roots, signal, executableI
1518
2029
  if (action === "install" || action === "apply") {
1519
2030
  const tgzPath = requireString(resolution.commandManifest, "tgz_path");
1520
2031
  const identity = tgzPath ? await tgzIdentity(tgzPath) : void 0;
1521
- if (!executableIdentity$1 || !tgzPath || !identity || identity.name !== target.package_id || identity.version !== target.version || identity.integrity !== target.integrity_digest || roots.profile?.name !== target.profile) return "unavailable";
2032
+ if (!executableIdentity$1 || !tgzPath || !identity || identity.name !== target.package_id || identity.version !== target.version || identity.integrity !== target.integrity_digest || roots.profile?.name !== target.profile) return { status: "unavailable" };
1522
2033
  await runCommand(roots, executableIdentity$1, [
1523
2034
  "plugin",
1524
2035
  "--profile",
@@ -1526,13 +2037,13 @@ async function executeGuardAction(action, resolution, roots, signal, executableI
1526
2037
  "add",
1527
2038
  `file:${tgzPath}`
1528
2039
  ], void 0, signal);
1529
- return "completed";
2040
+ return { status: "completed" };
1530
2041
  }
1531
2042
  if (action === "publish") {
1532
2043
  const tgzPath = requireString(resolution.commandManifest, "tgz_path");
1533
2044
  const identity = tgzPath ? await tgzIdentity(tgzPath) : void 0;
1534
2045
  const registry = typeof target.registry === "string" ? canonicalRegistryBase(target.registry, { allowLoopbackHttp: roots.allowLoopbackHttpRegistry }) : void 0;
1535
- if (!executableIdentity$1 || !tgzPath || !identity || identity.name !== target.artifact_id || identity.version !== target.version || identity.integrity !== target.integrity_digest || !registry || registry !== target.registry) return "unavailable";
2046
+ if (!executableIdentity$1 || !tgzPath || !identity || identity.name !== target.artifact_id || identity.version !== target.version || identity.integrity !== target.integrity_digest || !registry || registry !== target.registry) return { status: "unavailable" };
1536
2047
  await runCommand(roots, executableIdentity$1, [
1537
2048
  "publish",
1538
2049
  tgzPath,
@@ -1540,19 +2051,19 @@ async function executeGuardAction(action, resolution, roots, signal, executableI
1540
2051
  registry,
1541
2052
  "--ignore-scripts"
1542
2053
  ], void 0, signal);
1543
- return "completed";
2054
+ return { status: "completed" };
1544
2055
  }
1545
2056
  if (action === "restart") {
1546
2057
  const capabilities = await marketCapabilities(roots, signal);
1547
- if (!capabilities || !roots.marketOrigin || !resolutionCallId || !agent) return "unavailable";
1548
- if (!sameMarketProvider(target.pre_generation, capabilities.generation)) return "unavailable";
1549
- if (capabilities.generation !== target.pre_generation) return restartIntent(snapshotSessionEvents(agent.session), resolutionCallId, target) ? "completed" : "unavailable";
1550
- if (restartIntent(snapshotSessionEvents(agent.session), resolutionCallId, target)) return "handoff_pending";
2058
+ if (!capabilities || !roots.marketOrigin || !resolutionCallId || !agent) return { status: "unavailable" };
2059
+ if (!sameMarketProvider(target.pre_generation, capabilities.generation)) return { status: "unavailable" };
2060
+ if (capabilities.generation !== target.pre_generation) return restartIntent(snapshotSessionEvents(agent.session), resolutionCallId, target) ? { status: "completed" } : { status: "unavailable" };
2061
+ if (restartIntent(snapshotSessionEvents(agent.session), resolutionCallId, target)) return { status: "handoff_pending" };
1551
2062
  if (!roots.persistRestartIntent || !await roots.persistRestartIntent(agent, {
1552
2063
  resolutionCallId,
1553
2064
  serviceId: String(target.service_id),
1554
2065
  preGeneration: String(target.pre_generation)
1555
- })) return "unavailable";
2066
+ })) return { status: "unavailable" };
1556
2067
  const response = await (roots.fetcher ?? fetch)(`${roots.marketOrigin}/dsh-market/api/v1/restart`, {
1557
2068
  method: "POST",
1558
2069
  signal,
@@ -1564,21 +2075,25 @@ async function executeGuardAction(action, resolution, roots, signal, executableI
1564
2075
  },
1565
2076
  body: "{}"
1566
2077
  });
1567
- if (!response.ok) return "unavailable";
1568
- return record(await response.json())?.schema === MARKET_SCHEMA ? "handoff_pending" : "unavailable";
2078
+ if (!response.ok) return { status: "unavailable" };
2079
+ return record(await response.json())?.schema === MARKET_SCHEMA ? { status: "handoff_pending" } : { status: "unavailable" };
1569
2080
  }
1570
2081
  if (action === "commit" || action === "push" || action === "pull" || action === "fetch") {
1571
2082
  const binding = resolution.gitBinding;
1572
2083
  const repository = requireString(resolution.target, "repository");
1573
- if (!executableIdentity$1 || !binding || !repository) return "unavailable";
2084
+ if (!executableIdentity$1 || !binding || !repository) return { status: "unavailable" };
1574
2085
  const targetIdentity = {
1575
2086
  repository,
1576
2087
  ...typeof resolution.target.remote === "string" ? { remote: resolution.target.remote } : {},
1577
2088
  ...typeof resolution.target.refspec === "string" ? { refspec: resolution.target.refspec } : {}
1578
2089
  };
1579
2090
  const current = await gitPrestate(binding.manifest, repository, signal, executableIdentity$1.realpath);
1580
- if (!current) return "unavailable";
1581
- if ((await executeRevalidatedGitEffect(binding.envelope, binding.manifest, targetIdentity, current, async (_file, argv, workingDirectory) => runCommand(roots, executableIdentity$1, argv, workingDirectory, signal))).status !== "executed") return "unavailable";
2091
+ if (!current) return { status: "unavailable" };
2092
+ const executed = await executeRevalidatedGitEffect(binding.envelope, binding.manifest, targetIdentity, current, async (_file, argv, workingDirectory) => runCommand(roots, executableIdentity$1, argv, workingDirectory, signal));
2093
+ if (executed.status !== "executed") return {
2094
+ status: "unavailable",
2095
+ reasonCode: executed.reasonCode
2096
+ };
1582
2097
  if (action === "commit") {
1583
2098
  const commit = verifiedLinearCommitReadback(await gitBytes(repository, [
1584
2099
  "rev-list",
@@ -1593,10 +2108,10 @@ async function executeGuardAction(action, resolution, roots, signal, executableI
1593
2108
  "-z",
1594
2109
  commit.postHeadOid
1595
2110
  ], signal, executableIdentity$1.realpath)) : void 0;
1596
- if (!commit || postTree !== resolution.target.change_set_digest) return "unavailable";
2111
+ if (!commit || postTree !== resolution.target.change_set_digest) return { status: "unavailable" };
1597
2112
  } else if (action === "push") {
1598
2113
  const remoteOid = binding.manifest.remote && binding.manifest.destinationRef ? await exactRemoteOid(repository, binding.manifest.remote, binding.manifest.destinationRef, signal, executableIdentity$1.realpath) : void 0;
1599
- if (!remoteOid || remoteOid !== resolution.target.local_oid) return "unavailable";
2114
+ if (!remoteOid || remoteOid !== resolution.target.local_oid) return { status: "unavailable" };
1600
2115
  } else {
1601
2116
  const upstream = String(resolution.target.upstream_oid ?? "");
1602
2117
  const trackingRef = binding.manifest.action === "fetch" ? binding.manifest.trackingRef : binding.manifest.remote && binding.manifest.sourceRef ? `refs/remotes/${binding.manifest.remote}/${binding.manifest.sourceRef.slice(11)}` : void 0;
@@ -1605,13 +2120,13 @@ async function executeGuardAction(action, resolution, roots, signal, executableI
1605
2120
  "--verify",
1606
2121
  trackingRef
1607
2122
  ], signal, executableIdentity$1.realpath)) : void 0;
1608
- if (!tracking || tracking !== upstream) return "unavailable";
1609
- if (action === "pull" && oid(await git(repository, ["rev-parse", "HEAD"], signal, executableIdentity$1.realpath)) !== upstream) return "unavailable";
1610
- if (action === "fetch" && oid(await git(repository, ["rev-parse", "HEAD"], signal, executableIdentity$1.realpath)) !== resolution.target.pre_head_oid) return "unavailable";
2123
+ if (!tracking || tracking !== upstream) return { status: "unavailable" };
2124
+ if (action === "pull" && oid(await git(repository, ["rev-parse", "HEAD"], signal, executableIdentity$1.realpath)) !== upstream) return { status: "unavailable" };
2125
+ if (action === "fetch" && oid(await git(repository, ["rev-parse", "HEAD"], signal, executableIdentity$1.realpath)) !== resolution.target.pre_head_oid) return { status: "unavailable" };
1611
2126
  }
1612
- return "completed";
2127
+ return { status: "completed" };
1613
2128
  }
1614
- return "unavailable";
2129
+ return { status: "unavailable" };
1615
2130
  }
1616
2131
  async function gitBytes(repository, args, signal, file = "git") {
1617
2132
  const { stdout } = await execFileAsync(file, args, {
@@ -2050,7 +2565,9 @@ function normalizedRoots(options) {
2050
2565
  ...options.authorizeMutation ? { authorizeMutation: options.authorizeMutation } : {},
2051
2566
  ...options.prepareMutation ? { prepareMutation: options.prepareMutation } : {},
2052
2567
  ...options.readExecutableIdentity ? { readExecutableIdentity: options.readExecutableIdentity } : {},
2053
- ...options.allowLoopbackHttpRegistry ? { allowLoopbackHttpRegistry: true } : {}
2568
+ ...options.allowLoopbackHttpRegistry ? { allowLoopbackHttpRegistry: true } : {},
2569
+ ...options.releaseGate ? { releaseGate: options.releaseGate } : {},
2570
+ ...options.releaseSettle ? { releaseSettle: options.releaseSettle } : {}
2054
2571
  };
2055
2572
  }
2056
2573
  function createActionTool(options = {}) {
@@ -2219,14 +2736,70 @@ function createActionTool(options = {}) {
2219
2736
  ...identity
2220
2737
  };
2221
2738
  }
2739
+ const releaseOperation = action === "publish" ? "npm_publish" : void 0;
2740
+ let releaseGranted = false;
2741
+ let releaseContractId;
2742
+ if (releaseOperation && roots.releaseGate) {
2743
+ const observed = await observePublishCandidate(resolution, roots, exec.signal, cwdOf(agent));
2744
+ if (!observed) return {
2745
+ status: "unavailable",
2746
+ reason_code: "release_candidate_unobservable",
2747
+ ...identity
2748
+ };
2749
+ let decision;
2750
+ try {
2751
+ decision = await roots.releaseGate({
2752
+ agent,
2753
+ operation: releaseOperation,
2754
+ callId: args.resolution_call_id,
2755
+ resolvedTarget: resolution.target,
2756
+ observed
2757
+ });
2758
+ } catch {
2759
+ decision = void 0;
2760
+ }
2761
+ if (decision === void 0) return {
2762
+ status: "unavailable",
2763
+ reason_code: "release_gate_unavailable",
2764
+ ...identity
2765
+ };
2766
+ if (decision.status !== "granted") return {
2767
+ status: "unavailable",
2768
+ reason_code: decision.reasonCode,
2769
+ ...identity
2770
+ };
2771
+ releaseGranted = true;
2772
+ releaseContractId = decision.contractId;
2773
+ }
2222
2774
  try {
2223
- const status = await executeGuardAction(action, resolution, roots, exec.signal, currentExecutable, args.resolution_call_id, agent);
2775
+ const outcome = await executeGuardAction(action, resolution, roots, exec.signal, currentExecutable, args.resolution_call_id, agent);
2776
+ if (releaseOperation && releaseGranted && roots.releaseSettle) {
2777
+ const readback$1 = outcome.status === "completed" ? await publishReadback(resolution.target, roots, exec.signal) : void 0;
2778
+ await roots.releaseSettle({
2779
+ agent,
2780
+ operation: releaseOperation,
2781
+ callId: args.resolution_call_id,
2782
+ ...releaseContractId !== void 0 ? { contractId: releaseContractId } : {},
2783
+ effect: outcome.status === "completed" ? "completed" : outcome.status === "unavailable" ? "not_effected" : "unknown",
2784
+ readback: readback$1 === void 0 ? "unavailable" : {
2785
+ kind: "npm_integrity",
2786
+ identity: readback$1
2787
+ }
2788
+ });
2789
+ }
2224
2790
  return {
2225
- status,
2226
- reason_code: status === "completed" ? "action_completed" : status === "handoff_pending" ? "restart_handoff_pending" : "action_execution_failed",
2791
+ status: outcome.status,
2792
+ reason_code: outcome.status === "completed" ? "action_completed" : outcome.status === "handoff_pending" ? "restart_handoff_pending" : outcome.reasonCode === "effect_already_applied" ? "action_already_applied" : "action_execution_failed",
2227
2793
  ...identity
2228
2794
  };
2229
2795
  } catch {
2796
+ if (releaseOperation && releaseGranted && roots.releaseSettle) await roots.releaseSettle({
2797
+ agent,
2798
+ operation: releaseOperation,
2799
+ callId: args.resolution_call_id,
2800
+ effect: "unknown",
2801
+ readback: "unavailable"
2802
+ });
2230
2803
  return {
2231
2804
  status: "unavailable",
2232
2805
  reason_code: "action_execution_failed",
@@ -2236,6 +2809,23 @@ function createActionTool(options = {}) {
2236
2809
  }
2237
2810
  });
2238
2811
  }
2812
+ /**
2813
+ * The trusted npm readback identity for a completed publish: the registry's
2814
+ * own `dist.integrity` for the exact package version. `undefined` means no
2815
+ * producer is reachable, which keeps the release attempt `unconfirmed` instead
2816
+ * of claiming a verified release.
2817
+ */
2818
+ async function publishReadback(target, roots, signal) {
2819
+ const registry = typeof target.registry === "string" ? target.registry : void 0;
2820
+ const artifact = typeof target.artifact_id === "string" ? target.artifact_id : void 0;
2821
+ const version = typeof target.version === "string" ? target.version : void 0;
2822
+ if (!registry || !artifact || !version) return void 0;
2823
+ try {
2824
+ return await registryIntegrity(registry, artifact, version, roots, signal);
2825
+ } catch {
2826
+ return;
2827
+ }
2828
+ }
2239
2829
  function createEvidenceTool(options = {}) {
2240
2830
  const roots = normalizedRoots(options);
2241
2831
  return defineTool({
@@ -2452,17 +3042,26 @@ function createEvidenceTool(options = {}) {
2452
3042
  if (!agent) return unavailable(action, role, "producer_agent_unavailable");
2453
3043
  try {
2454
3044
  if (role === "resolution") {
2455
- const recipe = gitPreparation(action);
2456
- if (recipe) {
3045
+ const recipe = actionPreparation(action);
3046
+ {
2457
3047
  const manifest = record(args.command_manifest);
2458
3048
  const plannedArgs = record(manifest?.planned_arguments);
2459
3049
  const selector = record(args.selector);
3050
+ const needsManifestId = [
3051
+ "install",
3052
+ "apply",
3053
+ "publish",
3054
+ "restart"
3055
+ ].includes(action);
3056
+ const needsPlannedEffect = !needsManifestId;
2460
3057
  const missing = [
2461
3058
  ...recipe.selector_fields.filter((key) => !requireString(selector ?? {}, key)).map((key) => `selector.${key}`),
2462
- ...!requireString(manifest ?? {}, "planned_tool") ? ["command_manifest.planned_tool"] : [],
2463
- ...recipe.planned_argument_fields.filter((key) => !requireString(plannedArgs ?? {}, key)).map((key) => `command_manifest.planned_arguments.${key}`)
3059
+ ...needsManifestId && !requireString(manifest ?? {}, "manifest_id") ? ["command_manifest.manifest_id"] : [],
3060
+ ...needsManifestId && recipe.command_manifest_fields.includes("tgz_path") && !requireString(manifest ?? {}, "tgz_path") ? ["command_manifest.tgz_path"] : [],
3061
+ ...needsPlannedEffect && !requireString(manifest ?? {}, "planned_tool") ? ["command_manifest.planned_tool"] : [],
3062
+ ...needsPlannedEffect ? recipe.planned_argument_fields.filter((key) => !requireString(plannedArgs ?? {}, key)).map((key) => `command_manifest.planned_arguments.${key}`) : []
2464
3063
  ];
2465
- 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 Git input contract. A manifest_id alone does not describe a planned tool call.");
3064
+ 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.");
2466
3065
  }
2467
3066
  const executable$1 = executableFor(action);
2468
3067
  const executableBinding = executable$1 ? await (roots.readExecutableIdentity ?? executableIdentity)(executable$1, exec.signal) : void 0;
@@ -2514,6 +3113,135 @@ function createEvidenceTool(options = {}) {
2514
3113
  });
2515
3114
  }
2516
3115
 
3116
+ //#endregion
3117
+ //#region src/tools/release.ts
3118
+ const RELEASE_TOOL = "context_guard_release";
3119
+ function createReleaseTool(options) {
3120
+ return defineTool({
3121
+ name: RELEASE_TOOL,
3122
+ 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.",
3123
+ parameters: {
3124
+ operation: {
3125
+ type: "string",
3126
+ required: true,
3127
+ enum: ["status", "reconcile"]
3128
+ },
3129
+ contract_id: { type: "string" },
3130
+ resolution_call_id: { type: "string" }
3131
+ },
3132
+ output: {
3133
+ schema: {
3134
+ type: "object",
3135
+ additionalProperties: true
3136
+ },
3137
+ render: (_args, value) => [{
3138
+ type: "text",
3139
+ text: JSON.stringify(value)
3140
+ }]
3141
+ },
3142
+ async execute(rawArgs, exec) {
3143
+ const args = rawArgs;
3144
+ const projection = options.getProjection();
3145
+ if (!projection) return {
3146
+ status: "unknown",
3147
+ reason_code: "guard_unavailable"
3148
+ };
3149
+ if (args.operation === "status") return {
3150
+ status: "available",
3151
+ policy: projection.policy,
3152
+ state_damaged: projection.releaseStateDamaged,
3153
+ coverage_surface: Object.keys(RELEASE_OPERATION_SURFACES).map((operation$1) => ({
3154
+ operation: operation$1,
3155
+ ...RELEASE_OPERATION_SURFACES[operation$1]
3156
+ })),
3157
+ contracts: projection.releaseContracts.map((contract$1) => ({
3158
+ contract_id: contract$1.contractId,
3159
+ revoked_at_seq: contract$1.revokedAtSeq ?? null,
3160
+ adopted_at_revision: contract$1.adoptedAtRevision,
3161
+ operations: releaseCoverage(contract$1),
3162
+ candidate: contract$1.candidate
3163
+ })),
3164
+ reservations: projection.releaseReservations.map(({ contractId: contractId$1, operation: operation$1, callId: callId$1, startedAtSeq, observedArtifactSri }) => ({
3165
+ contract_id: contractId$1,
3166
+ operation: operation$1,
3167
+ resolution_call_id: callId$1,
3168
+ started_at_seq: startedAtSeq,
3169
+ observed_artifact_sri: observedArtifactSri ?? null,
3170
+ in_flight: inFlightReservation(projection, contractId$1, operation$1)?.callId === callId$1
3171
+ })),
3172
+ settlements: projection.releaseSettlements.map(({ contractId: contractId$1, operation: operation$1, callId: callId$1, outcome: outcome$1, readback: readback$2, settledAtSeq }) => ({
3173
+ contract_id: contractId$1,
3174
+ operation: operation$1,
3175
+ resolution_call_id: callId$1,
3176
+ outcome: outcome$1,
3177
+ readback: readback$2,
3178
+ settled_at_seq: settledAtSeq
3179
+ })),
3180
+ diagnostics: projection.releaseDiagnostics
3181
+ };
3182
+ if (args.operation !== "reconcile") return {
3183
+ status: "rejected",
3184
+ reason_code: "release_subcommand_unknown"
3185
+ };
3186
+ const contractId = args.contract_id ?? "";
3187
+ const callId = args.resolution_call_id ?? "";
3188
+ const contract = contractById(projection, contractId);
3189
+ if (!contract) return {
3190
+ status: "rejected",
3191
+ reason_code: "release_contract_unknown"
3192
+ };
3193
+ const reservation = reservationFor(projection, contractId, callId);
3194
+ if (!reservation) return {
3195
+ status: "rejected",
3196
+ reason_code: "release_reservation_unknown"
3197
+ };
3198
+ const operation = reservation.operation;
3199
+ const settled = projection.releaseSettlements.find((entry) => entry.contractId === contractId && entry.operation === operation && entry.callId === callId && (entry.outcome === "settled" || entry.outcome === "not_effected"));
3200
+ if (settled) return {
3201
+ status: "already_resolved",
3202
+ outcome: settled.outcome,
3203
+ reason_code: "release_already_resolved"
3204
+ };
3205
+ const registry = contract.candidate.registry;
3206
+ const packageId = contract.candidate.packageId;
3207
+ const version = contract.candidate.version;
3208
+ if (!registry || !packageId || !version) return {
3209
+ status: "unavailable",
3210
+ reason_code: "release_readback_identity_incomplete"
3211
+ };
3212
+ const identity = await registryIntegrity(registry, packageId, version, options, exec.signal);
3213
+ if (!identity) return {
3214
+ status: "unavailable",
3215
+ reason_code: "release_readback_unavailable"
3216
+ };
3217
+ const readback$1 = {
3218
+ kind: "npm_integrity",
3219
+ identity
3220
+ };
3221
+ const verdict = readbackSettlesContract(contract, readback$1, reservation.observedArtifactSri);
3222
+ const outcome = verdict === "settled" ? "settled" : "unknown";
3223
+ if (!await options.persistSettlement({
3224
+ agent: exec.agent,
3225
+ contractId,
3226
+ operation,
3227
+ callId,
3228
+ readback: readback$1,
3229
+ outcome
3230
+ })) return {
3231
+ status: "unknown",
3232
+ reason_code: "release_settlement_not_durable"
3233
+ };
3234
+ return {
3235
+ status: verdict === "settled" ? "settled" : verdict === "mismatch" ? "mismatch" : "unconfirmed",
3236
+ outcome,
3237
+ readback: readback$1,
3238
+ reason_code: verdict === "settled" ? "release_reconciled" : verdict === "mismatch" ? "release_readback_identity_mismatch" : "release_readback_identity_unavailable",
3239
+ note: "Reconciliation never re-sends the release operation."
3240
+ };
3241
+ }
3242
+ });
3243
+ }
3244
+
2517
3245
  //#endregion
2518
3246
  //#region src/tools/external-operation.ts
2519
3247
  function createExternalOperationTool(read, capability) {
@@ -2589,17 +3317,168 @@ function createExternalOperationTool(read, capability) {
2589
3317
  });
2590
3318
  }
2591
3319
 
3320
+ //#endregion
3321
+ //#region src/domain/migration.ts
3322
+ /** The frozen digest domains: a migration never rewrites any of them. */
3323
+ const PRESERVED_DIGEST_DOMAINS = [
3324
+ "ccg.sessionRefDigest.v3",
3325
+ "ccg.hostLockDigest.v3",
3326
+ "ccg.evidenceFact.v3",
3327
+ "ccg.evidenceSha256.v3",
3328
+ "ccg.predParams.v3",
3329
+ "ccg.binding.v3",
3330
+ "ccg.bindingDigest.v3",
3331
+ "ccg.certificationDigest.v3",
3332
+ "ccg.locator.v1",
3333
+ "ccg.proofManifest.v1"
3334
+ ];
3335
+ function migrationReport(projection) {
3336
+ const v5 = projection.boundaryProtocol === 5;
3337
+ const legacyItemIds = certifiableOpenItems(projection).filter((item) => item.unitId === void 0).map((item) => item.id);
3338
+ const closure = v5 ? certificateClosure(projection) : { itemIds: [] };
3339
+ const reasonCodes = [];
3340
+ if (!v5) reasonCodes.push("legacy_session_keeps_v4_contract");
3341
+ if (v5 && legacyItemIds.length > 0) reasonCodes.push("pre_v5_obligations_retained_in_closure");
3342
+ return {
3343
+ ruleMode: v5 ? "v5" : "legacy-v4",
3344
+ certificateVersion: v5 ? CERTIFICATE_VERSION_V2 : CERTIFICATE_VERSION,
3345
+ stopProtocolVersion: v5 ? STOP_PROTOCOL_VERSION_V2 : STOP_PROTOCOL_VERSION,
3346
+ unitClosure: v5,
3347
+ ...v5 && projection.currentUnitId !== void 0 ? { currentUnitId: projection.currentUnitId } : {},
3348
+ legacyItemIds,
3349
+ unitClosureItemIds: [...closure.itemIds],
3350
+ preservedDigestDomains: [...PRESERVED_DIGEST_DOMAINS],
3351
+ historyOnlyCertificateVersions: v5 ? [CERTIFICATE_VERSION] : [],
3352
+ rollbackRequiresStateSnapshot: v5,
3353
+ 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.",
3354
+ reasonCodes
3355
+ };
3356
+ }
3357
+
2592
3358
  //#endregion
2593
3359
  //#region src/commands/context-guard.ts
2594
3360
  function pendingCount(projection) {
2595
3361
  return [...projection.items.values()].filter((item) => item.status === "pending").length;
2596
3362
  }
3363
+ /**
3364
+ * The release surface. Adoption is deliberately NOT performed here: the
3365
+ * derivation adopts a contract only from the durable root `command/run` for
3366
+ * `/context-guard release adopt <json>`, so the command handler's job is to
3367
+ * report the resulting state (and to explain what is not protectable), never
3368
+ * to grant authority itself.
3369
+ */
3370
+ function releaseResponse(projection, rawInput) {
3371
+ const rest = rawInput.trim().slice(7).trim();
3372
+ const [verb] = rest.split(/\s+/, 1);
3373
+ if (verb === "adopt") {
3374
+ const payload = rest.slice(5).trim();
3375
+ let parsed;
3376
+ try {
3377
+ parsed = JSON.parse(payload);
3378
+ } catch {
3379
+ return {
3380
+ kind: "error",
3381
+ text: "Context Guard release adopt: the contract must be one JSON object."
3382
+ };
3383
+ }
3384
+ const normalized = normalizeReleaseContract(parsed, {
3385
+ seq: -1,
3386
+ digest: ""
3387
+ });
3388
+ if (!normalized.contract) return {
3389
+ kind: "error",
3390
+ text: `Context Guard release adopt rejected: ${normalized.errors.join(", ")}`
3391
+ };
3392
+ const contract = normalized.contract;
3393
+ const adopted = projection.releaseContracts.some((entry) => entry.contractId === contract.contractId) ? projection.releaseContracts.find((entry) => entry.contractId === contract.contractId) : contract;
3394
+ return {
3395
+ kind: "success",
3396
+ text: JSON.stringify({
3397
+ status: "adopted",
3398
+ contract_id: adopted.contractId,
3399
+ candidate: adopted.candidate,
3400
+ operations: releaseCoverage(adopted),
3401
+ readiness_refs: adopted.readinessRefs,
3402
+ closure_cert_ref: adopted.closureCertRef ?? null,
3403
+ expires_at_epoch_ms: adopted.expiresAtEpochMs ?? null,
3404
+ note: "The contract becomes authoritative from the durable root command that carried it. It grants no authority in this process."
3405
+ })
3406
+ };
3407
+ }
3408
+ if (verb === "revoke") {
3409
+ const contractId = rest.slice(6).trim();
3410
+ if (!contractId) return {
3411
+ kind: "error",
3412
+ text: "Usage: /context-guard release revoke <contract_id>"
3413
+ };
3414
+ const contract = projection.releaseContracts.find((entry) => entry.contractId === contractId);
3415
+ if (!contract) return {
3416
+ kind: "error",
3417
+ text: `Context Guard release revoke: unknown contract ${contractId}`
3418
+ };
3419
+ return {
3420
+ kind: "success",
3421
+ text: JSON.stringify({
3422
+ status: contract.revokedAtSeq === void 0 ? "revoking" : "revoked",
3423
+ contract_id: contractId,
3424
+ revoked_at_seq: contract.revokedAtSeq ?? null,
3425
+ 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 }) => ({
3426
+ operation,
3427
+ call_id: callId
3428
+ })),
3429
+ 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."
3430
+ })
3431
+ };
3432
+ }
3433
+ if (verb !== "" && verb !== "status") return {
3434
+ kind: "error",
3435
+ 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."
3436
+ };
3437
+ const contracts = projection.releaseContracts.map((contract) => ({
3438
+ contract_id: contract.contractId,
3439
+ adopted_at_seq: contract.adoptedBy.seq,
3440
+ revoked_at_seq: contract.revokedAtSeq ?? null,
3441
+ candidate: contract.candidate,
3442
+ readiness_refs: contract.readinessRefs,
3443
+ closure_cert_ref: contract.closureCertRef ?? null,
3444
+ operations: releaseCoverage(contract),
3445
+ expires_at_epoch_ms: contract.expiresAtEpochMs ?? null,
3446
+ consumed_operations: projection.releaseSettlements.filter((settlement) => settlement.contractId === contract.contractId && settlement.outcome === "settled").map((settlement) => settlement.operation),
3447
+ 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 }) => ({
3448
+ operation,
3449
+ call_id: callId,
3450
+ started_at_seq: startedAtSeq
3451
+ })),
3452
+ settlements: projection.releaseSettlements.filter((settlement) => settlement.contractId === contract.contractId).map(({ operation, callId, outcome, readback: readback$1, settledAtSeq }) => ({
3453
+ operation,
3454
+ call_id: callId,
3455
+ outcome,
3456
+ readback: readback$1,
3457
+ settled_at_seq: settledAtSeq
3458
+ }))
3459
+ }));
3460
+ return {
3461
+ kind: "success",
3462
+ text: JSON.stringify({
3463
+ profile_applicable: projection.policy === "release" || contracts.length > 0,
3464
+ policy: projection.policy,
3465
+ state_damaged: projection.releaseStateDamaged,
3466
+ adopted_contracts: contracts,
3467
+ coverage_surface: RELEASE_OPERATIONS.map((operation) => ({
3468
+ operation,
3469
+ ...RELEASE_OPERATION_SURFACES[operation]
3470
+ })),
3471
+ diagnostics: projection.releaseDiagnostics,
3472
+ 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."
3473
+ })
3474
+ };
3475
+ }
2597
3476
  function createContextGuardCommand(projectionFor, setEnabled, clearContract, lifecycleFor) {
2598
3477
  return {
2599
3478
  name: "context-guard",
2600
- description: "Enable, disable, clear, inspect, or diagnose Context Guard for this session.",
3479
+ description: "Enable, disable, clear, inspect, diagnose, or manage the explicit release contract for this session.",
2601
3480
  recordInput: true,
2602
- input: { hint: "on|off|clear|status|diagnose" },
3481
+ input: { hint: "on|off|clear|status|diagnose|migration|release status|release adopt <json>|release revoke <id>" },
2603
3482
  handler: ({ agent, rawInput }) => {
2604
3483
  const projection = projectionFor(agent);
2605
3484
  const [subcommand] = rawInput.trim().split(/\s+/, 1);
@@ -2627,22 +3506,31 @@ function createContextGuardCommand(projectionFor, setEnabled, clearContract, lif
2627
3506
  text: `Context Guard contract cleared: ${before - after} requirement/acceptance item(s) superseded; ${after} pending remain (prohibitions retained).`
2628
3507
  };
2629
3508
  }
3509
+ if (resolved === "release") return releaseResponse(projection, rawInput);
3510
+ if (resolved === "migration") return {
3511
+ kind: "success",
3512
+ text: JSON.stringify(migrationReport(projection))
3513
+ };
2630
3514
  if (resolved !== "status" && resolved !== "diagnose") return {
2631
3515
  kind: "error",
2632
- text: "Usage: /context-guard on|off|clear|status|diagnose"
3516
+ text: "Usage: /context-guard on|off|clear|status|diagnose|migration|release status|release adopt <json>|release revoke <id>"
2633
3517
  };
2634
3518
  const passed = [...projection.items.values()].filter((item) => item.status === "passed").length;
2635
3519
  let certifiable_missing_evidence = 0;
2636
3520
  let unsupported = 0;
3521
+ const reason_classes = {};
2637
3522
  for (const item of projection.items.values()) {
2638
3523
  if (item.status !== "pending") continue;
2639
3524
  const diagnosis = deriveItemDiagnosis(projection, item);
3525
+ reason_classes[diagnosis.reason_class] = (reason_classes[diagnosis.reason_class] ?? 0) + 1;
2640
3526
  if (diagnosis.repairability === "agent_repairable") certifiable_missing_evidence += 1;
2641
3527
  else if (diagnosis.certification === "unsupported") unsupported += 1;
2642
3528
  }
3529
+ const migration = migrationReport(projection);
2643
3530
  const response = {
2644
3531
  enabled: projection.enabled,
2645
3532
  lifecycle: lifecycleFor?.(agent) ?? (projection.enabled ? "active" : "disabled"),
3533
+ policy: projection.policy,
2646
3534
  epoch: projection.epoch,
2647
3535
  contract_revision: projection.contractRevision,
2648
3536
  pending: pendingCount(projection),
@@ -2650,11 +3538,25 @@ function createContextGuardCommand(projectionFor, setEnabled, clearContract, lif
2650
3538
  diagnosis: {
2651
3539
  certified: passed,
2652
3540
  certifiable_missing_evidence,
2653
- unsupported
3541
+ unsupported,
3542
+ reason_classes
2654
3543
  },
2655
3544
  evidence: projection.evidence.size,
2656
3545
  integrity: projection.integrity,
2657
- last_source_seq: projection.lastObservedSourceSeq
3546
+ last_source_seq: projection.lastObservedSourceSeq,
3547
+ migration: {
3548
+ rule_mode: migration.ruleMode,
3549
+ certificate_version: migration.certificateVersion,
3550
+ unit_closure: migration.unitClosure,
3551
+ legacy_open_items: migration.legacyItemIds.length
3552
+ },
3553
+ release: {
3554
+ policy: projection.policy,
3555
+ state_damaged: projection.releaseStateDamaged,
3556
+ adopted_contracts: projection.releaseContracts.length,
3557
+ in_flight: projection.releaseReservations.filter((reservation) => !projection.releaseSettlements.some((settlement) => settlement.callId === reservation.callId && settlement.outcome === "settled")).length,
3558
+ applicable: projection.policy === "release" || projection.releaseContracts.length > 0
3559
+ }
2658
3560
  };
2659
3561
  return {
2660
3562
  kind: "success",
@@ -3464,6 +4366,7 @@ defineMethod("transform", [
3464
4366
  //#region src/config.ts
3465
4367
  const Config = Schema.object({
3466
4368
  activation: Schema.string().default("opt-in"),
4369
+ policy: Schema.string().default("standard"),
3467
4370
  hostLockPlatform: Schema.string(),
3468
4371
  hostLockProfile: Schema.string(),
3469
4372
  hostLockPolicy: Schema.string(),
@@ -3478,6 +4381,8 @@ const Config = Schema.object({
3478
4381
  function resolveConfig(config) {
3479
4382
  const activation = config.activation ?? "opt-in";
3480
4383
  if (activation !== "opt-in" && activation !== "always") throw new TypeError(`activation must be "opt-in" or "always", received ${JSON.stringify(activation)}`);
4384
+ const policy = config.policy ?? "standard";
4385
+ if (policy !== "standard" && policy !== "strict" && policy !== "release") throw new TypeError(`policy must be "standard", "strict", or "release", received ${JSON.stringify(policy)}`);
3481
4386
  let hostLockPackages;
3482
4387
  if (config.hostLockPackages !== void 0) {
3483
4388
  if (!Array.isArray(config.hostLockPackages)) throw new TypeError("hostLockPackages must be an array");
@@ -3508,6 +4413,7 @@ function resolveConfig(config) {
3508
4413
  ...typeof config.hostLockRuntimeRoot === "string" ? { hostLockRuntimeRoot: config.hostLockRuntimeRoot } : {},
3509
4414
  ...typeof config.hostLockProfileRoot === "string" ? { hostLockProfileRoot: config.hostLockProfileRoot } : {},
3510
4415
  activation,
4416
+ policy,
3511
4417
  ...hostLockPackages ? { hostLockPackages } : {},
3512
4418
  ...config.hostLockPlatform ? { hostLockPlatform: config.hostLockPlatform } : {},
3513
4419
  ...config.hostLockProfile ? { hostLockProfile: config.hostLockProfile } : {}
@@ -3584,7 +4490,12 @@ function authorizeMutationFromProjection(projection, request) {
3584
4490
  status: "denied",
3585
4491
  reasonCode: "mutation_target_clarification_required"
3586
4492
  };
3587
- if (!requestedTargetAuthorizesMutation(request.action, item.requestedTarget, request.resolvedTarget)) return {
4493
+ const boundedAuthorized = (scope) => requestedTargetAuthorizesMutation(request.action, {
4494
+ ...item.requestedTarget,
4495
+ scope
4496
+ }, request.resolvedTarget);
4497
+ const selectionAuthorized = projection.trustedSelections.some((selection) => selection.kind === "directory" && typeof item.requestedTarget?.artifact_type === "string" && item.unitId !== void 0 && boundedAuthorized(selection.selected));
4498
+ if (!requestedTargetAuthorizesMutation(request.action, item.requestedTarget, request.resolvedTarget) && !selectionAuthorized) return {
3588
4499
  status: "denied",
3589
4500
  reasonCode: "mutation_requested_target_mismatch"
3590
4501
  };
@@ -3860,10 +4771,12 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3860
4771
  const session = agent.session;
3861
4772
  let pendingRecovery = false;
3862
4773
  let durabilityConfirmed = false;
4774
+ let durabilityWatermark = "unknown";
3863
4775
  let observedEpoch = -1;
3864
4776
  let observedCompactionSeq = -1;
3865
4777
  let observedContractRevision = -1;
3866
4778
  let protocolV4Present = false;
4779
+ let protocolV5Present = false;
3867
4780
  let realRootInputSeen = false;
3868
4781
  let lifecycle = "armed";
3869
4782
  const continuationAttempts = projection.continuationAttempts;
@@ -3882,7 +4795,10 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3882
4795
  if (!projection.integrityViolations.includes(code)) projection.integrityViolations.push(code);
3883
4796
  return;
3884
4797
  }
3885
- const derived = deriveProjection(events, { activation: config.activation }, {
4798
+ const derived = deriveProjection(events, {
4799
+ activation: config.activation,
4800
+ policy: config.policy
4801
+ }, {
3886
4802
  cwd: typeof header?.cwd === "string" ? header.cwd : "",
3887
4803
  sessionHeader
3888
4804
  }, durabilityConfirmed, hostLock);
@@ -3904,7 +4820,9 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3904
4820
  projection.continuationAttempts = continuationAttempts;
3905
4821
  projection.persistenceCorrectionAttempts = persistenceCorrectionAttempts;
3906
4822
  projection.lastRecoveryDigest = priorRecoveryDigest;
4823
+ projection.durabilityWatermark = durabilityWatermark;
3907
4824
  protocolV4Present = derived.protocolV4Present;
4825
+ protocolV5Present = derived.boundaryV5;
3908
4826
  realRootInputSeen = derived.realRootInputSeen;
3909
4827
  lifecycle = lifecyclePhase({
3910
4828
  enabled: projection.enabled,
@@ -3931,6 +4849,8 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3931
4849
  };
3932
4850
  const setDurability = (confirmed) => {
3933
4851
  durabilityConfirmed = confirmed;
4852
+ durabilityWatermark = confirmed ? "confirmed" : "failed";
4853
+ projection.durabilityWatermark = durabilityWatermark;
3934
4854
  };
3935
4855
  const markRecoveryNeeded = () => {
3936
4856
  pendingRecovery = true;
@@ -3950,6 +4870,9 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3950
4870
  get protocolV4Present() {
3951
4871
  return protocolV4Present;
3952
4872
  },
4873
+ get protocolV5Present() {
4874
+ return protocolV5Present;
4875
+ },
3953
4876
  sync,
3954
4877
  setEnabled,
3955
4878
  setDurability,
@@ -3957,6 +4880,15 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
3957
4880
  consumeRecovery
3958
4881
  };
3959
4882
  }
4883
+ /** The durable session working directory, when the V3 header carries one. */
4884
+ function sessionCwd(session) {
4885
+ const header = session.header;
4886
+ return typeof header?.cwd === "string" && header.cwd.length > 0 ? header.cwd : void 0;
4887
+ }
4888
+ /** A bounded signal for a short trusted readback performed by the gate. */
4889
+ function requestSignal() {
4890
+ return AbortSignal.timeout(5e3);
4891
+ }
3960
4892
  /** Delegated/subagent sessions never receive root-conversation injections. */
3961
4893
  function isDelegatedSession(session) {
3962
4894
  const header = session.header;
@@ -3992,9 +4924,9 @@ function revalidateCoreLock(config, expected) {
3992
4924
  };
3993
4925
  }
3994
4926
  }
3995
- function apply(ctx, rawConfig = {}) {
4927
+ function apply(ctx, rawConfig = {}, seams = {}) {
3996
4928
  const config = resolveConfig(rawConfig);
3997
- const installedHostLock = evaluateHostLock(config.hostLockPackages ?? [], {
4929
+ const installedHostLock = seams.hostLock ?? evaluateHostLock(config.hostLockPackages ?? [], {
3998
4930
  platform: config.hostLockPlatform,
3999
4931
  profileKind: config.hostLockProfile
4000
4932
  });
@@ -4006,7 +4938,7 @@ function apply(ctx, rawConfig = {}) {
4006
4938
  if (!runtime) {
4007
4939
  const goals = optionalGoalService(ctx, agent);
4008
4940
  const refreshHostLock = () => {
4009
- const current = bindLiveGoalCapability(revalidateCoreLock(config, installedHostLock), Boolean(goals) && hasPinnedUpdateGoalTool(agent));
4941
+ const current = bindLiveGoalCapability(seams.hostLock ?? revalidateCoreLock(config, installedHostLock), Boolean(goals) && hasPinnedUpdateGoalTool(agent));
4010
4942
  hostLocks.set(agent, current);
4011
4943
  return current;
4012
4944
  };
@@ -4045,8 +4977,41 @@ function apply(ctx, rawConfig = {}) {
4045
4977
  runtime.sync();
4046
4978
  return durable;
4047
4979
  }, () => runtime.markRecoveryNeeded()));
4980
+ /**
4981
+ * Persist one C10 release record through the plugin-notice channel and make
4982
+ * it durable before returning. A record that cannot be flushed is reported
4983
+ * as a failure: an unflushed reservation is not an in-flight operation, and
4984
+ * an unflushed settlement would let a consumed ticket look unused.
4985
+ */
4986
+ const persistReleaseRecord = async (toolAgent, prefix, payload) => {
4987
+ const target = toolAgent.session;
4988
+ target.append.bind(target)("user/message", createUserMessage({
4989
+ content: [{
4990
+ type: "text",
4991
+ text: `${prefix}${JSON.stringify(payload)}`
4992
+ }],
4993
+ source: {
4994
+ kind: "plugin",
4995
+ plugin: "context-guard",
4996
+ form: "notice",
4997
+ summary: boundContextSummary("recording an explicit release record")
4998
+ }
4999
+ }), { surfaceOp: "append" });
5000
+ let durable = false;
5001
+ try {
5002
+ durable = await ctx.sessions.flush(target);
5003
+ } catch {
5004
+ durable = false;
5005
+ }
5006
+ runtime.setDurability(durable);
5007
+ runtime.sync();
5008
+ return durable;
5009
+ };
4048
5010
  const evidenceOptions = {
4049
5011
  hostCapability: createHostCapabilityEvaluator(hostLocks.get(agent) ?? installedHostLock),
5012
+ ...seams.commandRunner ? { commandRunner: seams.commandRunner } : {},
5013
+ ...seams.fetcher ? { fetcher: seams.fetcher } : {},
5014
+ ...seams.allowLoopbackHttpRegistry ? { allowLoopbackHttpRegistry: true } : {},
4050
5015
  prepareMutation: async (toolAgent) => {
4051
5016
  if (toolAgent.session !== agent.session) return false;
4052
5017
  const durable = await ctx.sessions.flush(agent.session);
@@ -4059,6 +5024,63 @@ function apply(ctx, rawConfig = {}) {
4059
5024
  return authorizeMutationFromProjection(runtime.projection, request);
4060
5025
  },
4061
5026
  marketOrigin: optionalMarketOrigin(ctx, agent),
5027
+ releaseGate: async (request) => {
5028
+ runtime.sync();
5029
+ const projection = runtime.projection;
5030
+ if (!(projection.policy === "release" || projection.releaseContracts.length > 0)) return {
5031
+ status: "granted",
5032
+ reasonCode: "release_profile_not_adopted"
5033
+ };
5034
+ const observed = { ...request.observed };
5035
+ const declaredRef = releaseContractFor(projection, request.operation)?.candidate.ref;
5036
+ if (declaredRef !== void 0 && observed.ref === void 0) {
5037
+ const refSha = await resolveAuditedRef(sessionCwd(runtime.session), declaredRef, evidenceOptions, requestSignal());
5038
+ if (refSha !== void 0) {
5039
+ observed.ref = declaredRef;
5040
+ observed.refSha = refSha;
5041
+ }
5042
+ }
5043
+ const decision = releasePreEffectDecision(projection, {
5044
+ operation: request.operation,
5045
+ observed,
5046
+ resolvedTarget: request.resolvedTarget,
5047
+ nowEpochMs: Date.now()
5048
+ });
5049
+ if (decision.status !== "granted" || decision.contractId === void 0) return {
5050
+ status: "denied",
5051
+ reasonCode: decision.reasonCode
5052
+ };
5053
+ return await persistReleaseRecord(agent, RELEASE_RESERVATION_PREFIX, {
5054
+ contractId: decision.contractId,
5055
+ operation: request.operation,
5056
+ callId: request.callId,
5057
+ startedAtSeq: 0,
5058
+ status: "in_flight",
5059
+ ...observed.artifactSri !== void 0 ? { observedArtifactSri: observed.artifactSri } : {}
5060
+ }) ? {
5061
+ status: "granted",
5062
+ reasonCode: decision.reasonCode,
5063
+ contractId: decision.contractId
5064
+ } : {
5065
+ status: "denied",
5066
+ reasonCode: "release_reservation_not_durable"
5067
+ };
5068
+ },
5069
+ releaseSettle: async (request) => {
5070
+ const contractId = request.contractId ?? releaseContractFor(runtime.projection, request.operation)?.contractId ?? "unknown";
5071
+ const contract = runtime.projection.releaseContracts.find((entry) => entry.contractId === contractId);
5072
+ const reservation = reservationFor(runtime.projection, contractId, request.callId);
5073
+ let outcome = request.effect === "not_effected" ? "not_effected" : "unknown";
5074
+ if (request.effect === "completed" && contract) outcome = readbackSettlesContract(contract, request.readback, reservation?.observedArtifactSri) === "settled" ? "settled" : "unknown";
5075
+ await persistReleaseRecord(agent, RELEASE_SETTLEMENT_PREFIX, {
5076
+ contractId,
5077
+ operation: request.operation,
5078
+ callId: request.callId,
5079
+ settledAtSeq: 0,
5080
+ readback: request.readback,
5081
+ outcome
5082
+ });
5083
+ },
4062
5084
  persistRestartIntent: async (toolAgent, intent) => {
4063
5085
  const session = toolAgent.session;
4064
5086
  session.append.bind(session)("user/message", createUserMessage({
@@ -4094,7 +5116,35 @@ function apply(ctx, rawConfig = {}) {
4094
5116
  reasonCode: evaluation.reasonCode
4095
5117
  };
4096
5118
  },
4097
- commandTemplate: (action) => GIT_COMMAND_TEMPLATES[action]
5119
+ commandTemplate: (action) => GIT_COMMAND_TEMPLATES[action],
5120
+ refreshProjection: async () => {
5121
+ const durable = await ctx.sessions.flush(agent.session);
5122
+ runtime.setDurability(durable);
5123
+ runtime.sync();
5124
+ return durable;
5125
+ }
5126
+ }));
5127
+ agent.ctx.tools.register(createInterpretTool({
5128
+ getProjection: () => runtime.projection,
5129
+ refreshProjection: async () => {
5130
+ const durable = await ctx.sessions.flush(agent.session);
5131
+ runtime.setDurability(durable);
5132
+ runtime.sync();
5133
+ return durable;
5134
+ }
5135
+ }));
5136
+ agent.ctx.tools.register(createReleaseTool({
5137
+ getProjection: () => runtime.projection,
5138
+ fetcher: evidenceOptions.fetcher,
5139
+ ...evidenceOptions.allowLoopbackHttpRegistry ? { allowLoopbackHttpRegistry: true } : {},
5140
+ persistSettlement: async (request) => persistReleaseRecord(request.agent, RELEASE_SETTLEMENT_PREFIX, {
5141
+ contractId: request.contractId,
5142
+ operation: request.operation,
5143
+ callId: request.callId,
5144
+ settledAtSeq: 0,
5145
+ readback: request.readback,
5146
+ outcome: request.outcome
5147
+ })
4098
5148
  }));
4099
5149
  agent.ctx.tools.register(createExternalOperationTool((id, toolAgent) => readExternalOperation(ctx, toolAgent, id), () => evaluateExternalWaitCapability(hostLocks.get(agent) ?? installedHostLock)));
4100
5150
  agent.ctx.tools.guard((exec) => goalCompletionDenial(runtime.projection, exec.name, exec.arguments));
@@ -4108,12 +5158,14 @@ function apply(ctx, rawConfig = {}) {
4108
5158
  if (decision.kind !== "enter") return decision;
4109
5159
  const injected = [];
4110
5160
  const delegated = isDelegatedSession(agent.session);
4111
- let boundaryPending = !runtime.protocolV4Present;
5161
+ let boundaryPending = !runtime.protocolV5Present;
4112
5162
  const firstStep = previewFirstStepInjection({
4113
5163
  activation: config.activation,
4114
5164
  enabled: runtime.projection.enabled,
5165
+ boundaryV5Present: runtime.protocolV5Present,
4115
5166
  boundaryPresent: runtime.protocolV4Present,
4116
- delegated
5167
+ delegated,
5168
+ policy: runtime.projection.policy
4117
5169
  }, claimedBatchHasRealRootInput(decision.messages));
4118
5170
  if (firstStep) {
4119
5171
  injected.push(pluginNoticeMessage(firstStep.boundary, "Context Guard recorded a replay version boundary"));
@@ -4128,7 +5180,7 @@ function apply(ctx, rawConfig = {}) {
4128
5180
  if (recovery && digest$2 !== runtime.projection.lastRecoveryDigest) {
4129
5181
  runtime.projection.lastRecoveryDigest = digest$2;
4130
5182
  if (boundaryPending && !delegated) {
4131
- injected.push(pluginNoticeMessage(PROTOCOL_V4_NOTICE, "Context Guard recorded a replay version boundary"));
5183
+ injected.push(pluginNoticeMessage(PROTOCOL_V5_NOTICE, "Context Guard recorded a replay version boundary"));
4132
5184
  boundaryPending = false;
4133
5185
  }
4134
5186
  injected.push(createUserMessage({
@@ -4272,4 +5324,4 @@ function normalizeGoalState(value) {
4272
5324
  }
4273
5325
 
4274
5326
  //#endregion
4275
- 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 };
5327
+ 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, firstStepGuidance, 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 };