harnery 0.23.0 → 0.24.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 +1 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +212 -4
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +1 -1
- package/dist/core/workflow/spawn-cursor.d.ts +3 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +8 -4
- package/dist/core/workflow/workspaces/index.d.ts +1 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +1 -1
- package/dist/core/workflow/workspaces/integration.d.ts +8 -0
- package/dist/core/workflow/workspaces/integration.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/integration.js +15 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +19 -2
- package/package.json +1 -1
- package/src/commands/workflow.ts +257 -5
- package/src/core/workflow/index.ts +1 -0
- package/src/core/workflow/spawn-cursor.ts +8 -4
- package/src/core/workflow/workspaces/index.ts +5 -1
- package/src/core/workflow/workspaces/integration.ts +17 -1
- package/src/core/workflow/workspaces/leases.ts +24 -2
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- **Multi-agent coordination:** per-agent heartbeats in `.harnery/active/`, claim-time and commit-time guards, the canonical event stream, councils, a decision docket, per-agent scratchpads, and harness adapters for Claude Code / Cursor / Codex.
|
|
23
23
|
- **Cross-machine presence:** sessions on every machine that shares a repo see each other, labeled by machine. Zero-config over the repo's own git remote (presence refs), with an optional live-socket upgrade via an end-to-end-encrypted relay — the public `relay.harnery.com`, or self-host with `harn relay serve` / the bundled Cloudflare Worker (`relay/worker/`). The relay sees only ciphertext in opaquely-named rooms.
|
|
24
24
|
- **Coordination-aware workflows:** `harn workflow run <script>` executes bounded, schema-gated multi-subagent workflow scripts — plain JS stages fanning out to headless harness-CLI subagents that are born coordination-registered (heartbeats, events, claim guards), with deterministic code deciding the routing between stages. An optional host-owned policy gates dispatch and declared external mutations with `ALLOW`, `ASK`, and `DENY`; CLI `ASK` requests park durably for an explicit approve/deny and same-run resume, while library callers remain fail-closed by default.
|
|
25
|
-
- **Optional isolated workspaces:** add `--isolation worktree --workspace-root <dir>` to run a workflow in a host-owned local Git worktree. Harnery freezes the allocation and provider identity, verifies the workspace before execution and resume, requires review and policy authority before fast-forward integration, and keeps cleanup conservative. `harn workflow workspace
|
|
25
|
+
- **Optional isolated workspaces:** add `--isolation worktree --workspace-root <dir>` to run a workflow in a host-owned local Git worktree. Harnery freezes the allocation and provider identity, verifies the workspace before execution and resume, requires review and policy authority before fast-forward integration, and keeps cleanup conservative. Inspect with `harn workflow workspace`, then use the separate `workflow integration prepare`, `integration apply`, and `cleanup` phases; shared execution remains the default.
|
|
26
26
|
- **Durable work above attempts:** `harn work` preserves an objective across bounded workflow attempts, dependencies, approval parks, failures, and review. Reusable workflow code receives the exact work ID, title, objective, acceptance criteria, and attempt identity as frozen typed context. A retry also receives the prior run's bounded error and unresolved proof criteria, while retry authority and attempt ceilings remain unchanged. The manifest, proof, and append-only ledger bind the same context, and the one-shot reconciler reports the next explicit action without silently spawning, retrying, or accepting work.
|
|
27
27
|
- **Bounded goal supervision:** `harn supervisor` freezes a mission or existing root graph, specialist team, and safety limits, then runs scheduling cycles until reviewed mission success, human attention, no progress, or budget exhaustion. Its optional planner can create the first milestone, reassess accepted milestone proof, or recover a terminal graph using only a frozen workflow-template catalog. Plans can require independent bounded specialist review before ordinary approval or auto-apply authority, old generations remain immutable evidence, and all attempts still consume the goal budget. Use explicit foreground ticks or enroll goals in the optional heartbeat-backed background service.
|
|
28
28
|
- **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, per-project state, and a local coding-agent status view (Claude Code / Codex / Cursor usage, backed by `harn devtools`). Ships with the git clone, not the npm package (see [Install](#install)).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAiEnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAqqBjF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { resolveBinName, workflowSubscriptionOnly } from "../core/config.js";
|
|
3
4
|
import { createBuiltinHarnessRegistry } from "../core/harnesses/index.js";
|
|
4
5
|
import { findCoordRoot } from "../core/hooks/resolve/coord-root.js";
|
|
5
6
|
import { loadPolicyFile } from "../core/policy/index.js";
|
|
@@ -134,7 +135,7 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
134
135
|
else {
|
|
135
136
|
emit.text(`run ${err.runId} parked\napproval: ${err.approvalId}\n` +
|
|
136
137
|
`journal: ${err.journalPath}\n` +
|
|
137
|
-
`resume after resolution:
|
|
138
|
+
`resume after resolution: ${resolveBinName()} workflow resume ${err.runId}\n`);
|
|
138
139
|
}
|
|
139
140
|
return;
|
|
140
141
|
}
|
|
@@ -288,6 +289,186 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
288
289
|
: `${inspection.run_id}\tinvalid\t${inspection.error}`)
|
|
289
290
|
.join("\n")}\n`);
|
|
290
291
|
});
|
|
292
|
+
const integration = workflow
|
|
293
|
+
.command("integration")
|
|
294
|
+
.description("Prepare and apply proof-gated isolated-workspace integration.");
|
|
295
|
+
integration
|
|
296
|
+
.command("prepare <run-id>")
|
|
297
|
+
.description("Preview integration and write exact review and policy authority.")
|
|
298
|
+
.option("--policy <file>", "Current host policy JSON/JSONC for the Git mutation")
|
|
299
|
+
.option("--reviewer <name>", "Explicit reviewer for a standalone workflow")
|
|
300
|
+
.option("--reason <text>", "Reason recorded with the standalone review")
|
|
301
|
+
.option("--target-root <dir>", "Explicit target checkout (default: frozen source checkout)")
|
|
302
|
+
.option("--accept-unknown <code>", "Accept one verification unknown by code (repeatable)", collectOption, [])
|
|
303
|
+
.option("--approval-to <address>", "Address a durable policy ASK request")
|
|
304
|
+
.option("--json", "Emit the durable integration plan as JSON")
|
|
305
|
+
.action(async (runId, opts) => {
|
|
306
|
+
const coordRoot = findCoordRoot();
|
|
307
|
+
if (!coordRoot) {
|
|
308
|
+
emit.error({
|
|
309
|
+
code: "no_coord_root",
|
|
310
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
311
|
+
});
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
if (!opts.policy) {
|
|
315
|
+
emit.error({
|
|
316
|
+
code: "integration_policy_required",
|
|
317
|
+
message: "integration prepare requires --policy <file>",
|
|
318
|
+
});
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
if (opts.reason && !opts.reviewer) {
|
|
322
|
+
emit.error({
|
|
323
|
+
code: "integration_reviewer_required",
|
|
324
|
+
message: "--reason requires --reviewer <name>",
|
|
325
|
+
});
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
330
|
+
const { IntegrationPrepareParkedError, prepareIntegration } = await import("../core/workflow/index.js");
|
|
331
|
+
try {
|
|
332
|
+
const plan = await prepareIntegration({
|
|
333
|
+
coordRoot,
|
|
334
|
+
runId,
|
|
335
|
+
provider,
|
|
336
|
+
policy: loadPolicyFile(opts.policy),
|
|
337
|
+
targetRoot: opts.targetRoot,
|
|
338
|
+
review: opts.reviewer ? { actor: opts.reviewer, reason: opts.reason } : undefined,
|
|
339
|
+
acceptedUnknowns: opts.acceptUnknown,
|
|
340
|
+
approvalAddressee: opts.approvalTo,
|
|
341
|
+
});
|
|
342
|
+
if (opts.json) {
|
|
343
|
+
emit.config({ format: "json" });
|
|
344
|
+
emit.data(plan);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
emit.text(`integration plan ${plan.plan_id} authorized\n` +
|
|
348
|
+
`source: ${plan.provider_preview.source_commit}\n` +
|
|
349
|
+
`target: ${plan.provider_preview.target_ref} at ${plan.provider_preview.target_commit}\n` +
|
|
350
|
+
`changes: ${plan.provider_preview.changed_paths.length}\n` +
|
|
351
|
+
`apply: ${resolveBinName()} workflow integration apply ${runId} --yes\n`);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
if (error instanceof IntegrationPrepareParkedError) {
|
|
355
|
+
if (opts.json) {
|
|
356
|
+
emit.config({ format: "json" });
|
|
357
|
+
emit.data({
|
|
358
|
+
status: "parked",
|
|
359
|
+
runId: error.runId,
|
|
360
|
+
planId: error.planId,
|
|
361
|
+
approvalId: error.approvalId,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
emit.text(`integration preparation parked\n` +
|
|
366
|
+
`run: ${error.runId}\n` +
|
|
367
|
+
`plan: ${error.planId}\n` +
|
|
368
|
+
`approval: ${error.approvalId}\n` +
|
|
369
|
+
`approve: ${resolveBinName()} workflow approvals approve ${error.approvalId}\n`);
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
emit.error({
|
|
378
|
+
code: "workflow_integration_prepare_failed",
|
|
379
|
+
message: error instanceof Error ? error.message : String(error),
|
|
380
|
+
});
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
integration
|
|
385
|
+
.command("apply <run-id>")
|
|
386
|
+
.description("Apply an authorized durable integration plan.")
|
|
387
|
+
.option("--yes", "Confirm the target-branch mutation")
|
|
388
|
+
.option("--json", "Emit the durable integration receipt as JSON")
|
|
389
|
+
.action(async (runId, opts) => {
|
|
390
|
+
const coordRoot = findCoordRoot();
|
|
391
|
+
if (!coordRoot) {
|
|
392
|
+
emit.error({
|
|
393
|
+
code: "no_coord_root",
|
|
394
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
395
|
+
});
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
if (!opts.yes) {
|
|
399
|
+
emit.error({
|
|
400
|
+
code: "integration_confirmation_required",
|
|
401
|
+
message: "integration apply changes the target branch; pass --yes to confirm",
|
|
402
|
+
});
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
407
|
+
const { applyIntegration } = await import("../core/workflow/index.js");
|
|
408
|
+
const receipt = await applyIntegration({ coordRoot, runId, provider });
|
|
409
|
+
if (opts.json) {
|
|
410
|
+
emit.config({ format: "json" });
|
|
411
|
+
emit.data(receipt);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
emit.text(`integration ${receipt.status}: ${receipt.target_ref} at ${receipt.target_commit}\n` +
|
|
415
|
+
`receipt: ${receipt.receipt_id}\n` +
|
|
416
|
+
`cleanup: ${resolveBinName()} workflow cleanup ${runId} --yes\n`);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
emit.error({
|
|
420
|
+
code: "workflow_integration_apply_failed",
|
|
421
|
+
message: error instanceof Error ? error.message : String(error),
|
|
422
|
+
});
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
workflow
|
|
427
|
+
.command("cleanup <run-id>")
|
|
428
|
+
.description("Conservatively release a provider-owned isolated workspace.")
|
|
429
|
+
.option("--yes", "Confirm the worktree and provider-branch removal attempt")
|
|
430
|
+
.option("--json", "Emit the cleanup attempt or receipt as JSON")
|
|
431
|
+
.action(async (runId, opts) => {
|
|
432
|
+
const coordRoot = findCoordRoot();
|
|
433
|
+
if (!coordRoot) {
|
|
434
|
+
emit.error({
|
|
435
|
+
code: "no_coord_root",
|
|
436
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
437
|
+
});
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
if (!opts.yes) {
|
|
441
|
+
emit.error({
|
|
442
|
+
code: "cleanup_confirmation_required",
|
|
443
|
+
message: "workspace cleanup may remove a worktree and branch; pass --yes to confirm",
|
|
444
|
+
});
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
449
|
+
const { cleanupWorkspace } = await import("../core/workflow/index.js");
|
|
450
|
+
const result = await cleanupWorkspace({ coordRoot, runId, provider });
|
|
451
|
+
if (opts.json) {
|
|
452
|
+
emit.config({ format: "json" });
|
|
453
|
+
emit.data(result);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const receipt = "receipt_id" in result && typeof result.receipt_id === "string"
|
|
457
|
+
? `\nreceipt: ${result.receipt_id}`
|
|
458
|
+
: "";
|
|
459
|
+
const reason = "reason" in result && typeof result.reason === "string"
|
|
460
|
+
? `\nreason: ${result.reason}`
|
|
461
|
+
: "";
|
|
462
|
+
emit.text(`workspace cleanup ${result.status}: ${result.binding_id}${receipt}${reason}\n`);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
emit.error({
|
|
466
|
+
code: "workflow_cleanup_failed",
|
|
467
|
+
message: error instanceof Error ? error.message : String(error),
|
|
468
|
+
});
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
291
472
|
const approvals = workflow
|
|
292
473
|
.command("approvals")
|
|
293
474
|
.description("Inspect and resolve durable workflow policy approvals.");
|
|
@@ -379,7 +560,7 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
379
560
|
else {
|
|
380
561
|
emit.text(`${renderWorkflowApproval(resolved.approval)}` +
|
|
381
562
|
`${resolved.applied ? "decision recorded" : "decision already recorded"}\n` +
|
|
382
|
-
|
|
563
|
+
approvalNextActionHint(coordRoot, resolved.approval));
|
|
383
564
|
}
|
|
384
565
|
}
|
|
385
566
|
catch (err) {
|
|
@@ -422,3 +603,30 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
422
603
|
}
|
|
423
604
|
});
|
|
424
605
|
}
|
|
606
|
+
async function builtInProviderForRun(coordRoot, runId) {
|
|
607
|
+
const { createLocalGitWorktreeProvider, readWorkflowRunManifest } = await import("../core/workflow/index.js");
|
|
608
|
+
const manifest = readWorkflowRunManifest(coordRoot, runId);
|
|
609
|
+
const binding = manifest.execution.workspace_binding;
|
|
610
|
+
if (!binding) {
|
|
611
|
+
throw new Error(`workflow run ${runId} has no isolated workspace binding`);
|
|
612
|
+
}
|
|
613
|
+
if (binding.provider.id !== "local-git-worktree") {
|
|
614
|
+
throw new Error(`workflow run ${runId} requires unavailable workspace provider ${binding.provider.id}`);
|
|
615
|
+
}
|
|
616
|
+
return createLocalGitWorktreeProvider({ coordRoot });
|
|
617
|
+
}
|
|
618
|
+
function collectOption(value, previous) {
|
|
619
|
+
return [...previous, value];
|
|
620
|
+
}
|
|
621
|
+
/** Next operator command after resolving an approval. Mid-run parks resume the
|
|
622
|
+
* workflow; integration prepare parks after the run is already terminal, so
|
|
623
|
+
* the durable plan is authorized by re-running prepare (resume would fail). */
|
|
624
|
+
function approvalNextActionHint(coordRoot, approval) {
|
|
625
|
+
const bin = resolveBinName();
|
|
626
|
+
const runId = approval.request.run_id;
|
|
627
|
+
const integrationPlanPath = join(coordRoot, ".harnery", "workflows", runId, "integration", "plan.json");
|
|
628
|
+
if (existsSync(integrationPlanPath)) {
|
|
629
|
+
return `prepare: ${bin} workflow integration prepare ${runId}\n`;
|
|
630
|
+
}
|
|
631
|
+
return `resume: ${bin} workflow resume ${runId}\n`;
|
|
632
|
+
}
|
|
@@ -8,5 +8,5 @@ export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowS
|
|
|
8
8
|
export type { AcceptanceCriterion, AcceptanceResult, AcceptanceStatus, AcceptanceSummary, AgentOpts, EngineOpts, EvidenceKind, EvidenceSource, EvidenceStatus, HarnessEvidenceCapability, HarnessEvidenceCoverage, HarnessName, ResultDigest, RunReport, Spawner, SpawnRequest, SpawnResult, StageSchema, WorkflowAgentProof, WorkflowApprovalMode, WorkflowAttemptContext, WorkflowAttemptFailureCause, WorkflowAttemptPriorContext, WorkflowAttemptUnresolvedCriterion, WorkflowContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowModule, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowRunStatus, WorkflowSpecialistProfile, WorkflowWorkContext, } from "./types.js";
|
|
9
9
|
export { WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION, WORKFLOW_PROOF_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "./types.js";
|
|
10
10
|
export type { ApplyIntegrationInput, AuthorizedIntegrationPlan, AuthorizedProviderIntegrationInput, CancelWorkspaceInput, CleanupWorkspaceInput, FilesystemIdentity, IntegrationApplyAttempt, IntegrationApplyReceipt, IntegrationAuthorization, IntegrationPlan, IntegrationReviewRecord, IntegrationTarget, LocalGitWorktreeProviderOptions, PrepareIntegrationInput, ProviderIntegrationInput, ProviderIntegrationPreview, ProviderIntegrationResult, ProviderUnknown, ProviderUnsupportedReason, RepositoryBinding, ValidatedFilesystemPath, WorkflowWorkspaceInspection, WorkflowWorkspaceSelection, WorkflowWorkspaceStatus, WorkflowWorkspaceVerification, WorkspaceAllocationRequest, WorkspaceAttestation, WorkspaceBinding, WorkspaceBoundExecutionEvidence, WorkspaceCancellationReceipt, WorkspaceCancellationResult, WorkspaceCleanupAttempt, WorkspaceCleanupIntent, WorkspaceCleanupMode, WorkspaceCleanupReceipt, WorkspaceCleanupResult, WorkspaceCompatibilityExecutionEvidence, WorkspaceExecutionEvidence, WorkspaceIntegrationState, WorkspaceIsolation, WorkspaceLifecycleProjection, WorkspaceLifecycleProjectionInput, WorkspaceLifecycleState, WorkspaceObservation, WorkspaceOwner, WorkspaceProbeInput, WorkspaceProbeResult, WorkspaceProofOutcome, WorkspaceProvider, WorkspaceProviderCapabilities, WorkspaceProviderRef, WorkspaceResourceState, WorkspaceUnsupportedExecutionEvidence, } from "./workspaces/index.js";
|
|
11
|
-
export { applyIntegration, assertWorkspaceLifecycleTransition, cancelWorkspace, cleanupWorkspace, createLocalGitWorktreeProvider, deriveWorkspaceLifecycle, inspectWorkflowWorkspace, isWorkspaceLifecycleState, listWorkflowWorkspaceInspections, prepareIntegration, probeLocalGitWorktreeProvider, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, validateProviderEventChain, WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./workspaces/index.js";
|
|
11
|
+
export { applyIntegration, assertWorkspaceLifecycleTransition, cancelWorkspace, cleanupWorkspace, createLocalGitWorktreeProvider, deriveWorkspaceLifecycle, IntegrationPrepareParkedError, inspectWorkflowWorkspace, isWorkspaceLifecycleState, listWorkflowWorkspaceInspections, prepareIntegration, probeLocalGitWorktreeProvider, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, validateProviderEventChain, WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./workspaces/index.js";
|
|
12
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,4 +4,4 @@ export { runWorkflow, WorkflowParkedError, WorkflowRunError } from "./engine.js"
|
|
|
4
4
|
export { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
|
|
5
5
|
export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, readWorkflowRunManifest, WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION, workflowRunManifestPath, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
|
|
6
6
|
export { WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION, WORKFLOW_PROOF_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "./types.js";
|
|
7
|
-
export { applyIntegration, assertWorkspaceLifecycleTransition, cancelWorkspace, cleanupWorkspace, createLocalGitWorktreeProvider, deriveWorkspaceLifecycle, inspectWorkflowWorkspace, isWorkspaceLifecycleState, listWorkflowWorkspaceInspections, prepareIntegration, probeLocalGitWorktreeProvider, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, validateProviderEventChain, WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./workspaces/index.js";
|
|
7
|
+
export { applyIntegration, assertWorkspaceLifecycleTransition, cancelWorkspace, cleanupWorkspace, createLocalGitWorktreeProvider, deriveWorkspaceLifecycle, IntegrationPrepareParkedError, inspectWorkflowWorkspace, isWorkspaceLifecycleState, listWorkflowWorkspaceInspections, prepareIntegration, probeLocalGitWorktreeProvider, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, validateProviderEventChain, WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./workspaces/index.js";
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* session_id, …}`).
|
|
11
11
|
* - `--trust` is required: headless runs refuse untrusted workspaces (exit 1,
|
|
12
12
|
* "Workspace Trust Required") — see the argv comment below.
|
|
13
|
+
* - `--force` is required: print mode has no channel for an operator to approve
|
|
14
|
+
* shell commands, so Smart Auto can leave a child able to patch files but
|
|
15
|
+
* unable to test or commit them.
|
|
13
16
|
* - Envelope drift guard: when stdout doesn't parse as JSON but the process
|
|
14
17
|
* exited 0, the raw stdout is returned as the reply text.
|
|
15
18
|
* - No per-run cost surface → undefined. No max-turns equivalent → `maxTurns`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAU1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA6BxE;AAED,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC"}
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* session_id, …}`).
|
|
11
11
|
* - `--trust` is required: headless runs refuse untrusted workspaces (exit 1,
|
|
12
12
|
* "Workspace Trust Required") — see the argv comment below.
|
|
13
|
+
* - `--force` is required: print mode has no channel for an operator to approve
|
|
14
|
+
* shell commands, so Smart Auto can leave a child able to patch files but
|
|
15
|
+
* unable to test or commit them.
|
|
13
16
|
* - Envelope drift guard: when stdout doesn't parse as JSON but the process
|
|
14
17
|
* exited 0, the raw stdout is returned as the reply text.
|
|
15
18
|
* - No per-run cost surface → undefined. No max-turns equivalent → `maxTurns`
|
|
@@ -36,10 +39,11 @@ export function parseCursorOutput(stdout) {
|
|
|
36
39
|
export function buildCursorInvocation(req) {
|
|
37
40
|
validateHarnessEffort("cursor", req.effort);
|
|
38
41
|
// --trust: headless cursor-agent refuses untrusted workspaces (exit 1,
|
|
39
|
-
// "Workspace Trust Required").
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
42
|
+
// "Workspace Trust Required"). --force: a print-mode child has no interactive
|
|
43
|
+
// approval channel, so host-authorized workflow dispatch must let it run
|
|
44
|
+
// commands. Neither flag maps host policy into child tools; that capability
|
|
45
|
+
// remains explicitly unsupported.
|
|
46
|
+
const argv = ["cursor-agent", "-p", req.prompt, "--output-format", "json", "--trust", "--force"];
|
|
43
47
|
if (req.model)
|
|
44
48
|
argv.push("--model", req.model);
|
|
45
49
|
return { argv };
|
|
@@ -5,7 +5,7 @@ export { cleanupWorkspace } from "./cleanup.js";
|
|
|
5
5
|
export type { WorkflowWorkspaceInspection, WorkflowWorkspaceSelection, WorkflowWorkspaceStatus, WorkflowWorkspaceVerification, } from "./inspect.js";
|
|
6
6
|
export { inspectWorkflowWorkspace, listWorkflowWorkspaceInspections, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, } from "./inspect.js";
|
|
7
7
|
export type { ApplyIntegrationInput, PrepareIntegrationInput } from "./integration.js";
|
|
8
|
-
export { applyIntegration, prepareIntegration } from "./integration.js";
|
|
8
|
+
export { applyIntegration, IntegrationPrepareParkedError, prepareIntegration, } from "./integration.js";
|
|
9
9
|
export type { WorkspaceLifecycleProjection, WorkspaceLifecycleProjectionInput, } from "./lifecycle.js";
|
|
10
10
|
export { assertWorkspaceLifecycleTransition, deriveWorkspaceLifecycle, isWorkspaceLifecycleState, validateProviderEventChain, } from "./lifecycle.js";
|
|
11
11
|
export type { LocalGitWorktreeProviderOptions } from "./local-git.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EACL,gBAAgB,EAChB,6BAA6B,EAC7B,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,4BAA4B,EAC5B,iCAAiC,GAClC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kCAAkC,EAClC,wBAAwB,EACxB,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EACL,8BAA8B,EAC9B,KAAK,IAAI,6BAA6B,GACvC,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,yBAAyB,EACzB,kCAAkC,EAClC,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,EACf,uBAAuB,EACvB,iBAAiB,EACjB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,0BAA0B,EAC1B,oBAAoB,EACpB,gBAAgB,EAChB,+BAA+B,EAC/B,4BAA4B,EAC5B,2BAA2B,EAC3B,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,uCAAuC,EACvC,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,4BAA4B,EAC5B,qBAAqB,EACrB,iBAAiB,EACjB,6BAA6B,EAC7B,oBAAoB,EACpB,sBAAsB,EACtB,qCAAqC,GACtC,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gCAAgC,EAChC,gCAAgC,GACjC,MAAM,YAAY,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { cancelWorkspace } from "./cancellation.js";
|
|
2
2
|
export { cleanupWorkspace } from "./cleanup.js";
|
|
3
3
|
export { inspectWorkflowWorkspace, listWorkflowWorkspaceInspections, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, } from "./inspect.js";
|
|
4
|
-
export { applyIntegration, prepareIntegration } from "./integration.js";
|
|
4
|
+
export { applyIntegration, IntegrationPrepareParkedError, prepareIntegration, } from "./integration.js";
|
|
5
5
|
export { assertWorkspaceLifecycleTransition, deriveWorkspaceLifecycle, isWorkspaceLifecycleState, validateProviderEventChain, } from "./lifecycle.js";
|
|
6
6
|
export { createLocalGitWorktreeProvider, probe as probeLocalGitWorktreeProvider, } from "./local-git.js";
|
|
7
7
|
export { WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./types.js";
|
|
@@ -19,6 +19,14 @@ export interface ApplyIntegrationInput {
|
|
|
19
19
|
provider: WorkspaceProvider;
|
|
20
20
|
plan?: IntegrationPlan;
|
|
21
21
|
}
|
|
22
|
+
/** Host-policy ASK still pending during integration prepare. Carries the exact
|
|
23
|
+
* durable IDs an operator needs to approve and resume the same plan. */
|
|
24
|
+
export declare class IntegrationPrepareParkedError extends Error {
|
|
25
|
+
readonly runId: string;
|
|
26
|
+
readonly planId: string;
|
|
27
|
+
readonly approvalId: string;
|
|
28
|
+
constructor(runId: string, planId: string, approvalId: string);
|
|
29
|
+
}
|
|
22
30
|
export declare function prepareIntegration(input: PrepareIntegrationInput): Promise<IntegrationPlan>;
|
|
23
31
|
export declare function applyIntegration(input: ApplyIntegrationInput): Promise<IntegrationApplyReceipt>;
|
|
24
32
|
//# sourceMappingURL=integration.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/integration.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EAGrB,KAAK,UAAU,EAGhB,MAAM,uBAAuB,CAAC;AAgB/B,OAAO,KAAK,EAEV,uBAAuB,EAEvB,eAAe,EAOf,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAUpB,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EAAE,UAAU,GAAG,gBAAgB,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CA2GjG;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,uBAAuB,CAAC,CAuNlC"}
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/integration.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EAGrB,KAAK,UAAU,EAGhB,MAAM,uBAAuB,CAAC;AAgB/B,OAAO,KAAK,EAEV,uBAAuB,EAEvB,eAAe,EAOf,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAUpB,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EAAE,UAAU,GAAG,gBAAgB,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED;wEACwE;AACxE,qBAAa,6BAA8B,SAAQ,KAAK;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;CAO9D;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CA2GjG;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,uBAAuB,CAAC,CAuNlC"}
|
|
@@ -10,6 +10,20 @@ import { appendIntegrationAttempt, appendWorkflowJournalEvent, fileSha256, readI
|
|
|
10
10
|
import { isWorkspaceAllocationRequest, isWorkspaceAttestation, isWorkspaceBoundExecutionEvidence, } from "./validate.js";
|
|
11
11
|
const INTEGRATION_DECISION_ID = "p99999";
|
|
12
12
|
const TARGET_LEASE_STALE_MS = 5 * 60 * 1_000;
|
|
13
|
+
/** Host-policy ASK still pending during integration prepare. Carries the exact
|
|
14
|
+
* durable IDs an operator needs to approve and resume the same plan. */
|
|
15
|
+
export class IntegrationPrepareParkedError extends Error {
|
|
16
|
+
runId;
|
|
17
|
+
planId;
|
|
18
|
+
approvalId;
|
|
19
|
+
constructor(runId, planId, approvalId) {
|
|
20
|
+
super(`integration preparation parked: run ${runId}, plan ${planId}, approval ${approvalId}`);
|
|
21
|
+
this.name = "IntegrationPrepareParkedError";
|
|
22
|
+
this.runId = runId;
|
|
23
|
+
this.planId = planId;
|
|
24
|
+
this.approvalId = approvalId;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
13
27
|
export async function prepareIntegration(input) {
|
|
14
28
|
const evidence = await readAndValidateEvidence(input.coordRoot, input.runId, input.provider);
|
|
15
29
|
const targetRoot = resolve(input.targetRoot ?? requiredIntegrationRoot(evidence.binding));
|
|
@@ -476,7 +490,7 @@ function authorizePlan(input, plan) {
|
|
|
476
490
|
});
|
|
477
491
|
approvalId = created.approval.request.id;
|
|
478
492
|
if (created.approval.status === "pending") {
|
|
479
|
-
throw new
|
|
493
|
+
throw new IntegrationPrepareParkedError(input.runId, plan.plan_id, approvalId);
|
|
480
494
|
}
|
|
481
495
|
if (created.approval.status !== "approved" || !created.approval.decision) {
|
|
482
496
|
throw new Error(`integration approval ${approvalId} was denied`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leases.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/leases.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,CAAC,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,iBAAiB,GACzB,eAAe,GACf,cAAc,GACd,iBAAiB,GACjB,oBAAoB,CAAC;AAEzB,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC;IAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,OAAO,IAAI,IAAI,CAAC;CACjB;AAUD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"leases.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/leases.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,CAAC,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,iBAAiB,GACzB,eAAe,GACf,cAAc,GACd,iBAAiB,GACjB,oBAAoB,CAAC;AAEzB,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC;IAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,OAAO,IAAI,IAAI,CAAC;CACjB;AAUD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,cAAc,CAmGvF"}
|
|
@@ -66,8 +66,15 @@ export function acquireNoClobberLease(input) {
|
|
|
66
66
|
recoveryClaimed = true;
|
|
67
67
|
input.onRecoveryStep?.("claim_created");
|
|
68
68
|
const pinPath = join(recoveryPath, "stale-pin");
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
try {
|
|
70
|
+
linkSync(currentPath, pinPath);
|
|
71
|
+
assertSameInodeAndBytes(currentPath, pinPath, observed.bytes);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
abortUnchangedRecoveryClaim(leaseDir, recoveryPath, pinPath, owner);
|
|
75
|
+
recoveryClaimed = false;
|
|
76
|
+
throw new Error(`lease ${input.scope} recovery raced with another contender`);
|
|
77
|
+
}
|
|
71
78
|
fsyncDirectory(recoveryPath);
|
|
72
79
|
input.onRecoveryStep?.("stale_pinned");
|
|
73
80
|
assertSameInodeAndBytes(currentPath, pinPath, observed.bytes);
|
|
@@ -199,6 +206,16 @@ function reclaimAbandonedRecovery(leaseDir, recoveryPath, input, now) {
|
|
|
199
206
|
rmdirSync(quarantine);
|
|
200
207
|
fsyncDirectory(leaseDir);
|
|
201
208
|
}
|
|
209
|
+
function abortUnchangedRecoveryClaim(leaseDir, recoveryPath, pinPath, claimant) {
|
|
210
|
+
const claim = readRecoveryClaim(recoveryPath);
|
|
211
|
+
if (claim.claimant.owner_id !== claimant.owner_id) {
|
|
212
|
+
throw new Error(`lease ${claimant.scope} recovery ownership changed`);
|
|
213
|
+
}
|
|
214
|
+
safeUnlink(pinPath);
|
|
215
|
+
unlinkSync(join(recoveryPath, "claim.json"));
|
|
216
|
+
rmdirSync(recoveryPath);
|
|
217
|
+
fsyncDirectory(leaseDir);
|
|
218
|
+
}
|
|
202
219
|
function readRecoveryClaim(recoveryPath) {
|
|
203
220
|
const path = join(recoveryPath, "claim.json");
|
|
204
221
|
const size = lstatSync(path).size;
|
package/package.json
CHANGED
package/src/commands/workflow.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
2
3
|
import type { Command } from "commander";
|
|
3
4
|
import type { EmitContext } from "../commander.ts";
|
|
4
|
-
import { workflowSubscriptionOnly } from "../core/config.ts";
|
|
5
|
+
import { resolveBinName, workflowSubscriptionOnly } from "../core/config.ts";
|
|
5
6
|
import { createBuiltinHarnessRegistry } from "../core/harnesses/index.ts";
|
|
6
7
|
import { findCoordRoot } from "../core/hooks/resolve/coord-root.ts";
|
|
7
8
|
import type { PolicyIsolation } from "../core/policy/index.ts";
|
|
8
9
|
import { loadPolicyFile } from "../core/policy/index.ts";
|
|
9
|
-
import type { WorkflowApprovalStatus } from "../core/workflow/approvals.ts";
|
|
10
|
+
import type { WorkflowApproval, WorkflowApprovalStatus } from "../core/workflow/approvals.ts";
|
|
10
11
|
import { WorkflowParkedError } from "../core/workflow/engine.ts";
|
|
12
|
+
import type { WorkspaceProvider } from "../core/workflow/index.ts";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* `workflow run <script>`: execute a workflow script — bounded, schema-gated,
|
|
@@ -49,6 +51,21 @@ interface WorkflowApprovalDecisionOpts {
|
|
|
49
51
|
json?: boolean;
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
interface WorkflowIntegrationPrepareOpts {
|
|
55
|
+
policy?: string;
|
|
56
|
+
reviewer?: string;
|
|
57
|
+
reason?: string;
|
|
58
|
+
targetRoot?: string;
|
|
59
|
+
acceptUnknown?: string[];
|
|
60
|
+
approvalTo?: string;
|
|
61
|
+
json?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface WorkflowConfirmedMutationOpts {
|
|
65
|
+
yes?: boolean;
|
|
66
|
+
json?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
export function registerWorkflowCommand(program: Command, emit: EmitContext): void {
|
|
53
70
|
const registry = createBuiltinHarnessRegistry();
|
|
54
71
|
const harnesses = registry.ids();
|
|
@@ -211,7 +228,7 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
|
|
|
211
228
|
emit.text(
|
|
212
229
|
`run ${err.runId} parked\napproval: ${err.approvalId}\n` +
|
|
213
230
|
`journal: ${err.journalPath}\n` +
|
|
214
|
-
`resume after resolution:
|
|
231
|
+
`resume after resolution: ${resolveBinName()} workflow resume ${err.runId}\n`,
|
|
215
232
|
);
|
|
216
233
|
}
|
|
217
234
|
return;
|
|
@@ -387,6 +404,200 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
|
|
|
387
404
|
);
|
|
388
405
|
});
|
|
389
406
|
|
|
407
|
+
const integration = workflow
|
|
408
|
+
.command("integration")
|
|
409
|
+
.description("Prepare and apply proof-gated isolated-workspace integration.");
|
|
410
|
+
|
|
411
|
+
integration
|
|
412
|
+
.command("prepare <run-id>")
|
|
413
|
+
.description("Preview integration and write exact review and policy authority.")
|
|
414
|
+
.option("--policy <file>", "Current host policy JSON/JSONC for the Git mutation")
|
|
415
|
+
.option("--reviewer <name>", "Explicit reviewer for a standalone workflow")
|
|
416
|
+
.option("--reason <text>", "Reason recorded with the standalone review")
|
|
417
|
+
.option("--target-root <dir>", "Explicit target checkout (default: frozen source checkout)")
|
|
418
|
+
.option(
|
|
419
|
+
"--accept-unknown <code>",
|
|
420
|
+
"Accept one verification unknown by code (repeatable)",
|
|
421
|
+
collectOption,
|
|
422
|
+
[],
|
|
423
|
+
)
|
|
424
|
+
.option("--approval-to <address>", "Address a durable policy ASK request")
|
|
425
|
+
.option("--json", "Emit the durable integration plan as JSON")
|
|
426
|
+
.action(async (runId: string, opts: WorkflowIntegrationPrepareOpts) => {
|
|
427
|
+
const coordRoot = findCoordRoot();
|
|
428
|
+
if (!coordRoot) {
|
|
429
|
+
emit.error({
|
|
430
|
+
code: "no_coord_root",
|
|
431
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
432
|
+
});
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
if (!opts.policy) {
|
|
436
|
+
emit.error({
|
|
437
|
+
code: "integration_policy_required",
|
|
438
|
+
message: "integration prepare requires --policy <file>",
|
|
439
|
+
});
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
if (opts.reason && !opts.reviewer) {
|
|
443
|
+
emit.error({
|
|
444
|
+
code: "integration_reviewer_required",
|
|
445
|
+
message: "--reason requires --reviewer <name>",
|
|
446
|
+
});
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
451
|
+
const { IntegrationPrepareParkedError, prepareIntegration } = await import(
|
|
452
|
+
"../core/workflow/index.ts"
|
|
453
|
+
);
|
|
454
|
+
try {
|
|
455
|
+
const plan = await prepareIntegration({
|
|
456
|
+
coordRoot,
|
|
457
|
+
runId,
|
|
458
|
+
provider,
|
|
459
|
+
policy: loadPolicyFile(opts.policy),
|
|
460
|
+
targetRoot: opts.targetRoot,
|
|
461
|
+
review: opts.reviewer ? { actor: opts.reviewer, reason: opts.reason } : undefined,
|
|
462
|
+
acceptedUnknowns: opts.acceptUnknown,
|
|
463
|
+
approvalAddressee: opts.approvalTo,
|
|
464
|
+
});
|
|
465
|
+
if (opts.json) {
|
|
466
|
+
emit.config({ format: "json" });
|
|
467
|
+
emit.data(plan);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
emit.text(
|
|
471
|
+
`integration plan ${plan.plan_id} authorized\n` +
|
|
472
|
+
`source: ${plan.provider_preview.source_commit}\n` +
|
|
473
|
+
`target: ${plan.provider_preview.target_ref} at ${plan.provider_preview.target_commit}\n` +
|
|
474
|
+
`changes: ${plan.provider_preview.changed_paths.length}\n` +
|
|
475
|
+
`apply: ${resolveBinName()} workflow integration apply ${runId} --yes\n`,
|
|
476
|
+
);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
if (error instanceof IntegrationPrepareParkedError) {
|
|
479
|
+
if (opts.json) {
|
|
480
|
+
emit.config({ format: "json" });
|
|
481
|
+
emit.data({
|
|
482
|
+
status: "parked",
|
|
483
|
+
runId: error.runId,
|
|
484
|
+
planId: error.planId,
|
|
485
|
+
approvalId: error.approvalId,
|
|
486
|
+
});
|
|
487
|
+
} else {
|
|
488
|
+
emit.text(
|
|
489
|
+
`integration preparation parked\n` +
|
|
490
|
+
`run: ${error.runId}\n` +
|
|
491
|
+
`plan: ${error.planId}\n` +
|
|
492
|
+
`approval: ${error.approvalId}\n` +
|
|
493
|
+
`approve: ${resolveBinName()} workflow approvals approve ${error.approvalId}\n`,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
emit.error({
|
|
502
|
+
code: "workflow_integration_prepare_failed",
|
|
503
|
+
message: error instanceof Error ? error.message : String(error),
|
|
504
|
+
});
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
integration
|
|
510
|
+
.command("apply <run-id>")
|
|
511
|
+
.description("Apply an authorized durable integration plan.")
|
|
512
|
+
.option("--yes", "Confirm the target-branch mutation")
|
|
513
|
+
.option("--json", "Emit the durable integration receipt as JSON")
|
|
514
|
+
.action(async (runId: string, opts: WorkflowConfirmedMutationOpts) => {
|
|
515
|
+
const coordRoot = findCoordRoot();
|
|
516
|
+
if (!coordRoot) {
|
|
517
|
+
emit.error({
|
|
518
|
+
code: "no_coord_root",
|
|
519
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
520
|
+
});
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
if (!opts.yes) {
|
|
524
|
+
emit.error({
|
|
525
|
+
code: "integration_confirmation_required",
|
|
526
|
+
message: "integration apply changes the target branch; pass --yes to confirm",
|
|
527
|
+
});
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
532
|
+
const { applyIntegration } = await import("../core/workflow/index.ts");
|
|
533
|
+
const receipt = await applyIntegration({ coordRoot, runId, provider });
|
|
534
|
+
if (opts.json) {
|
|
535
|
+
emit.config({ format: "json" });
|
|
536
|
+
emit.data(receipt);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
emit.text(
|
|
540
|
+
`integration ${receipt.status}: ${receipt.target_ref} at ${receipt.target_commit}\n` +
|
|
541
|
+
`receipt: ${receipt.receipt_id}\n` +
|
|
542
|
+
`cleanup: ${resolveBinName()} workflow cleanup ${runId} --yes\n`,
|
|
543
|
+
);
|
|
544
|
+
} catch (error) {
|
|
545
|
+
emit.error({
|
|
546
|
+
code: "workflow_integration_apply_failed",
|
|
547
|
+
message: error instanceof Error ? error.message : String(error),
|
|
548
|
+
});
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
workflow
|
|
554
|
+
.command("cleanup <run-id>")
|
|
555
|
+
.description("Conservatively release a provider-owned isolated workspace.")
|
|
556
|
+
.option("--yes", "Confirm the worktree and provider-branch removal attempt")
|
|
557
|
+
.option("--json", "Emit the cleanup attempt or receipt as JSON")
|
|
558
|
+
.action(async (runId: string, opts: WorkflowConfirmedMutationOpts) => {
|
|
559
|
+
const coordRoot = findCoordRoot();
|
|
560
|
+
if (!coordRoot) {
|
|
561
|
+
emit.error({
|
|
562
|
+
code: "no_coord_root",
|
|
563
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
564
|
+
});
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
if (!opts.yes) {
|
|
568
|
+
emit.error({
|
|
569
|
+
code: "cleanup_confirmation_required",
|
|
570
|
+
message: "workspace cleanup may remove a worktree and branch; pass --yes to confirm",
|
|
571
|
+
});
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
576
|
+
const { cleanupWorkspace } = await import("../core/workflow/index.ts");
|
|
577
|
+
const result = await cleanupWorkspace({ coordRoot, runId, provider });
|
|
578
|
+
if (opts.json) {
|
|
579
|
+
emit.config({ format: "json" });
|
|
580
|
+
emit.data(result);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const receipt =
|
|
584
|
+
"receipt_id" in result && typeof result.receipt_id === "string"
|
|
585
|
+
? `\nreceipt: ${result.receipt_id}`
|
|
586
|
+
: "";
|
|
587
|
+
const reason =
|
|
588
|
+
"reason" in result && typeof result.reason === "string"
|
|
589
|
+
? `\nreason: ${result.reason}`
|
|
590
|
+
: "";
|
|
591
|
+
emit.text(`workspace cleanup ${result.status}: ${result.binding_id}${receipt}${reason}\n`);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
emit.error({
|
|
594
|
+
code: "workflow_cleanup_failed",
|
|
595
|
+
message: error instanceof Error ? error.message : String(error),
|
|
596
|
+
});
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
390
601
|
const approvals = workflow
|
|
391
602
|
.command("approvals")
|
|
392
603
|
.description("Inspect and resolve durable workflow policy approvals.");
|
|
@@ -488,7 +699,7 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
|
|
|
488
699
|
emit.text(
|
|
489
700
|
`${renderWorkflowApproval(resolved.approval)}` +
|
|
490
701
|
`${resolved.applied ? "decision recorded" : "decision already recorded"}\n` +
|
|
491
|
-
|
|
702
|
+
approvalNextActionHint(coordRoot, resolved.approval),
|
|
492
703
|
);
|
|
493
704
|
}
|
|
494
705
|
} catch (err) {
|
|
@@ -533,3 +744,44 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
|
|
|
533
744
|
}
|
|
534
745
|
});
|
|
535
746
|
}
|
|
747
|
+
|
|
748
|
+
async function builtInProviderForRun(coordRoot: string, runId: string): Promise<WorkspaceProvider> {
|
|
749
|
+
const { createLocalGitWorktreeProvider, readWorkflowRunManifest } = await import(
|
|
750
|
+
"../core/workflow/index.ts"
|
|
751
|
+
);
|
|
752
|
+
const manifest = readWorkflowRunManifest(coordRoot, runId);
|
|
753
|
+
const binding = manifest.execution.workspace_binding;
|
|
754
|
+
if (!binding) {
|
|
755
|
+
throw new Error(`workflow run ${runId} has no isolated workspace binding`);
|
|
756
|
+
}
|
|
757
|
+
if (binding.provider.id !== "local-git-worktree") {
|
|
758
|
+
throw new Error(
|
|
759
|
+
`workflow run ${runId} requires unavailable workspace provider ${binding.provider.id}`,
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
return createLocalGitWorktreeProvider({ coordRoot });
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function collectOption(value: string, previous: string[]): string[] {
|
|
766
|
+
return [...previous, value];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** Next operator command after resolving an approval. Mid-run parks resume the
|
|
770
|
+
* workflow; integration prepare parks after the run is already terminal, so
|
|
771
|
+
* the durable plan is authorized by re-running prepare (resume would fail). */
|
|
772
|
+
function approvalNextActionHint(coordRoot: string, approval: WorkflowApproval): string {
|
|
773
|
+
const bin = resolveBinName();
|
|
774
|
+
const runId = approval.request.run_id;
|
|
775
|
+
const integrationPlanPath = join(
|
|
776
|
+
coordRoot,
|
|
777
|
+
".harnery",
|
|
778
|
+
"workflows",
|
|
779
|
+
runId,
|
|
780
|
+
"integration",
|
|
781
|
+
"plan.json",
|
|
782
|
+
);
|
|
783
|
+
if (existsSync(integrationPlanPath)) {
|
|
784
|
+
return `prepare: ${bin} workflow integration prepare ${runId}\n`;
|
|
785
|
+
}
|
|
786
|
+
return `resume: ${bin} workflow resume ${runId}\n`;
|
|
787
|
+
}
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* session_id, …}`).
|
|
11
11
|
* - `--trust` is required: headless runs refuse untrusted workspaces (exit 1,
|
|
12
12
|
* "Workspace Trust Required") — see the argv comment below.
|
|
13
|
+
* - `--force` is required: print mode has no channel for an operator to approve
|
|
14
|
+
* shell commands, so Smart Auto can leave a child able to patch files but
|
|
15
|
+
* unable to test or commit them.
|
|
13
16
|
* - Envelope drift guard: when stdout doesn't parse as JSON but the process
|
|
14
17
|
* exited 0, the raw stdout is returned as the reply text.
|
|
15
18
|
* - No per-run cost surface → undefined. No max-turns equivalent → `maxTurns`
|
|
@@ -51,10 +54,11 @@ export function parseCursorOutput(stdout: string): {
|
|
|
51
54
|
export function buildCursorInvocation(req: SpawnRequest): HarnessInvocation {
|
|
52
55
|
validateHarnessEffort("cursor", req.effort);
|
|
53
56
|
// --trust: headless cursor-agent refuses untrusted workspaces (exit 1,
|
|
54
|
-
// "Workspace Trust Required").
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
57
|
+
// "Workspace Trust Required"). --force: a print-mode child has no interactive
|
|
58
|
+
// approval channel, so host-authorized workflow dispatch must let it run
|
|
59
|
+
// commands. Neither flag maps host policy into child tools; that capability
|
|
60
|
+
// remains explicitly unsupported.
|
|
61
|
+
const argv = ["cursor-agent", "-p", req.prompt, "--output-format", "json", "--trust", "--force"];
|
|
58
62
|
if (req.model) argv.push("--model", req.model);
|
|
59
63
|
return { argv };
|
|
60
64
|
}
|
|
@@ -15,7 +15,11 @@ export {
|
|
|
15
15
|
renderWorkflowWorkspaceStatus,
|
|
16
16
|
} from "./inspect.ts";
|
|
17
17
|
export type { ApplyIntegrationInput, PrepareIntegrationInput } from "./integration.ts";
|
|
18
|
-
export {
|
|
18
|
+
export {
|
|
19
|
+
applyIntegration,
|
|
20
|
+
IntegrationPrepareParkedError,
|
|
21
|
+
prepareIntegration,
|
|
22
|
+
} from "./integration.ts";
|
|
19
23
|
export type {
|
|
20
24
|
WorkspaceLifecycleProjection,
|
|
21
25
|
WorkspaceLifecycleProjectionInput,
|
|
@@ -64,6 +64,22 @@ export interface ApplyIntegrationInput {
|
|
|
64
64
|
plan?: IntegrationPlan;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/** Host-policy ASK still pending during integration prepare. Carries the exact
|
|
68
|
+
* durable IDs an operator needs to approve and resume the same plan. */
|
|
69
|
+
export class IntegrationPrepareParkedError extends Error {
|
|
70
|
+
readonly runId: string;
|
|
71
|
+
readonly planId: string;
|
|
72
|
+
readonly approvalId: string;
|
|
73
|
+
|
|
74
|
+
constructor(runId: string, planId: string, approvalId: string) {
|
|
75
|
+
super(`integration preparation parked: run ${runId}, plan ${planId}, approval ${approvalId}`);
|
|
76
|
+
this.name = "IntegrationPrepareParkedError";
|
|
77
|
+
this.runId = runId;
|
|
78
|
+
this.planId = planId;
|
|
79
|
+
this.approvalId = approvalId;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
export async function prepareIntegration(input: PrepareIntegrationInput): Promise<IntegrationPlan> {
|
|
68
84
|
const evidence = await readAndValidateEvidence(input.coordRoot, input.runId, input.provider);
|
|
69
85
|
const targetRoot = resolve(input.targetRoot ?? requiredIntegrationRoot(evidence.binding));
|
|
@@ -662,7 +678,7 @@ function authorizePlan(input: PrepareIntegrationInput, plan: IntegrationPlan): v
|
|
|
662
678
|
});
|
|
663
679
|
approvalId = created.approval.request.id;
|
|
664
680
|
if (created.approval.status === "pending") {
|
|
665
|
-
throw new
|
|
681
|
+
throw new IntegrationPrepareParkedError(input.runId, plan.plan_id, approvalId);
|
|
666
682
|
}
|
|
667
683
|
if (created.approval.status !== "approved" || !created.approval.decision) {
|
|
668
684
|
throw new Error(`integration approval ${approvalId} was denied`);
|
|
@@ -134,8 +134,14 @@ export function acquireNoClobberLease(input: AcquireNoClobberLeaseInput): NoClob
|
|
|
134
134
|
input.onRecoveryStep?.("claim_created");
|
|
135
135
|
|
|
136
136
|
const pinPath = join(recoveryPath, "stale-pin");
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
try {
|
|
138
|
+
linkSync(currentPath, pinPath);
|
|
139
|
+
assertSameInodeAndBytes(currentPath, pinPath, observed.bytes);
|
|
140
|
+
} catch {
|
|
141
|
+
abortUnchangedRecoveryClaim(leaseDir, recoveryPath, pinPath, owner);
|
|
142
|
+
recoveryClaimed = false;
|
|
143
|
+
throw new Error(`lease ${input.scope} recovery raced with another contender`);
|
|
144
|
+
}
|
|
139
145
|
fsyncDirectory(recoveryPath);
|
|
140
146
|
input.onRecoveryStep?.("stale_pinned");
|
|
141
147
|
|
|
@@ -292,6 +298,22 @@ function reclaimAbandonedRecovery(
|
|
|
292
298
|
fsyncDirectory(leaseDir);
|
|
293
299
|
}
|
|
294
300
|
|
|
301
|
+
function abortUnchangedRecoveryClaim(
|
|
302
|
+
leaseDir: string,
|
|
303
|
+
recoveryPath: string,
|
|
304
|
+
pinPath: string,
|
|
305
|
+
claimant: NoClobberLeaseOwner,
|
|
306
|
+
): void {
|
|
307
|
+
const claim = readRecoveryClaim(recoveryPath);
|
|
308
|
+
if (claim.claimant.owner_id !== claimant.owner_id) {
|
|
309
|
+
throw new Error(`lease ${claimant.scope} recovery ownership changed`);
|
|
310
|
+
}
|
|
311
|
+
safeUnlink(pinPath);
|
|
312
|
+
unlinkSync(join(recoveryPath, "claim.json"));
|
|
313
|
+
rmdirSync(recoveryPath);
|
|
314
|
+
fsyncDirectory(leaseDir);
|
|
315
|
+
}
|
|
316
|
+
|
|
295
317
|
function readRecoveryClaim(recoveryPath: string): RecoveryClaim {
|
|
296
318
|
const path = join(recoveryPath, "claim.json");
|
|
297
319
|
const size = lstatSync(path).size;
|