sortie-dogs 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/acceptance-continuity.d.ts +20 -0
- package/dist/core/acceptance-continuity.js +61 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/plugin/continuation.js +9 -7
- package/dist/plugin/index.js +244 -24
- package/dist/runtime-assets.d.ts +7 -7
- package/dist/runtime-assets.js +75 -40
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Requirements: Node.js 22.6 or newer, npm, and OpenCode.
|
|
|
24
24
|
|
|
25
25
|
Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
|
|
26
26
|
|
|
27
|
-
Release: [v0.7.
|
|
27
|
+
Release: [v0.7.1](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.7.1)
|
|
28
28
|
|
|
29
29
|
## Quick start
|
|
30
30
|
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Version of the installable runtime assets. Kept in its own module so the plugin can compare an
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
|
-
export declare const RUNTIME_ASSET_VERSION = "0.3.
|
|
5
|
+
export declare const RUNTIME_ASSET_VERSION = "0.3.52-acceptance-continuity-v1";
|
|
6
6
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Version of the installable runtime assets. Kept in its own module so the plugin can compare an
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
|
-
export const RUNTIME_ASSET_VERSION = "0.3.
|
|
5
|
+
export const RUNTIME_ASSET_VERSION = "0.3.52-acceptance-continuity-v1";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const ACCEPTANCE_CONTINUITY_EXTENSION: "sortie-dogs/acceptance-continuity";
|
|
2
|
+
export declare const ACCEPTANCE_CONTINUITY_SCHEMA_VERSION: "0.1";
|
|
3
|
+
export declare const ACCEPTANCE_CONTINUITY_AUTHORITY: "dispatch";
|
|
4
|
+
export declare const MAX_ACCEPTANCE_CONTINUITY_BYTES: number;
|
|
5
|
+
export declare const MAX_ACCEPTANCE_CRITERIA = 24;
|
|
6
|
+
export interface AcceptanceContinuityLedger {
|
|
7
|
+
readonly schema_version: "0.1";
|
|
8
|
+
readonly authority: "dispatch";
|
|
9
|
+
readonly task_id: string;
|
|
10
|
+
readonly criteria: readonly string[];
|
|
11
|
+
readonly fingerprint: string;
|
|
12
|
+
readonly parent_fingerprint: "none" | string;
|
|
13
|
+
}
|
|
14
|
+
export interface AcceptanceContinuityInspection {
|
|
15
|
+
readonly ledger: AcceptanceContinuityLedger | undefined;
|
|
16
|
+
readonly error: "absent" | "malformed" | "oversize" | undefined;
|
|
17
|
+
}
|
|
18
|
+
export declare function normalizeAcceptanceCriteria(criteria: readonly string[]): string[];
|
|
19
|
+
export declare function acceptanceContinuityFingerprint(criteria: readonly string[]): string;
|
|
20
|
+
export declare function inspectAcceptanceContinuity(handoff: unknown): AcceptanceContinuityInspection;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export const ACCEPTANCE_CONTINUITY_EXTENSION = "sortie-dogs/acceptance-continuity";
|
|
3
|
+
export const ACCEPTANCE_CONTINUITY_SCHEMA_VERSION = "0.1";
|
|
4
|
+
export const ACCEPTANCE_CONTINUITY_AUTHORITY = "dispatch";
|
|
5
|
+
export const MAX_ACCEPTANCE_CONTINUITY_BYTES = 32 * 1024;
|
|
6
|
+
export const MAX_ACCEPTANCE_CRITERIA = 24;
|
|
7
|
+
const FINGERPRINT = /^sha256:[0-9a-f]{64}$/u;
|
|
8
|
+
const MAX_TASK_ID = 256;
|
|
9
|
+
const MAX_CRITERION = 2_000;
|
|
10
|
+
function object(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
export function normalizeAcceptanceCriteria(criteria) {
|
|
14
|
+
return criteria.map((criterion) => criterion.replace(/\r\n?/gu, "\n").normalize("NFC"));
|
|
15
|
+
}
|
|
16
|
+
export function acceptanceContinuityFingerprint(criteria) {
|
|
17
|
+
const canonical = JSON.stringify(normalizeAcceptanceCriteria(criteria));
|
|
18
|
+
return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
|
|
19
|
+
}
|
|
20
|
+
export function inspectAcceptanceContinuity(handoff) {
|
|
21
|
+
if (!object(handoff) || !object(handoff.ext) || !(ACCEPTANCE_CONTINUITY_EXTENSION in handoff.ext)) {
|
|
22
|
+
return { ledger: undefined, error: "absent" };
|
|
23
|
+
}
|
|
24
|
+
const raw = handoff.ext[ACCEPTANCE_CONTINUITY_EXTENSION];
|
|
25
|
+
let bytes;
|
|
26
|
+
try {
|
|
27
|
+
bytes = new TextEncoder().encode(JSON.stringify(raw)).byteLength;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { ledger: undefined, error: "malformed" };
|
|
31
|
+
}
|
|
32
|
+
if (bytes > MAX_ACCEPTANCE_CONTINUITY_BYTES)
|
|
33
|
+
return { ledger: undefined, error: "oversize" };
|
|
34
|
+
if (!object(raw) || Object.keys(raw).some((key) => ![
|
|
35
|
+
"schema_version", "authority", "task_id", "criteria", "fingerprint", "parent_fingerprint",
|
|
36
|
+
].includes(key)) || raw.schema_version !== ACCEPTANCE_CONTINUITY_SCHEMA_VERSION ||
|
|
37
|
+
raw.authority !== ACCEPTANCE_CONTINUITY_AUTHORITY || typeof raw.task_id !== "string" ||
|
|
38
|
+
raw.task_id.length === 0 || raw.task_id.length > MAX_TASK_ID || !Array.isArray(raw.criteria) ||
|
|
39
|
+
raw.criteria.length === 0 || raw.criteria.length > MAX_ACCEPTANCE_CRITERIA ||
|
|
40
|
+
!raw.criteria.every((criterion) => typeof criterion === "string" && criterion.length > 0 && criterion.length <= MAX_CRITERION) ||
|
|
41
|
+
new Set(raw.criteria).size !== raw.criteria.length || typeof raw.fingerprint !== "string" ||
|
|
42
|
+
!FINGERPRINT.test(raw.fingerprint) || !(raw.parent_fingerprint === "none" ||
|
|
43
|
+
(typeof raw.parent_fingerprint === "string" && FINGERPRINT.test(raw.parent_fingerprint)))) {
|
|
44
|
+
return { ledger: undefined, error: "malformed" };
|
|
45
|
+
}
|
|
46
|
+
const criteria = normalizeAcceptanceCriteria(raw.criteria);
|
|
47
|
+
if (new Set(criteria).size !== criteria.length || acceptanceContinuityFingerprint(criteria) !== raw.fingerprint) {
|
|
48
|
+
return { ledger: undefined, error: "malformed" };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
ledger: {
|
|
52
|
+
schema_version: ACCEPTANCE_CONTINUITY_SCHEMA_VERSION,
|
|
53
|
+
authority: ACCEPTANCE_CONTINUITY_AUTHORITY,
|
|
54
|
+
task_id: raw.task_id,
|
|
55
|
+
criteria,
|
|
56
|
+
fingerprint: raw.fingerprint,
|
|
57
|
+
parent_fingerprint: raw.parent_fingerprint,
|
|
58
|
+
},
|
|
59
|
+
error: undefined,
|
|
60
|
+
};
|
|
61
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,5 +19,7 @@ export type { HandoffDenialReason, OpenCodeEvent, OpenCodeHooks, OpenCodePlugin,
|
|
|
19
19
|
export { CONSULTATION_CAPABILITIES, CONSULTATION_ROLE_POLICY, MAX_REVIEW_ARTIFACT_BYTES, SOURCE_REVIEW_RISK_TAGS, STRATEGY_TRIGGERS, evaluateReviewAvailability, evaluateReviewGate, evaluateSourceReviewRequirement, isSourceReviewRiskTag, requiresSourceReview, shouldConsultStrategy, validateReviewArtifact, validateReviewVerdict, } from "./core/consultation.js";
|
|
20
20
|
export type { ConsultationAdapter, ConsultationCapability, ConsultationRequest, ConsultationResult, ReviewArtifact, ReviewAvailability, ReviewFinding, ReviewFindingSeverity, ReviewGateInput, ReviewGateResult, ReviewVerdict, ReviewVerdictKind, SourceReviewConsultationRequest, SourceReviewConsultationResult, SourceReviewRequirement, SourceReviewRequirementInput, SourceReviewRiskTag, StrategyConsultationRequest, StrategyConsultationResult, StrategyTrigger, StrategyTriggerInput, UnavailableConsultationResult, ValidationResult, } from "./core/consultation.js";
|
|
21
21
|
export type * from "./core/types.js";
|
|
22
|
+
export { ACCEPTANCE_CONTINUITY_AUTHORITY, ACCEPTANCE_CONTINUITY_EXTENSION, ACCEPTANCE_CONTINUITY_SCHEMA_VERSION, acceptanceContinuityFingerprint, inspectAcceptanceContinuity, MAX_ACCEPTANCE_CONTINUITY_BYTES, MAX_ACCEPTANCE_CRITERIA, normalizeAcceptanceCriteria, } from "./core/acceptance-continuity.js";
|
|
23
|
+
export type { AcceptanceContinuityInspection, AcceptanceContinuityLedger, } from "./core/acceptance-continuity.js";
|
|
22
24
|
export { inspectRetainedStateCapsule, MAX_RETAINED_STATE_BYTES, MAX_RETAINED_STATE_ITEMS, MAX_RETAINED_STATE_WARNINGS, RETAINED_STATE_AUTHORITY, RETAINED_STATE_EXTENSION, RETAINED_STATE_SCHEMA_VERSION, } from "./core/retained-state.js";
|
|
23
25
|
export type { AdmissionReceipt, NextEvidenceDecision, RetainedStateCapsule, RetainedStateInspection, RetainedStateWarning, RetainedValidationAttempt, } from "./core/retained-state.js";
|
package/dist/index.js
CHANGED
|
@@ -15,4 +15,5 @@ export { SortieDogsPlugin } from "./plugin/index.js";
|
|
|
15
15
|
*/
|
|
16
16
|
export { HandoffDeniedError, InvalidModelTargetError, ModelRoutingDeniedError, isExplicitTaskHandoff, } from "./plugin/index.js";
|
|
17
17
|
export { CONSULTATION_CAPABILITIES, CONSULTATION_ROLE_POLICY, MAX_REVIEW_ARTIFACT_BYTES, SOURCE_REVIEW_RISK_TAGS, STRATEGY_TRIGGERS, evaluateReviewAvailability, evaluateReviewGate, evaluateSourceReviewRequirement, isSourceReviewRiskTag, requiresSourceReview, shouldConsultStrategy, validateReviewArtifact, validateReviewVerdict, } from "./core/consultation.js";
|
|
18
|
+
export { ACCEPTANCE_CONTINUITY_AUTHORITY, ACCEPTANCE_CONTINUITY_EXTENSION, ACCEPTANCE_CONTINUITY_SCHEMA_VERSION, acceptanceContinuityFingerprint, inspectAcceptanceContinuity, MAX_ACCEPTANCE_CONTINUITY_BYTES, MAX_ACCEPTANCE_CRITERIA, normalizeAcceptanceCriteria, } from "./core/acceptance-continuity.js";
|
|
18
19
|
export { inspectRetainedStateCapsule, MAX_RETAINED_STATE_BYTES, MAX_RETAINED_STATE_ITEMS, MAX_RETAINED_STATE_WARNINGS, RETAINED_STATE_AUTHORITY, RETAINED_STATE_EXTENSION, RETAINED_STATE_SCHEMA_VERSION, } from "./core/retained-state.js";
|
|
@@ -89,25 +89,27 @@ const ROLLOVER_PROMPT = [
|
|
|
89
89
|
"If a section has nothing, write exactly '- なし'. Never delete a heading.",
|
|
90
90
|
"At most 2 content lines per section. Keep the whole output under 1200 characters.",
|
|
91
91
|
"Copy task identity, manifest paths, commands, exit codes, and counter values character-for-character.",
|
|
92
|
+
"Never author, shorten, infer, or treat summary text as acceptance authority. Preserve the exact acceptance fingerprint and handoff path; the resumed coordinator rereads that immutable handoff before dispatch.",
|
|
93
|
+
"The unmet-user-requirements, constraints, candidate queue, blocker, and next-action sections never replace the immutable handoff acceptance ledger.",
|
|
92
94
|
"The coordinator final report immediately before compaction is the newest source of truth. Its outcomes and next action override older context.",
|
|
93
95
|
"Never list a unit as uncommitted or next when that final report says it was committed or completed.",
|
|
94
96
|
"Drop the finished unit's conversation, raw logs, diffs, and tool output.",
|
|
95
|
-
"Preserve task identity,
|
|
97
|
+
"Preserve task identity, acceptance fingerprint, exact handoff path, both manifests, ordered validation history, batchTarget, batchAttempted, batchCommitted, batchReconciled, inventory fingerprint, bounded candidate queue, pending tracker updates, tracker flush state, blocker state, and the exact next action. If a value is absent, write なし; never guess one.",
|
|
96
98
|
"Never write credentials, API keys, tokens, personal data, or source code.",
|
|
97
99
|
"Never output an HTML comment.",
|
|
98
100
|
"",
|
|
99
101
|
ROLLOVER_TOKEN,
|
|
100
102
|
"",
|
|
101
103
|
"## 未達のユーザー要求",
|
|
102
|
-
"-
|
|
104
|
+
"- <acceptance criteriaを除く、まだ満たされていない明示要求>",
|
|
103
105
|
"",
|
|
104
106
|
"## task identity と制約",
|
|
105
107
|
"- <task_id・候補識別子・project root。該当なしはなし>",
|
|
106
|
-
"-
|
|
108
|
+
"- <守り続ける非acceptance制約。該当なしはなし>",
|
|
107
109
|
"",
|
|
108
110
|
"## manifest",
|
|
109
|
-
"-
|
|
110
|
-
"- operation_manifest: <exact path または none>",
|
|
111
|
+
"- handoff_path: <exact path または none> / acceptance_fingerprint: <exact fingerprint または none>",
|
|
112
|
+
"- source_manifest: <exact entries または none> / operation_manifest: <exact path または none>",
|
|
111
113
|
"",
|
|
112
114
|
"## validation履歴",
|
|
113
115
|
"- <command> | exit <code> | <fingerprint>",
|
|
@@ -116,7 +118,7 @@ const ROLLOVER_PROMPT = [
|
|
|
116
118
|
"- batchTarget: <n> / batchAttempted: <n> / batchCommitted: <n> / batchReconciled: <n>",
|
|
117
119
|
"",
|
|
118
120
|
"## tracker batch state",
|
|
119
|
-
"- inventoryFingerprint: <fingerprint> / candidateQueue: <bounded identity, status, ordering, implementation root,
|
|
121
|
+
"- inventoryFingerprint: <fingerprint> / candidateQueue: <bounded identity, status, ordering, implementation root, exact handoff path, and opaque acceptance fingerprint only>",
|
|
120
122
|
"- pendingTrackerUpdates: <terminal outcomes awaiting flush> / flushState: <pending | flushed | reconciliation-required>",
|
|
121
123
|
"",
|
|
122
124
|
"## 未解決blocker",
|
|
@@ -127,7 +129,7 @@ const ROLLOVER_PROMPT = [
|
|
|
127
129
|
].join("\n");
|
|
128
130
|
const RECOVERY_ROLLOVER_PROMPT = ROLLOVER_PROMPT
|
|
129
131
|
.replace("The coordinator final report immediately before compaction is the newest source of truth. Its outcomes and next action override older context.", "The recovery report overrides only terminal outcomes and batch counters. Preserve unmet user requirements, ordered scope, no-stop constraints, and the exact next unit from the conversation.")
|
|
130
|
-
.replace("Preserve task identity,
|
|
132
|
+
.replace("Preserve task identity, acceptance fingerprint, exact handoff path, both manifests, ordered validation history, batchTarget, batchAttempted, batchCommitted, batchReconciled, inventory fingerprint, bounded candidate queue, pending tracker updates, tracker flush state, blocker state, and the exact next action. If a value is absent, write なし; never guess one.", "Preserve task identity, acceptance fingerprint, exact handoff path, both manifests, ordered validation history, batchTarget, batchAttempted, batchCommitted, batchReconciled, inventory fingerprint, bounded candidate queue, pending tracker updates, tracker flush state, blocker state, unmet ordered scope, and the exact next unit. Never derive acceptance from summary text. If a value is absent, write なし; never guess one.");
|
|
131
133
|
const ROLLOVER_HEADINGS = [
|
|
132
134
|
"## 未達のユーザー要求",
|
|
133
135
|
"## task identity と制約",
|
package/dist/plugin/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { lstat, mkdir, open, readFile, realpath, rm, stat } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { RUNTIME_ASSET_VERSION } from "../asset-version.js";
|
|
5
|
+
import { ACCEPTANCE_CONTINUITY_AUTHORITY, ACCEPTANCE_CONTINUITY_EXTENSION, ACCEPTANCE_CONTINUITY_SCHEMA_VERSION, acceptanceContinuityFingerprint, inspectAcceptanceContinuity, normalizeAcceptanceCriteria, } from "../core/acceptance-continuity.js";
|
|
6
|
+
import { resolveGlobalConfigRoot } from "../core/initialize.js";
|
|
5
7
|
import { normalizeRelativePath, RelativePathError } from "../core/path.js";
|
|
6
8
|
import { ScopeLeaseError, ScopeLeaseRegistry } from "../core/scope-lease-registry.js";
|
|
7
9
|
import { produceWorktreeCommitArtifact, recoverWorktreeCommitArtifact, resolveValidationExecutable, WorktreeCommitArtifactError, } from "../core/worktree-commit-artifact.js";
|
|
@@ -36,6 +38,7 @@ const GIT_POINTER_LIMIT = 4096;
|
|
|
36
38
|
const PARALLEL_OUTCOME_MARKER = "SORTIE_PARALLEL_OUTCOME";
|
|
37
39
|
const CANONICAL_CONTRACT_DIRECTORY = ".sortie-dogs/contracts";
|
|
38
40
|
const CANONICAL_CONTRACT_HANDOFF = `${CANONICAL_CONTRACT_DIRECTORY}/handoff.json`;
|
|
41
|
+
const GENERATED_PARALLEL_ACCEPTANCE = "Complete the prepared parallel descriptor within its declared scope.";
|
|
39
42
|
export const PARALLEL_COMMIT_ARTIFACT_CAPABILITY = "sortie_create_parallel_commit_artifact";
|
|
40
43
|
const CONTRACT_DEFECTS = { limit: 8, pointerCharacters: 120 };
|
|
41
44
|
function contractPointer(pointer) {
|
|
@@ -498,7 +501,7 @@ function taskHeaderCount(text, keys) {
|
|
|
498
501
|
}
|
|
499
502
|
function taskInlineValues(text, keys) {
|
|
500
503
|
const aliases = keys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("|");
|
|
501
|
-
return [...text.matchAll(new RegExp(
|
|
504
|
+
return [...text.matchAll(new RegExp(`^[\\t ]*(?:${aliases})[\\t ]*:[\\t ]*(\\S.*?)[\\t ]*$`, "gimu"))]
|
|
502
505
|
.map((match) => match[1]);
|
|
503
506
|
}
|
|
504
507
|
function taskBlockHasContent(text, keys) {
|
|
@@ -524,6 +527,45 @@ function taskBlockHasContent(text, keys) {
|
|
|
524
527
|
}
|
|
525
528
|
return false;
|
|
526
529
|
}
|
|
530
|
+
function taskAcceptanceCriteria(text) {
|
|
531
|
+
const inline = taskInlineValues(text, ["acceptance"]);
|
|
532
|
+
if (inline.length === 1) {
|
|
533
|
+
const array = parseStringArray(inline[0]);
|
|
534
|
+
return normalizeAcceptanceCriteria(array ?? [unquoteValue(inline[0])]);
|
|
535
|
+
}
|
|
536
|
+
if (inline.length > 1)
|
|
537
|
+
return undefined;
|
|
538
|
+
const lines = text.split(/\r?\n/u);
|
|
539
|
+
const headers = lines.flatMap((line, index) => {
|
|
540
|
+
const match = /^(\s*)acceptance\s*:\s*$/iu.exec(line);
|
|
541
|
+
return match === null ? [] : [{ index, indent: match[1].replaceAll("\t", " ").length }];
|
|
542
|
+
});
|
|
543
|
+
if (headers.length !== 1)
|
|
544
|
+
return undefined;
|
|
545
|
+
const values = [];
|
|
546
|
+
for (let index = headers[0].index + 1; index < lines.length; index += 1) {
|
|
547
|
+
const line = lines[index];
|
|
548
|
+
if (line.trim().length === 0)
|
|
549
|
+
continue;
|
|
550
|
+
const indent = /^\s*/u.exec(line)[0].replaceAll("\t", " ").length;
|
|
551
|
+
if (indent <= headers[0].indent)
|
|
552
|
+
break;
|
|
553
|
+
const item = /^\s*-\s+(.+?)\s*$/u.exec(line)?.[1];
|
|
554
|
+
if (item === undefined)
|
|
555
|
+
return undefined;
|
|
556
|
+
let value;
|
|
557
|
+
try {
|
|
558
|
+
value = item.startsWith("\"") ? JSON.parse(item) : unquoteValue(item);
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
return undefined;
|
|
562
|
+
}
|
|
563
|
+
if (typeof value !== "string" || value.length === 0)
|
|
564
|
+
return undefined;
|
|
565
|
+
values.push(value);
|
|
566
|
+
}
|
|
567
|
+
return values.length === 0 ? undefined : normalizeAcceptanceCriteria(values);
|
|
568
|
+
}
|
|
527
569
|
function taskContractText(text) {
|
|
528
570
|
const lines = text.split(/\r?\n/u);
|
|
529
571
|
const digestIndex = lines.findIndex((line) => /^\s*context_digest\s*:\s*$/iu.test(line));
|
|
@@ -801,7 +843,9 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
801
843
|
let manifestAbsent = false;
|
|
802
844
|
let bootstrapRequired = false;
|
|
803
845
|
let bootstrapCompleted = false;
|
|
804
|
-
|
|
846
|
+
const assetVersionPins = new Map();
|
|
847
|
+
const rootAcceptanceContinuity = new Map();
|
|
848
|
+
const parallelAcceptanceContinuity = new Map();
|
|
805
849
|
const globalConfig = await readOptionalGlobalConfig();
|
|
806
850
|
const sessionOperationMetrics = new Map();
|
|
807
851
|
function appLogInfo(message, sessionID, extra) {
|
|
@@ -957,7 +1001,6 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
957
1001
|
loading = (async () => {
|
|
958
1002
|
try {
|
|
959
1003
|
project ??= await createProjectPaths(resolveProjectRoot(input));
|
|
960
|
-
await reportAssetVersionSkew(project);
|
|
961
1004
|
const projectConfig = await readOptionalProjectConfig(project);
|
|
962
1005
|
const environmentConfig = readEnvironmentConfig();
|
|
963
1006
|
const parsed = resolvePluginConfigurationSourcesWithGlobal(globalConfig, projectConfig, environmentConfig, options);
|
|
@@ -998,19 +1041,54 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
998
1041
|
}
|
|
999
1042
|
/**
|
|
1000
1043
|
* Installed agents and this plugin implement two halves of one contract. A project that installed
|
|
1001
|
-
* a different asset version is
|
|
1002
|
-
*
|
|
1044
|
+
* a different asset version is sticky for this plugin instance. Reinstalling files cannot update an
|
|
1045
|
+
* already-running coordinator prompt, so worker dispatch remains blocked until a fresh session loads.
|
|
1003
1046
|
*/
|
|
1004
|
-
async function
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1047
|
+
async function readAssetVersionMarker(path) {
|
|
1048
|
+
try {
|
|
1049
|
+
const value = (await readFile(path, "utf8")).trim();
|
|
1050
|
+
return value.length === 0 ? { kind: "corrupt" } : { kind: "present", value };
|
|
1051
|
+
}
|
|
1052
|
+
catch (error) {
|
|
1053
|
+
return isRecord(error) && error.code === "ENOENT" ? { kind: "absent" } : { kind: "corrupt" };
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
async function currentAssetVersionStatus(paths) {
|
|
1057
|
+
const local = await readAssetVersionMarker(paths.absolute(PROJECT_VERSION_MARKER));
|
|
1058
|
+
if (local.kind === "corrupt")
|
|
1059
|
+
return "mismatch";
|
|
1060
|
+
if (local.kind === "present")
|
|
1061
|
+
return local.value === RUNTIME_ASSET_VERSION ? "current" : "mismatch";
|
|
1062
|
+
let globalRoot;
|
|
1063
|
+
try {
|
|
1064
|
+
globalRoot = await resolveGlobalConfigRoot();
|
|
1065
|
+
}
|
|
1066
|
+
catch {
|
|
1067
|
+
return "mismatch";
|
|
1068
|
+
}
|
|
1069
|
+
const global = await readAssetVersionMarker(join(globalRoot, "sortie-dogs.version"));
|
|
1070
|
+
if (global.kind === "absent")
|
|
1071
|
+
return "unmarked";
|
|
1072
|
+
return global.kind === "present" && global.value === RUNTIME_ASSET_VERSION ? "current" : "mismatch";
|
|
1073
|
+
}
|
|
1074
|
+
async function pinAssetVersion(sessionID) {
|
|
1075
|
+
const pinned = assetVersionPins.get(sessionID);
|
|
1076
|
+
if (pinned !== undefined)
|
|
1077
|
+
return pinned;
|
|
1078
|
+
project ??= await createProjectPaths(resolveProjectRoot(input));
|
|
1079
|
+
const status = await currentAssetVersionStatus(project);
|
|
1080
|
+
assetVersionPins.set(sessionID, status);
|
|
1081
|
+
while (assetVersionPins.size > ACTIVE_SESSION_CACHE.maximum) {
|
|
1082
|
+
const candidate = assetVersionPins.keys().next().value;
|
|
1083
|
+
if (coordinatorRoots.has(candidate))
|
|
1084
|
+
break;
|
|
1085
|
+
assetVersionPins.delete(candidate);
|
|
1086
|
+
}
|
|
1087
|
+
if (status === "mismatch") {
|
|
1088
|
+
console.warn(`Sortie-dogs: installed agent assets do not match ${RUNTIME_ASSET_VERSION}. ` +
|
|
1089
|
+
"Repair the marker, then start a fresh coordinator session.");
|
|
1090
|
+
}
|
|
1091
|
+
return status;
|
|
1014
1092
|
}
|
|
1015
1093
|
/**
|
|
1016
1094
|
* A project without an operation manifest never opted into the write gate. Enforcing a scope that
|
|
@@ -1277,6 +1355,20 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1277
1355
|
operation_manifest: join(descriptor.managed_path, CANONICAL_CONTRACT_DIRECTORY, `${descriptor.task_id}.operation-manifest.json`),
|
|
1278
1356
|
};
|
|
1279
1357
|
}
|
|
1358
|
+
function parallelAcceptanceKey(descriptor) {
|
|
1359
|
+
return `${descriptor.run_id}\u0000${descriptor.dispatch_id}`;
|
|
1360
|
+
}
|
|
1361
|
+
function generatedParallelAcceptance(descriptor, parent) {
|
|
1362
|
+
const criteria = [...(parent?.criteria ?? []), GENERATED_PARALLEL_ACCEPTANCE];
|
|
1363
|
+
return {
|
|
1364
|
+
schema_version: ACCEPTANCE_CONTINUITY_SCHEMA_VERSION,
|
|
1365
|
+
authority: ACCEPTANCE_CONTINUITY_AUTHORITY,
|
|
1366
|
+
task_id: descriptor.task_id,
|
|
1367
|
+
criteria,
|
|
1368
|
+
fingerprint: acceptanceContinuityFingerprint(criteria),
|
|
1369
|
+
parent_fingerprint: parent?.fingerprint ?? "none",
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1280
1372
|
async function ensureParallelContractDirectory(canonicalRoot) {
|
|
1281
1373
|
let parent = canonicalRoot;
|
|
1282
1374
|
for (const segment of [".sortie-dogs", "contracts"]) {
|
|
@@ -1300,7 +1392,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1300
1392
|
}
|
|
1301
1393
|
return parent;
|
|
1302
1394
|
}
|
|
1303
|
-
async function createParallelControlFiles(descriptor, validationCommands) {
|
|
1395
|
+
async function createParallelControlFiles(descriptor, validationCommands, parentAcceptance) {
|
|
1304
1396
|
const canonicalRoot = await realpath(descriptor.managed_path);
|
|
1305
1397
|
if (!samePath(canonicalRoot, descriptor.managed_path)) {
|
|
1306
1398
|
throw new ParallelDispatchError("lifecycle-failed", "Managed worktree identity changed before contract creation.");
|
|
@@ -1311,6 +1403,33 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1311
1403
|
if (!samePath(contractDirectory, expectedContractDirectory)) {
|
|
1312
1404
|
throw new ParallelDispatchError("lifecycle-failed", "Parallel contract directory escapes the managed worktree.");
|
|
1313
1405
|
}
|
|
1406
|
+
const expectedAcceptance = generatedParallelAcceptance(descriptor, parentAcceptance);
|
|
1407
|
+
let acceptance = parallelAcceptanceContinuity.get(parallelAcceptanceKey(descriptor));
|
|
1408
|
+
if (acceptance === undefined) {
|
|
1409
|
+
const existing = await readFile(paths.handoff_path, "utf8").catch(() => undefined);
|
|
1410
|
+
if (existing !== undefined) {
|
|
1411
|
+
try {
|
|
1412
|
+
const parsed = JSON.parse(existing);
|
|
1413
|
+
const validated = validateHandoffSchema(parsed);
|
|
1414
|
+
const inspectedAcceptance = validated.ok ? inspectAcceptanceContinuity(validated.value).ledger : undefined;
|
|
1415
|
+
if (inspectedAcceptance?.task_id === descriptor.task_id &&
|
|
1416
|
+
inspectedAcceptance.criteria.at(-1) === GENERATED_PARALLEL_ACCEPTANCE &&
|
|
1417
|
+
(parentAcceptance === undefined ||
|
|
1418
|
+
(inspectedAcceptance.fingerprint === expectedAcceptance.fingerprint &&
|
|
1419
|
+
inspectedAcceptance.parent_fingerprint === expectedAcceptance.parent_fingerprint))) {
|
|
1420
|
+
acceptance = inspectedAcceptance;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
catch {
|
|
1424
|
+
// Exact generated-content comparison below rejects malformed or unrelated existing controls.
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
acceptance ??= expectedAcceptance;
|
|
1429
|
+
parallelAcceptanceContinuity.set(parallelAcceptanceKey(descriptor), acceptance);
|
|
1430
|
+
while (parallelAcceptanceContinuity.size > ACTIVE_SESSION_CACHE.maximum * 3) {
|
|
1431
|
+
parallelAcceptanceContinuity.delete(parallelAcceptanceContinuity.keys().next().value);
|
|
1432
|
+
}
|
|
1314
1433
|
const manifestValue = {
|
|
1315
1434
|
version: "0.1.0",
|
|
1316
1435
|
task_id: descriptor.task_id,
|
|
@@ -1323,10 +1442,13 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1323
1442
|
profile: "minimal",
|
|
1324
1443
|
id: descriptor.task_id,
|
|
1325
1444
|
created_at: new Date().toISOString(),
|
|
1326
|
-
ext: {
|
|
1445
|
+
ext: {
|
|
1446
|
+
"sortie-dogs/write-gate": {
|
|
1327
1447
|
operation_manifest: relative(descriptor.managed_path, paths.operation_manifest).replaceAll("\\", "/"),
|
|
1328
1448
|
project_root: descriptor.managed_path,
|
|
1329
|
-
}
|
|
1449
|
+
},
|
|
1450
|
+
[ACCEPTANCE_CONTINUITY_EXTENSION]: acceptance,
|
|
1451
|
+
},
|
|
1330
1452
|
task: {
|
|
1331
1453
|
title: `Parallel task ${descriptor.task_id}`,
|
|
1332
1454
|
objective: "Complete the prepared parallel descriptor within its declared scope.",
|
|
@@ -1389,12 +1511,14 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1389
1511
|
rm(paths.operation_manifest, { force: true }),
|
|
1390
1512
|
]);
|
|
1391
1513
|
}
|
|
1392
|
-
async function ensureParallelReadyControls(snapshot) {
|
|
1393
|
-
if (snapshot.archived || snapshot.cancelled
|
|
1514
|
+
async function ensureParallelReadyControls(snapshot, parentAcceptance) {
|
|
1515
|
+
if (snapshot.archived || snapshot.cancelled)
|
|
1394
1516
|
return;
|
|
1395
1517
|
await ensureLoaded();
|
|
1396
1518
|
const validationCommands = loaded?.manifest?.validation ?? [];
|
|
1397
|
-
await Promise.all(snapshot.
|
|
1519
|
+
await Promise.all(snapshot.tasks
|
|
1520
|
+
.filter(({ phase }) => phase === "pending" || phase === "reserved")
|
|
1521
|
+
.map(({ descriptor }) => createParallelControlFiles(descriptor, validationCommands, parentAcceptance)));
|
|
1398
1522
|
}
|
|
1399
1523
|
async function restoreActiveParallelControls(snapshot) {
|
|
1400
1524
|
await ensureLoaded();
|
|
@@ -1410,7 +1534,16 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1410
1534
|
cancelled: snapshot.cancelled,
|
|
1411
1535
|
archived: snapshot.archived,
|
|
1412
1536
|
terminal_reason: snapshot.terminal_reason,
|
|
1413
|
-
ready: snapshot.ready.map((descriptor) =>
|
|
1537
|
+
ready: snapshot.ready.map((descriptor) => {
|
|
1538
|
+
const acceptance = parallelAcceptanceContinuity.get(parallelAcceptanceKey(descriptor));
|
|
1539
|
+
return {
|
|
1540
|
+
...descriptor,
|
|
1541
|
+
...parallelControlPaths(descriptor),
|
|
1542
|
+
acceptance: acceptance?.criteria ?? [],
|
|
1543
|
+
acceptance_fingerprint: acceptance?.fingerprint,
|
|
1544
|
+
acceptance_parent_fingerprint: acceptance?.parent_fingerprint,
|
|
1545
|
+
};
|
|
1546
|
+
}),
|
|
1414
1547
|
tasks: snapshot.tasks.map(({ descriptor, worktree_id, phase, call_id, child_session_id, outcome, artifact }) => ({
|
|
1415
1548
|
task_id: descriptor.task_id,
|
|
1416
1549
|
worktree_id,
|
|
@@ -1528,10 +1661,10 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1528
1661
|
if (result.status === "serial-fallback")
|
|
1529
1662
|
return JSON.stringify(result);
|
|
1530
1663
|
try {
|
|
1531
|
-
await ensureParallelReadyControls(result.snapshot);
|
|
1664
|
+
await ensureParallelReadyControls(result.snapshot, rootAcceptanceContinuity.get(ownerRoot));
|
|
1532
1665
|
}
|
|
1533
1666
|
catch (error) {
|
|
1534
|
-
await Promise.all(result.snapshot.
|
|
1667
|
+
await Promise.all(result.snapshot.tasks.map(({ descriptor }) => removeParallelControlFiles(descriptor).catch(() => undefined)));
|
|
1535
1668
|
await coordinator.cancel(ownerRoot, result.snapshot.run_id).catch(() => undefined);
|
|
1536
1669
|
throw error;
|
|
1537
1670
|
}
|
|
@@ -1916,11 +2049,15 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1916
2049
|
}
|
|
1917
2050
|
preparedDirectories.push(...(extraction.createdDirectories ?? []).map((directory) => resolve(inspectedProjectRoot, directory)));
|
|
1918
2051
|
}
|
|
2052
|
+
const continuity = inspectAcceptanceContinuity(validation.value);
|
|
1919
2053
|
const identity = {
|
|
1920
2054
|
explicitWriteGate: extension !== undefined,
|
|
2055
|
+
handoffID: validation.value.id,
|
|
1921
2056
|
manifestPath,
|
|
1922
2057
|
projectRoot: inspectedProjectRoot,
|
|
1923
2058
|
validationCommands: new Set(manifest.validation.map(normalizeCommand)),
|
|
2059
|
+
acceptanceContinuity: continuity.ledger,
|
|
2060
|
+
acceptanceContinuityError: continuity.error,
|
|
1924
2061
|
};
|
|
1925
2062
|
if (sessionID === undefined)
|
|
1926
2063
|
return identity;
|
|
@@ -2337,6 +2474,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2337
2474
|
else {
|
|
2338
2475
|
coordinatorRoots.delete(sessionID);
|
|
2339
2476
|
explicitCoordinatorModels.delete(sessionID);
|
|
2477
|
+
assetVersionPins.delete(sessionID);
|
|
2340
2478
|
}
|
|
2341
2479
|
}
|
|
2342
2480
|
const limit = ACTIVE_SESSION_CACHE.maximum - (reserveSlot ? 1 : 0);
|
|
@@ -2344,6 +2482,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2344
2482
|
const sessionID = coordinatorRoots.keys().next().value;
|
|
2345
2483
|
coordinatorRoots.delete(sessionID);
|
|
2346
2484
|
explicitCoordinatorModels.delete(sessionID);
|
|
2485
|
+
assetVersionPins.delete(sessionID);
|
|
2347
2486
|
}
|
|
2348
2487
|
}
|
|
2349
2488
|
async function rememberCoordinatorRoot(sessionID) {
|
|
@@ -2485,6 +2624,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2485
2624
|
function evictSession(sessionID) {
|
|
2486
2625
|
activeSessions.delete(sessionID);
|
|
2487
2626
|
sessionOperationMetrics.delete(sessionID);
|
|
2627
|
+
rootAcceptanceContinuity.delete(sessionID);
|
|
2488
2628
|
abandonSessionLease(sessionID);
|
|
2489
2629
|
sessionAuthorizations.delete(sessionID);
|
|
2490
2630
|
bindingPins.delete(sessionID);
|
|
@@ -2494,6 +2634,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2494
2634
|
}
|
|
2495
2635
|
expiredSessions.delete(sessionID);
|
|
2496
2636
|
coordinatorRoots.delete(sessionID);
|
|
2637
|
+
assetVersionPins.delete(sessionID);
|
|
2497
2638
|
explicitCoordinatorModels.delete(sessionID);
|
|
2498
2639
|
bindingDenials.delete(sessionID);
|
|
2499
2640
|
sessionTaskIDs.delete(sessionID);
|
|
@@ -2649,6 +2790,24 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2649
2790
|
return undefined;
|
|
2650
2791
|
}
|
|
2651
2792
|
}
|
|
2793
|
+
function acceptanceParentPrefix(ledger) {
|
|
2794
|
+
if (ledger.parent_fingerprint === "none")
|
|
2795
|
+
return undefined;
|
|
2796
|
+
for (let length = 1; length < ledger.criteria.length; length += 1) {
|
|
2797
|
+
const prefix = ledger.criteria.slice(0, length);
|
|
2798
|
+
if (acceptanceContinuityFingerprint(prefix) === ledger.parent_fingerprint)
|
|
2799
|
+
return prefix;
|
|
2800
|
+
}
|
|
2801
|
+
return undefined;
|
|
2802
|
+
}
|
|
2803
|
+
async function recoverAcceptanceParent(sessionID, ledger, descriptor) {
|
|
2804
|
+
if (descriptor === undefined || acceptanceParentPrefix(ledger) === undefined ||
|
|
2805
|
+
ledger.parent_fingerprint === "none")
|
|
2806
|
+
return false;
|
|
2807
|
+
const snapshot = await (await getParallelCoordinator()).snapshot(sessionID, descriptor.run_id);
|
|
2808
|
+
return snapshot?.tasks.some(({ phase, descriptor: durable }) => (phase === "pending" || phase === "reserved" || phase === "running") &&
|
|
2809
|
+
sameParallelDescriptor(durable, descriptor)) === true;
|
|
2810
|
+
}
|
|
2652
2811
|
async function assistantMessageText(sessionID, messageID, expectedAgent, partID) {
|
|
2653
2812
|
const messages = input.client?.session?.messages;
|
|
2654
2813
|
if (messages === undefined)
|
|
@@ -2715,6 +2874,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2715
2874
|
if (persistedTurn !== undefined && persistedTurn.agent !== COORDINATOR_AGENT)
|
|
2716
2875
|
return false;
|
|
2717
2876
|
await rememberCoordinatorRoot(sessionID);
|
|
2877
|
+
await pinAssetVersion(sessionID);
|
|
2718
2878
|
releaseSessionEnforcement(sessionID);
|
|
2719
2879
|
fastLane.beginTurn(sessionID, persistedTurn?.synthetic ?? false);
|
|
2720
2880
|
return true;
|
|
@@ -3021,6 +3181,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
3021
3181
|
...(metrics ?? {}),
|
|
3022
3182
|
...operationMetricsSnapshot(textInput.sessionID),
|
|
3023
3183
|
});
|
|
3184
|
+
if (runOutcome === "DONE")
|
|
3185
|
+
rootAcceptanceContinuity.delete(textInput.sessionID);
|
|
3024
3186
|
}
|
|
3025
3187
|
await completeContinuationText(textInput.sessionID, textOutput.text, false);
|
|
3026
3188
|
},
|
|
@@ -3077,6 +3239,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
3077
3239
|
fastLane.beginTurn(chatInput.sessionID, synthetic);
|
|
3078
3240
|
releaseSessionEnforcement(chatInput.sessionID);
|
|
3079
3241
|
await rememberCoordinatorRoot(chatInput.sessionID);
|
|
3242
|
+
await pinAssetVersion(chatInput.sessionID);
|
|
3080
3243
|
}
|
|
3081
3244
|
else {
|
|
3082
3245
|
if (!synthetic && isCoordinatorSession(chatInput.sessionID)) {
|
|
@@ -3400,7 +3563,14 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
3400
3563
|
let parallelWorkerAuthorized = false;
|
|
3401
3564
|
let parallelWorkerAlreadyBound = false;
|
|
3402
3565
|
let reservedParallelDescriptor;
|
|
3566
|
+
let validatedRootAcceptance;
|
|
3403
3567
|
if (toolInput.tool === "task" && taskRole === "dog-worker" && isRecord(output.args)) {
|
|
3568
|
+
await ensureLoaded();
|
|
3569
|
+
const assetVersionStatus = await pinAssetVersion(toolInput.sessionID);
|
|
3570
|
+
if (assetVersionStatus === "mismatch") {
|
|
3571
|
+
throw new Error("SORTIE_FRESH_SESSION_REQUIRED: installed agent assets and the loaded plugin use different contracts; " +
|
|
3572
|
+
"run sortie-dogs init . and start a fresh coordinator session");
|
|
3573
|
+
}
|
|
3404
3574
|
const prompt = typeof output.args.prompt === "string" ? output.args.prompt : "";
|
|
3405
3575
|
const contractPrompt = taskContractText(prompt);
|
|
3406
3576
|
reservedParallelDescriptor = parallelDescriptor(prompt);
|
|
@@ -3522,6 +3692,49 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
3522
3692
|
defects: [contractDefect("contract", "/", "dispatch_identity_mismatch")],
|
|
3523
3693
|
});
|
|
3524
3694
|
}
|
|
3695
|
+
if (assetVersionStatus === "current") {
|
|
3696
|
+
const ledger = identity.acceptanceContinuity;
|
|
3697
|
+
if (ledger === undefined) {
|
|
3698
|
+
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
3699
|
+
defects: [contractDefect("handoff", "/ext/sortie-dogs~1acceptance-continuity", `acceptance_continuity_${identity.acceptanceContinuityError ?? "missing"}`)],
|
|
3700
|
+
});
|
|
3701
|
+
}
|
|
3702
|
+
const criteria = taskAcceptanceCriteria(contractPrompt);
|
|
3703
|
+
if (taskIDs.length !== 1 || taskIDs[0] !== identity.handoffID ||
|
|
3704
|
+
ledger.task_id !== identity.handoffID || criteria === undefined ||
|
|
3705
|
+
criteria.length !== ledger.criteria.length ||
|
|
3706
|
+
criteria.some((criterion, index) => criterion !== ledger.criteria[index])) {
|
|
3707
|
+
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
3708
|
+
defects: [contractDefect("contract", "/acceptance", "acceptance_continuity_mismatch")],
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3711
|
+
const previous = rootAcceptanceContinuity.get(toolInput.sessionID);
|
|
3712
|
+
if (previous === undefined) {
|
|
3713
|
+
if (ledger.parent_fingerprint !== "none" &&
|
|
3714
|
+
!await recoverAcceptanceParent(toolInput.sessionID, ledger, reservedParallelDescriptor)) {
|
|
3715
|
+
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
3716
|
+
defects: [contractDefect("handoff", "/ext/sortie-dogs~1acceptance-continuity", "acceptance_parent_continuity_mismatch")],
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
else if (previous.task_id === ledger.task_id && previous.fingerprint === ledger.fingerprint) {
|
|
3721
|
+
if (ledger.parent_fingerprint !== previous.parent_fingerprint) {
|
|
3722
|
+
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
3723
|
+
defects: [contractDefect("handoff", "/ext/sortie-dogs~1acceptance-continuity", "acceptance_parent_continuity_mismatch")],
|
|
3724
|
+
});
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
else {
|
|
3728
|
+
if (ledger.parent_fingerprint !== previous.fingerprint ||
|
|
3729
|
+
ledger.criteria.length <= previous.criteria.length ||
|
|
3730
|
+
previous.criteria.some((criterion, index) => criterion !== ledger.criteria[index])) {
|
|
3731
|
+
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
3732
|
+
defects: [contractDefect("handoff", "/ext/sortie-dogs~1acceptance-continuity", "acceptance_parent_continuity_mismatch")],
|
|
3733
|
+
});
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
validatedRootAcceptance = ledger;
|
|
3737
|
+
}
|
|
3525
3738
|
}
|
|
3526
3739
|
}
|
|
3527
3740
|
if (reservedParallelDescriptor !== undefined) {
|
|
@@ -3546,6 +3759,13 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
3546
3759
|
parallelWorkerAlreadyBound,
|
|
3547
3760
|
parallelWorkerAuthorized,
|
|
3548
3761
|
});
|
|
3762
|
+
if (validatedRootAcceptance !== undefined && reservedParallelDescriptor === undefined) {
|
|
3763
|
+
rootAcceptanceContinuity.delete(toolInput.sessionID);
|
|
3764
|
+
rootAcceptanceContinuity.set(toolInput.sessionID, validatedRootAcceptance);
|
|
3765
|
+
while (rootAcceptanceContinuity.size > ACTIVE_SESSION_CACHE.maximum) {
|
|
3766
|
+
rootAcceptanceContinuity.delete(rootAcceptanceContinuity.keys().next().value);
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3549
3769
|
if (resumedWorkerSessionID !== undefined) {
|
|
3550
3770
|
const recoverableParallel = parallelRecoverableChildren.get(resumedWorkerSessionID);
|
|
3551
3771
|
if (recoverableParallel !== undefined) {
|