sortie-dogs 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/cli/main.js +13 -5
- package/dist/core/diagnostics.js +9 -1
- package/dist/core/goal-declaration-format.d.ts +4 -0
- package/dist/core/goal-declaration-format.js +33 -0
- package/dist/core/initialize.js +9 -1
- package/dist/core/validate-manifest.js +9 -1
- package/dist/core/validate-semantics.js +9 -1
- package/dist/plugin/gate.d.ts +2 -0
- package/dist/plugin/gate.js +27 -0
- package/dist/plugin/index.js +93 -13
- package/dist/runtime-assets.d.ts +1446 -9
- package/dist/runtime-assets.js +16 -3
- package/package.json +1 -1
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.9.
|
|
27
|
+
Release: [v0.9.3](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.3)
|
|
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.80-review-evidence-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.80-review-evidence-v1";
|
package/dist/cli/main.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
3
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
4
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
5
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
return path;
|
|
9
|
+
};
|
|
2
10
|
import { open, realpath } from "node:fs/promises";
|
|
3
11
|
import { resolve } from "node:path";
|
|
4
12
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
const core = await import(`../core/validate-schema.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`);
|
|
6
|
-
const diagnostics = await import(`../core/diagnostics.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`);
|
|
7
|
-
const manifestValidator = await import(`../core/validate-manifest.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`);
|
|
8
|
-
const pathUtils = await import(`../core/path.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`);
|
|
9
|
-
const initializer = await import(`../core/initialize.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`);
|
|
13
|
+
const core = await import(__rewriteRelativeImportExtension(`../core/validate-schema.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
14
|
+
const diagnostics = await import(__rewriteRelativeImportExtension(`../core/diagnostics.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
15
|
+
const manifestValidator = await import(__rewriteRelativeImportExtension(`../core/validate-manifest.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
16
|
+
const pathUtils = await import(__rewriteRelativeImportExtension(`../core/path.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
17
|
+
const initializer = await import(__rewriteRelativeImportExtension(`../core/initialize.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
10
18
|
const LIMITS = {
|
|
11
19
|
handoffBytes: 2 * 1024 * 1024,
|
|
12
20
|
manifestBytes: 512 * 1024,
|
package/dist/core/diagnostics.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
const validateSemantics = await import(__rewriteRelativeImportExtension(`./validate-semantics.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
2
10
|
const DEFAULT_SEVERITIES = {
|
|
3
11
|
H001: "error",
|
|
4
12
|
H002: "error",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Shared planner guidance and admission enums; no inferred acceptance or dispatch authority. */
|
|
2
|
+
export declare const GOAL_DELIVERY_INTENTS: readonly ["design", "registration", "implementation", "repair", "controlled-change"];
|
|
3
|
+
export declare const GOAL_DELIVERY_MODES: readonly ["planning-only", "mvp-first", "repair-first", "controlled-change"];
|
|
4
|
+
export declare const GOAL_DECLARATION_FORMAT: string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Shared planner guidance and admission enums; no inferred acceptance or dispatch authority. */
|
|
2
|
+
export const GOAL_DELIVERY_INTENTS = ["design", "registration", "implementation", "repair", "controlled-change"];
|
|
3
|
+
export const GOAL_DELIVERY_MODES = ["planning-only", "mvp-first", "repair-first", "controlled-change"];
|
|
4
|
+
export const GOAL_DECLARATION_FORMAT = `Goal declaration belongs in the Task prompt, not in the handoff JSON or operation manifest.
|
|
5
|
+
delivery_intent must be exactly one of: ${GOAL_DELIVERY_INTENTS.join(" | ")}. Never use prose or a boolean.
|
|
6
|
+
delivery_mode is a separate optional enum: ${GOAL_DELIVERY_MODES.join(" | ")}.
|
|
7
|
+
Use flat key: value lines. Each criterion begins with its own goal_criterion_id: line.
|
|
8
|
+
Repeat the entire flat criterion block for multiple criteria. Do not use goal_criteria, goal_acceptance.criteria,
|
|
9
|
+
YAML object/list wrappers, inline { goal_criterion_id: ... }, or JSON objects for these blocks.
|
|
10
|
+
Replace every placeholder with the accepted task's actual value; preserve its acceptance and validation:
|
|
11
|
+
goal_acceptance_fingerprint: <sha256: followed by exactly 64 lowercase hexadecimal characters>
|
|
12
|
+
delivery_intent: implementation
|
|
13
|
+
delivery_mode: mvp-first
|
|
14
|
+
usable_path_established: false
|
|
15
|
+
controlled_change: false
|
|
16
|
+
goal_budget_units: <accepted positive integer>
|
|
17
|
+
goal_criterion_id: <stable criterion id>
|
|
18
|
+
goal_target: <requested behavior>
|
|
19
|
+
goal_entrypoint: <real entrypoint>
|
|
20
|
+
goal_workload: <requested workload>
|
|
21
|
+
goal_oracle_coverage: ["<actual oracle>"]
|
|
22
|
+
goal_build_boundary: <included | excluded | not-applicable>
|
|
23
|
+
goal_source: <fixed source label>
|
|
24
|
+
goal_candidate: <fixed candidate label>
|
|
25
|
+
goal_source_binding: current-protected
|
|
26
|
+
goal_candidate_binding: current-protected
|
|
27
|
+
goal_fixture: <actual fixture identity>
|
|
28
|
+
goal_proof_scope: requested-full
|
|
29
|
+
goal_expected_outcome: pass
|
|
30
|
+
goal_validation_command: <exact operation manifest validation command>
|
|
31
|
+
The example's implementation/mvp-first selections apply only to an implementation without its usable path;
|
|
32
|
+
select other enum values only from the accepted request. A declaration rejection launches no child.
|
|
33
|
+
Repair the Task prompt's named fields, not unrelated files; retry only with the corrected declaration.`;
|
package/dist/core/initialize.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
1
9
|
import { createHash } from "node:crypto";
|
|
2
10
|
import { constants } from "node:fs";
|
|
3
11
|
import { lstat, mkdir, open, readFile, realpath, rm, rmdir, stat } from "node:fs/promises";
|
|
4
12
|
import { homedir } from "node:os";
|
|
5
13
|
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
6
|
-
const assets = await import(`../runtime-assets.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`);
|
|
14
|
+
const assets = await import(__rewriteRelativeImportExtension(`../runtime-assets.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
7
15
|
const { runtimeAssets } = assets;
|
|
8
16
|
const OPEN_CODE_DIRECTORY = ".opencode";
|
|
9
17
|
const VERSION_MARKER = `${OPEN_CODE_DIRECTORY}/sortie-dogs.version`;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
const pathUtils = await import(__rewriteRelativeImportExtension(`./path.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
2
10
|
const MESSAGES = {
|
|
3
11
|
H001_PATH_RELATIVE: "Manifest path must be a valid repository-relative path.",
|
|
4
12
|
H011_VALIDATION_MISSING: "A required manifest validation has not passed.",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
const pathUtils = await import(__rewriteRelativeImportExtension(`./path.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
2
10
|
const HASH_LENGTHS = {
|
|
3
11
|
sha256: 64,
|
|
4
12
|
sha512: 128,
|
package/dist/plugin/gate.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export declare function isRemoteMutation(tool: string, args: unknown): boolean;
|
|
|
60
60
|
* same command written with different spacing compares equal.
|
|
61
61
|
*/
|
|
62
62
|
export declare function normalizeCommand(command: string): string;
|
|
63
|
+
/** Canonicalize only an exact or unambiguous basename-shortened sequence of declared validations. */
|
|
64
|
+
export declare function canonicalDeclaredValidationSequence(command: string, declared: ReadonlySet<string>): string | undefined;
|
|
63
65
|
/** Unbound sessions may invoke only tools whose complete input is known to be read-only. */
|
|
64
66
|
export declare function isKnownReadOnlyTool(tool: string, args: unknown, additionalReadOnlyTools?: ReadonlySet<string>): boolean;
|
|
65
67
|
/** Canonical scopes let sibling worker bindings reject equal or ancestor ownership. */
|
package/dist/plugin/gate.js
CHANGED
|
@@ -797,6 +797,33 @@ export function normalizeCommand(command) {
|
|
|
797
797
|
? normalized
|
|
798
798
|
: `${callOperator[2]}${normalized.slice(callOperator[0].length)}`;
|
|
799
799
|
}
|
|
800
|
+
function declaredCommandMatch(segment, declared) {
|
|
801
|
+
const normalized = normalizeCommand(segment);
|
|
802
|
+
const exact = declared.filter((command) => normalizeCommand(command) === normalized);
|
|
803
|
+
if (exact.length === 1)
|
|
804
|
+
return normalizeCommand(exact[0]);
|
|
805
|
+
const firstSpace = normalized.indexOf(" ");
|
|
806
|
+
const executable = firstSpace === -1 ? normalized : normalized.slice(0, firstSpace);
|
|
807
|
+
const suffix = firstSpace === -1 ? "" : normalized.slice(firstSpace);
|
|
808
|
+
if (isAbsolute(executable))
|
|
809
|
+
return undefined;
|
|
810
|
+
const equivalent = declared.map(normalizeCommand).filter((command) => {
|
|
811
|
+
const boundary = command.indexOf(" ");
|
|
812
|
+
const declaredExecutable = boundary === -1 ? command : command.slice(0, boundary);
|
|
813
|
+
const declaredSuffix = boundary === -1 ? "" : command.slice(boundary);
|
|
814
|
+
return isAbsolute(declaredExecutable) && basename(declaredExecutable) === executable && declaredSuffix === suffix;
|
|
815
|
+
});
|
|
816
|
+
return equivalent.length === 1 ? equivalent[0] : undefined;
|
|
817
|
+
}
|
|
818
|
+
/** Canonicalize only an exact or unambiguous basename-shortened sequence of declared validations. */
|
|
819
|
+
export function canonicalDeclaredValidationSequence(command, declared) {
|
|
820
|
+
const normalized = normalizeCommand(command);
|
|
821
|
+
const segments = shellSegments(command, "posix").map((segment) => segment.trim()).filter(Boolean);
|
|
822
|
+
if (segments.length === 0 || normalized !== segments.map(normalizeCommand).join(" && "))
|
|
823
|
+
return undefined;
|
|
824
|
+
const canonical = segments.map((segment) => declaredCommandMatch(segment, [...declared]));
|
|
825
|
+
return canonical.every((segment) => segment !== undefined) ? canonical.join(" && ") : undefined;
|
|
826
|
+
}
|
|
800
827
|
/** Unbound sessions may invoke only tools whose complete input is known to be read-only. */
|
|
801
828
|
export function isKnownReadOnlyTool(tool, args, additionalReadOnlyTools = new Set()) {
|
|
802
829
|
const name = tool.toLowerCase();
|
package/dist/plugin/index.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
1
9
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { lstat, mkdir, open, readFile, readdir, realpath, rm, stat } from "node:fs/promises";
|
|
10
|
+
import { lstat, mkdir, open, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
3
11
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
12
|
import { RUNTIME_ASSET_VERSION } from "../asset-version.js";
|
|
13
|
+
import { GOAL_DECLARATION_FORMAT, GOAL_DELIVERY_INTENTS, GOAL_DELIVERY_MODES } from "../core/goal-declaration-format.js";
|
|
5
14
|
import { ACCEPTANCE_CONTINUITY_AUTHORITY, ACCEPTANCE_CONTINUITY_EXTENSION, ACCEPTANCE_CONTINUITY_SCHEMA_VERSION, acceptanceContinuityFingerprint, inspectAcceptanceContinuity, normalizeAcceptanceCriteria, } from "../core/acceptance-continuity.js";
|
|
6
15
|
import { resolveGlobalConfigRoot } from "../core/initialize.js";
|
|
7
16
|
import { admitLunaFabric } from "../core/luna-fabric-contract.js";
|
|
@@ -19,7 +28,7 @@ import { validateManifest } from "../core/validate-manifest.js";
|
|
|
19
28
|
import { safeSchemaPointer, validateHandoffSchema, validateOperationManifestSchema, } from "../core/validate-schema.js";
|
|
20
29
|
import { DEFAULT_PLUGIN_OPTIONS, resolvePluginConfiguration, resolvePluginConfigurationSourcesWithGlobal, } from "./config.js";
|
|
21
30
|
import { CONTINUATION_CAPABILITY, CONTINUATION_MARKER, ROLLOVER_MARKER, createContinuationHooks, } from "./continuation.js";
|
|
22
|
-
import { WriteDeniedError, canonicalManifestReadScopes, canonicalManifestWriteScopes, createProjectPaths, createWriteGate, describeUnclassifiedCommand, extractWritePaths, bootstrapWritePaths, isGitMutation, isKnownReadOnlyTool, isRemoteMutation, normalizeCommand, resolveProjectRoot, safePath, writeScopesOverlap, } from "./gate.js";
|
|
31
|
+
import { WriteDeniedError, canonicalManifestReadScopes, canonicalManifestWriteScopes, canonicalDeclaredValidationSequence, createProjectPaths, createWriteGate, describeUnclassifiedCommand, extractWritePaths, bootstrapWritePaths, isGitMutation, isKnownReadOnlyTool, isRemoteMutation, normalizeCommand, resolveProjectRoot, safePath, writeScopesOverlap, } from "./gate.js";
|
|
23
32
|
import { BACKLOG_DRAIN_CAPABILITY, FastLaneController } from "./fast-lane.js";
|
|
24
33
|
import { createModelRoutingHook, } from "./model-routing-hook.js";
|
|
25
34
|
import { createTaskResultRepairHook, lastAssistantText, markConsultationFallbackRetry, taskChildSessionID, } from "./task-result-repair.js";
|
|
@@ -112,7 +121,8 @@ export class HandoffDeniedError extends Error {
|
|
|
112
121
|
const defects = normalizeDefects(options?.defects);
|
|
113
122
|
super(`Handoff denied for "${safePath(path)}": handoff and operation manifest contract.` +
|
|
114
123
|
(defects.length === 0 ? "" : ` Defects: ${describeDefects(defects)}.`) +
|
|
115
|
-
|
|
124
|
+
(path === "<goal-declaration>" ? ` ${GOAL_DECLARATION_FORMAT}` :
|
|
125
|
+
" Correct the registered handoff or its operation manifest, then read the handoff again."), options);
|
|
116
126
|
this.name = "HandoffDeniedError";
|
|
117
127
|
this.reason = reason;
|
|
118
128
|
this.defects = defects;
|
|
@@ -728,6 +738,30 @@ function taskAcceptanceCriteria(text, key = "acceptance") {
|
|
|
728
738
|
}
|
|
729
739
|
return values.length === 0 ? undefined : normalizeAcceptanceCriteria(values);
|
|
730
740
|
}
|
|
741
|
+
function canonicalTaskAcceptance(text, criteria) {
|
|
742
|
+
const lines = text.split(/\r?\n/u);
|
|
743
|
+
const headers = lines.flatMap((line, index) => /^([\t ]*)acceptance[\t ]*:/iu.exec(line) === null ? [] : [index]);
|
|
744
|
+
if (headers.length !== 1)
|
|
745
|
+
return undefined;
|
|
746
|
+
const index = headers[0];
|
|
747
|
+
const indent = /^[\t ]*/u.exec(lines[index])[0];
|
|
748
|
+
const headerIndent = indent.replaceAll("\t", " ").length;
|
|
749
|
+
let end = index + 1;
|
|
750
|
+
if (/^\s*acceptance\s*:\s*$/iu.test(lines[index])) {
|
|
751
|
+
while (end < lines.length) {
|
|
752
|
+
const line = lines[end];
|
|
753
|
+
if (line.trim().length === 0) {
|
|
754
|
+
end += 1;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (/^\s*/u.exec(line)[0].replaceAll("\t", " ").length <= headerIndent)
|
|
758
|
+
break;
|
|
759
|
+
end += 1;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
lines.splice(index, end - index, `${indent}acceptance: ${JSON.stringify(criteria)}`);
|
|
763
|
+
return lines.join("\n");
|
|
764
|
+
}
|
|
731
765
|
function taskContractText(text) {
|
|
732
766
|
const lines = text.split(/\r?\n/u);
|
|
733
767
|
const digestIndexes = lines.flatMap((line, index) => /^\s*context_digest\s*:\s*$/iu.test(line) ? [index] : []);
|
|
@@ -1058,7 +1092,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1058
1092
|
throw new Error("invalid-child-lifecycle-check-wait");
|
|
1059
1093
|
}
|
|
1060
1094
|
const pluginModuleName = "@opencode-ai/plugin";
|
|
1061
|
-
const pluginModule = await import(pluginModuleName).catch(() => undefined);
|
|
1095
|
+
const pluginModule = await import(__rewriteRelativeImportExtension(pluginModuleName)).catch(() => undefined);
|
|
1062
1096
|
const toolCandidate = pluginModule?.tool;
|
|
1063
1097
|
const toolSchema = typeof toolCandidate === "function"
|
|
1064
1098
|
? toolCandidate.schema
|
|
@@ -1328,8 +1362,11 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1328
1362
|
const unitID = authorization.taskID;
|
|
1329
1363
|
const unitBound = unitID !== undefined && goal.outstanding_reservations.some((reservation) => reservation.unit_id === unitID && reservation.session_id === identity.parentID);
|
|
1330
1364
|
const criteria = goal.acceptance_contract?.criteria.filter((criterion) => criterion.validation_command === rawCommand && criterion.expected_outcome === "pass") ?? [];
|
|
1331
|
-
if (!unitBound
|
|
1365
|
+
if (!unitBound)
|
|
1332
1366
|
throw new Error("SORTIE_VALIDATION_BUDGET_DENIED: requirement-unbound");
|
|
1367
|
+
// Exact generation and formatting checks may support acceptance without proving a criterion.
|
|
1368
|
+
if (criteria.length === 0)
|
|
1369
|
+
return;
|
|
1333
1370
|
const scope = criteria.every((criterion) => criterion.proof_scope === "requested-full") ? "full" : "targeted";
|
|
1334
1371
|
const request = { run_id: goal.goal_id, operation_id: toolInput.callID,
|
|
1335
1372
|
source_snapshot: snapshot.source, candidate: snapshot.candidate, command: [rawCommand], scope,
|
|
@@ -1481,6 +1518,10 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1481
1518
|
return state.receipt ?? undefined;
|
|
1482
1519
|
const records = (await ledger.readGoal()).records;
|
|
1483
1520
|
const settledEvidence = records.flatMap(({ event }) => event.kind === "unit.settled" ? event.evidence : []);
|
|
1521
|
+
if (status === "succeeded" && (state.acceptance_contract === null ||
|
|
1522
|
+
state.acceptance_contract.criteria.length === 0 ||
|
|
1523
|
+
!state.acceptance_contract.criteria.every(({ criterion_id }) => state.satisfied_criteria.includes(criterion_id))))
|
|
1524
|
+
return undefined;
|
|
1484
1525
|
if (status === "succeeded" && state.acceptance_contract !== null) {
|
|
1485
1526
|
for (const criterion of state.acceptance_contract.criteria) {
|
|
1486
1527
|
if (criterion.source_binding !== "current-protected" && criterion.candidate_binding !== "current-protected")
|
|
@@ -1674,14 +1715,14 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1674
1715
|
defects.push(contractDefect("contract", "/goal_acceptance_fingerprint", "goal_fingerprint_format"));
|
|
1675
1716
|
}
|
|
1676
1717
|
const intent = handoffValue(entries, ["delivery_intent"]);
|
|
1677
|
-
const intents =
|
|
1718
|
+
const intents = GOAL_DELIVERY_INTENTS;
|
|
1678
1719
|
if (intent === undefined)
|
|
1679
1720
|
defects.push(contractDefect("contract", "/delivery_intent", "delivery_intent_missing"));
|
|
1680
1721
|
else if (!intents.includes(intent)) {
|
|
1681
1722
|
defects.push(contractDefect("contract", "/delivery_intent", "delivery_intent_invalid"));
|
|
1682
1723
|
}
|
|
1683
1724
|
const mode = handoffValue(entries, ["delivery_mode"]);
|
|
1684
|
-
const modes =
|
|
1725
|
+
const modes = GOAL_DELIVERY_MODES;
|
|
1685
1726
|
if (mode !== undefined && !modes.includes(mode)) {
|
|
1686
1727
|
defects.push(contractDefect("contract", "/delivery_mode", "delivery_mode_invalid"));
|
|
1687
1728
|
}
|
|
@@ -5293,7 +5334,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5293
5334
|
? undefined
|
|
5294
5335
|
: await terminalGoalFromHostText(textInput.sessionID, textOutput.text);
|
|
5295
5336
|
if (runOutcome === "DONE" && terminal !== undefined && (terminal.delivery === "running" ||
|
|
5296
|
-
(terminal.receipt === undefined && terminal.goal !== undefined && terminal.goal.
|
|
5337
|
+
(terminal.receipt === undefined && terminal.goal !== undefined && terminal.goal.goal_id !== null))) {
|
|
5297
5338
|
textOutput.text = replaceDoneTerminalStatus(textOutput.text, terminal.delivery === "running"
|
|
5298
5339
|
? "status: IN_PROGRESS — durable delivery active; same sessionでjoinまたはstale reconcileが必要"
|
|
5299
5340
|
: "status: IN_PROGRESS\ngoal_control: accepted criteria remain unproved");
|
|
@@ -5855,6 +5896,13 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5855
5896
|
diagnosis.tools.add(toolInput.callID);
|
|
5856
5897
|
return;
|
|
5857
5898
|
}
|
|
5899
|
+
if (isRecord(output.args) && typeof output.args.command === "string") {
|
|
5900
|
+
const authorization = sessionAuthorizations.get(toolInput.sessionID);
|
|
5901
|
+
const canonical = authorization === undefined ? undefined
|
|
5902
|
+
: canonicalDeclaredValidationSequence(output.args.command, authorization.validationCommands);
|
|
5903
|
+
if (canonical !== undefined)
|
|
5904
|
+
output.args.command = canonical;
|
|
5905
|
+
}
|
|
5858
5906
|
await recordHostGoalStart(toolInput, output);
|
|
5859
5907
|
const coordinatorRoot = isCoordinatorSession(toolInput.sessionID) || await recoverCoordinatorRoot(toolInput.sessionID);
|
|
5860
5908
|
const readonlyDiagnosis = coordinatorRoot && toolInput.tool === "task" && isRecord(output.args)
|
|
@@ -6073,21 +6121,31 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6073
6121
|
});
|
|
6074
6122
|
}
|
|
6075
6123
|
if (assetVersionStatus === "current") {
|
|
6076
|
-
const
|
|
6077
|
-
if (
|
|
6124
|
+
const inspectedLedger = identity.acceptanceContinuity;
|
|
6125
|
+
if (inspectedLedger === undefined) {
|
|
6078
6126
|
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
6079
6127
|
defects: [contractDefect("handoff", "/ext/sortie-dogs~1acceptance-continuity", `acceptance_continuity_${identity.acceptanceContinuityError ?? "missing"}`)],
|
|
6080
6128
|
});
|
|
6081
6129
|
}
|
|
6130
|
+
let ledger = inspectedLedger;
|
|
6082
6131
|
const criteria = taskAcceptanceCriteria(contractPrompt);
|
|
6083
6132
|
if (taskIDs.length !== 1 || taskIDs[0] !== identity.handoffID ||
|
|
6084
|
-
ledger.task_id !== identity.handoffID
|
|
6085
|
-
criteria.length !== ledger.criteria.length ||
|
|
6086
|
-
criteria.some((criterion, index) => criterion !== ledger.criteria[index])) {
|
|
6133
|
+
ledger.task_id !== identity.handoffID) {
|
|
6087
6134
|
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
6088
6135
|
defects: [contractDefect("contract", "/acceptance", "acceptance_continuity_mismatch")],
|
|
6089
6136
|
});
|
|
6090
6137
|
}
|
|
6138
|
+
if (criteria === undefined || criteria.length !== ledger.criteria.length ||
|
|
6139
|
+
criteria.some((criterion, index) => criterion !== ledger.criteria[index])) {
|
|
6140
|
+
const canonical = canonicalTaskAcceptance(prompt, ledger.criteria);
|
|
6141
|
+
if (canonical === undefined) {
|
|
6142
|
+
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
6143
|
+
defects: [contractDefect("contract", "/acceptance", "acceptance_continuity_mismatch")],
|
|
6144
|
+
});
|
|
6145
|
+
}
|
|
6146
|
+
output.args.prompt = canonical;
|
|
6147
|
+
prompt = canonical;
|
|
6148
|
+
}
|
|
6091
6149
|
const previous = rootAcceptanceContinuity.get(toolInput.sessionID);
|
|
6092
6150
|
if (previous === undefined) {
|
|
6093
6151
|
if (ledger.parent_fingerprint !== "none" &&
|
|
@@ -6109,6 +6167,24 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6109
6167
|
previous.criteria.every((criterion, index) => criterion === ledger.criteria[index]);
|
|
6110
6168
|
const strictAppend = ledger.criteria.length > previous.criteria.length &&
|
|
6111
6169
|
previous.criteria.every((criterion, index) => criterion === ledger.criteria[index]);
|
|
6170
|
+
// Serial parent identity is controller-owned. Repair only an omitted link on
|
|
6171
|
+
// an otherwise exact carry-forward/append; explicit conflicting links still fail.
|
|
6172
|
+
// Persist it so the worker's Read and later recovery see the same contract.
|
|
6173
|
+
if (reservedParallelDescriptor === undefined && ledger.parent_fingerprint === "none" &&
|
|
6174
|
+
(exactCarryForward || strictAppend)) {
|
|
6175
|
+
const handoff = JSON.parse(await readFile(handoffPaths[0], "utf8"));
|
|
6176
|
+
const validated = validateHandoffSchema(handoff);
|
|
6177
|
+
const current = validated.ok ? inspectAcceptanceContinuity(validated.value).ledger : undefined;
|
|
6178
|
+
if (current === undefined || JSON.stringify(current) !== JSON.stringify(ledger)) {
|
|
6179
|
+
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
6180
|
+
defects: [contractDefect("handoff", "/ext/sortie-dogs~1acceptance-continuity", "acceptance_parent_continuity_mismatch")],
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
6183
|
+
ledger = { ...ledger, parent_fingerprint: previous.fingerprint };
|
|
6184
|
+
handoff.ext[ACCEPTANCE_CONTINUITY_EXTENSION] = ledger;
|
|
6185
|
+
await writeFile(handoffPaths[0], `${JSON.stringify(handoff, null, 2)}\n`);
|
|
6186
|
+
await inspect(handoffPaths[0], undefined, { report: true });
|
|
6187
|
+
}
|
|
6112
6188
|
if (ledger.parent_fingerprint !== previous.fingerprint || (!exactCarryForward && !strictAppend)) {
|
|
6113
6189
|
throw new HandoffDeniedError("contract-invalid", handoffPaths[0], {
|
|
6114
6190
|
defects: [contractDefect("handoff", "/ext/sortie-dogs~1acceptance-continuity", "acceptance_parent_continuity_mismatch")],
|
|
@@ -6255,6 +6331,10 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6255
6331
|
// boundary; unrelated commands continue through the ordinary write gate.
|
|
6256
6332
|
if (activeState?.parallel === "valid" && command !== undefined && authorization?.validationCommands.has(command) === true)
|
|
6257
6333
|
throw new WriteDeniedError("parallel-validation", "<parallel-unit>");
|
|
6334
|
+
const declaredSequence = command === undefined || authorization === undefined ? undefined
|
|
6335
|
+
: canonicalDeclaredValidationSequence(command, authorization.validationCommands);
|
|
6336
|
+
if (activeState?.parallel !== "valid" && declaredSequence !== undefined)
|
|
6337
|
+
return;
|
|
6258
6338
|
if (activeState?.parallel === "valid") {
|
|
6259
6339
|
const extracted = extractWritePaths(toolInput.tool, output.args);
|
|
6260
6340
|
const relativeWrite = extracted.paths.find((path) => !isAbsolute(path));
|