@martinloop/mcp 0.8.9 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/server-validation.js +2 -3
- package/dist/server.d.ts +1 -0
- package/dist/server.js +181 -24
- package/dist/tools/get-status.d.ts +3 -0
- package/dist/tools/get-status.js +11 -2
- package/dist/tools/logs.d.ts +2 -0
- package/dist/tools/logs.js +25 -3
- package/dist/tools/run-controls.d.ts +51 -6
- package/dist/tools/run-controls.js +905 -35
- package/dist/tools/run-loop.js +7 -0
- package/dist/vendor/adapters/process-supervisor.d.ts +12 -0
- package/dist/vendor/adapters/process-supervisor.js +40 -2
- package/dist/vendor/adapters/verifier-only.js +4 -0
- package/dist/vendor/contracts/index.d.ts +8 -0
- package/dist/vendor/contracts/index.js +1 -0
- package/dist/vendor/contracts/swarm-timing.d.ts +7 -0
- package/dist/vendor/contracts/swarm-timing.js +22 -0
- package/dist/vendor/core/index.d.ts +37 -2
- package/dist/vendor/core/index.js +120 -14
- package/dist/vendor/core/persistence/index.d.ts +1 -1
- package/dist/vendor/core/persistence/index.js +1 -1
- package/dist/vendor/core/persistence/integrity.d.ts +1 -0
- package/dist/vendor/core/persistence/integrity.js +1 -1
- package/dist/vendor/core/policy.js +11 -1
- package/dist/vendor/core/resume.d.ts +25 -1
- package/dist/vendor/core/resume.js +187 -13
- package/dist/vendor/core/rollback.d.ts +14 -0
- package/dist/vendor/core/rollback.js +91 -34
- package/dist/vendor/core/swarm/live-runtime.js +2 -0
- package/dist/vendor/core/verified-handoff.js +62 -2
- package/package.json +3 -7
- package/server.json +2 -2
package/dist/tools/run-loop.js
CHANGED
|
@@ -4,6 +4,7 @@ import { normalizeSafePathPatterns, resolveSafeRepoRoot } from "../server-valida
|
|
|
4
4
|
import { MartinToolError } from "./tool-errors.js";
|
|
5
5
|
import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, buildRunRecordPaths, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
|
|
6
6
|
import { normalizeLoopBudget } from "./workflow-governance.js";
|
|
7
|
+
import { createFileRunControlSource } from "./run-controls.js";
|
|
7
8
|
let proofModeVerifierSpawnImpl;
|
|
8
9
|
let runStoreOverrideForTests;
|
|
9
10
|
export function __setProofModeVerifierSpawnImplForTests(spawnImpl) {
|
|
@@ -210,6 +211,12 @@ export async function runLoopTool(input) {
|
|
|
210
211
|
workspaceId: input.workspaceId ?? "ws_mcp",
|
|
211
212
|
projectId: input.projectId ?? "proj_mcp",
|
|
212
213
|
store: runStoreOverrideForTests ?? createFileRunStore({ runsRoot }),
|
|
214
|
+
// The in-memory store override is a verification-only composition with no
|
|
215
|
+
// durable run directory to serialize. Production always couples the file
|
|
216
|
+
// store and file control source over the same runs root.
|
|
217
|
+
runControlSource: runStoreOverrideForTests
|
|
218
|
+
? undefined
|
|
219
|
+
: createFileRunControlSource(runsRoot),
|
|
213
220
|
receiptScope,
|
|
214
221
|
task: {
|
|
215
222
|
title: input.objective.slice(0, 100),
|
|
@@ -64,4 +64,16 @@ export declare function spawnSupervisedProcessForTest(command: string, args: rea
|
|
|
64
64
|
* @internal - deterministic pure classification for process-supervisor tests
|
|
65
65
|
*/
|
|
66
66
|
export declare function classifyPosixProcessGroupSnapshot(output: string, ownedGroupPid: number): "closed" | "alive" | "absent" | "invalid";
|
|
67
|
+
type WindowsProcessTreeRow = {
|
|
68
|
+
ProcessId: number;
|
|
69
|
+
ParentProcessId: number;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Classify descendants that remain visible after a failed termination attempt.
|
|
73
|
+
* Two point-in-time snapshots cannot prove closure across the interval because
|
|
74
|
+
* a descendant may be created and reparented between them. Absence is therefore
|
|
75
|
+
* indeterminate and must retain the original termination failure.
|
|
76
|
+
* @internal - deterministic pure classification for process-supervisor tests
|
|
77
|
+
*/
|
|
78
|
+
export declare function classifyWindowsDescendantPostcondition(initialRows: WindowsProcessTreeRow[], postTerminationRows: WindowsProcessTreeRow[], rootPid: number): "closed" | "alive" | "invalid";
|
|
67
79
|
export {};
|
|
@@ -456,6 +456,39 @@ function isErrno(error, code) {
|
|
|
456
456
|
return typeof error === "object" && error !== null && "code" in error
|
|
457
457
|
&& String(error.code) === code;
|
|
458
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Classify descendants that remain visible after a failed termination attempt.
|
|
461
|
+
* Two point-in-time snapshots cannot prove closure across the interval because
|
|
462
|
+
* a descendant may be created and reparented between them. Absence is therefore
|
|
463
|
+
* indeterminate and must retain the original termination failure.
|
|
464
|
+
* @internal - deterministic pure classification for process-supervisor tests
|
|
465
|
+
*/
|
|
466
|
+
export function classifyWindowsDescendantPostcondition(initialRows, postTerminationRows, rootPid) {
|
|
467
|
+
if (!isOwnedPid(rootPid))
|
|
468
|
+
return "invalid";
|
|
469
|
+
const allRows = [...initialRows, ...postTerminationRows];
|
|
470
|
+
if (allRows.some((row) => !isOwnedPid(row.ProcessId) || !Number.isSafeInteger(row.ParentProcessId))) {
|
|
471
|
+
return "invalid";
|
|
472
|
+
}
|
|
473
|
+
const ownedLineage = new Set([rootPid]);
|
|
474
|
+
const expandLineage = (rows) => {
|
|
475
|
+
let changed = true;
|
|
476
|
+
while (changed) {
|
|
477
|
+
changed = false;
|
|
478
|
+
for (const row of rows) {
|
|
479
|
+
if (ownedLineage.has(row.ParentProcessId) && !ownedLineage.has(row.ProcessId)) {
|
|
480
|
+
ownedLineage.add(row.ProcessId);
|
|
481
|
+
changed = true;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
expandLineage(initialRows);
|
|
487
|
+
expandLineage(postTerminationRows);
|
|
488
|
+
return postTerminationRows.some((row) => ownedLineage.has(row.ProcessId))
|
|
489
|
+
? "alive"
|
|
490
|
+
: "invalid";
|
|
491
|
+
}
|
|
459
492
|
async function terminateWindowsDescendantsAfterRootExit(rootPid, graceMs, waitImpl) {
|
|
460
493
|
let rows;
|
|
461
494
|
try {
|
|
@@ -471,11 +504,16 @@ async function terminateWindowsDescendantsAfterRootExit(rootPid, graceMs, waitIm
|
|
|
471
504
|
childrenByParent.set(row.ParentProcessId, children);
|
|
472
505
|
}
|
|
473
506
|
const directChildren = childrenByParent.get(rootPid) ?? [];
|
|
507
|
+
let taskkillFailure;
|
|
474
508
|
for (const childPid of directChildren) {
|
|
475
509
|
const failure = await runTaskkill(childPid, spawnProcess, graceMs, waitImpl);
|
|
476
|
-
|
|
477
|
-
return failure;
|
|
510
|
+
taskkillFailure ??= failure;
|
|
478
511
|
}
|
|
512
|
+
// Once taskkill has failed, a later process-table snapshot cannot prove the
|
|
513
|
+
// complete descendant set closed. Return the bounded failure immediately;
|
|
514
|
+
// starting another PowerShell query here could otherwise stall cleanup.
|
|
515
|
+
if (taskkillFailure)
|
|
516
|
+
return taskkillFailure;
|
|
479
517
|
return { state: "closed" };
|
|
480
518
|
}
|
|
481
519
|
function readWindowsProcessTree() {
|
|
@@ -49,6 +49,10 @@ export function createVerifierOnlyAdapter(options = {}) {
|
|
|
49
49
|
providerId: "verifier",
|
|
50
50
|
model: "verify-only",
|
|
51
51
|
transport: "cli",
|
|
52
|
+
budgetPreflight: {
|
|
53
|
+
pricePerMTokenUsd: 0,
|
|
54
|
+
basis: "Verifier-only execution launches no model provider."
|
|
55
|
+
},
|
|
52
56
|
capabilities: createAdapterCapabilities({
|
|
53
57
|
usageSettlement: true,
|
|
54
58
|
diffArtifacts: true,
|
|
@@ -497,6 +497,12 @@ export interface RollbackBoundaryArtifact {
|
|
|
497
497
|
trackedDirtyFiles: string[];
|
|
498
498
|
untrackedFiles: string[];
|
|
499
499
|
snapshots: RollbackFileSnapshot[];
|
|
500
|
+
/**
|
|
501
|
+
* Exact repo-relative roots owned by MartinLoop persistence for this run.
|
|
502
|
+
* These roots are not agent workspace mutations and must remain excluded
|
|
503
|
+
* from observation and rollback after the boundary is persisted/resumed.
|
|
504
|
+
*/
|
|
505
|
+
managedPathExclusions?: string[];
|
|
500
506
|
/**
|
|
501
507
|
* Git-ignored files present at the boundary. Dependency and build output
|
|
502
508
|
* directories are recorded as a single collapsed `dir/` entry.
|
|
@@ -557,4 +563,6 @@ export type { ChainIntegrityState, ContextCircuitBreakResult, ContextExclusionDe
|
|
|
557
563
|
export { MISSION_SCHEMA_VERSION, MISSION_STATUSES, ALLOWED_MISSION_TRANSITIONS, createMissionRecord, isMissionTransitionAllowed } from './mission.js';
|
|
558
564
|
export type { MissionStatus, MissionDecision, MissionBudget, MissionCost, MissionRunLink, MissionRunRole, MissionApproval, MissionOutcome, MissionEvent, MissionEventKind, MissionRecord, MissionDraft } from './mission.js';
|
|
559
565
|
export { SWARM_LIVE_ENGINES, SWARM_ORCHESTRATION_STRATEGIES, SWARM_EVENT_TYPES, SWARM_PARENT_RECEIPT_SCHEMA_VERSION, SWARM_SCHEMA_VERSION, computeSwarmLivePlanHash, createSwarmLivePlan, createSwarmRunRecord, normalizeSwarmPathPattern, swarmConcretePathMatchesPattern, swarmPathPatternContains, swarmPathPatternsOverlap, validateSwarmConcretePath, validateSwarmChildReceiptLink, validateSwarmDeterministicDemoReceiptEvidence, validateSwarmChildContract, validateSwarmLiveEvent, validateSwarmLivePlan, validateSwarmLiveRevision, validateSwarmParentContract, validateSwarmRunRecord } from "./swarm.js";
|
|
566
|
+
export { SWARM_CHILD_MAX_WALL_CLOCK_MS, SWARM_PARENT_VERIFIER_ALLOWANCE_MS, resolveSwarmDeadlineMs, } from "./swarm-timing.js";
|
|
567
|
+
export type { SwarmExecutionShape } from "./swarm-timing.js";
|
|
560
568
|
export type { SwarmAgentRecord, SwarmAgentStatus, SwarmBudgetLedger, SwarmBudgetLease, SwarmBudgetUsage, SwarmCandidate, SwarmChildContract, SwarmChildReceiptLink, SwarmChildWorkspaceRecord, SwarmCleanupRecord, SwarmCleanupState, SwarmConcretePathResult, SwarmConflictRecord, SwarmDependencyWaiver, SwarmDemoReferentialBindingState, SwarmDeterministicDemoReceiptEvidence, SwarmEvent, SwarmEventType, SwarmGlobalVerification, SwarmGlobalVerificationCommandState, SwarmIntegrationWorkspaceRecord, SwarmIntegrationStrategy, SwarmLeaseStatus, SwarmLiveEngine, SwarmLiveEngineProfile, SwarmLiveEvent, SwarmLivePlan, SwarmLivePlanDraft, SwarmLiveRevision, SwarmOrchestrationStrategy, SwarmOutcome, SwarmOutcomeState, SwarmParentContract, SwarmParentReceipt, SwarmPatchAdmission, SwarmPatchAdmissionReasonCode, SwarmPatchAdmissionState, SwarmPathPatternResult, SwarmPermissions, SwarmProcessCloseState, SwarmRecoveryPolicy, SwarmReceiptBlockedAction, SwarmReceiptReassignment, SwarmRunDraft, SwarmRunRecord, SwarmScope, SwarmStopDisposition, SwarmStopPolicy, SwarmTaskNode, SwarmTaskStatus, SwarmValidationError, SwarmValidationResult, SwarmVerifierMutationState, SwarmVerifierSubprocessResult, SwarmVerificationRecord, SwarmVerifierAuthority, SwarmVerifierWorkspaceRecord, SwarmWorkspaceKind, SwarmWorkspaceRecord, SwarmWorkspaceRemovalState, SwarmWorkspaceState } from "./swarm.js";
|
|
@@ -283,3 +283,4 @@ export { EVIDENCE_STATUSES, EXECUTION_MODES, TEST_INTEGRITY_STATUSES, TEST_INTEG
|
|
|
283
283
|
export { HANDOFF_SCHEMA_VERSION } from "./context-handoff.js";
|
|
284
284
|
export { MISSION_SCHEMA_VERSION, MISSION_STATUSES, ALLOWED_MISSION_TRANSITIONS, createMissionRecord, isMissionTransitionAllowed } from './mission.js';
|
|
285
285
|
export { SWARM_LIVE_ENGINES, SWARM_ORCHESTRATION_STRATEGIES, SWARM_EVENT_TYPES, SWARM_PARENT_RECEIPT_SCHEMA_VERSION, SWARM_SCHEMA_VERSION, computeSwarmLivePlanHash, createSwarmLivePlan, createSwarmRunRecord, normalizeSwarmPathPattern, swarmConcretePathMatchesPattern, swarmPathPatternContains, swarmPathPatternsOverlap, validateSwarmConcretePath, validateSwarmChildReceiptLink, validateSwarmDeterministicDemoReceiptEvidence, validateSwarmChildContract, validateSwarmLiveEvent, validateSwarmLivePlan, validateSwarmLiveRevision, validateSwarmParentContract, validateSwarmRunRecord } from "./swarm.js";
|
|
286
|
+
export { SWARM_CHILD_MAX_WALL_CLOCK_MS, SWARM_PARENT_VERIFIER_ALLOWANCE_MS, resolveSwarmDeadlineMs, } from "./swarm-timing.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const SWARM_CHILD_MAX_WALL_CLOCK_MS = 240000;
|
|
2
|
+
export declare const SWARM_PARENT_VERIFIER_ALLOWANCE_MS = 120000;
|
|
3
|
+
export interface SwarmExecutionShape {
|
|
4
|
+
agents: number;
|
|
5
|
+
maxConcurrency: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveSwarmDeadlineMs(shape: SwarmExecutionShape): number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const SWARM_CHILD_MAX_WALL_CLOCK_MS = 240_000;
|
|
2
|
+
export const SWARM_PARENT_VERIFIER_ALLOWANCE_MS = 120_000;
|
|
3
|
+
const NODE_MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
4
|
+
export function resolveSwarmDeadlineMs(shape) {
|
|
5
|
+
assertPositiveSafeInteger(shape.agents, "agents");
|
|
6
|
+
assertPositiveSafeInteger(shape.maxConcurrency, "maxConcurrency");
|
|
7
|
+
if (shape.maxConcurrency > shape.agents) {
|
|
8
|
+
throw new Error("maxConcurrency cannot exceed agents");
|
|
9
|
+
}
|
|
10
|
+
const executionWaves = Math.ceil(shape.agents / shape.maxConcurrency);
|
|
11
|
+
const deadlineMs = (executionWaves * SWARM_CHILD_MAX_WALL_CLOCK_MS
|
|
12
|
+
+ SWARM_PARENT_VERIFIER_ALLOWANCE_MS);
|
|
13
|
+
if (!Number.isSafeInteger(deadlineMs) || deadlineMs > NODE_MAX_TIMER_DELAY_MS) {
|
|
14
|
+
throw new Error("derived deadline exceeds the Node timer maximum");
|
|
15
|
+
}
|
|
16
|
+
return deadlineMs;
|
|
17
|
+
}
|
|
18
|
+
function assertPositiveSafeInteger(value, label) {
|
|
19
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
20
|
+
throw new Error(`${label} must be a positive safe integer`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -7,7 +7,7 @@ import { type RunStore } from "./persistence/index.js";
|
|
|
7
7
|
import { type ExitPolicyOverrides } from "./exits.js";
|
|
8
8
|
import { type ExitSignalSource } from "./exit-signal.js";
|
|
9
9
|
import { type VerifierExecutionBinding } from "./verified-handoff.js";
|
|
10
|
-
import { acquireRunExecutionLease, evaluateRunResumability, loadResumeCandidateBoundary, loadResumeVerifierFailure } from "./resume.js";
|
|
10
|
+
import { acquireRunExecutionLease, readActiveRunExecutionLease, evaluateRunResumability, loadResumeCandidateBoundary, loadResumeVerifierFailure } from "./resume.js";
|
|
11
11
|
export type { AgentExecutionIntent, ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase, CallStage, AgentRole, FirstDelta, RoutingEconomics } from "../contracts/index.js";
|
|
12
12
|
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
13
13
|
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary };
|
|
@@ -23,7 +23,7 @@ export type { LocalIdentityAuthority } from "./identity.js";
|
|
|
23
23
|
export { compileExecutionPolicy } from "./policy-compiler.js";
|
|
24
24
|
export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
|
|
25
25
|
export { calculateAvoidedUsd, calculateLoopAvoidedUsd } from "./savings.js";
|
|
26
|
-
export { acquireRunExecutionLease, evaluateRunResumability, loadResumeCandidateBoundary, loadResumeVerifierFailure };
|
|
26
|
+
export { acquireRunExecutionLease, readActiveRunExecutionLease, evaluateRunResumability, loadResumeCandidateBoundary, loadResumeVerifierFailure };
|
|
27
27
|
export type { AvoidedUsdInput } from "./savings.js";
|
|
28
28
|
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
29
29
|
export type { RouteDecision, RouteClassificationInput } from "./routing.js";
|
|
@@ -206,6 +206,8 @@ export interface MartinAdapter {
|
|
|
206
206
|
budgetPreflight?: {
|
|
207
207
|
/** Estimated token floor required before the adapter can report first-turn usage. */
|
|
208
208
|
minimumViableTokens?: number;
|
|
209
|
+
/** Adapter-specific provider price used by the admission estimate. */
|
|
210
|
+
pricePerMTokenUsd?: number;
|
|
209
211
|
basis?: string;
|
|
210
212
|
};
|
|
211
213
|
capabilities?: {
|
|
@@ -291,6 +293,39 @@ export interface RunMartinInput {
|
|
|
291
293
|
observedUsageGovernor?: MartinObservedUsageGovernor;
|
|
292
294
|
/** Optional parent cancellation signal. Relayed into the active governed attempt. */
|
|
293
295
|
signal?: AbortSignal;
|
|
296
|
+
/** Optional durable cooperative pause/continue source. */
|
|
297
|
+
runControlSource?: RunControlSource;
|
|
298
|
+
/** Poll interval while a cooperative pause is applied (default 250 ms). */
|
|
299
|
+
runControlPollIntervalMs?: number;
|
|
300
|
+
}
|
|
301
|
+
export interface RunControlCommand {
|
|
302
|
+
controlId: string;
|
|
303
|
+
loopId: string;
|
|
304
|
+
action: "pause" | "continue";
|
|
305
|
+
requestedAt: string;
|
|
306
|
+
/** Explicit local-process boundary; not an authenticated human identity. */
|
|
307
|
+
principal: "local_mcp_client";
|
|
308
|
+
receiptHash: string;
|
|
309
|
+
reason?: string;
|
|
310
|
+
expectedPreviousControlId?: string;
|
|
311
|
+
}
|
|
312
|
+
export interface RunControlSnapshot {
|
|
313
|
+
pause?: RunControlCommand;
|
|
314
|
+
continuedBy?: RunControlCommand;
|
|
315
|
+
}
|
|
316
|
+
export interface RunControlApplication {
|
|
317
|
+
loopId: string;
|
|
318
|
+
controlId: string;
|
|
319
|
+
action: "pause_applied" | "continue_applied";
|
|
320
|
+
appliedAt: string;
|
|
321
|
+
relatedControlId?: string;
|
|
322
|
+
receiptHash: string;
|
|
323
|
+
}
|
|
324
|
+
export interface RunControlSource {
|
|
325
|
+
poll(runId: string): Promise<RunControlSnapshot>;
|
|
326
|
+
recordApplication(application: RunControlApplication): Promise<void>;
|
|
327
|
+
/** Serialize terminal intent against new control receipts before persistence. */
|
|
328
|
+
recordTerminalIntent?(runId: string): Promise<void>;
|
|
294
329
|
}
|
|
295
330
|
export interface RunMartinResult {
|
|
296
331
|
loop: LoopRecord;
|
|
@@ -2,10 +2,10 @@ import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, ap
|
|
|
2
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, isSafeUnverifiedDiscard, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
|
|
3
3
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
4
4
|
import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
|
|
5
|
-
import { captureRollbackBoundary, findEscapingLinks, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
5
|
+
import { authenticateManagedPathExclusions, captureRollbackBoundary, filterManagedRollbackPaths, findEscapingLinks, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
|
|
6
6
|
import { findWeakenedTestFiles } from "./test-integrity.js";
|
|
7
7
|
import { compilePromptPacket } from "./compiler.js";
|
|
8
|
-
import { makeLedgerEvent, resolveRunsRoot, runDir } from "./persistence/index.js";
|
|
8
|
+
import { makeLedgerEvent, resolveReceiptIntegrityRoot, resolveRunsRoot, runDir } from "./persistence/index.js";
|
|
9
9
|
import { createDefaultExitPolicy, evaluateExitPolicy, hashProgressState, toLegacyExitDecision } from "./exits.js";
|
|
10
10
|
import { createFileExitSignalSource, isTerminalExitSignal, startExitSignalMonitor } from "./exit-signal.js";
|
|
11
11
|
import { persistTerminationEnvelope } from "./termination-store.js";
|
|
@@ -14,7 +14,7 @@ import { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
14
14
|
import { decideCircuitBreak } from "./trajectory.js";
|
|
15
15
|
import { verifyContextHandoff, decideContextCircuitBreak } from "./context-handoff.js";
|
|
16
16
|
import { verifierActuallyPassed, } from "./verified-handoff.js";
|
|
17
|
-
import { acquireRunExecutionLease, evaluateRunResumability, loadResumeCandidateBoundary, loadResumeVerifierFailure } from "./resume.js";
|
|
17
|
+
import { acquireRunExecutionLease, readActiveRunExecutionLease, evaluateRunResumability, loadResumeCandidateBoundary, loadResumeVerifierFailure } from "./resume.js";
|
|
18
18
|
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
19
19
|
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary };
|
|
20
20
|
export { describeMalformedVerifier } from "./verifier-input.js";
|
|
@@ -24,7 +24,7 @@ export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, ver
|
|
|
24
24
|
export { compileExecutionPolicy } from "./policy-compiler.js";
|
|
25
25
|
export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
|
|
26
26
|
export { calculateAvoidedUsd, calculateLoopAvoidedUsd } from "./savings.js";
|
|
27
|
-
export { acquireRunExecutionLease, evaluateRunResumability, loadResumeCandidateBoundary, loadResumeVerifierFailure };
|
|
27
|
+
export { acquireRunExecutionLease, readActiveRunExecutionLease, evaluateRunResumability, loadResumeCandidateBoundary, loadResumeVerifierFailure };
|
|
28
28
|
// ─── Routing economics ─────────────────────────────────────────────────────
|
|
29
29
|
export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
|
|
30
30
|
// ─── Test Integrity Validation ──────────────────────────────────────────────
|
|
@@ -217,6 +217,7 @@ export async function runMartin(input) {
|
|
|
217
217
|
runsRoot: input.store.runsRoot,
|
|
218
218
|
runId: loop.loopId,
|
|
219
219
|
owner: resumeLoop ? "runMartin:resume" : "runMartin",
|
|
220
|
+
runControlCapable: input.runControlSource !== undefined,
|
|
220
221
|
now: () => loop.updatedAt
|
|
221
222
|
})
|
|
222
223
|
: undefined;
|
|
@@ -236,6 +237,9 @@ export async function runMartin(input) {
|
|
|
236
237
|
}, { now: now(), idFactory });
|
|
237
238
|
const adapterChain = [input.adapter, ...(input.fallbackAdapters ?? [])];
|
|
238
239
|
const runsRoot = input.store?.runsRoot ?? resolveRunsRoot();
|
|
240
|
+
const managedRollbackRoots = input.store?.runsRoot
|
|
241
|
+
? [input.store.runsRoot, resolveReceiptIntegrityRoot()]
|
|
242
|
+
: [];
|
|
239
243
|
const observesWorkspaceMutations = task.repoRoot !== undefined &&
|
|
240
244
|
adapterChain.some((adapter) => adapter.metadata.capabilities?.workspaceMutations !== false);
|
|
241
245
|
const touchedDuringExecution = new Set();
|
|
@@ -247,7 +251,8 @@ export async function runMartin(input) {
|
|
|
247
251
|
try {
|
|
248
252
|
completionBoundary = await captureRollbackBoundary({
|
|
249
253
|
repoRoot: task.repoRoot,
|
|
250
|
-
capturedAt: loop.createdAt
|
|
254
|
+
capturedAt: loop.createdAt,
|
|
255
|
+
managedRoots: managedRollbackRoots
|
|
251
256
|
});
|
|
252
257
|
if (!completionBoundary) {
|
|
253
258
|
completionBoundaryReason = "A run-wide file-effect observation boundary was not available.";
|
|
@@ -265,6 +270,7 @@ export async function runMartin(input) {
|
|
|
265
270
|
const fileEffects = observeRunFileEffects({
|
|
266
271
|
repoRoot: task.repoRoot,
|
|
267
272
|
boundary: completionBoundary,
|
|
273
|
+
managedRoots: managedRollbackRoots,
|
|
268
274
|
touchedDuringExecution,
|
|
269
275
|
unavailableReason: completionBoundaryReason
|
|
270
276
|
});
|
|
@@ -675,7 +681,84 @@ export async function runMartin(input) {
|
|
|
675
681
|
const evaluation = evaluateExitPolicy(exitPolicy, currentExitSnapshot(phase, result));
|
|
676
682
|
if (!evaluation.shouldExit)
|
|
677
683
|
return undefined;
|
|
678
|
-
|
|
684
|
+
const terminal = await selectTerminalEvaluation(evaluation);
|
|
685
|
+
await input.runControlSource?.recordTerminalIntent?.(loop.loopId);
|
|
686
|
+
return terminal;
|
|
687
|
+
}
|
|
688
|
+
async function waitForControlPoll(intervalMs) {
|
|
689
|
+
if (runController.signal.aborted)
|
|
690
|
+
return;
|
|
691
|
+
await new Promise((resolve) => {
|
|
692
|
+
let settled = false;
|
|
693
|
+
const finish = () => {
|
|
694
|
+
if (settled)
|
|
695
|
+
return;
|
|
696
|
+
settled = true;
|
|
697
|
+
clearTimeout(timer);
|
|
698
|
+
runController.signal.removeEventListener("abort", finish);
|
|
699
|
+
resolve();
|
|
700
|
+
};
|
|
701
|
+
const timer = setTimeout(finish, intervalMs);
|
|
702
|
+
timer.unref?.();
|
|
703
|
+
runController.signal.addEventListener("abort", finish, { once: true });
|
|
704
|
+
if (runController.signal.aborted)
|
|
705
|
+
finish();
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
async function awaitCooperativeRunControl() {
|
|
709
|
+
const source = input.runControlSource;
|
|
710
|
+
if (!source)
|
|
711
|
+
return undefined;
|
|
712
|
+
const initial = await source.poll(loop.loopId);
|
|
713
|
+
const pause = initial.pause;
|
|
714
|
+
if (!pause)
|
|
715
|
+
return undefined;
|
|
716
|
+
if (pause.loopId !== loop.loopId || pause.action !== "pause") {
|
|
717
|
+
throw new Error("Run control integrity check failed before pause application.");
|
|
718
|
+
}
|
|
719
|
+
await source.recordApplication({
|
|
720
|
+
loopId: loop.loopId,
|
|
721
|
+
controlId: pause.controlId,
|
|
722
|
+
action: "pause_applied",
|
|
723
|
+
appliedAt: now(),
|
|
724
|
+
receiptHash: pause.receiptHash
|
|
725
|
+
});
|
|
726
|
+
const pollIntervalMs = input.runControlPollIntervalMs ?? 250;
|
|
727
|
+
while (true) {
|
|
728
|
+
const terminal = await evaluateCheckpoint("pre_attempt");
|
|
729
|
+
if (terminal)
|
|
730
|
+
return terminal;
|
|
731
|
+
await waitForControlPoll(pollIntervalMs);
|
|
732
|
+
// Cancellation aborts the wait immediately. Reconcile terminal state
|
|
733
|
+
// before polling cooperative receipts because the production control
|
|
734
|
+
// source intentionally clears pause/continue after a cancel receipt.
|
|
735
|
+
const awakenedTerminal = await evaluateCheckpoint("pre_attempt");
|
|
736
|
+
if (awakenedTerminal)
|
|
737
|
+
return awakenedTerminal;
|
|
738
|
+
const snapshot = await source.poll(loop.loopId);
|
|
739
|
+
if (snapshot.pause) {
|
|
740
|
+
if (snapshot.pause.controlId !== pause.controlId || snapshot.pause.loopId !== loop.loopId) {
|
|
741
|
+
throw new Error("Run control integrity check failed while pause was applied.");
|
|
742
|
+
}
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
const continuedBy = snapshot.continuedBy;
|
|
746
|
+
if (!continuedBy ||
|
|
747
|
+
continuedBy.loopId !== loop.loopId ||
|
|
748
|
+
continuedBy.action !== "continue" ||
|
|
749
|
+
continuedBy.expectedPreviousControlId !== pause.controlId) {
|
|
750
|
+
throw new Error("Run control integrity check failed while releasing pause.");
|
|
751
|
+
}
|
|
752
|
+
await source.recordApplication({
|
|
753
|
+
loopId: loop.loopId,
|
|
754
|
+
controlId: continuedBy.controlId,
|
|
755
|
+
action: "continue_applied",
|
|
756
|
+
relatedControlId: pause.controlId,
|
|
757
|
+
appliedAt: now(),
|
|
758
|
+
receiptHash: continuedBy.receiptHash
|
|
759
|
+
});
|
|
760
|
+
return await evaluateCheckpoint("pre_attempt");
|
|
761
|
+
}
|
|
679
762
|
}
|
|
680
763
|
/**
|
|
681
764
|
* Blocker 1 / Blocker 2 — Authoritative retained-candidate terminal restoration.
|
|
@@ -711,6 +794,7 @@ export async function runMartin(input) {
|
|
|
711
794
|
const rollbackOutcome = await restoreRollbackBoundary({
|
|
712
795
|
repoRoot: task.repoRoot,
|
|
713
796
|
boundary: capturedBoundary,
|
|
797
|
+
managedRoots: managedRollbackRoots,
|
|
714
798
|
restoredAt,
|
|
715
799
|
decision: "DISCARD"
|
|
716
800
|
});
|
|
@@ -867,6 +951,9 @@ export async function runMartin(input) {
|
|
|
867
951
|
if (preRunTerminal !== undefined)
|
|
868
952
|
return await terminateRun(preRunTerminal);
|
|
869
953
|
while (loop.attempts.length < loop.budget.maxIterations) {
|
|
954
|
+
const controlTerminal = await awaitCooperativeRunControl();
|
|
955
|
+
if (controlTerminal !== undefined)
|
|
956
|
+
return await terminateRun(controlTerminal);
|
|
870
957
|
const distilled = distillContext(loop, {
|
|
871
958
|
maxRecentAttempts: useCompressedContext ? 1 : (input.maxRecentAttempts ?? 3)
|
|
872
959
|
});
|
|
@@ -880,6 +967,7 @@ export async function runMartin(input) {
|
|
|
880
967
|
remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
|
|
881
968
|
remainingTokens: distilled.constraints.remainingTokens,
|
|
882
969
|
minimumViableTokens: executingAdapter.metadata.budgetPreflight?.minimumViableTokens,
|
|
970
|
+
pricePerMTokenUsd: executingAdapter.metadata.budgetPreflight?.pricePerMTokenUsd,
|
|
883
971
|
// D2: pass remaining iterations so the preflight can compute a budget-aware cap
|
|
884
972
|
// rather than using the old rigid maxUsd * 0.25 that blocked viable attempts.
|
|
885
973
|
remainingIterations: distilled.constraints.remainingIterations
|
|
@@ -1117,7 +1205,8 @@ export async function runMartin(input) {
|
|
|
1117
1205
|
const rollbackBoundary = tracksWorkspaceMutations
|
|
1118
1206
|
? await captureRollbackBoundary({
|
|
1119
1207
|
repoRoot: request.context.repoRoot,
|
|
1120
|
-
capturedAt: attemptStartedAt
|
|
1208
|
+
capturedAt: attemptStartedAt,
|
|
1209
|
+
managedRoots: managedRollbackRoots
|
|
1121
1210
|
})
|
|
1122
1211
|
: undefined;
|
|
1123
1212
|
// Persist attempt.started state before blocking on provider execution.
|
|
@@ -1352,10 +1441,14 @@ export async function runMartin(input) {
|
|
|
1352
1441
|
let weakenedTestFiles = [];
|
|
1353
1442
|
try {
|
|
1354
1443
|
changedFiles = tracksWorkspaceMutations
|
|
1355
|
-
? resolveChangedFiles(result, request.context.repoRoot, rollbackBoundary)
|
|
1444
|
+
? resolveChangedFiles(result, request.context.repoRoot, rollbackBoundary, managedRollbackRoots)
|
|
1356
1445
|
: [];
|
|
1357
1446
|
ignoredChanges = tracksWorkspaceMutations
|
|
1358
|
-
? listIgnoredFileChangesSinceBoundary({
|
|
1447
|
+
? listIgnoredFileChangesSinceBoundary({
|
|
1448
|
+
repoRoot: request.context.repoRoot,
|
|
1449
|
+
boundary: rollbackBoundary,
|
|
1450
|
+
managedRoots: managedRollbackRoots
|
|
1451
|
+
})
|
|
1359
1452
|
: ignoredChanges;
|
|
1360
1453
|
escapingLinks =
|
|
1361
1454
|
tracksWorkspaceMutations && request.context.repoRoot
|
|
@@ -1382,6 +1475,7 @@ export async function runMartin(input) {
|
|
|
1382
1475
|
rollbackOutcome = (await restoreRollbackBoundary({
|
|
1383
1476
|
repoRoot: request.context.repoRoot,
|
|
1384
1477
|
boundary: rollbackBoundary,
|
|
1478
|
+
managedRoots: managedRollbackRoots,
|
|
1385
1479
|
restoredAt: attemptCompletedAt,
|
|
1386
1480
|
decision: "DISCARD",
|
|
1387
1481
|
removePaths: ignoredChanges.created,
|
|
@@ -1495,6 +1589,7 @@ export async function runMartin(input) {
|
|
|
1495
1589
|
rollbackOutcome = await restoreRollbackBoundary({
|
|
1496
1590
|
repoRoot: request.context.repoRoot,
|
|
1497
1591
|
boundary: rollbackBoundary,
|
|
1592
|
+
managedRoots: managedRollbackRoots,
|
|
1498
1593
|
restoredAt: attemptCompletedAt,
|
|
1499
1594
|
decision: patchDecision.decision,
|
|
1500
1595
|
removePaths: violatingIgnoredPaths(filesystemDecision.violations, ignoredChanges.created),
|
|
@@ -1592,6 +1687,7 @@ export async function runMartin(input) {
|
|
|
1592
1687
|
rollbackOutcome = await restoreRollbackBoundary({
|
|
1593
1688
|
repoRoot: request.context.repoRoot,
|
|
1594
1689
|
boundary: rollbackBoundary,
|
|
1690
|
+
managedRoots: managedRollbackRoots,
|
|
1595
1691
|
restoredAt: attemptCompletedAt,
|
|
1596
1692
|
decision: patchDecision.decision
|
|
1597
1693
|
}) ?? undefined;
|
|
@@ -1724,6 +1820,7 @@ export async function runMartin(input) {
|
|
|
1724
1820
|
rollbackOutcome = await restoreRollbackBoundary({
|
|
1725
1821
|
repoRoot: request.context.repoRoot,
|
|
1726
1822
|
boundary: rollbackBoundary,
|
|
1823
|
+
managedRoots: managedRollbackRoots,
|
|
1727
1824
|
restoredAt: attemptCompletedAt,
|
|
1728
1825
|
decision: patchDecision?.decision ?? "DISCARD"
|
|
1729
1826
|
});
|
|
@@ -1733,6 +1830,7 @@ export async function runMartin(input) {
|
|
|
1733
1830
|
rollbackOutcome = await restoreRollbackBoundary({
|
|
1734
1831
|
repoRoot: request.context.repoRoot,
|
|
1735
1832
|
boundary: rollbackBoundary,
|
|
1833
|
+
managedRoots: managedRollbackRoots,
|
|
1736
1834
|
restoredAt: attemptCompletedAt,
|
|
1737
1835
|
decision: "DISCARD"
|
|
1738
1836
|
});
|
|
@@ -2001,6 +2099,7 @@ export async function runMartin(input) {
|
|
|
2001
2099
|
await restoreRollbackBoundary({
|
|
2002
2100
|
repoRoot: task.repoRoot,
|
|
2003
2101
|
boundary: preCandidateBoundary,
|
|
2102
|
+
managedRoots: managedRollbackRoots,
|
|
2004
2103
|
restoredAt: now(),
|
|
2005
2104
|
decision: "DISCARD"
|
|
2006
2105
|
});
|
|
@@ -2091,11 +2190,11 @@ function finalizeLoop(loop, decision, timestamp, idFactory, routingEconomicsInpu
|
|
|
2091
2190
|
};
|
|
2092
2191
|
}
|
|
2093
2192
|
function observeRunFileEffects(input) {
|
|
2094
|
-
const
|
|
2193
|
+
const observedTouchedDuringExecution = normalizeFileEffectPaths(input.touchedDuringExecution);
|
|
2095
2194
|
if (!input.repoRoot || !input.boundary) {
|
|
2096
2195
|
return {
|
|
2097
2196
|
observationStatus: "unavailable",
|
|
2098
|
-
touchedDuringExecution,
|
|
2197
|
+
touchedDuringExecution: observedTouchedDuringExecution,
|
|
2099
2198
|
netChangedAtCompletion: [],
|
|
2100
2199
|
revertedBeforeCompletion: [],
|
|
2101
2200
|
observationReason: input.unavailableReason ??
|
|
@@ -2103,9 +2202,12 @@ function observeRunFileEffects(input) {
|
|
|
2103
2202
|
};
|
|
2104
2203
|
}
|
|
2105
2204
|
try {
|
|
2205
|
+
const exclusions = authenticateManagedPathExclusions(input.repoRoot, input.boundary, input.managedRoots);
|
|
2206
|
+
const touchedDuringExecution = filterManagedRollbackPaths(observedTouchedDuringExecution, exclusions);
|
|
2106
2207
|
const netChangedAtCompletion = normalizeFileEffectPaths(listAttemptChangedFilesSinceBoundary({
|
|
2107
2208
|
repoRoot: input.repoRoot,
|
|
2108
2209
|
boundary: input.boundary,
|
|
2210
|
+
managedRoots: input.managedRoots,
|
|
2109
2211
|
includeBaselineSnapshotChanges: true
|
|
2110
2212
|
}));
|
|
2111
2213
|
const netChanged = new Set(netChangedAtCompletion);
|
|
@@ -2119,7 +2221,7 @@ function observeRunFileEffects(input) {
|
|
|
2119
2221
|
catch (error) {
|
|
2120
2222
|
return {
|
|
2121
2223
|
observationStatus: "unavailable",
|
|
2122
|
-
touchedDuringExecution,
|
|
2224
|
+
touchedDuringExecution: observedTouchedDuringExecution,
|
|
2123
2225
|
netChangedAtCompletion: [],
|
|
2124
2226
|
revertedBeforeCompletion: [],
|
|
2125
2227
|
observationReason: `Final file-effect observation was unavailable: ${fileEffectErrorMessage(error)}`
|
|
@@ -2234,11 +2336,15 @@ function violatingIgnoredPaths(violations, ignoredPaths) {
|
|
|
2234
2336
|
const violating = new Set(violations.map((violation) => violation.file?.replace(/\/$/u, "")).filter(Boolean));
|
|
2235
2337
|
return ignoredPaths.filter((filePath) => violating.has(filePath.replace(/\/$/u, "")));
|
|
2236
2338
|
}
|
|
2237
|
-
function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
|
|
2238
|
-
const
|
|
2339
|
+
function resolveChangedFiles(result, repoRoot, rollbackBoundary, managedRoots = []) {
|
|
2340
|
+
const exclusions = repoRoot && rollbackBoundary
|
|
2341
|
+
? authenticateManagedPathExclusions(repoRoot, rollbackBoundary, managedRoots)
|
|
2342
|
+
: [];
|
|
2343
|
+
const adapterReported = filterManagedRollbackPaths(normalizeFileEffectPaths(result.execution?.changedFiles ?? []), exclusions);
|
|
2239
2344
|
const gitObserved = listAttemptChangedFilesSinceBoundary({
|
|
2240
2345
|
repoRoot,
|
|
2241
2346
|
boundary: rollbackBoundary,
|
|
2347
|
+
managedRoots,
|
|
2242
2348
|
includeBaselineSnapshotChanges: true
|
|
2243
2349
|
});
|
|
2244
2350
|
return normalizeFileEffectPaths([...adapterReported, ...gitObserved]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { makeLedgerEvent } from "./ledger.js";
|
|
2
2
|
export type { LedgerEvent, LedgerEventDraft, LedgerEventKind } from "./ledger.js";
|
|
3
|
-
export { resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./integrity.js";
|
|
3
|
+
export { resolveReceiptIntegrityRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./integrity.js";
|
|
4
4
|
export type { ReceiptIntegrityChainEntry, StoredReceiptIntegrityMaterial } from "./integrity.js";
|
|
5
5
|
export { artifactDir, createFileRunStore, resolveRunsRoot, runDir } from "./store.js";
|
|
6
6
|
export type { AttemptArtifacts, RunContract, RunStore } from "./store.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { makeLedgerEvent } from "./ledger.js";
|
|
2
|
-
export { resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./integrity.js";
|
|
2
|
+
export { resolveReceiptIntegrityRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./integrity.js";
|
|
3
3
|
export { artifactDir, createFileRunStore, resolveRunsRoot, runDir } from "./store.js";
|
|
4
4
|
export { readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile } from "./runs-reader.js";
|
|
5
5
|
export { compileAndPersistContext } from "./compiler.js";
|
|
@@ -35,6 +35,7 @@ export declare function verifyReceiptIntegrityFromFiles(input: {
|
|
|
35
35
|
runsRoot: string;
|
|
36
36
|
loopRecordPath: string;
|
|
37
37
|
ledgerPath: string;
|
|
38
|
+
integrityPath?: string;
|
|
38
39
|
}): Promise<ReceiptIntegritySummary>;
|
|
39
40
|
export declare function resolveReceiptIntegrityPath(runsRoot: string, runId: string): string;
|
|
40
41
|
export declare function ensurePrivateIntegrityKey(runsRoot: string, keyFileName: string): Promise<[string, string] | undefined>;
|
|
@@ -40,7 +40,7 @@ export async function writeReceiptIntegrityMaterial(input) {
|
|
|
40
40
|
return material;
|
|
41
41
|
}
|
|
42
42
|
export async function verifyReceiptIntegrityFromFiles(input) {
|
|
43
|
-
const integrityPath = resolveReceiptIntegrityPath(input.runsRoot, input.runId);
|
|
43
|
+
const integrityPath = input.integrityPath ?? resolveReceiptIntegrityPath(input.runsRoot, input.runId);
|
|
44
44
|
const [rawMaterial, rawLoopRecord, rawLedger, key] = await Promise.all([
|
|
45
45
|
readFile(integrityPath, "utf8").catch(() => null),
|
|
46
46
|
readFile(input.loopRecordPath, "utf8").catch(() => null),
|
|
@@ -385,7 +385,10 @@ function roundUsd(value) {
|
|
|
385
385
|
return Math.round(value * 100) / 100;
|
|
386
386
|
}
|
|
387
387
|
export function evaluateBudgetPreflight(input) {
|
|
388
|
-
const
|
|
388
|
+
const declaredPrice = input.pricePerMTokenUsd;
|
|
389
|
+
const validDeclaredPrice = declaredPrice === undefined
|
|
390
|
+
|| (Number.isFinite(declaredPrice) && declaredPrice >= 0);
|
|
391
|
+
const pricePerMToken = validDeclaredPrice ? (declaredPrice ?? 3.0) : 3.0;
|
|
389
392
|
const rawPromptTokens = Math.ceil(input.promptCharCount / 4);
|
|
390
393
|
const estimatedPromptTokens = Math.ceil(rawPromptTokens * 1.2);
|
|
391
394
|
const estimatedToolOverheadTokens = 800 + input.attemptCount * 200;
|
|
@@ -407,6 +410,13 @@ export function evaluateBudgetPreflight(input) {
|
|
|
407
410
|
estimatedAttemptCostUsd,
|
|
408
411
|
provenance
|
|
409
412
|
};
|
|
413
|
+
if (!validDeclaredPrice) {
|
|
414
|
+
return {
|
|
415
|
+
allowed: false,
|
|
416
|
+
reason: "Preflight: invalid adapter price; pricePerMTokenUsd must be finite and non-negative.",
|
|
417
|
+
estimate
|
|
418
|
+
};
|
|
419
|
+
}
|
|
410
420
|
if (input.remainingTokens !== undefined
|
|
411
421
|
&& estimatedMinimumViableTokens > 0
|
|
412
422
|
&& estimatedTotalTokens > input.remainingTokens) {
|