dsh-completion-guard 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -1
- package/CHANGELOG.zh-CN.md +26 -1
- package/README.md +12 -7
- package/README.zh-CN.md +12 -7
- package/bin/dsh-completion-guard-host-lock.mjs +93 -6
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/index.js +2 -2
- package/dist/{domain-Jo13xgPJ.js → domain-DKqyOtgY.js} +2782 -496
- package/dist/{index-jKw6mANp.d.ts → index-BmTaAEkJ.d.ts} +251 -12
- package/dist/index.d.ts +4 -2
- package/dist/index.js +716 -132
- package/docs/COMPATIBILITY.md +46 -2
- package/docs/HOST_LOCK_UPGRADE.md +30 -8
- package/docs/LOCAL_ACCEPTANCE.md +15 -1
- package/docs/README.md +13 -8
- package/docs/WRITER_LOCK_PROTOCOL.md +63 -0
- package/manifests/rc020-rc2-byte-audit.json +948 -0
- package/manifests/supported-host.v1.json +93 -93
- package/package.json +37 -37
- package/docs/CORE_ALIGNMENT_PLAN_REVIEW.json +0 -144
- package/docs/DEVELOPMENT_PLAN_0_7_0.md +0 -93
- package/docs/DEVELOPMENT_PLAN_DSH_0_1_7_RC2.md +0 -283
- package/docs/DSH_0_1_7_RC2_ACCEPTANCE.md +0 -57
- package/docs/RELEASE_NOTES_0_7_1.md +0 -21
- package/docs/dsh-0.1.7-rc.2-planning-evidence.json +0 -585
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as segmentAuthorityBlocks, $a as createHostAuditSession, $i as acquireHostTrust, $n as evidenceAvailabilityReason, $r as presentExplanationHead, $t as normalizeSettlement, A as PROTOCOL_V3_NOTICE, Aa as parseHostVersion, Ai as packageRowsFromActiveGraph, An as certifiableOpenItems, Ar as clauseIsProtected, At as MIN_RECOVERY_CHAR_BUDGET, B as evidenceFromPersistedToolResult, Ba as STOP_PROTOCOL_VERSION_V2, Bi as verifyComposedHostLockDump, Bn as createProjection, Br as isExplanationScope, Bt as renderRecoveryPacket, C as SESSION_API_UNSUPPORTED, Ca as HOST_VALIDATED_VERSIONS, Ci as evaluateActiveHostLock, Cn as observeAssistantOutcome, Cr as classifyUserInteraction, Ct as sessionQueryV2, D as projectCoreV2, Da as SUPPORTED_HOST_VERSIONS, Di as injectActiveProfileHostLock, Dn as goalCompletionDenial, Dr as clarifiedSpanOf, Dt as CLEANUP_CONDITION_RULE_COMPACT, E as snapshotSessionEvents, Ea as SUPPORTED_HOST_RANGE, Ei as hostLockRowsFromComposedDump, En as v6TestPredicate, Er as actionVerbMatches, Et as CLEANUP_CONDITION_RULE, F as deriveProjection, Fa as CERTIFICATE_VERSION, Fi as readActiveHostGraph, Fn as availableBoundaryQualifications, Fr as hasWorkPredicate, Ft as cleanupConditionFor, G as persistedToolResultStatus, Ga as requestedIdentityKey, Gi as hasDesktopImporterState, Gn as rebindAttemptKey, Gr as itemHoldsExecutionAuthority, Gt as sourceItemForCoreRequirement, H as extractToolSubject, Ha as actionCompatible, Hi as DESKTOP_PROFILE_PACKAGE_NAME, Hn as proposeRebind, Hr as isOpenObligation, Ht as confirmedV6CoreDiagnostic, I as legacyRecordsNeedingReview, Ia as CERTIFICATE_VERSION_V2, Ii as resolveActiveProfileHostLock, In as effectuateBoundary, Ir as interpretClause, It as closingHint, J as canonicalArgvFromCommand, Ja as semanticActionFromCommand, Ji as readDesktopAppRuntime, Jn as CONFIRM_LINE_PATTERN, Jr as maskCodeSpans, Jt as RELEASE_OPERATION_SURFACES, K as shellReadbackOutcome, Ka as requestedTargetAuthorizesMutation, Ki as readAsarFile, Kn as rebindResponse, Kr as kindOfScope, Kt as OUTCOME_STRENGTH, L as rootLocatorFlavor, La as SEMANTIC_ACTIONS, Li as resolveDesktopProfileHostLock, Ln as isCurrentAcceptedBoundary, Lr as interpretMessage, Lt as openItems, M as PROTOCOL_V5_NOTICE, Ma as ACTION_MANIFEST, Mi as prepareActiveHostTrust, Mn as needsReviewObligations, Mr as governedClauseRestrictsExecution, Mt as V6_ORDINARY_COMPLETION_RULE_COMPACT, N as PROTOCOL_V6_NOTICE, Na as ACTION_MANIFEST_VERSION, Ni as prepareDesktopHostTrust, Nn as unitDescendantIds, Nr as hasOrderedCoordination, Nt as V6_ORDINARY_COMPLETION_RULE_SHORT, O as CAPTURE_V042_NOTICE, Oa as compareHostVersions, Oi as inspectDesktopTargetGraph, On as hasCurrentCertificate, Or as clauseAsksOwnQuestion, Ot as CLEANUP_CONDITION_RULE_SHORT, P as applyUpgradeEligibility, Pa as BOUNDED_ARTIFACT_TYPES, Pi as prepareTargetHostTrust, Pn as BOUNDARY_RECORD_PREFIX, Pr as hasQuestionScope, Pt as carriesCleanupCondition, Q as authorityCaptureCounts, Qa as RC020_RC2_HOST_PACKAGES, Qi as HostTrustError, Qn as deriveItemDiagnosis, Qr as opensWithDirective, Qt as normalizeReservation, R as supersedeItem, Ra as STATEFUL_ACTIONS, Ri as resolveInstalledHostLock, Rn as qualifyBoundary, Rr as introducesActionClause, Rt as recoveryDigest, S as sourcedNamedTestRoot, Sa as selectHostCohort, Si as combineHostPolicy, Sn as latestRootInstruction, Sr as classifyTaskIntent, St as sessionQuery, T as SessionApiError, Ta as MIN_SUPPORTED_HOST_VERSION, Ti as hostLockContextFromComposedDump, Tn as testOutcomePredicate, Tr as LEGACY_QUALIFICATION, Tt as validateProofManifestV2, U as isDeterministicCheck, Ua as boundedArtifactChoiceMatches, Ui as DESKTOP_RUNTIME_PACKAGE_NAME, Un as proposeRebindOutcome, Ur as isQuestionScopeNeedingReview, Ut as currentV6Feedback, V as extractTextContent, Va as SUPPORTED_EVIDENCE_ADAPTERS, Vi as DESKTOP_IDENTITY_FACTS, Vn as confirmRebind, Vr as isInformationalFragment, Vt as v6CurrentRootBoundaries, W as nativeGitParentOidVerified, Wa as isStatefulAction, Wi as auditDesktopInstalledImplementation, Wn as proposeRebindV042, Wr as isRestatement, Wt as isV6PendingRootWait, X as parsePwshCommand, Xa as validateActionManifest, Xi as readDesktopTargetGraph, Xn as parseConfirmationMessage, Xr as namedActions, Xt as inFlightReservation, Y as isRunExecutable, Ya as semanticActionFromText, Yi as readDesktopDependency, Yn as isFrozenV042RebindResponse, Yr as maskQuotedSpans, Yt as contractById, Z as parseShellCommand, Za as validateActionTarget, Zi as writeDesktopRuntimeReceipt, Zn as capabilityRemedyPhrase, Zr as opensConditionLead, Zt as normalizeReleaseContract, _ as previewFirstStepInjection, _a as evaluateGraphDerivedHostLock, _i as activeRendererModule, _n as decideTurnStopping, _r as extractOperation, _t as proofHostSurfacesOf, a as commitTreeSnapshotDigest, aa as ACTIVE_HOST_COHORT_ID, ai as splitTextFragments, an as bindingSatisfies, ar as admissibleForRemoval, at as PROOF_MANIFEST_DOMAIN_V2, b as HOST_WORKDIR_PREFIX, ba as evaluateToolSurfaceCapability, bi as auditedForegroundRenderers, bn as isWholeTaskCompletionClaim, br as canonicalRegistryBase, bt as requiredSubjectsOf, c as gitCommandMatchesTarget, ca as BASE_HOST_PACKAGES, ci as COMMAND_SURFACE_MANIFEST, cn as isVerifyingCapability, cr as partialFailureOf, ct as bindProofToProjection, d as verifiedLinearCommitReadback, da as GOAL_HOST_PACKAGES, di as digestStrings, dn as NO_PROGRESS_TURNS_BEFORE_STOP, dr as captureClause, dt as createProofManifest, ea as hostTrustDigest, ei as qualificationOfClause, en as readbackSettlesContract, er as itemDiagnosis, et as bindingIndividuallyAccepted, f as FIRST_STEP_GUIDANCE, fa as HOST_CAPABILITY_PACKAGE_GROUPS, fi as normalizeClause, fn as assessmentAction, fr as captureItem, ft as createProofManifestV2, g as lifecyclePhase, ga as evaluateExternalWaitCapability, gi as HostProfileError, gn as decideTurnBoundary, gr as extractMethod, gt as proofEvidenceConstraints, h as firstStepGuidanceV6, ha as bindLiveGoalCapability, hi as sha256, hn as currentActionBases, hr as extractArtifactPaths, ht as proofDigestV2, i as commitIndexSnapshotDigest, ia as registryArchiveModules, ii as semanticActionOfScope, in as reservationFor, ir as actionHasCertificationPath, it as PROOF_KINDS_V2, j as PROTOCOL_V4_NOTICE, ja as satisfiesSupportedHostRange, ji as packageRowsFromPnpmLock, jn as certificateClosure, jr as explanationHasActionResidue, jt as V6_ORDINARY_COMPLETION_RULE, k as DEFAULT_DELEGATION_TOOL_NAMES, ka as evaluateMinimumHostVersion, ki as inspectTargetHostGraph, kn as ancestorConstraintForBinding, kr as clauseIsGoverned, kt as DEFAULT_RECOVERY_CHAR_BUDGET, l as parseGitCommandManifest, la as DEFAULT_HOST_LOCK, li as validateManifest, ln as CONTROL_RECORD_PREFIX, lr as removalIsComplete, lt as bindProofV2ToProjection, m as firstStepGuidance, ma as bindExecutableIdentity, mi as sanitizeUrl, mn as classifyCompletionClaim, mr as environmentDefaultRepositoryTarget, mt as proofDigest, n as GIT_COMMAND_MANIFEST_IDS, na as qualifyHostTrust, ni as reportingHeadGoverns, nn as releaseCoverage, nr as relevantEvidence, nt as PROOF_CAPABILITY_MATRIX, o as createGitPrestateEnvelope, oa as ACTIVE_HOST_COHORT_IDS, oi as statefulActionsOfScope, on as evidenceCoverage, or as capabilityConsequence, ot as PROOF_PROTOCOL_VERSION, p as claimedBatchHasRealRootInput, pa as HOST_COHORTS, pi as sanitizeClauseText, pn as assessmentOutcomePredicate, pr as classifyClause, pt as proofCapabilityReport, q as withDurability, qa as requestedTargetMatchesResolved, qi as readAsarIndex, qn as replayRebindResult, qr as legacyQuestionReadingIsInformational, qt as RELEASE_OPERATIONS, r as GIT_COMMAND_TEMPLATES, ra as registryArchiveFiles, ri as restatedContentOf, rn as releasePreEffectDecision, rr as DEPENDENCY_FREE_ONLY_CONDITION, rt as PROOF_KINDS, s as executeRevalidatedGitEffect, sa as ACTIVE_HOST_LAUNCHER_VERSION, si as verbIsNegated, sn as evidenceMatchesItem, sr as capabilityFactOf, st as PROOF_PROTOCOL_VERSION_V2, t as RC020_RC1_HOST_PACKAGES, ta as parseHostTrust, ti as questionHeadsClause, tn as releaseContractFor, tr as nativeFileTwoRole, tt as certifyCheckpoint, u as revalidateGitPrestate, ua as EXPECTED_HOST_PACKAGES, ui as canonicalizePath, un as NO_PROGRESS_RECORD_PREFIX, ur as removalIsPartiallyKnown, ut as canonicalProjection, v as projectSessionCoreV2, va as evaluateHostCapability, vi as auditedDefaultWorkdirHost, vn as decisionBoundaryKey, vr as isInformationalMessage, vt as proofOperationMatches, w as SESSION_EVENT_ENVELOPE_INVALID, wa as LATEST_TESTED_HOST_VERSION, wi as evaluateConfiguredHostLock, wn as progressFingerprint, wr as GRANTED_QUALIFICATION, wt as validateProofManifest, x as captureHostWorkdir, xa as hostVersionFromPackages, xi as auditedHostImplementation, xn as latestAssistantText, xr as npmEscapedPackageName, xt as scopeCoverageDigest, y as sessionCoreSnapshot, ya as evaluateHostLock, yi as auditedDefaultWorkdirProvider, yn as isRootPauseRequest, yr as segmentClauses, yt as proofV2Rejection, z as NATIVE_GIT_ROOT_PARENT_OID, za as STOP_PROTOCOL_VERSION, zi as revalidateDesktopCoreLock, zn as currentContractDigest, zr as isExecutableItem, zt as recoveryTitle } from "./domain-DKqyOtgY.js";
|
|
2
2
|
import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { basename, delimiter, dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
|
-
import { closeSync, constants, existsSync, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, unlinkSync, writeSync } from "node:fs";
|
|
5
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
6
|
+
import { closeSync, constants, existsSync, fchmodSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeSync } from "node:fs";
|
|
7
7
|
import { gunzip } from "node:zlib";
|
|
8
8
|
import { execFile } from "node:child_process";
|
|
9
|
-
import { homedir, tmpdir } from "node:os";
|
|
9
|
+
import { homedir, hostname, tmpdir } from "node:os";
|
|
10
10
|
import { JobId } from "@deepseek-ai/dsh-jobs";
|
|
11
11
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
12
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -18,7 +18,7 @@ import { SESSION_FORMAT_VERSION, Session, SessionId } from "@deepseek-ai/dsh-ses
|
|
|
18
18
|
function createRebindTool(getProjection, prepare) {
|
|
19
19
|
return defineTool({
|
|
20
20
|
name: "context_guard_rebind",
|
|
21
|
-
description: "Propose an exact source-clause partition
|
|
21
|
+
description: "Propose, query or withdraw an exact source-clause partition. Replacement requires durable root-user confirmation; adds no execution authority.",
|
|
22
22
|
parameters: {
|
|
23
23
|
operation: {
|
|
24
24
|
type: "string",
|
|
@@ -64,6 +64,64 @@ function createRebindTool(getProjection, prepare) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/tools/checkpoint-feedback.ts
|
|
69
|
+
/** Request-local display evidence. Never marks items passed or mints a certificate. */
|
|
70
|
+
function signingFeedback(p, bindings, result, selected) {
|
|
71
|
+
if (result.checkpoint || result.status !== "incomplete" || p.integrity !== "valid" || p.hostStatus !== "supported") return void 0;
|
|
72
|
+
const scope = new Set([...certificateClosure(p).itemIds, ...needsReviewObligations(p).map((item) => item.id)]);
|
|
73
|
+
const groups = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const binding of bindings) {
|
|
75
|
+
const bucket = groups.get(binding.itemId);
|
|
76
|
+
if (bucket) bucket.push(binding);
|
|
77
|
+
else groups.set(binding.itemId, [binding]);
|
|
78
|
+
}
|
|
79
|
+
const core = confirmedV6CoreDiagnostic(p);
|
|
80
|
+
const matched = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const [id, group] of groups) {
|
|
82
|
+
const item = p.items.get(id), binding = group[0];
|
|
83
|
+
if (core?.openIds.includes(id) || !item || !scope.has(id) || group.length !== 1 || item.legacyFlags?.length || item.needsReview || item.waitAuthorization || item.authorityDisposition === "conditional_wait" || item.actionPlan?.length || ancestorConstraintForBinding(p, item, binding.resolvedTarget) || result.rejectedBindings.some((row) => row.itemId === id) || !bindingIndividuallyAccepted(p, item, binding)) continue;
|
|
84
|
+
matched.add(id);
|
|
85
|
+
}
|
|
86
|
+
const remaining = new Set([...scope].filter((id) => !matched.has(id)));
|
|
87
|
+
if (core) for (const id of core.openIds) remaining.add(id);
|
|
88
|
+
for (const row of result.rejectedBindings) if (row.itemId !== "*") remaining.add(row.itemId);
|
|
89
|
+
const reasons = /* @__PURE__ */ new Map();
|
|
90
|
+
const sample = [];
|
|
91
|
+
const firstByReason = /* @__PURE__ */ new Set();
|
|
92
|
+
for (const id of remaining) {
|
|
93
|
+
const reason = core?.predicates[id] ?? "binding_not_matched";
|
|
94
|
+
reasons.set(reason, (reasons.get(reason) ?? 0) + 1);
|
|
95
|
+
if (sample.length < 4 && !firstByReason.has(reason)) {
|
|
96
|
+
sample.push({
|
|
97
|
+
id: id.length <= 128 ? id : `sha256:${sha256(id)}`,
|
|
98
|
+
reason_code: reason.slice(0, 160)
|
|
99
|
+
});
|
|
100
|
+
firstByReason.add(reason);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const unresolvedGlobal = result.rejectedBindings.some((row) => row.itemId === "*" && (row.reasonCode !== "current_closure_unmet" || core === void 0 || core.openIds.length === 0));
|
|
104
|
+
return {
|
|
105
|
+
matched,
|
|
106
|
+
remaining,
|
|
107
|
+
summary: {
|
|
108
|
+
scope: "whole_contract",
|
|
109
|
+
scope_total: scope.size,
|
|
110
|
+
matched_binding_total: matched.size,
|
|
111
|
+
remaining_total: remaining.size,
|
|
112
|
+
remaining_sample: sample,
|
|
113
|
+
remaining_reason_counts: [...reasons].slice(0, 8).map(([reason_code, count]) => ({
|
|
114
|
+
reason_code,
|
|
115
|
+
count
|
|
116
|
+
})),
|
|
117
|
+
folded_reason_count: Math.max(0, reasons.size - 8),
|
|
118
|
+
filtered_out_total: selected?.length ? [...remaining].filter((id) => !selected.includes(id)).length : 0,
|
|
119
|
+
unresolved_global: unresolvedGlobal,
|
|
120
|
+
next_step: matched.size ? "Retain matched bindings; do not repeat their effects or resubmit the same partial checkpoint. Resolve remaining closure/proof blockers. Omit item_ids and cursor to inspect the whole scope; keep bindings and filters unchanged when continuing a cursor." : "Resolve the remaining closure/proof blockers. Omit item_ids and cursor to inspect the whole scope; keep bindings and filters unchanged when continuing a cursor."
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
67
125
|
//#endregion
|
|
68
126
|
//#region src/tools/cursor-identity.ts
|
|
69
127
|
/** The Session observation watermark advances when a read-only Guard tool
|
|
@@ -134,7 +192,7 @@ function checkpointPage(p, query, full) {
|
|
|
134
192
|
if (item_ids?.length) {
|
|
135
193
|
rows.open_items = rows.open_items.filter((row) => item_ids.includes(String(row.id)));
|
|
136
194
|
rows.active_constraints = rows.active_constraints.filter((row) => item_ids.includes(String(row.id)));
|
|
137
|
-
rows.rejected_bindings = rows.rejected_bindings.filter((row) => item_ids.includes(String(row.item_id)));
|
|
195
|
+
rows.rejected_bindings = rows.rejected_bindings.filter((row) => row.item_id === "*" || item_ids.includes(String(row.item_id)));
|
|
138
196
|
}
|
|
139
197
|
if (evidence_ids?.length) rows.available_evidence = rows.available_evidence.filter((row) => evidence_ids.includes(String(row.id)));
|
|
140
198
|
if (lane && offset > rows[lane].length) return invalid("invalid_cursor_offset");
|
|
@@ -156,7 +214,11 @@ function checkpointPage(p, query, full) {
|
|
|
156
214
|
detail_offset,
|
|
157
215
|
detail_chunk: chunk,
|
|
158
216
|
next_detail_offset: detail_offset + chunk.length < data.length ? detail_offset + chunk.length : null,
|
|
159
|
-
snapshot: identity
|
|
217
|
+
snapshot: identity,
|
|
218
|
+
...full.signing_feedback ? {
|
|
219
|
+
blockers: full.signing_feedback,
|
|
220
|
+
certificate_status: "not_issued"
|
|
221
|
+
} : {}
|
|
160
222
|
};
|
|
161
223
|
}
|
|
162
224
|
const reasons = /* @__PURE__ */ new Map();
|
|
@@ -189,6 +251,10 @@ function checkpointPage(p, query, full) {
|
|
|
189
251
|
output.current_action_total = (full.current_actions ?? []).length;
|
|
190
252
|
output.certificate_status = "not_requested";
|
|
191
253
|
}
|
|
254
|
+
if (full.signing_feedback) {
|
|
255
|
+
Object.assign(output.blockers, full.signing_feedback);
|
|
256
|
+
output.certificate_status = "not_issued";
|
|
257
|
+
}
|
|
192
258
|
if (full.certificate) output.certificate = full.certificate;
|
|
193
259
|
if (full.proof_state) output.proof_state = full.proof_state;
|
|
194
260
|
const pagination = {
|
|
@@ -215,9 +281,16 @@ function checkpointPage(p, query, full) {
|
|
|
215
281
|
revision: row.revision,
|
|
216
282
|
kind: row.kind,
|
|
217
283
|
semantic_action: row.semantic_action,
|
|
284
|
+
status: row.status,
|
|
285
|
+
binding_status: row.binding_status,
|
|
218
286
|
next_step: typeof row.next_step === "string" ? row.next_step.slice(0, 240) : void 0,
|
|
219
287
|
adapter_disposition: row.adapter_disposition,
|
|
220
288
|
...row.binding_template !== void 0 ? { binding_template: row.binding_template } : {},
|
|
289
|
+
...row.expected_transition_template !== void 0 ? {
|
|
290
|
+
expected_transition_template: row.expected_transition_template,
|
|
291
|
+
binding_template_unavailable: row.binding_template_unavailable,
|
|
292
|
+
binding_guidance: row.binding_guidance
|
|
293
|
+
} : {},
|
|
221
294
|
omitted: true,
|
|
222
295
|
detail_id: lookup(String(row.id ?? row.item_id))
|
|
223
296
|
};
|
|
@@ -371,12 +444,22 @@ function bindingTemplate(projection, item) {
|
|
|
371
444
|
}
|
|
372
445
|
}
|
|
373
446
|
}
|
|
374
|
-
function openItemForTool(projection, item) {
|
|
447
|
+
function openItemForTool(projection, item, requestMatched = false, rejection) {
|
|
375
448
|
const action = item.semanticAction ?? "generic_run";
|
|
376
449
|
const spec = ACTION_MANIFEST.actions[action];
|
|
377
|
-
const template = bindingTemplate(projection, item);
|
|
378
450
|
const diagnosis = deriveItemDiagnosis(projection, item);
|
|
451
|
+
const evidenceGap = diagnosis.reason_code === "missing_evidence";
|
|
452
|
+
const matched = requestMatched && evidenceGap;
|
|
453
|
+
const bindingRejection = evidenceGap ? rejection : void 0;
|
|
454
|
+
const template = matched || !evidenceGap ? void 0 : bindingTemplate(projection, item);
|
|
455
|
+
const candidateHints = !matched && evidenceGap && !template && diagnosis.capability.actionSupported && spec.evidenceProducer === "supported" && !isStatefulAction(action) && !["generic_run", "inspect_remote_updates"].includes(action);
|
|
379
456
|
const compact = itemDiagnosis(projection, item);
|
|
457
|
+
const effectCandidates = !candidateHints ? [] : [...projection.evidence.values()].filter((e) => e.epoch === projection.epoch && e.outcome === "success" && e.semanticAction === action && (e.evidenceRole ?? "effect") === "effect").sort((a, b) => b.toolResultSeq - a.toolResultSeq).slice(0, 3).map((e) => ({
|
|
458
|
+
evidence_id: e.id,
|
|
459
|
+
resolved_target: targetForTool(e.resolvedTarget),
|
|
460
|
+
matches_captured_target: sameTuple(item.requestedTarget, pick$1(e.resolvedTarget ?? {}, Object.keys(item.requestedTarget ?? {})))
|
|
461
|
+
}));
|
|
462
|
+
const targetDiscrepancy = effectCandidates.length > 0 && effectCandidates.every((e) => !e.matches_captured_target);
|
|
380
463
|
return {
|
|
381
464
|
id: item.id,
|
|
382
465
|
revision: item.revision,
|
|
@@ -389,14 +472,15 @@ function openItemForTool(projection, item) {
|
|
|
389
472
|
semantic_action: action,
|
|
390
473
|
requested_target: targetForTool(item.requestedTarget),
|
|
391
474
|
certifiable: compact.certifiable,
|
|
392
|
-
reason_code: diagnosis.reason_code,
|
|
393
|
-
|
|
475
|
+
reason_code: matched ? "binding_matched_not_certified" : bindingRejection?.reasonCode ?? (targetDiscrepancy ? "successful_effect_target_mismatch" : diagnosis.reason_code),
|
|
476
|
+
...matched ? { binding_status: "matched_not_certified" } : {},
|
|
477
|
+
next_step: matched ? "This request binding matches individually; the item is still pending and no certificate was issued. Retain it while resolving the whole-contract blockers; do not repeat the effect or collect the same evidence again." : bindingRejection && template ? "Correct the rejected binding using binding_template unchanged; retain the successful effect and do not rerun it merely to repair binding syntax." : bindingRejection ? `${bindingRejection.reason.slice(0, 150)}. Inspect captured target and successful evidence before another action.` : targetDiscrepancy ? "Successful effect candidates target a different scope. Retain them and resolve the captured/effect target discrepancy against the root instruction before another action." : diagnosis.capability.remedy === "await_root_input" ? (diagnosis.next_action.resume_condition ?? capabilityRemedyPhrase(diagnosis.capability.remedy)).slice(0, 240) : capabilityRemedyPhrase(diagnosis.capability.remedy),
|
|
394
478
|
...item.targetCaptureStatus ? { target_capture_status: item.targetCaptureStatus } : {},
|
|
395
479
|
capability: {
|
|
396
480
|
action_supported: diagnosis.capability.actionSupported,
|
|
397
481
|
certifiable: diagnosis.capability.certifiable,
|
|
398
482
|
gap: diagnosis.capability.gap,
|
|
399
|
-
remedy: diagnosis.capability.remedy
|
|
483
|
+
remedy: matched ? "none" : diagnosis.capability.remedy
|
|
400
484
|
},
|
|
401
485
|
producer_disposition: ACTION_MANIFEST.actions[action].evidenceProducer,
|
|
402
486
|
...item.targetCaptureReasonCode ? { target_capture_reason_code: item.targetCaptureReasonCode } : {},
|
|
@@ -408,7 +492,18 @@ function openItemForTool(projection, item) {
|
|
|
408
492
|
observed_state_keys: spec.observedStateKeys,
|
|
409
493
|
pred_params_kind: "inline"
|
|
410
494
|
},
|
|
411
|
-
...template ? { binding_template: template } : {}
|
|
495
|
+
...template ? { binding_template: template } : {},
|
|
496
|
+
...candidateHints ? {
|
|
497
|
+
expected_transition_template: expectedTransitionForTool({
|
|
498
|
+
predicateId: spec.predicateId,
|
|
499
|
+
version: 1,
|
|
500
|
+
predParamsKind: "inline",
|
|
501
|
+
parameters: expectedParameters(action, {}, {})
|
|
502
|
+
}),
|
|
503
|
+
binding_template_unavailable: "no_matching_successful_effect",
|
|
504
|
+
successful_effect_candidates: effectCandidates,
|
|
505
|
+
binding_guidance: "Retain successful effects. Resolve any captured/effect target discrepancy against the root instruction before further execution; do not guess predicate parameters or rerun a successful test to repair binding syntax."
|
|
506
|
+
} : {}
|
|
412
507
|
};
|
|
413
508
|
}
|
|
414
509
|
function createCheckpointTool(getProjection, onRejected, prepare = async () => true, revalidate = () => {}) {
|
|
@@ -589,7 +684,7 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
|
|
|
589
684
|
current_action_total: { type: "integer" },
|
|
590
685
|
certificate_status: {
|
|
591
686
|
type: "string",
|
|
592
|
-
enum: ["not_requested"]
|
|
687
|
+
enum: ["not_requested", "not_issued"]
|
|
593
688
|
},
|
|
594
689
|
contract_revision: { type: "integer" },
|
|
595
690
|
active_constraints: {
|
|
@@ -907,12 +1002,17 @@ function createCheckpointTool(getProjection, onRejected, prepare = async () => t
|
|
|
907
1002
|
})) } : {}
|
|
908
1003
|
} } : {}
|
|
909
1004
|
}));
|
|
1005
|
+
const feedback = signingFeedback(projection, bindings, result, args.item_ids);
|
|
910
1006
|
return checkpointPage(projection, args, {
|
|
1007
|
+
...feedback ? {
|
|
1008
|
+
signing_feedback: feedback.summary,
|
|
1009
|
+
certificate_status: "not_issued"
|
|
1010
|
+
} : {},
|
|
911
1011
|
status: result.status,
|
|
912
1012
|
proof_state: proofState,
|
|
913
1013
|
contract_revision: result.contractRevision,
|
|
914
|
-
blocking_total: result.openItems.length,
|
|
915
|
-
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)),
|
|
1014
|
+
blocking_total: feedback ? Math.max(feedback.remaining.size, feedback.summary.unresolved_global ? 1 : 0) : result.openItems.length,
|
|
1015
|
+
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, feedback?.matched.has(item.id), result.rejectedBindings.find((row) => row.itemId === item.id))),
|
|
916
1016
|
active_constraints: [...projection.items.values()].filter((item) => item.kind === "prohibition" && item.status === "pending").map((item) => openItemForTool(projection, item)),
|
|
917
1017
|
available_evidence,
|
|
918
1018
|
available_qualifications: availableBoundaryQualifications(projection).map((row) => ({
|
|
@@ -1402,7 +1502,7 @@ function discoveryItemIds(p) {
|
|
|
1402
1502
|
function createPrepareTool(options) {
|
|
1403
1503
|
return defineTool({
|
|
1404
1504
|
name: "context_guard_prepare",
|
|
1405
|
-
description: "Read-only diagnosis
|
|
1505
|
+
description: "Read-only contract diagnosis; grants no permission. Use host tools for ordinary work. Omit item_id to list items; page_cursor continues.",
|
|
1406
1506
|
parameters: {
|
|
1407
1507
|
item_id: { type: "string" },
|
|
1408
1508
|
item_revision: { type: "number" },
|
|
@@ -1796,7 +1896,7 @@ function itemExtent(spans) {
|
|
|
1796
1896
|
function createInterpretTool(options) {
|
|
1797
1897
|
return defineTool({
|
|
1798
1898
|
name: "context_guard_interpret",
|
|
1799
|
-
description: "Record
|
|
1899
|
+
description: "Record unresolved input: use an item ID from context_guard_prepare and partition information_spans/unknown_spans (unneeded for attached assets). Bookkeeping only; no action or authority. Information closes on this turn's host-confirmed final answer; unknown stays pending. Correctness stays uncertified.",
|
|
1800
1900
|
parameters: {
|
|
1801
1901
|
item_id: {
|
|
1802
1902
|
type: "string",
|
|
@@ -3043,7 +3143,7 @@ function createActionTool(options = {}) {
|
|
|
3043
3143
|
const roots = normalizedRoots(options);
|
|
3044
3144
|
return defineTool({
|
|
3045
3145
|
name: ACTION_TOOL,
|
|
3046
|
-
description: "
|
|
3146
|
+
description: "Legacy compatibility: ordinary mutations return migration diagnostics without effects. Only explicitly adopted registry publication uses the controlled path.",
|
|
3047
3147
|
parameters: {
|
|
3048
3148
|
semantic_action: {
|
|
3049
3149
|
type: "string",
|
|
@@ -3607,7 +3707,7 @@ const RELEASE_TOOL = "context_guard_release";
|
|
|
3607
3707
|
function createReleaseTool(options) {
|
|
3608
3708
|
return defineTool({
|
|
3609
3709
|
name: RELEASE_TOOL,
|
|
3610
|
-
description: "Read
|
|
3710
|
+
description: "Read release state or reconcile an unknown effect through external identity readback. Never re-sends a release.",
|
|
3611
3711
|
parameters: {
|
|
3612
3712
|
operation: {
|
|
3613
3713
|
type: "string",
|
|
@@ -3744,7 +3844,7 @@ const record = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
3744
3844
|
function createNativeFileObserver(host) {
|
|
3745
3845
|
return defineTool({
|
|
3746
3846
|
name: "context_guard_observe_file",
|
|
3747
|
-
description: "Read back
|
|
3847
|
+
description: "Read back exact UTF-8 bytes after a persisted native write/edit. Does not modify files or authorize edits.",
|
|
3748
3848
|
parameters: { effect_call_id: {
|
|
3749
3849
|
type: "string",
|
|
3750
3850
|
required: true
|
|
@@ -4013,14 +4113,19 @@ function createNativeGitObserver(host) {
|
|
|
4013
4113
|
const message = record(result.message);
|
|
4014
4114
|
const source = record(message?.source);
|
|
4015
4115
|
if (source?.kind !== "tool" || source.callId !== args.effect_call_id) return missing("native_effect_missing");
|
|
4016
|
-
const resultStatus = persistedToolResultStatus(result, args.effect_call_id);
|
|
4017
|
-
if (resultStatus !== "clean") return missing(resultStatus === "failure" ? "native_effect_failed" : "native_effect_untrusted");
|
|
4018
4116
|
let effectArgs;
|
|
4019
4117
|
try {
|
|
4020
4118
|
effectArgs = record(JSON.parse(String(call.arguments)));
|
|
4021
4119
|
} catch {
|
|
4022
4120
|
return missing("native_effect_arguments_invalid");
|
|
4023
4121
|
}
|
|
4122
|
+
const resultStatus = persistedToolResultStatus(result, args.effect_call_id);
|
|
4123
|
+
if (resultStatus !== "clean") return missing(resultStatus === "failure" ? "native_effect_failed" : "native_effect_untrusted");
|
|
4124
|
+
const readbackOutcome = shellReadbackOutcome(call.name, effectArgs ?? {}, {
|
|
4125
|
+
meta: result.meta,
|
|
4126
|
+
textContent: Array.isArray(message?.content) ? extractTextContent(message.content) : ""
|
|
4127
|
+
});
|
|
4128
|
+
if (readbackOutcome !== "success") return missing(readbackOutcome === "failure" ? "native_effect_failed" : "native_effect_untrusted");
|
|
4024
4129
|
const command = effectArgs?.command;
|
|
4025
4130
|
const repository = effectArgs?.workdir;
|
|
4026
4131
|
if (typeof command !== "string" || typeof repository !== "string" || !repository) return missing("native_git_target_missing");
|
|
@@ -4031,6 +4136,7 @@ function createNativeGitObserver(host) {
|
|
|
4031
4136
|
if (action !== "commit" && action !== "push") return missing("native_git_command_unsupported");
|
|
4032
4137
|
const gitAction = action;
|
|
4033
4138
|
const output = Array.isArray(message?.content) ? extractTextContent(message.content) : "";
|
|
4139
|
+
const combinedSignal = typeof AbortSignal.any === "function" ? AbortSignal.any([exec.signal, AbortSignal.timeout(2e4)]) : exec.signal ?? AbortSignal.timeout(2e4);
|
|
4034
4140
|
const git$1 = async (...query) => (await execFileAsync("git", [
|
|
4035
4141
|
"-C",
|
|
4036
4142
|
repository,
|
|
@@ -4038,7 +4144,8 @@ function createNativeGitObserver(host) {
|
|
|
4038
4144
|
], {
|
|
4039
4145
|
encoding: "utf8",
|
|
4040
4146
|
timeout: 5e3,
|
|
4041
|
-
maxBuffer: 1024 * 1024
|
|
4147
|
+
maxBuffer: 1024 * 1024,
|
|
4148
|
+
signal: combinedSignal
|
|
4042
4149
|
})).stdout.trim();
|
|
4043
4150
|
try {
|
|
4044
4151
|
const top = await git$1("rev-parse", "--show-toplevel");
|
|
@@ -4049,8 +4156,17 @@ function createNativeGitObserver(host) {
|
|
|
4049
4156
|
if (!/^[0-9a-f]{40,64}$/.test(postOid)) return missing("native_git_readback_unavailable");
|
|
4050
4157
|
if (action === "commit") {
|
|
4051
4158
|
if (argv.length !== 4 || argv[2] !== "-m" || !argv[3] || !(/* @__PURE__ */ new RegExp(`\\b${postOid.slice(0, 7)}[0-9a-f]*\\b`)).test(output)) return missing("native_git_effect_output_unbound");
|
|
4052
|
-
const
|
|
4053
|
-
const
|
|
4159
|
+
const headerLines = (await git$1("--no-replace-objects", "cat-file", "commit", postOid)).split("\n");
|
|
4160
|
+
const headerEnd = headerLines.indexOf("");
|
|
4161
|
+
const header = (headerEnd >= 0 ? headerLines.slice(0, headerEnd) : headerLines).filter(Boolean);
|
|
4162
|
+
const treeLine = header.find((line) => line.startsWith("tree "));
|
|
4163
|
+
const parentLines = header.filter((line) => line.startsWith("parent "));
|
|
4164
|
+
const objectOid = /^[0-9a-f]{40,64}$/.test(treeLine?.slice(5).trim() ?? "") ? treeLine.slice(5).trim() : void 0;
|
|
4165
|
+
if (!treeLine || objectOid === void 0) return missing("native_git_readback_unavailable");
|
|
4166
|
+
const treeOid = objectOid;
|
|
4167
|
+
const parents = parentLines.map((line) => line.slice(7).trim());
|
|
4168
|
+
if (parents.some((oid$1) => !/^[0-9a-f]{40,64}$/.test(oid$1))) return missing("native_git_readback_unavailable");
|
|
4169
|
+
const parentOid = parents.length === 0 ? NATIVE_GIT_ROOT_PARENT_OID : parents[0];
|
|
4054
4170
|
return {
|
|
4055
4171
|
status: "observed",
|
|
4056
4172
|
reason_code: "git_commit_observed",
|
|
@@ -5501,6 +5617,7 @@ const Config = Schema.object({
|
|
|
5501
5617
|
hostLockRuntimeRoot: Schema.string(),
|
|
5502
5618
|
hostLockProfileRoot: Schema.string(),
|
|
5503
5619
|
hostLockTrust: Schema.string(),
|
|
5620
|
+
hostLockDesktopDigest: Schema.string(),
|
|
5504
5621
|
hostLockPackages: Schema.array(Schema.object({
|
|
5505
5622
|
name: Schema.string().required(),
|
|
5506
5623
|
version: Schema.string(),
|
|
@@ -5529,7 +5646,7 @@ function resolveConfig(config) {
|
|
|
5529
5646
|
});
|
|
5530
5647
|
}
|
|
5531
5648
|
if (config.hostLockPlatform !== void 0 && config.hostLockPlatform !== "posix" && config.hostLockPlatform !== "windows") throw new TypeError("hostLockPlatform must be \"posix\" or \"windows\"");
|
|
5532
|
-
if (config.hostLockProfile !== void 0 && config.hostLockProfile !== "headless" && config.hostLockProfile !== "web") throw new TypeError("hostLockProfile must be \"headless\" or \"
|
|
5649
|
+
if (config.hostLockProfile !== void 0 && config.hostLockProfile !== "headless" && config.hostLockProfile !== "web" && config.hostLockProfile !== "desktop") throw new TypeError("hostLockProfile must be \"headless\", \"web\", or \"desktop\"");
|
|
5533
5650
|
const hasHostRows = hostLockPackages !== void 0;
|
|
5534
5651
|
if (hasHostRows !== (config.hostLockPlatform !== void 0) || hasHostRows !== (config.hostLockProfile !== void 0)) throw new TypeError("hostLockPackages, hostLockPlatform, and hostLockProfile must be injected together");
|
|
5535
5652
|
for (const key of [
|
|
@@ -5538,7 +5655,9 @@ function resolveConfig(config) {
|
|
|
5538
5655
|
"hostLockProfileRoot",
|
|
5539
5656
|
"hostLockTrust"
|
|
5540
5657
|
]) if (config[key] !== void 0 && (typeof config[key] !== "string" || !config[key])) throw new TypeError(`${key} must be a non-empty string`);
|
|
5658
|
+
if (config.hostLockDesktopDigest !== void 0 && (config.hostLockProfile !== "desktop" || typeof config.hostLockDesktopDigest !== "string" || !/^[a-f0-9]{64}$/.test(config.hostLockDesktopDigest))) throw new TypeError("hostLockDesktopDigest must be an injected Desktop SHA-256");
|
|
5541
5659
|
return {
|
|
5660
|
+
...typeof config.hostLockDesktopDigest === "string" ? { hostLockDesktopDigest: config.hostLockDesktopDigest } : {},
|
|
5542
5661
|
...typeof config.hostLockTrust === "string" ? { hostLockTrust: config.hostLockTrust } : {},
|
|
5543
5662
|
...typeof config.hostLockPolicy === "string" ? { hostLockPolicy: config.hostLockPolicy } : {},
|
|
5544
5663
|
...typeof config.hostLockRuntimeRoot === "string" ? { hostLockRuntimeRoot: config.hostLockRuntimeRoot } : {},
|
|
@@ -5580,7 +5699,6 @@ const contextDigest = (context, root) => sha256(canonical({
|
|
|
5580
5699
|
}));
|
|
5581
5700
|
const ledgerPath = (root, context) => join(root, `${sessionDigest(context.sessionId)}.jsonl`);
|
|
5582
5701
|
const anchorPath = (root) => join(root, "session-anchors.v1.jsonl");
|
|
5583
|
-
const rootLockPath = (root) => join(root, ".writer.lock");
|
|
5584
5702
|
const privateLedgerContractDigest = (contract) => {
|
|
5585
5703
|
if (!contract || typeof contract !== "object" || Array.isArray(contract)) return sha256(canonical(contract));
|
|
5586
5704
|
const { revokedAtSeq: _mutableRevocation,...immutable } = contract;
|
|
@@ -5603,14 +5721,23 @@ function ensureRoot(root) {
|
|
|
5603
5721
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("private_ledger_root_unsafe");
|
|
5604
5722
|
}
|
|
5605
5723
|
function openRegular(path, flags, mode = 384) {
|
|
5724
|
+
try {
|
|
5725
|
+
const entry = lstatSync(path);
|
|
5726
|
+
if (!entry.isFile() || entry.isSymbolicLink()) throw new Error("private_ledger_file_unsafe");
|
|
5727
|
+
} catch (error) {
|
|
5728
|
+
if (error.code !== "ENOENT") throw error;
|
|
5729
|
+
}
|
|
5606
5730
|
const fd = openSync(path, flags | (process.platform === "win32" ? 0 : constants.O_NOFOLLOW), mode);
|
|
5607
|
-
|
|
5608
|
-
|
|
5731
|
+
try {
|
|
5732
|
+
const stat = fstatSync(fd);
|
|
5733
|
+
const entry = lstatSync(path);
|
|
5734
|
+
if (!stat.isFile() || !entry.isFile() || entry.isSymbolicLink() || stat.dev !== entry.dev || stat.ino !== entry.ino) throw new Error("private_ledger_file_unsafe");
|
|
5735
|
+
if (process.platform !== "win32" && (stat.mode & 63) !== 0) fchmodSync(fd, 384);
|
|
5736
|
+
return fd;
|
|
5737
|
+
} catch (error) {
|
|
5609
5738
|
closeSync(fd);
|
|
5610
|
-
throw
|
|
5739
|
+
throw error;
|
|
5611
5740
|
}
|
|
5612
|
-
if (process.platform !== "win32" && (stat.mode & 63) !== 0) fchmodSync(fd, 384);
|
|
5613
|
-
return fd;
|
|
5614
5741
|
}
|
|
5615
5742
|
function writeAll(fd, text) {
|
|
5616
5743
|
const bytes = Buffer.from(text);
|
|
@@ -5656,6 +5783,369 @@ function readAnchors(root) {
|
|
|
5656
5783
|
}
|
|
5657
5784
|
return anchors;
|
|
5658
5785
|
}
|
|
5786
|
+
/**
|
|
5787
|
+
* The one full ledger read+chain-verify, shared by the read and append paths
|
|
5788
|
+
* (CG-083-PERF05: an append used to re-read the anchors AND the whole ledger
|
|
5789
|
+
* after already reading the anchors under the lock). Callers must already
|
|
5790
|
+
* hold the writer lock or accept the unlocked-read race the public API had.
|
|
5791
|
+
* Returns `undefined` for "ledger file missing" so callers can distinguish
|
|
5792
|
+
* the anchored-but-missing (damaged) case from an empty ledger.
|
|
5793
|
+
*/
|
|
5794
|
+
function readVerifiedLedgerRecords(root, context, session, expectedContext) {
|
|
5795
|
+
const path = ledgerPath(root, context);
|
|
5796
|
+
if (!existsSync(path)) return void 0;
|
|
5797
|
+
const fd = openRegular(path, constants.O_RDONLY);
|
|
5798
|
+
let raw;
|
|
5799
|
+
try {
|
|
5800
|
+
raw = readFileSync(fd, "utf8");
|
|
5801
|
+
} finally {
|
|
5802
|
+
closeSync(fd);
|
|
5803
|
+
}
|
|
5804
|
+
if (raw && !raw.endsWith("\n")) return void 0;
|
|
5805
|
+
const records = [];
|
|
5806
|
+
let prior = null;
|
|
5807
|
+
for (const [index, line] of raw.split("\n").filter(Boolean).entries()) {
|
|
5808
|
+
const value = JSON.parse(line);
|
|
5809
|
+
const { record_sha256,...unsigned } = value;
|
|
5810
|
+
if (value.version !== 1 || value.session_sha256 !== session || value.context_sha256 !== expectedContext || value.position !== index + 1 || value.prior_sha256 !== prior || ![
|
|
5811
|
+
"release_reservation",
|
|
5812
|
+
"release_settlement",
|
|
5813
|
+
"restart_intent"
|
|
5814
|
+
].includes(value.kind) || !value.payload || typeof value.payload !== "object" || Array.isArray(value.payload) || record_sha256 !== digestRecord(unsigned)) return void 0;
|
|
5815
|
+
records.push(value);
|
|
5816
|
+
prior = record_sha256;
|
|
5817
|
+
}
|
|
5818
|
+
return records;
|
|
5819
|
+
}
|
|
5820
|
+
/**
|
|
5821
|
+
* CG-083-F1 (revision 3): arbitration-log writer lock with optimistic
|
|
5822
|
+
* concurrency control. See docs/WRITER_LOCK_PROTOCOL.md "Revision 3".
|
|
5823
|
+
*
|
|
5824
|
+
* All authority decisions are records appended to `arbitration.log` (atomicity
|
|
5825
|
+
* assumptions A1/A2 in the doc). A claim carries `prev` — the holder nonce the
|
|
5826
|
+
* claimant observed — and the deterministic replay grants it only if the
|
|
5827
|
+
* current holder at its log position equals `prev`. Recovery appends
|
|
5828
|
+
* `evict {prev: deadNonce}` after an ESRCH probe; a delayed recoverer's record
|
|
5829
|
+
* is adjudicated against the CURRENT holder and simply has no effect when the
|
|
5830
|
+
* holder changed (L1/L2 structurally impossible: nothing is ever deleted from
|
|
5831
|
+
* or done to a pathname on the basis of an older observation).
|
|
5832
|
+
*
|
|
5833
|
+
* Bidirectional v2 upgrade barrier (L3): the v3 critical section also holds
|
|
5834
|
+
* the v2-shaped root `.writer.lock` (O_EXCL, version-2-shaped owner record).
|
|
5835
|
+
* A live v2 writer refuses (file exists); v3 refuses while a live/unknown v2
|
|
5836
|
+
* record exists; a v2 record whose pid answers ESRCH is removed by name (v2
|
|
5837
|
+
* has no self-recovery) and the migration proceeds.
|
|
5838
|
+
*/
|
|
5839
|
+
const ARBITRATION_NAME = "arbitration.log";
|
|
5840
|
+
const LEGACY_V2_LOCK_NAME = ".writer.lock";
|
|
5841
|
+
const PENDING_PREFIX = "pending.";
|
|
5842
|
+
const LOG_COMPACTION_RECORDS = 8192;
|
|
5843
|
+
function arbitrationPath(root) {
|
|
5844
|
+
return join(root, ARBITRATION_NAME);
|
|
5845
|
+
}
|
|
5846
|
+
function legacyV2Path(root) {
|
|
5847
|
+
return join(root, LEGACY_V2_LOCK_NAME);
|
|
5848
|
+
}
|
|
5849
|
+
/**
|
|
5850
|
+
* Parse the log (docs/WRITER_LOCK_PROTOCOL.md Revision 3.1, correction 3).
|
|
5851
|
+
* Unparseable lines — torn tails from a crash mid-append — are SKIPPED: they
|
|
5852
|
+
* never took effect, so skipping revokes nothing, and later complete records
|
|
5853
|
+
* stay visible (there is no truncation anywhere in the protocol, so a
|
|
5854
|
+
* truncate-and-revive race cannot exist). A final line without a trailing
|
|
5855
|
+
* newline is still replayed when it parses: it was fully written.
|
|
5856
|
+
*/
|
|
5857
|
+
function parseArbitration(root) {
|
|
5858
|
+
let raw;
|
|
5859
|
+
try {
|
|
5860
|
+
raw = readFileSync(arbitrationPath(root), "utf8");
|
|
5861
|
+
} catch {
|
|
5862
|
+
return [];
|
|
5863
|
+
}
|
|
5864
|
+
const records = [];
|
|
5865
|
+
for (const line of raw.split("\n")) {
|
|
5866
|
+
if (line === "") continue;
|
|
5867
|
+
try {
|
|
5868
|
+
const value = JSON.parse(line);
|
|
5869
|
+
if (value && value.v === 3 && (value.op === "claim" || value.op === "release" || value.op === "evict") && typeof value.nonce === "string" && value.nonce.length === 32 && typeof value.pid === "number" && Number.isSafeInteger(value.pid) && value.pid > 0 && typeof value.hostname === "string" && typeof value.created_at_epoch_ms === "number" && (value.prev === null || typeof value.prev === "string")) records.push(value);
|
|
5870
|
+
} catch {}
|
|
5871
|
+
}
|
|
5872
|
+
return records;
|
|
5873
|
+
}
|
|
5874
|
+
/**
|
|
5875
|
+
* Deterministic replay (docs/WRITER_LOCK_PROTOCOL.md Revision 3.1). A claim
|
|
5876
|
+
* is effective ONLY when the slot is empty: a claim recorded while another
|
|
5877
|
+
* holder is current (e.g. one whose barrier was stolen on a stale legacy
|
|
5878
|
+
* observation) is stored but INEFFECTIVE, and the claimant exits on its
|
|
5879
|
+
* read-back. Release/evict take effect only for the current holder nonce.
|
|
5880
|
+
*/
|
|
5881
|
+
function replayHolder(records) {
|
|
5882
|
+
let holder = null;
|
|
5883
|
+
for (const record$2 of records) {
|
|
5884
|
+
const isHolder = holder !== null && holder.nonce === record$2.prev;
|
|
5885
|
+
if (record$2.op === "claim") {
|
|
5886
|
+
if (holder === null) holder = {
|
|
5887
|
+
nonce: record$2.nonce,
|
|
5888
|
+
pid: record$2.pid,
|
|
5889
|
+
hostname: record$2.hostname,
|
|
5890
|
+
created_at_epoch_ms: record$2.created_at_epoch_ms
|
|
5891
|
+
};
|
|
5892
|
+
} else if (record$2.op === "release" || record$2.op === "evict") {
|
|
5893
|
+
if (isHolder) holder = null;
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
return holder;
|
|
5897
|
+
}
|
|
5898
|
+
function readHolder(root) {
|
|
5899
|
+
return replayHolder(parseArbitration(root));
|
|
5900
|
+
}
|
|
5901
|
+
/** Only ESRCH proves a recorded owner is gone; any other answer refuses. */
|
|
5902
|
+
function processExists(pid) {
|
|
5903
|
+
try {
|
|
5904
|
+
process.kill(pid, 0);
|
|
5905
|
+
return true;
|
|
5906
|
+
} catch (error) {
|
|
5907
|
+
return error.code !== "ESRCH";
|
|
5908
|
+
}
|
|
5909
|
+
}
|
|
5910
|
+
function unlinkIfExists(path) {
|
|
5911
|
+
try {
|
|
5912
|
+
unlinkSync(path);
|
|
5913
|
+
} catch (error) {
|
|
5914
|
+
if (error.code !== "ENOENT") throw error;
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5917
|
+
function appendArbitration(root, record$2) {
|
|
5918
|
+
let needsNewline = false;
|
|
5919
|
+
try {
|
|
5920
|
+
const raw = readFileSync(arbitrationPath(root), "utf8");
|
|
5921
|
+
needsNewline = raw.length > 0 && !raw.endsWith("\n");
|
|
5922
|
+
} catch {
|
|
5923
|
+
needsNewline = false;
|
|
5924
|
+
}
|
|
5925
|
+
const fd = openRegular(arbitrationPath(root), constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND, 384);
|
|
5926
|
+
try {
|
|
5927
|
+
writeAll(fd, `${needsNewline ? "\n" : ""}${canonical(record$2)}\n`);
|
|
5928
|
+
fsyncSync(fd);
|
|
5929
|
+
} finally {
|
|
5930
|
+
closeSync(fd);
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
function readLegacyV2(root) {
|
|
5934
|
+
let raw;
|
|
5935
|
+
try {
|
|
5936
|
+
raw = readFileSync(legacyV2Path(root), "utf8");
|
|
5937
|
+
} catch {
|
|
5938
|
+
return;
|
|
5939
|
+
}
|
|
5940
|
+
if (!raw.trim()) return "legacy";
|
|
5941
|
+
try {
|
|
5942
|
+
const value = JSON.parse(raw);
|
|
5943
|
+
if (value && (value.version === 2 || value.version === 3) && typeof value.nonce === "string" && (value.nonce.length === 32 || value.version === 2) && typeof value.pid === "number" && Number.isSafeInteger(value.pid) && value.pid > 0 && typeof value.hostname === "string" && typeof value.created_at_epoch_ms === "number") return value;
|
|
5944
|
+
} catch {}
|
|
5945
|
+
return "legacy";
|
|
5946
|
+
}
|
|
5947
|
+
function pendingPath(root, nonce) {
|
|
5948
|
+
return join(root, `${PENDING_PREFIX}${nonce}.json`);
|
|
5949
|
+
}
|
|
5950
|
+
function removeOwnPending(root, nonce) {
|
|
5951
|
+
unlinkIfExists(pendingPath(root, nonce));
|
|
5952
|
+
}
|
|
5953
|
+
function readRecord(path) {
|
|
5954
|
+
let raw;
|
|
5955
|
+
try {
|
|
5956
|
+
raw = readFileSync(path, "utf8");
|
|
5957
|
+
} catch {
|
|
5958
|
+
return;
|
|
5959
|
+
}
|
|
5960
|
+
if (!raw.trim()) return "legacy";
|
|
5961
|
+
try {
|
|
5962
|
+
const value = JSON.parse(raw);
|
|
5963
|
+
if (value && (value.version === 2 || value.version === 3) && typeof value.nonce === "string" && (value.nonce.length === 32 || value.version === 2) && typeof value.pid === "number" && Number.isSafeInteger(value.pid) && value.pid > 0 && typeof value.hostname === "string" && typeof value.created_at_epoch_ms === "number") return value;
|
|
5964
|
+
} catch {}
|
|
5965
|
+
return "legacy";
|
|
5966
|
+
}
|
|
5967
|
+
function recordIsProvablyDead(record$2) {
|
|
5968
|
+
return record$2.hostname === hostname() && !processExists(record$2.pid);
|
|
5969
|
+
}
|
|
5970
|
+
/** Append one arbitration record (the ONLY mutation of the log). */
|
|
5971
|
+
function appendLog(root, op, nonce, prev) {
|
|
5972
|
+
appendArbitration(root, {
|
|
5973
|
+
v: 3,
|
|
5974
|
+
op,
|
|
5975
|
+
nonce,
|
|
5976
|
+
prev,
|
|
5977
|
+
pid: process.pid,
|
|
5978
|
+
hostname: hostname(),
|
|
5979
|
+
created_at_epoch_ms: Date.now()
|
|
5980
|
+
});
|
|
5981
|
+
}
|
|
5982
|
+
/**
|
|
5983
|
+
* Acquire the writer lock. Returns the held nonce, or undefined when refused
|
|
5984
|
+
* (live/unknown holder, v2 barrier, or a lost optimistic claim).
|
|
5985
|
+
*/
|
|
5986
|
+
/**
|
|
5987
|
+
* Admission (Revision 3.1 + 3.2). The v2-compat barrier is PUBLISHED
|
|
5988
|
+
* atomically: the writer prepares `pending.<nonce>` with its complete owner
|
|
5989
|
+
* record (write + fsync) and hard-links it to `.writer.lock` — the barrier
|
|
5990
|
+
* therefore never exists with empty or partial content from this protocol
|
|
5991
|
+
* (S2 closed: a crash before the link leaves only an inert pending file, and
|
|
5992
|
+
* a crash after it leaves a complete record naming a provably dead owner,
|
|
5993
|
+
* both recoverable). A barrier left by a crashed V2 WRITER (empty, partial,
|
|
5994
|
+
* or complete-with-dead-pid) is classified separately: unknown owners refuse
|
|
5995
|
+
* with the documented manual migration; a complete same-host record whose
|
|
5996
|
+
* pid answers ESRCH is ADOPTED untouched. The log claim is effective only on
|
|
5997
|
+
* an empty slot and is verified by read-back.
|
|
5998
|
+
*/
|
|
5999
|
+
function acquireWriterLock(root) {
|
|
6000
|
+
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
6001
|
+
gcPendingFiles(root);
|
|
6002
|
+
let barrierState = "absent";
|
|
6003
|
+
const legacy = readLegacyV2(root);
|
|
6004
|
+
if (legacy !== void 0) if (legacy === "legacy") barrierState = "unknown";
|
|
6005
|
+
else if (legacy.hostname !== hostname()) barrierState = "unknown";
|
|
6006
|
+
else if (processExists(legacy.pid)) barrierState = "live";
|
|
6007
|
+
else barrierState = "adopted";
|
|
6008
|
+
if (barrierState === "unknown" || barrierState === "live") return void 0;
|
|
6009
|
+
const observed = readHolder(root);
|
|
6010
|
+
if (observed !== null) {
|
|
6011
|
+
if (observed.hostname !== hostname()) return void 0;
|
|
6012
|
+
if (!processExists(observed.pid)) {
|
|
6013
|
+
appendLog(root, "evict", randomBytes(16).toString("hex"), observed.nonce);
|
|
6014
|
+
continue;
|
|
6015
|
+
}
|
|
6016
|
+
return;
|
|
6017
|
+
}
|
|
6018
|
+
const nonce = randomBytes(16).toString("hex");
|
|
6019
|
+
let ownedBarrier = false;
|
|
6020
|
+
if (barrierState === "absent") {
|
|
6021
|
+
if (!publishBarrier(root, nonce)) continue;
|
|
6022
|
+
ownedBarrier = true;
|
|
6023
|
+
}
|
|
6024
|
+
appendLog(root, "claim", nonce, null);
|
|
6025
|
+
const holder = readHolder(root);
|
|
6026
|
+
if (holder !== null && holder.nonce === nonce) {
|
|
6027
|
+
compactLogInTenure(root, {
|
|
6028
|
+
nonce,
|
|
6029
|
+
ownedBarrier
|
|
6030
|
+
});
|
|
6031
|
+
return {
|
|
6032
|
+
nonce,
|
|
6033
|
+
ownedBarrier
|
|
6034
|
+
};
|
|
6035
|
+
}
|
|
6036
|
+
if (ownedBarrier) {
|
|
6037
|
+
const barrier = readLegacyV2(root);
|
|
6038
|
+
if (barrier !== void 0 && barrier !== "legacy" && barrier.nonce === nonce) unlinkIfExists(legacyV2Path(root));
|
|
6039
|
+
}
|
|
6040
|
+
unlinkIfExists(pendingPath(root, nonce));
|
|
6041
|
+
return;
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
/**
|
|
6045
|
+
* Publish the v2-compat barrier atomically (Revision 3.2): write the complete
|
|
6046
|
+
* owner record to a unique pending file (O_EXCL + fsync), then hard-link it
|
|
6047
|
+
* onto the barrier pathname. link() succeeds only when the barrier is absent,
|
|
6048
|
+
* and the linked content is the fully written record — the barrier can never
|
|
6049
|
+
* be observed empty or partial from this protocol. Returns false when the
|
|
6050
|
+
* barrier exists (caller re-classifies).
|
|
6051
|
+
*/
|
|
6052
|
+
function publishBarrier(root, nonce) {
|
|
6053
|
+
const pending = pendingPath(root, nonce);
|
|
6054
|
+
let fd;
|
|
6055
|
+
try {
|
|
6056
|
+
fd = openRegular(pending, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
|
|
6057
|
+
} catch (error) {
|
|
6058
|
+
if (error.code === "EEXIST") return false;
|
|
6059
|
+
throw error;
|
|
6060
|
+
}
|
|
6061
|
+
try {
|
|
6062
|
+
const record$2 = {
|
|
6063
|
+
version: 2,
|
|
6064
|
+
nonce,
|
|
6065
|
+
pid: process.pid,
|
|
6066
|
+
hostname: hostname(),
|
|
6067
|
+
created_at_epoch_ms: Date.now()
|
|
6068
|
+
};
|
|
6069
|
+
writeAll(fd, `${canonical(record$2)}\n`);
|
|
6070
|
+
fsyncSync(fd);
|
|
6071
|
+
} finally {
|
|
6072
|
+
try {
|
|
6073
|
+
closeSync(fd);
|
|
6074
|
+
} catch {}
|
|
6075
|
+
}
|
|
6076
|
+
try {
|
|
6077
|
+
linkSync(pending, legacyV2Path(root));
|
|
6078
|
+
return true;
|
|
6079
|
+
} catch (error) {
|
|
6080
|
+
unlinkIfExists(pending);
|
|
6081
|
+
if (error.code === "EEXIST") return false;
|
|
6082
|
+
throw error;
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
/** GC pending files whose embedded creator is provably dead (by name). */
|
|
6086
|
+
function gcPendingFiles(root) {
|
|
6087
|
+
let entries;
|
|
6088
|
+
try {
|
|
6089
|
+
entries = readdirSync(root);
|
|
6090
|
+
} catch {
|
|
6091
|
+
return;
|
|
6092
|
+
}
|
|
6093
|
+
for (const entry of entries) {
|
|
6094
|
+
if (!entry.startsWith(PENDING_PREFIX)) continue;
|
|
6095
|
+
const record$2 = readRecord(join(root, entry));
|
|
6096
|
+
if (record$2 !== void 0 && record$2 !== "legacy" && recordIsProvablyDead(record$2)) unlinkIfExists(join(root, entry));
|
|
6097
|
+
}
|
|
6098
|
+
}
|
|
6099
|
+
/**
|
|
6100
|
+
* Release (Revision 3.1): append the release record, then remove ONLY a
|
|
6101
|
+
* barrier this writer created in its own critical section (nonce-conditional).
|
|
6102
|
+
* An ADOPTED legacy barrier is left untouched — it keeps blocking v2 writers
|
|
6103
|
+
* until the documented manual migration removes it, and every later v3
|
|
6104
|
+
* admission re-adopts it.
|
|
6105
|
+
*/
|
|
6106
|
+
function releaseWriterLock(root, held) {
|
|
6107
|
+
if (held === void 0) return;
|
|
6108
|
+
appendLog(root, "release", held.nonce, held.nonce);
|
|
6109
|
+
if (held.ownedBarrier) {
|
|
6110
|
+
const barrier = readLegacyV2(root);
|
|
6111
|
+
if (barrier !== void 0 && barrier !== "legacy" && barrier.nonce === held.nonce) unlinkIfExists(legacyV2Path(root));
|
|
6112
|
+
}
|
|
6113
|
+
removeOwnPending(root, held.nonce);
|
|
6114
|
+
}
|
|
6115
|
+
/** Compaction threshold; test-overridable via the internals hook. */
|
|
6116
|
+
let compactionRecordThreshold = LOG_COMPACTION_RECORDS;
|
|
6117
|
+
/**
|
|
6118
|
+
* Compaction (Revision 3.1, correction 4): performed ONLY by the current
|
|
6119
|
+
* holder inside its verified critical section. During a live holder's tenure
|
|
6120
|
+
* no concurrent record can be effective (claims need an empty slot; evicts
|
|
6121
|
+
* need a dead holder), so the rename window cannot drop an effective record:
|
|
6122
|
+
* the baseline re-installs the holder, and readers see the old or the new
|
|
6123
|
+
* file, both replaying to the same holder. State-equivalence is asserted by
|
|
6124
|
+
* the lock tests.
|
|
6125
|
+
*/
|
|
6126
|
+
function compactLogInTenure(root, held) {
|
|
6127
|
+
if (parseArbitration(root).length <= compactionRecordThreshold) return;
|
|
6128
|
+
const baseline = {
|
|
6129
|
+
v: 3,
|
|
6130
|
+
op: "claim",
|
|
6131
|
+
nonce: held.nonce,
|
|
6132
|
+
pid: process.pid,
|
|
6133
|
+
hostname: hostname(),
|
|
6134
|
+
created_at_epoch_ms: Date.now(),
|
|
6135
|
+
prev: null
|
|
6136
|
+
};
|
|
6137
|
+
const tmp = join(root, `${ARBITRATION_NAME}.compact`);
|
|
6138
|
+
const fd = openSync(tmp, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC, 384);
|
|
6139
|
+
try {
|
|
6140
|
+
writeAll(fd, `${canonical(baseline)}\n`);
|
|
6141
|
+
fsyncSync(fd);
|
|
6142
|
+
} finally {
|
|
6143
|
+
closeSync(fd);
|
|
6144
|
+
}
|
|
6145
|
+
try {
|
|
6146
|
+
renameSync(tmp, arbitrationPath(root));
|
|
6147
|
+
} catch {}
|
|
6148
|
+
}
|
|
5659
6149
|
function readPrivateLedger(root, input) {
|
|
5660
6150
|
if (!root) return {
|
|
5661
6151
|
records: [],
|
|
@@ -5683,46 +6173,24 @@ function readPrivateLedger(root, input) {
|
|
|
5683
6173
|
damaged: true,
|
|
5684
6174
|
anchored: true
|
|
5685
6175
|
};
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
const fd = openRegular(path, constants.O_RDONLY);
|
|
5698
|
-
let raw;
|
|
5699
|
-
try {
|
|
5700
|
-
raw = readFileSync(fd, "utf8");
|
|
5701
|
-
} finally {
|
|
5702
|
-
closeSync(fd);
|
|
6176
|
+
if (anchored === void 0) {
|
|
6177
|
+
if (existsSync(ledgerPath(root, context))) return {
|
|
6178
|
+
records: [],
|
|
6179
|
+
damaged: true,
|
|
6180
|
+
anchored: false
|
|
6181
|
+
};
|
|
6182
|
+
return {
|
|
6183
|
+
records: [],
|
|
6184
|
+
damaged: false,
|
|
6185
|
+
anchored: false
|
|
6186
|
+
};
|
|
5703
6187
|
}
|
|
5704
|
-
|
|
6188
|
+
const records = readVerifiedLedgerRecords(root, context, session, expectedContext);
|
|
6189
|
+
if (records === void 0) return {
|
|
5705
6190
|
records: [],
|
|
5706
6191
|
damaged: true,
|
|
5707
6192
|
anchored: true
|
|
5708
6193
|
};
|
|
5709
|
-
const records = [];
|
|
5710
|
-
let prior = null;
|
|
5711
|
-
for (const [index, line] of raw.split("\n").filter(Boolean).entries()) {
|
|
5712
|
-
const value = JSON.parse(line);
|
|
5713
|
-
const { record_sha256,...unsigned } = value;
|
|
5714
|
-
if (value.version !== 1 || value.session_sha256 !== session || value.context_sha256 !== expectedContext || value.position !== index + 1 || value.prior_sha256 !== prior || ![
|
|
5715
|
-
"release_reservation",
|
|
5716
|
-
"release_settlement",
|
|
5717
|
-
"restart_intent"
|
|
5718
|
-
].includes(value.kind) || !value.payload || typeof value.payload !== "object" || Array.isArray(value.payload) || record_sha256 !== digestRecord(unsigned)) return {
|
|
5719
|
-
records: [],
|
|
5720
|
-
damaged: true,
|
|
5721
|
-
anchored: true
|
|
5722
|
-
};
|
|
5723
|
-
records.push(value);
|
|
5724
|
-
prior = record_sha256;
|
|
5725
|
-
}
|
|
5726
6194
|
return {
|
|
5727
6195
|
records,
|
|
5728
6196
|
damaged: false,
|
|
@@ -5743,7 +6211,8 @@ function initializePrivateLedger(root, input) {
|
|
|
5743
6211
|
try {
|
|
5744
6212
|
const context = normalizeContext(input);
|
|
5745
6213
|
ensureRoot(root);
|
|
5746
|
-
lockFd =
|
|
6214
|
+
lockFd = acquireWriterLock(root);
|
|
6215
|
+
if (lockFd === void 0) return false;
|
|
5747
6216
|
const session = sessionDigest(context.sessionId);
|
|
5748
6217
|
const expectedContext = contextDigest(context, root);
|
|
5749
6218
|
const anchored = readAnchors(root).get(session);
|
|
@@ -5774,14 +6243,7 @@ function initializePrivateLedger(root, input) {
|
|
|
5774
6243
|
} catch {
|
|
5775
6244
|
return false;
|
|
5776
6245
|
} finally {
|
|
5777
|
-
|
|
5778
|
-
try {
|
|
5779
|
-
closeSync(lockFd);
|
|
5780
|
-
} catch {}
|
|
5781
|
-
try {
|
|
5782
|
-
unlinkSync(rootLockPath(root));
|
|
5783
|
-
} catch {}
|
|
5784
|
-
}
|
|
6246
|
+
releaseWriterLock(root, lockFd);
|
|
5785
6247
|
}
|
|
5786
6248
|
}
|
|
5787
6249
|
function appendPrivateLedger(root, input, kind, payload) {
|
|
@@ -5790,17 +6252,18 @@ function appendPrivateLedger(root, input, kind, payload) {
|
|
|
5790
6252
|
try {
|
|
5791
6253
|
const context = normalizeContext(input);
|
|
5792
6254
|
ensureRoot(root);
|
|
5793
|
-
lockFd =
|
|
6255
|
+
lockFd = acquireWriterLock(root);
|
|
6256
|
+
if (lockFd === void 0) return false;
|
|
5794
6257
|
const session = sessionDigest(context.sessionId);
|
|
5795
6258
|
const expectedContext = contextDigest(context, root);
|
|
5796
6259
|
const anchored = readAnchors(root).get(session);
|
|
5797
6260
|
if (anchored !== void 0 && anchored !== expectedContext) return false;
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
6261
|
+
let records = [];
|
|
6262
|
+
if (anchored !== void 0) {
|
|
6263
|
+
const verified = readVerifiedLedgerRecords(root, context, session, expectedContext);
|
|
6264
|
+
if (verified === void 0) return false;
|
|
6265
|
+
records = verified;
|
|
6266
|
+
} else {
|
|
5804
6267
|
const unsignedAnchor = {
|
|
5805
6268
|
version: 1,
|
|
5806
6269
|
session_sha256: session,
|
|
@@ -5820,7 +6283,7 @@ function appendPrivateLedger(root, input, kind, payload) {
|
|
|
5820
6283
|
}
|
|
5821
6284
|
if (kind === "release_settlement") {
|
|
5822
6285
|
const settlement = normalizeSettlement(payload);
|
|
5823
|
-
const reservation = settlement && [...
|
|
6286
|
+
const reservation = settlement && [...records].reverse().find((record$3) => record$3.kind === "release_reservation" && record$3.payload.contractId === settlement.contractId && record$3.payload.operation === settlement.operation && record$3.payload.callId === settlement.callId);
|
|
5824
6287
|
if (!reservation) return false;
|
|
5825
6288
|
payload = {
|
|
5826
6289
|
...payload,
|
|
@@ -5833,8 +6296,8 @@ function appendPrivateLedger(root, input, kind, payload) {
|
|
|
5833
6296
|
version: 1,
|
|
5834
6297
|
session_sha256: session,
|
|
5835
6298
|
context_sha256: expectedContext,
|
|
5836
|
-
position:
|
|
5837
|
-
prior_sha256:
|
|
6299
|
+
position: records.length + 1,
|
|
6300
|
+
prior_sha256: records.at(-1)?.record_sha256 ?? null,
|
|
5838
6301
|
kind,
|
|
5839
6302
|
payload
|
|
5840
6303
|
};
|
|
@@ -5854,14 +6317,7 @@ function appendPrivateLedger(root, input, kind, payload) {
|
|
|
5854
6317
|
} catch {
|
|
5855
6318
|
return false;
|
|
5856
6319
|
} finally {
|
|
5857
|
-
|
|
5858
|
-
try {
|
|
5859
|
-
closeSync(lockFd);
|
|
5860
|
-
} catch {}
|
|
5861
|
-
try {
|
|
5862
|
-
unlinkSync(rootLockPath(root));
|
|
5863
|
-
} catch {}
|
|
5864
|
-
}
|
|
6320
|
+
releaseWriterLock(root, lockFd);
|
|
5865
6321
|
}
|
|
5866
6322
|
}
|
|
5867
6323
|
function applyPrivateLedger(projection, snapshot$1) {
|
|
@@ -6303,20 +6759,80 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
|
|
|
6303
6759
|
let synchronizedOnce = false;
|
|
6304
6760
|
const continuationAttempts = projection.continuationAttempts;
|
|
6305
6761
|
const persistenceCorrectionAttempts = projection.persistenceCorrectionAttempts;
|
|
6306
|
-
|
|
6762
|
+
let refreshEpoch = 0;
|
|
6763
|
+
let lastFullSync;
|
|
6764
|
+
const sameSnapshotIdentity = (candidate, consumed) => {
|
|
6765
|
+
if (candidate.length !== consumed.length) return false;
|
|
6766
|
+
for (let index = 0; index < candidate.length; index += 1) if (candidate[index] !== consumed[index]) return false;
|
|
6767
|
+
return true;
|
|
6768
|
+
};
|
|
6769
|
+
/**
|
|
6770
|
+
* F2/R1: element-wise identity alone cannot protect the fast path when the
|
|
6771
|
+
* session view hands out the SAME MUTABLE array (or mutably nested event
|
|
6772
|
+
* objects): the saved reference compares equal to itself while its content
|
|
6773
|
+
* drifts. The adapter only validates envelopes, not immutability, so the
|
|
6774
|
+
* fast path is served ONLY for snapshots whose immutability is PROVABLE:
|
|
6775
|
+
* every container in the snapshot is Object.isFrozen, checked recursively
|
|
6776
|
+
* under a node budget. A snapshot that fails (or exhausts) the check is
|
|
6777
|
+
* "not provable" and always takes the full rebuild — fail-closed, never
|
|
6778
|
+
* cached. Passing results are remembered per array object: freezing is
|
|
6779
|
+
* irreversible, so a previously proven snapshot cannot become mutable.
|
|
6780
|
+
*/
|
|
6781
|
+
const provenImmutableSnapshots = /* @__PURE__ */ new WeakSet();
|
|
6782
|
+
const IMMUTABILITY_PROOF_NODE_BUDGET = 4e5;
|
|
6783
|
+
/**
|
|
6784
|
+
* F2 (round 3): freezing a container does NOT freeze its content when a
|
|
6785
|
+
* property is an accessor — a frozen getter's return value follows its
|
|
6786
|
+
* closure. Eligibility therefore requires every property to be an ordinary
|
|
6787
|
+
* DATA descriptor whose value is itself provably immutable. Accessor
|
|
6788
|
+
* descriptors (get/set) make the whole snapshot unprovable: full rebuild,
|
|
6789
|
+
* never cached. The value of a data descriptor is read for the recursion
|
|
6790
|
+
* only — a single read is not treated as a permanent immutability proof of
|
|
6791
|
+
* anything mutable; only the descriptor SHAPE plus frozen containers are.
|
|
6792
|
+
*/
|
|
6793
|
+
const snapshotIsProvablyImmutable = (events) => {
|
|
6794
|
+
if (provenImmutableSnapshots.has(events)) return true;
|
|
6795
|
+
let visited = 0;
|
|
6796
|
+
const walk = (value) => {
|
|
6797
|
+
if (value === null || typeof value !== "object") return true;
|
|
6798
|
+
if (++visited > IMMUTABILITY_PROOF_NODE_BUDGET) return false;
|
|
6799
|
+
if (!Object.isFrozen(value)) return false;
|
|
6800
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
6801
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
6802
|
+
if (!descriptor) return false;
|
|
6803
|
+
if (descriptor.get !== void 0 || descriptor.set !== void 0) return false;
|
|
6804
|
+
if (descriptor.value !== void 0 && !walk(descriptor.value)) return false;
|
|
6805
|
+
}
|
|
6806
|
+
return true;
|
|
6807
|
+
};
|
|
6808
|
+
if (!walk(events)) return false;
|
|
6809
|
+
provenImmutableSnapshots.add(events);
|
|
6810
|
+
return true;
|
|
6811
|
+
};
|
|
6812
|
+
const readGoalOnce = () => {
|
|
6813
|
+
if (!readGoalState) return {
|
|
6814
|
+
state: void 0,
|
|
6815
|
+
failed: false
|
|
6816
|
+
};
|
|
6817
|
+
try {
|
|
6818
|
+
return {
|
|
6819
|
+
state: normalizeGoalState(readGoalState()),
|
|
6820
|
+
failed: false
|
|
6821
|
+
};
|
|
6822
|
+
} catch {
|
|
6823
|
+
return {
|
|
6824
|
+
state: void 0,
|
|
6825
|
+
failed: true
|
|
6826
|
+
};
|
|
6827
|
+
}
|
|
6828
|
+
};
|
|
6829
|
+
const goalKeyOf = (goal) => goal.failed ? "error" : goal.state ? `${goal.state.id}\0${goal.state.revision}\0${goal.state.phase}\0${goal.state.activation}` : "none";
|
|
6830
|
+
const ledgerSnapshotKey = (snapshot$1) => `${snapshot$1.records.length}\0${snapshot$1.records.at(-1)?.record_sha256 ?? ""}\0${snapshot$1.damaged ? "d" : "-"}\0${snapshot$1.anchored ? "a" : "-"}`;
|
|
6831
|
+
const rebuild = (events, goal, ledger) => {
|
|
6307
6832
|
const previousReleaseContracts = projection.releaseContracts.length;
|
|
6308
6833
|
const header = session.header;
|
|
6309
6834
|
const priorRecoveryDigest = projection.lastRecoveryDigest;
|
|
6310
6835
|
const sessionHeader = sessionHeaderForDigest(session);
|
|
6311
|
-
let events;
|
|
6312
|
-
try {
|
|
6313
|
-
events = snapshotSessionEvents(session);
|
|
6314
|
-
} catch (error) {
|
|
6315
|
-
const code = error instanceof SessionApiError ? error.code : "session_snapshot_failed";
|
|
6316
|
-
projection.integrity = "unknown";
|
|
6317
|
-
if (!projection.integrityViolations.includes(code)) projection.integrityViolations.push(code);
|
|
6318
|
-
return;
|
|
6319
|
-
}
|
|
6320
6836
|
const derived = deriveProjection(events, {
|
|
6321
6837
|
activation: config.activation,
|
|
6322
6838
|
policy: config.policy
|
|
@@ -6325,8 +6841,8 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
|
|
|
6325
6841
|
sessionHeader
|
|
6326
6842
|
}, durabilityConfirmed, hostLock);
|
|
6327
6843
|
Object.assign(projection, derived.projection);
|
|
6328
|
-
if (readPrivateRecords) {
|
|
6329
|
-
let privateSnapshot =
|
|
6844
|
+
if (readPrivateRecords && ledger) {
|
|
6845
|
+
let privateSnapshot = ledger;
|
|
6330
6846
|
if (!synchronizedOnce && events.length === 0 && !privateSnapshot.damaged && !privateSnapshot.anchored && initializePrivateRecords?.()) privateSnapshot = readPrivateRecords();
|
|
6331
6847
|
if (projection.releaseContracts.length > 0 && !privateSnapshot.damaged && !privateSnapshot.anchored) if (synchronizedOnce && previousReleaseContracts === 0 && initializePrivateRecords?.()) privateSnapshot = readPrivateRecords();
|
|
6332
6848
|
else privateSnapshot = {
|
|
@@ -6335,21 +6851,27 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
|
|
|
6335
6851
|
anchored: false
|
|
6336
6852
|
};
|
|
6337
6853
|
applyPrivateLedger(projection, privateSnapshot);
|
|
6854
|
+
ledger = privateSnapshot;
|
|
6338
6855
|
}
|
|
6339
6856
|
synchronizedOnce = true;
|
|
6857
|
+
lastFullSync = {
|
|
6858
|
+
events,
|
|
6859
|
+
headerRef: session.header,
|
|
6860
|
+
durability: durabilityConfirmed,
|
|
6861
|
+
goalKey: goalKeyOf(goal),
|
|
6862
|
+
ledgerKey: ledger ? ledgerSnapshotKey(ledger) : "",
|
|
6863
|
+
refreshEpoch
|
|
6864
|
+
};
|
|
6340
6865
|
if (!sessionHeader) {
|
|
6341
6866
|
projection.integrity = "unknown";
|
|
6342
6867
|
if (!projection.integrityViolations.includes("session_ref_unavailable")) projection.integrityViolations.push("session_ref_unavailable");
|
|
6343
6868
|
}
|
|
6344
|
-
if (
|
|
6345
|
-
const state = normalizeGoalState(readGoalState());
|
|
6346
|
-
if (state && projection.currentGoalRef?.id === state.id && projection.currentGoalRef.revision === state.revision) {
|
|
6347
|
-
projection.currentGoalPhase = state.phase;
|
|
6348
|
-
projection.currentGoalActivation = state.activation;
|
|
6349
|
-
}
|
|
6350
|
-
} catch {
|
|
6869
|
+
if (goal.failed) {
|
|
6351
6870
|
projection.integrity = "unknown";
|
|
6352
6871
|
projection.integrityViolations.push("goal_readback_unavailable");
|
|
6872
|
+
} else if (goal.state && projection.currentGoalRef?.id === goal.state.id && projection.currentGoalRef.revision === goal.state.revision) {
|
|
6873
|
+
projection.currentGoalPhase = goal.state.phase;
|
|
6874
|
+
projection.currentGoalActivation = goal.state.activation;
|
|
6353
6875
|
}
|
|
6354
6876
|
projection.continuationAttempts = continuationAttempts;
|
|
6355
6877
|
projection.persistenceCorrectionAttempts = persistenceCorrectionAttempts;
|
|
@@ -6357,7 +6879,7 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
|
|
|
6357
6879
|
projection.durabilityWatermark = durabilityWatermark;
|
|
6358
6880
|
if (projection.boundaryProtocol === 6 && durabilityWatermark === "confirmed") try {
|
|
6359
6881
|
const displayOrigins = /* @__PURE__ */ new Map();
|
|
6360
|
-
projection.coreV2 = projectSessionCoreV2(
|
|
6882
|
+
projection.coreV2 = projectSessionCoreV2(events, projection, displayOrigins);
|
|
6361
6883
|
projection.coreV2RequirementOrigins = projection.coreV2 ? displayOrigins : void 0;
|
|
6362
6884
|
projection.coreV2Reason = projection.coreV2 ? void 0 : "source_not_projectable";
|
|
6363
6885
|
} catch {
|
|
@@ -6406,8 +6928,22 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
|
|
|
6406
6928
|
});
|
|
6407
6929
|
}
|
|
6408
6930
|
}
|
|
6931
|
+
refreshEpoch += 1;
|
|
6932
|
+
}
|
|
6933
|
+
let events;
|
|
6934
|
+
try {
|
|
6935
|
+
events = snapshotSessionEvents(session);
|
|
6936
|
+
} catch (error) {
|
|
6937
|
+
const code = error instanceof SessionApiError ? error.code : "session_snapshot_failed";
|
|
6938
|
+
projection.integrity = "unknown";
|
|
6939
|
+
if (!projection.integrityViolations.includes(code)) projection.integrityViolations.push(code);
|
|
6940
|
+
lastFullSync = void 0;
|
|
6941
|
+
return;
|
|
6409
6942
|
}
|
|
6410
|
-
|
|
6943
|
+
const goal = readGoalOnce();
|
|
6944
|
+
const ledger = readPrivateRecords ? readPrivateRecords() : void 0;
|
|
6945
|
+
if (lastFullSync && snapshotIsProvablyImmutable(events) && lastFullSync.refreshEpoch === refreshEpoch && sameSnapshotIdentity(events, lastFullSync.events) && Object.is(lastFullSync.headerRef, session.header) && lastFullSync.durability === durabilityConfirmed && lastFullSync.goalKey === goalKeyOf(goal) && (!readPrivateRecords || lastFullSync.ledgerKey === ledgerSnapshotKey(ledger))) return;
|
|
6946
|
+
rebuild(events, goal, ledger);
|
|
6411
6947
|
};
|
|
6412
6948
|
const runHostLockEntry = async (operation) => {
|
|
6413
6949
|
if (!refreshHostLock) return await operation();
|
|
@@ -6434,7 +6970,7 @@ function createRuntime(agent, config, hostLock = DEFAULT_HOST_LOCK, readGoalStat
|
|
|
6434
6970
|
pendingRecoveryCauses = /* @__PURE__ */ new Set();
|
|
6435
6971
|
return was;
|
|
6436
6972
|
};
|
|
6437
|
-
|
|
6973
|
+
sync();
|
|
6438
6974
|
return {
|
|
6439
6975
|
projection,
|
|
6440
6976
|
session,
|
|
@@ -6483,6 +7019,41 @@ function revalidateCoreLock(config, expected, providedSession) {
|
|
|
6483
7019
|
goalAvailable: false,
|
|
6484
7020
|
reasonCode: "host_lock_migration_required"
|
|
6485
7021
|
};
|
|
7022
|
+
if (config.hostLockProfile === "desktop") {
|
|
7023
|
+
if (!config.hostLockDesktopDigest) return {
|
|
7024
|
+
...expected,
|
|
7025
|
+
status: "unavailable",
|
|
7026
|
+
goalAvailable: false,
|
|
7027
|
+
reasonCode: "host_lock_migration_required"
|
|
7028
|
+
};
|
|
7029
|
+
try {
|
|
7030
|
+
const configured = evaluateConfiguredHostLock(config.hostLockPackages ?? [], {
|
|
7031
|
+
platform: config.hostLockPlatform,
|
|
7032
|
+
profileKind: "desktop"
|
|
7033
|
+
}, config.hostLockTrust, config.hostLockProfileRoot);
|
|
7034
|
+
if (configured.status !== "supported") return configured;
|
|
7035
|
+
const actual = revalidateDesktopCoreLock(config.hostLockRuntimeRoot, config.hostLockProfileRoot, expected, config.hostLockTrust);
|
|
7036
|
+
if (actual.status !== "supported") return actual;
|
|
7037
|
+
const currentRows = evaluateConfiguredHostLock(actual.packages, {
|
|
7038
|
+
platform: config.hostLockPlatform,
|
|
7039
|
+
profileKind: "desktop"
|
|
7040
|
+
}, config.hostLockTrust, config.hostLockProfileRoot);
|
|
7041
|
+
if (actual.digest !== config.hostLockDesktopDigest || currentRows.digest !== configured.digest) return {
|
|
7042
|
+
...actual,
|
|
7043
|
+
status: "unsupported",
|
|
7044
|
+
goalAvailable: false,
|
|
7045
|
+
reasonCode: "host_lock_installed_graph_drift"
|
|
7046
|
+
};
|
|
7047
|
+
return actual;
|
|
7048
|
+
} catch {
|
|
7049
|
+
return {
|
|
7050
|
+
...expected,
|
|
7051
|
+
status: "unavailable",
|
|
7052
|
+
goalAvailable: false,
|
|
7053
|
+
reasonCode: "host_lock_missing"
|
|
7054
|
+
};
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
6486
7057
|
const session = providedSession ?? createHostAuditSession();
|
|
6487
7058
|
try {
|
|
6488
7059
|
const runtimeRoot = session.realpath(config.hostLockRuntimeRoot);
|
|
@@ -6518,12 +7089,16 @@ function registerPassiveHostWorkdirObserver(agent, hostLockAtCall, attestedRoute
|
|
|
6518
7089
|
if (typeof agent.ctx.on !== "function") return () => {};
|
|
6519
7090
|
const pending = /* @__PURE__ */ new WeakMap();
|
|
6520
7091
|
const pre = agent.ctx.on("tools/pre-execute", async (exec, next) => {
|
|
6521
|
-
if (exec.agent === agent && (exec.name === "bash" || exec.name === "pwsh"))
|
|
6522
|
-
const
|
|
6523
|
-
const
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
7092
|
+
if (exec.agent === agent && (exec.name === "bash" || exec.name === "pwsh")) {
|
|
7093
|
+
const args = typeof exec.arguments === "object" && exec.arguments !== null ? exec.arguments : {};
|
|
7094
|
+
const command = String(args.command ?? "").trim();
|
|
7095
|
+
if (exec.parent === void 0 && exec.rootCallId === exec.callId && !Object.hasOwn(args, "workdir") && args.run_in_background !== true && /^(?:npm|pnpm)\s+test$/u.test(command)) try {
|
|
7096
|
+
const policy = agent.ctx.get("sandboxPolicy");
|
|
7097
|
+
const audit = createHostAuditSession();
|
|
7098
|
+
const receipt = captureHostWorkdir(agent.session, exec, hostLockAtCall(audit), policy, await attestedRouteAtCall(exec.name, agent.ctx.get("shell"), policy, audit), sourcedRootAtCall?.(exec) ?? null, SESSION_FORMAT_VERSION);
|
|
7099
|
+
if (receipt) pending.set(exec, receipt);
|
|
7100
|
+
} catch {}
|
|
7101
|
+
}
|
|
6527
7102
|
return next();
|
|
6528
7103
|
});
|
|
6529
7104
|
const post = agent.ctx.on("tools/post-execute", async (exec, _result, next) => {
|
|
@@ -6555,10 +7130,14 @@ function registerPassiveHostWorkdirObserver(agent, hostLockAtCall, attestedRoute
|
|
|
6555
7130
|
}
|
|
6556
7131
|
function apply(ctx, rawConfig = {}, seams = {}) {
|
|
6557
7132
|
const config = resolveConfig(rawConfig);
|
|
6558
|
-
const
|
|
7133
|
+
const configuredHostLock = seams.hostLock ?? evaluateConfiguredHostLock(config.hostLockPackages ?? [], {
|
|
6559
7134
|
platform: config.hostLockPlatform,
|
|
6560
7135
|
profileKind: config.hostLockProfile
|
|
6561
7136
|
}, config.hostLockTrust, config.hostLockProfileRoot);
|
|
7137
|
+
const installedHostLock = seams.hostLock ?? (config.hostLockProfile === "desktop" && config.hostLockDesktopDigest ? {
|
|
7138
|
+
...configuredHostLock,
|
|
7139
|
+
digest: config.hostLockDesktopDigest
|
|
7140
|
+
} : configuredHostLock);
|
|
6562
7141
|
const runtimes = /* @__PURE__ */ new Map();
|
|
6563
7142
|
const privateLedgerRoot = seams.privateLedgerRoot ?? resolvePrivateLedgerRoot(void 0, process.env.DSH_HOME, homedir());
|
|
6564
7143
|
const hostLocks = /* @__PURE__ */ new Map();
|
|
@@ -6597,19 +7176,24 @@ function apply(ctx, rawConfig = {}, seams = {}) {
|
|
|
6597
7176
|
};
|
|
6598
7177
|
const ensure = (agent) => {
|
|
6599
7178
|
let runtime = runtimes.get(agent);
|
|
7179
|
+
let created = false;
|
|
6600
7180
|
if (!runtime) {
|
|
6601
7181
|
const goals = qualifiedGoalService(agent);
|
|
6602
7182
|
const agentHostLock = refreshAgentHostLock(agent);
|
|
6603
7183
|
runtime = createRuntime(agent, config, agentHostLock, goals ? () => goals.get(agent) : void 0, () => refreshAgentHostLock(agent), privateLedgerRoot ? () => readPrivateLedger(privateLedgerRoot, ledgerContext(agent, agentHostLock)) : void 0, privateLedgerRoot ? () => initializePrivateLedger(privateLedgerRoot, ledgerContext(agent, agentHostLock)) : void 0);
|
|
6604
7184
|
runtimes.set(agent, runtime);
|
|
7185
|
+
created = true;
|
|
6605
7186
|
}
|
|
6606
|
-
return
|
|
7187
|
+
return {
|
|
7188
|
+
runtime,
|
|
7189
|
+
created
|
|
7190
|
+
};
|
|
6607
7191
|
};
|
|
6608
|
-
ctx.commands.register(createContextGuardCommand((agent) => ensure(agent).projection, (agent, enabled) => ensure(agent).setEnabled(enabled), (agent) => ensure(agent).sync(), (agent) => ensure(agent).lifecycle));
|
|
7192
|
+
ctx.commands.register(createContextGuardCommand((agent) => ensure(agent).runtime.projection, (agent, enabled) => ensure(agent).runtime.setEnabled(enabled), (agent) => ensure(agent).runtime.sync(), (agent) => ensure(agent).runtime.lifecycle));
|
|
6609
7193
|
const attach = (agent, source) => {
|
|
6610
7194
|
if (registrations.has(agent)) {
|
|
6611
7195
|
if (source === "resume" || source === "compact") {
|
|
6612
|
-
const runtime = ensure(agent);
|
|
7196
|
+
const { runtime } = ensure(agent);
|
|
6613
7197
|
runtime.sync();
|
|
6614
7198
|
runtime.projection.lastRecoveryDigest = void 0;
|
|
6615
7199
|
runtime.markRecoveryNeeded(source === "resume" ? "resume" : "compaction");
|
|
@@ -6626,8 +7210,8 @@ function apply(ctx, rawConfig = {}, seams = {}) {
|
|
|
6626
7210
|
guard: (...args) => own(agent.ctx.tools.guard(...args))
|
|
6627
7211
|
};
|
|
6628
7212
|
try {
|
|
6629
|
-
const runtime = ensure(agent);
|
|
6630
|
-
runtime.sync();
|
|
7213
|
+
const { runtime, created } = ensure(agent);
|
|
7214
|
+
if (!created) runtime.sync();
|
|
6631
7215
|
if (source === "resume" || source === "compact") {
|
|
6632
7216
|
runtime.projection.lastRecoveryDigest = void 0;
|
|
6633
7217
|
runtime.markRecoveryNeeded(source === "resume" ? "resume" : "compaction");
|
|
@@ -6853,7 +7437,7 @@ function apply(ctx, rawConfig = {}, seams = {}) {
|
|
|
6853
7437
|
for (const agent of ctx.get?.("agents")?.list() ?? []) attach(agent);
|
|
6854
7438
|
ctx.on("agent/pre-step", async ({ agent }, next) => {
|
|
6855
7439
|
const durability = await ctx.sessions.flush(agent.session);
|
|
6856
|
-
const runtime = ensure(agent);
|
|
7440
|
+
const { runtime } = ensure(agent);
|
|
6857
7441
|
runtime.setDurability(durability);
|
|
6858
7442
|
runtime.sync();
|
|
6859
7443
|
const decision = await next();
|
|
@@ -6909,7 +7493,7 @@ function apply(ctx, rawConfig = {}, seams = {}) {
|
|
|
6909
7493
|
return decision;
|
|
6910
7494
|
});
|
|
6911
7495
|
ctx.on("agent/turn-stopping", async ({ agent }) => {
|
|
6912
|
-
const runtime = ensure(agent);
|
|
7496
|
+
const { runtime } = ensure(agent);
|
|
6913
7497
|
const goals = qualifiedGoalService(agent);
|
|
6914
7498
|
await handleGuardTurnStopping(agent, runtime, {
|
|
6915
7499
|
flush: () => ctx.sessions.flush(agent.session),
|
|
@@ -7141,4 +7725,4 @@ async function replayRawV2(input) {
|
|
|
7141
7725
|
}
|
|
7142
7726
|
|
|
7143
7727
|
//#endregion
|
|
7144
|
-
export { ACTION_MANIFEST, ACTION_MANIFEST_VERSION, ACTIVE_HOST_COHORT_ID, ACTIVE_HOST_COHORT_IDS, ACTIVE_HOST_LAUNCHER_VERSION, BASE_HOST_PACKAGES, BOUNDARY_RECORD_PREFIX, BOUNDED_ARTIFACT_TYPES, CAPTURE_V042_NOTICE, CERTIFICATE_VERSION, CERTIFICATE_VERSION_V2, CLEANUP_CONDITION_RULE, CLEANUP_CONDITION_RULE_COMPACT, CLEANUP_CONDITION_RULE_SHORT, COMMAND_SURFACE_MANIFEST, CONFIRM_LINE_PATTERN, CONTROL_RECORD_PREFIX, Config, DEFAULT_DELEGATION_TOOL_NAMES, DEFAULT_HOST_LOCK, DEFAULT_RECOVERY_CHAR_BUDGET, DEPENDENCY_FREE_ONLY_CONDITION, EXPECTED_HOST_PACKAGES, FIRST_STEP_GUIDANCE, GIT_COMMAND_MANIFEST_IDS, GIT_COMMAND_TEMPLATES, GOAL_HOST_PACKAGES, GRANTED_QUALIFICATION, HOST_CAPABILITY_PACKAGE_GROUPS, HOST_COHORTS, HOST_VALIDATED_VERSIONS, HostProfileError, HostTrustError, LATEST_TESTED_HOST_VERSION, LEGACY_QUALIFICATION, 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, PROTOCOL_V6_NOTICE, RC020_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, V6_ORDINARY_COMPLETION_RULE, V6_ORDINARY_COMPLETION_RULE_COMPACT, V6_ORDINARY_COMPLETION_RULE_SHORT, acquireHostTrust, actionCompatible, actionHasCertificationPath, actionVerbMatches, activeRendererModule, admissibleForRemoval, apply, applyUpgradeEligibility, assessmentAction, assessmentOutcomePredicate, auditedDefaultWorkdirHost, auditedDefaultWorkdirProvider, auditedForegroundRenderers, auditedHostImplementation, authorityCaptureCounts, availableBoundaryQualifications, bindExecutableIdentity, bindLiveGoalCapability, bindProofToProjection, bindProofV2ToProjection, bindingIndividuallyAccepted, bindingSatisfies, boundedArtifactChoiceMatches, canonicalArgvFromCommand, canonicalProjection, canonicalizePath, capabilityConsequence, capabilityFactOf, capabilityRemedyPhrase, captureClause, captureItem, carriesCleanupCondition, certifyCheckpoint, claimedBatchHasRealRootInput, clarifiedSpanOf, classifyClause, classifyCompletionClaim, classifyTaskIntent, classifyUserInteraction, clauseAsksOwnQuestion, clauseIsGoverned, clauseIsProtected, cleanupConditionFor, closingHint, combineHostPolicy, commitIndexSnapshotDigest, commitTreeSnapshotDigest, compareHostVersions, confirmRebind, createGitPrestateEnvelope, createProjection, createProofManifest, createProofManifestV2, currentActionBases, currentContractDigest, decideTurnBoundary, decideTurnStopping, decisionBoundaryKey, deriveItemDiagnosis, deriveProjection, digestStrings, effectuateBoundary, environmentDefaultRepositoryTarget, evaluateActiveHostLock, evaluateConfiguredHostLock, evaluateExternalWaitCapability, evaluateGraphDerivedHostLock, evaluateHostCapability, evaluateHostLock, evaluateMinimumHostVersion, evaluateToolSurfaceCapability, evidenceAvailabilityReason, evidenceCoverage, evidenceFromPersistedToolResult, evidenceMatchesItem, executeRevalidatedGitEffect, explanationHasActionResidue, extractArtifactPaths, extractMethod, extractOperation, extractTextContent, extractToolSubject, firstStepGuidance, firstStepGuidanceV6, gitCommandMatchesTarget, goalCompletionDenial, governedClauseRestrictsExecution, hasCurrentCertificate, hasOrderedCoordination, hasQuestionScope, hasWorkPredicate, hostLockContextFromComposedDump, hostLockRowsFromComposedDump, hostTrustDigest, hostVersionFromPackages, inject, injectActiveProfileHostLock, inspectTargetHostGraph, interpretClause, interpretMessage, introducesActionClause, isCurrentAcceptedBoundary, isDeterministicCheck, isExecutableItem, isExplanationScope, isFrozenV042RebindResponse, isInformationalFragment, isInformationalMessage, isOpenObligation, isQuestionScopeNeedingReview, isRestatement, isRootPauseRequest, isRunExecutable, isStatefulAction, isVerifyingCapability, isWholeTaskCompletionClaim, itemDiagnosis, itemHoldsExecutionAuthority, kindOfScope, latestAssistantText, latestRootInstruction, legacyQuestionReadingIsInformational, legacyRecordsNeedingReview, lifecyclePhase, maskCodeSpans, maskQuotedSpans, name, namedActions, nativeFileTwoRole, normalizeClause, observeAssistantOutcome, openItems, opensConditionLead, opensWithDirective, packageRowsFromActiveGraph, packageRowsFromPnpmLock, parseConfirmationMessage, parseGitCommandManifest, parseHostTrust, parseHostVersion, parsePwshCommand, parseShellCommand, partialFailureOf, persistedToolResultStatus, prepareActiveHostTrust, prepareTargetHostTrust, presentExplanationHead, previewFirstStepInjection, progressFingerprint, projectCoreV2, projectSessionCoreV2, proofCapabilityReport, proofDigest, proofDigestV2, proofEvidenceConstraints, proofHostSurfacesOf, proofOperationMatches, proofV2Rejection, proposeRebind, proposeRebindOutcome, proposeRebindV042, qualificationOfClause, qualifyBoundary, qualifyHostTrust, questionHeadsClause, readActiveHostGraph, rebindAttemptKey, rebindResponse, recoveryDigest, recoveryTitle, registryArchiveFiles, registryArchiveModules, relevantEvidence, removalIsComplete, removalIsPartiallyKnown, renderRecoveryPacket, replayRawV2, replayRebindResult, reportingHeadGoverns, requestedIdentityKey, requestedTargetAuthorizesMutation, requestedTargetMatchesResolved, requiredSubjectsOf, resolveActiveProfileHostLock, resolveInstalledHostLock, restatedContentOf, revalidateGitPrestate, rootLocatorFlavor, sanitizeClauseText, sanitizeUrl, satisfiesSupportedHostRange, scopeCoverageDigest, segmentAuthorityBlocks, segmentClauses, selectHostCohort, semanticActionFromCommand, semanticActionFromText, semanticActionOfScope, sessionQuery, sessionQueryV2, sha256, snapshotSessionEvents, splitTextFragments, statefulActionsOfScope, supersedeItem, testOutcomePredicate, v6CurrentRootBoundaries, v6TestPredicate, validateActionManifest, validateActionTarget, validateManifest, validateProofManifest, validateProofManifestV2, verbIsNegated, verifiedLinearCommitReadback, verifyComposedHostLockDump, withDurability };
|
|
7728
|
+
export { ACTION_MANIFEST, ACTION_MANIFEST_VERSION, ACTIVE_HOST_COHORT_ID, ACTIVE_HOST_COHORT_IDS, ACTIVE_HOST_LAUNCHER_VERSION, BASE_HOST_PACKAGES, BOUNDARY_RECORD_PREFIX, BOUNDED_ARTIFACT_TYPES, CAPTURE_V042_NOTICE, CERTIFICATE_VERSION, CERTIFICATE_VERSION_V2, CLEANUP_CONDITION_RULE, CLEANUP_CONDITION_RULE_COMPACT, CLEANUP_CONDITION_RULE_SHORT, COMMAND_SURFACE_MANIFEST, CONFIRM_LINE_PATTERN, CONTROL_RECORD_PREFIX, Config, DEFAULT_DELEGATION_TOOL_NAMES, DEFAULT_HOST_LOCK, DEFAULT_RECOVERY_CHAR_BUDGET, DEPENDENCY_FREE_ONLY_CONDITION, DESKTOP_IDENTITY_FACTS, DESKTOP_PROFILE_PACKAGE_NAME, DESKTOP_RUNTIME_PACKAGE_NAME, EXPECTED_HOST_PACKAGES, FIRST_STEP_GUIDANCE, GIT_COMMAND_MANIFEST_IDS, GIT_COMMAND_TEMPLATES, GOAL_HOST_PACKAGES, GRANTED_QUALIFICATION, HOST_CAPABILITY_PACKAGE_GROUPS, HOST_COHORTS, HOST_VALIDATED_VERSIONS, HostProfileError, HostTrustError, LATEST_TESTED_HOST_VERSION, LEGACY_QUALIFICATION, MIN_RECOVERY_CHAR_BUDGET, MIN_SUPPORTED_HOST_VERSION, NATIVE_GIT_ROOT_PARENT_OID, 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, PROTOCOL_V6_NOTICE, RC020_RC1_HOST_PACKAGES, RC020_RC2_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, V6_ORDINARY_COMPLETION_RULE, V6_ORDINARY_COMPLETION_RULE_COMPACT, V6_ORDINARY_COMPLETION_RULE_SHORT, acquireHostTrust, actionCompatible, actionHasCertificationPath, actionVerbMatches, activeRendererModule, admissibleForRemoval, apply, applyUpgradeEligibility, assessmentAction, assessmentOutcomePredicate, auditDesktopInstalledImplementation, auditedDefaultWorkdirHost, auditedDefaultWorkdirProvider, auditedForegroundRenderers, auditedHostImplementation, authorityCaptureCounts, availableBoundaryQualifications, bindExecutableIdentity, bindLiveGoalCapability, bindProofToProjection, bindProofV2ToProjection, bindingIndividuallyAccepted, bindingSatisfies, boundedArtifactChoiceMatches, canonicalArgvFromCommand, canonicalProjection, canonicalizePath, capabilityConsequence, capabilityFactOf, capabilityRemedyPhrase, captureClause, captureItem, carriesCleanupCondition, certifyCheckpoint, claimedBatchHasRealRootInput, clarifiedSpanOf, classifyClause, classifyCompletionClaim, classifyTaskIntent, classifyUserInteraction, clauseAsksOwnQuestion, clauseIsGoverned, clauseIsProtected, cleanupConditionFor, closingHint, combineHostPolicy, commitIndexSnapshotDigest, commitTreeSnapshotDigest, compareHostVersions, confirmRebind, createGitPrestateEnvelope, createProjection, createProofManifest, createProofManifestV2, currentActionBases, currentContractDigest, decideTurnBoundary, decideTurnStopping, decisionBoundaryKey, deriveItemDiagnosis, deriveProjection, digestStrings, effectuateBoundary, environmentDefaultRepositoryTarget, evaluateActiveHostLock, evaluateConfiguredHostLock, evaluateExternalWaitCapability, evaluateGraphDerivedHostLock, evaluateHostCapability, evaluateHostLock, evaluateMinimumHostVersion, evaluateToolSurfaceCapability, evidenceAvailabilityReason, evidenceCoverage, evidenceFromPersistedToolResult, evidenceMatchesItem, executeRevalidatedGitEffect, explanationHasActionResidue, extractArtifactPaths, extractMethod, extractOperation, extractTextContent, extractToolSubject, firstStepGuidance, firstStepGuidanceV6, gitCommandMatchesTarget, goalCompletionDenial, governedClauseRestrictsExecution, hasCurrentCertificate, hasDesktopImporterState, hasOrderedCoordination, hasQuestionScope, hasWorkPredicate, hostLockContextFromComposedDump, hostLockRowsFromComposedDump, hostTrustDigest, hostVersionFromPackages, inject, injectActiveProfileHostLock, inspectDesktopTargetGraph, inspectTargetHostGraph, interpretClause, interpretMessage, introducesActionClause, isCurrentAcceptedBoundary, isDeterministicCheck, isExecutableItem, isExplanationScope, isFrozenV042RebindResponse, isInformationalFragment, isInformationalMessage, isOpenObligation, isQuestionScopeNeedingReview, isRestatement, isRootPauseRequest, isRunExecutable, isStatefulAction, isVerifyingCapability, isWholeTaskCompletionClaim, itemDiagnosis, itemHoldsExecutionAuthority, kindOfScope, latestAssistantText, latestRootInstruction, legacyQuestionReadingIsInformational, legacyRecordsNeedingReview, lifecyclePhase, maskCodeSpans, maskQuotedSpans, name, namedActions, nativeFileTwoRole, nativeGitParentOidVerified, normalizeClause, observeAssistantOutcome, openItems, opensConditionLead, opensWithDirective, packageRowsFromActiveGraph, packageRowsFromPnpmLock, parseConfirmationMessage, parseGitCommandManifest, parseHostTrust, parseHostVersion, parsePwshCommand, parseShellCommand, partialFailureOf, persistedToolResultStatus, prepareActiveHostTrust, prepareDesktopHostTrust, prepareTargetHostTrust, presentExplanationHead, previewFirstStepInjection, progressFingerprint, projectCoreV2, projectSessionCoreV2, proofCapabilityReport, proofDigest, proofDigestV2, proofEvidenceConstraints, proofHostSurfacesOf, proofOperationMatches, proofV2Rejection, proposeRebind, proposeRebindOutcome, proposeRebindV042, qualificationOfClause, qualifyBoundary, qualifyHostTrust, questionHeadsClause, readActiveHostGraph, readAsarFile, readAsarIndex, readDesktopAppRuntime, readDesktopDependency, readDesktopTargetGraph, rebindAttemptKey, rebindResponse, recoveryDigest, recoveryTitle, registryArchiveFiles, registryArchiveModules, relevantEvidence, removalIsComplete, removalIsPartiallyKnown, renderRecoveryPacket, replayRawV2, replayRebindResult, reportingHeadGoverns, requestedIdentityKey, requestedTargetAuthorizesMutation, requestedTargetMatchesResolved, requiredSubjectsOf, resolveActiveProfileHostLock, resolveDesktopProfileHostLock, resolveInstalledHostLock, restatedContentOf, revalidateDesktopCoreLock, revalidateGitPrestate, rootLocatorFlavor, sanitizeClauseText, sanitizeUrl, satisfiesSupportedHostRange, scopeCoverageDigest, segmentAuthorityBlocks, segmentClauses, selectHostCohort, semanticActionFromCommand, semanticActionFromText, semanticActionOfScope, sessionQuery, sessionQueryV2, sha256, shellReadbackOutcome, snapshotSessionEvents, splitTextFragments, statefulActionsOfScope, supersedeItem, testOutcomePredicate, v6CurrentRootBoundaries, v6TestPredicate, validateActionManifest, validateActionTarget, validateManifest, validateProofManifest, validateProofManifestV2, verbIsNegated, verifiedLinearCommitReadback, verifyComposedHostLockDump, withDurability, writeDesktopRuntimeReceipt };
|