sortie-dogs 0.13.16 → 0.13.17

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 CHANGED
@@ -19,8 +19,8 @@ evidence-backed result, not more agents for their own sake.
19
19
  [![Node.js](https://img.shields.io/node/v/sortie-dogs)](https://www.npmjs.com/package/sortie-dogs)
20
20
  [![MIT License](https://img.shields.io/npm/l/sortie-dogs)](LICENSE)
21
21
 
22
- **Current release: [v0.13.16](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.13.16)**
23
- · [Release notes](docs/release-v0.13.16.md)
22
+ **Current release: [v0.13.17](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.13.17)**
23
+ · [Release notes](docs/release-v0.13.17.md)
24
24
 
25
25
  [Quick start](#quick-start) · [Workflow](#mission-workflow) · [Models](#default-models) ·
26
26
  [Measured results](#measured-results) · [Configuration](#configuration) · [Documentation](#documentation)
@@ -57,7 +57,7 @@ Use an existing Codex CLI signed in with **ChatGPT**. On Windows, PowerShell **7
57
57
  available; POSIX uses bash. Sortie does not install Codex, start login or use a metered API fallback.
58
58
 
59
59
  ```sh
60
- npm install --save-dev sortie-dogs@0.13.16
60
+ npm install --save-dev sortie-dogs@0.13.17
61
61
  npx --no-install sortie-dogs codex init .
62
62
  ```
63
63
 
@@ -86,7 +86,7 @@ bridge. [Codex guide: SDK, permissions, progress, resume and manifest route](doc
86
86
  Use **OpenCode V2**. In the target project:
87
87
 
88
88
  ```sh
89
- npm install --save-dev sortie-dogs@0.13.16
89
+ npm install --save-dev sortie-dogs@0.13.17
90
90
  npx --no-install sortie-dogs init .
91
91
  ```
92
92
 
@@ -188,7 +188,7 @@ known expense. Official local evaluation and leaderboard acceptance are separate
188
188
  **8/23 (34.8%)**, versus v0.12.25's 7/23; all seven prior resolutions retained, no empty patches or
189
189
  official evaluation errors. [Fixed conditions, stopped-run accounting and costs](docs/benchmarks/swebench-lite-history.md#v0131-dev23-2026-09-30).
190
190
 
191
- All scores belong to their fixed candidates, **not v0.13.16**. SWE-bench remains an optional
191
+ All scores belong to their fixed candidates, **not v0.13.17**. SWE-bench remains an optional
192
192
  measurement, not a release gate. Different budgets, models and conditions are not controlled comparisons.
193
193
 
194
194
  ## Configuration
@@ -211,7 +211,7 @@ Install the desired package version and rerun **your host's** initializer:
211
211
 
212
212
  An OpenCode config-local bridge may resolve a separate package; npm-global update alone does not
213
213
  update it. [Global/update instructions](docs/configuration.md#global-availability).
214
- The current Mission asset marker is `0.13.16-practical-continuity-v1`; the unchanged Codex skill marker is
214
+ The current Mission asset marker is `0.13.17-handoff-continuity-v1`; the unchanged Codex skill marker is
215
215
  `0.13.11-codex-skill-v2`. Installed markers alone do not prove a running host reloaded the new version.
216
216
 
217
217
  There is no supported uninstall command. Remove the dependency and only known Sortie-owned paths;
@@ -227,7 +227,7 @@ follow the [manual removal guide](docs/uninstall.md), never delete the entire `.
227
227
  [SWE-bench history](docs/benchmarks/swebench-lite-history.md) · [Historical local case study](docs/benchmark-reference.md).
228
228
  - **Development:** [Testing](docs/testing.md) · [CLI testing](docs/cli-testing.md) ·
229
229
  [Windows tests](docs/windows-tests.md) · [Release routine](docs/release-batch.md).
230
- - **Releases:** [v0.13.16](docs/release-v0.13.16.md) · [v0.13.15](docs/release-v0.13.15.md) · [v0.13.14](docs/release-v0.13.14.md) · [v0.13.13](docs/release-v0.13.13.md) · [v0.13.12](docs/release-v0.13.12.md) · [v0.13.11](docs/release-v0.13.11.md) · [v0.13.10](docs/release-v0.13.10.md) ·
230
+ - **Releases:** [v0.13.17](docs/release-v0.13.17.md) · [v0.13.16](docs/release-v0.13.16.md) · [v0.13.15](docs/release-v0.13.15.md) · [v0.13.14](docs/release-v0.13.14.md) · [v0.13.13](docs/release-v0.13.13.md) · [v0.13.12](docs/release-v0.13.12.md) · [v0.13.11](docs/release-v0.13.11.md) · [v0.13.10](docs/release-v0.13.10.md) ·
231
231
  [v0.13.9](docs/release-v0.13.9.md) · [All GitHub releases](https://github.com/zufall-upon/Sortie-dogs/releases).
232
232
 
233
233
  ## Community
@@ -3,6 +3,6 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export declare const V010_RUNTIME_ASSET_VERSION = "0.13.16-practical-continuity-v1";
6
+ export declare const V010_RUNTIME_ASSET_VERSION = "0.13.17-handoff-continuity-v1";
7
7
  export declare const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
8
8
  export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
@@ -3,5 +3,5 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export const V010_RUNTIME_ASSET_VERSION = "0.13.16-practical-continuity-v1";
6
+ export const V010_RUNTIME_ASSET_VERSION = "0.13.17-handoff-continuity-v1";
7
7
  export const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
@@ -224,7 +224,11 @@ export class OperatorMissionRuntime {
224
224
  continuity: { notes: state.continuity?.notes, sources, progress: state.progress,
225
225
  attempts: (state.attempts ?? []).map(({ runID, unitID, status, failure, resultClass, childSessionID }) => ({ run_id: runID, unit_id: unitID, status, failure, result_class: resultClass, session_id: childSessionID })),
226
226
  execution: state.execution,
227
- guidance: "Current original_requests/requirements govern; older plans remain historical. Continue from known decisions, environment, completed work and failures. Read snapshot JSON entries by message/call IDs only for a relevant gap. Recheck mutable facts when needed or conflicting; do not repeat unchanged setup/investigation merely because ownership changed. Historical results are not fresh formal validation or new authority." } };
227
+ source_authors: state.sourceAuthors ?? [],
228
+ corrections: (state.corrections ?? []).map(({ initialPrompt: _prompt, inlineReview: _inline, ...correction }) => correction),
229
+ retained_review: state.review ? { run_id: state.review.runID, verdict: state.review.verdict,
230
+ mode: state.review.mode, author: state.review.child, result: state.review.result, self_recheck: state.review.selfRecheck } : null,
231
+ guidance: "Current original_requests/requirements govern; older plans remain historical. Continue from known decisions, environment, completed work, retained findings and failures. A new plan or author does not resolve findings or turn self-recheck into independent approval. Read snapshot JSON entries by message/call IDs only for a relevant gap. Recheck mutable facts when needed or conflicting; do not repeat unchanged setup/investigation/checks merely because ownership changed. Successful evidence remains with its actual author/call and input bindings; historical results are not new authority." } };
228
232
  }
229
233
  async load(file) {
230
234
  try {
@@ -804,14 +808,14 @@ export function missionPacket(mission, run) {
804
808
  ? "Operator: use acceptance_summary to compare the submitted candidate with the verbatim original requests and actual evidence, inspect concrete gaps only, then complete_mission if satisfied. Do not routinely search archives or reread all source. Historical PASS is not current PASS. Report remaining evidence gaps; they are not a review PASS."
805
809
  : run?.phase === "awaiting-acceptance" && mission.corrections?.some(item => item.runID === run.runID && item.status === "ready") && !reviewAccepted
806
810
  ? mission.corrections.find(item => item.runID === run.runID)?.selfRecheck?.unresolvedFindings.length
807
- ? "Known Major/Medium findings remain after self-recheck. Call repair_review for the SAME original correction owner; do not dispatch another Reviewer or accept unresolved Medium. Retain original requirements and cumulative spend."
811
+ ? "Known Major/Medium findings remain after self-recheck. Correct them with repair_review (reuse the author by default, or select an available Reviewer), or plan_units for Worker/direct execution. Retain findings, original requirements and cumulative spend; unresolved Medium is not acceptance."
808
812
  : mission.corrections.find(item => item.runID === run.runID)?.selfRecheck?.residualMajor
809
813
  ? "A concrete reachable Major risk remains after native author self-recheck. Call review_mission for a DIFFERENT Reviewer of the correction, retained findings and relevant impact; dispatch the exact returned Task if not already active. No fresh Worker or unchanged validation is required."
810
- : "Correction ready; call review_mission for the SAME native author to explicitly self-recheck the retained findings, relevant impact and original requirements. Only a concrete reachable Major risk remaining after self-recheck requires a DIFFERENT Reviewer. Known Major/Medium defects still require correction. Do not restart unchanged checks or investigation; CORRECTION_READY alone is not acceptance."
814
+ : "Correction ready; reuse the native author for explicit self-recheck or select an independent Reviewer with review_mission when useful. Remaining authorized implementation may proceed with plan_units without acceptance. Known Major/Medium defects still require correction. Do not restart unchanged checks or investigation; CORRECTION_READY alone is not acceptance."
811
815
  : mission.kind === "operation" && operationStatus === "running"
812
816
  ? "The declared operation is already running. Inspect its native shell/progress; do not start another Worker or run. Wait for a terminal result, or report the existing run as blocked if its completion cannot be observed."
813
817
  : run?.phase === "awaiting-decision" && mission.corrections?.some(item => item.runID === run.runID && item.status === "failed")
814
- ? "Correction validation or native execution failed. Call repair_review for a new admission in the SAME original Reviewer's native session; fix its own regression, rerun inherited checks and retain the commit/clean boundary and cumulative spend. Do not use ordinary retry, Rescue or a fresh Worker replan. Failure is never acceptance."
818
+ ? "Correction validation or native execution failed. Reuse its context with repair_review, select another available Reviewer, or plan_units for Worker/direct correction. Retain findings, affected checks, requested delivery and cumulative spend. Failure is never acceptance."
815
819
  : run?.phase === "awaiting-decision" ? (run.units.some(unit => unit.dispatchDenial)
816
820
  ? "Coordinator: inspect units[].dispatch_denial before changing the plan. Correct only its diagnosed cause; do not repeat an unchanged refused Task or replan for a host-state mismatch. Report an unresolved runtime mismatch with the loaded runtime identity; preserve requirements and cumulative spend."
817
821
  : run.units.some(unit => unit.status === "failed" && unit.resultClass === "acceptance" && unit.failure?.outcome === "fail" && !unit.normalRemediationUsed)
@@ -822,8 +826,8 @@ export function missionPacket(mission, run) {
822
826
  : run?.phase === "awaiting-acceptance" && !operationComplete
823
827
  ? `Requested operation is ${operationStatus}. Continue the actual operation or report its blocker; auxiliary checks and review disposition cannot complete it.`
824
828
  : run?.phase === "awaiting-acceptance" ? (reviewAccepted
825
- ? "Coordinator: review permits submission. Retain advisory review notes; do not repeat passed validation or review for evidence formatting. Operator performs final acceptance against the original requirements."
826
- : "Coordinator: correct known Major/Medium findings, then obtain explicit native author self-recheck (different Reviewer only for residual concrete Major risk), or initial independent review, then submit_mission. Operator compares all requirements with source/evidence before complete_mission.")
829
+ ? "Coordinator: review permits submission, not completion of remaining implementation. Continue authorized remaining work with plan_units, or submit when the original requirements are met. Retain review notes; do not repeat passed validation or review for evidence formatting. Operator final acceptance remains separate."
830
+ : "Coordinator: correct known Major/Medium findings, then obtain native author self-recheck or useful independent review (required for concrete residual Major risk). Remaining authorized implementation can proceed without submission/acceptance. Operator compares all requirements with source/evidence before complete_mission.")
827
831
  : "Coordinator: continue the next useful unit within original requirements. Return only a completion candidate, user-only decision, or scope/budget extension." };
828
832
  }
829
833
  /** Models forward a short capability, never recopy the host's evidence hashes and source packet. */
@@ -280,6 +280,8 @@ export declare class OperatorRuntime {
280
280
  private file;
281
281
  /** Exact persisted source for read-only status projections. */
282
282
  statePath(root: string): string;
283
+ /** Stable detail source for a settled run replaced by Mission replanning. */
284
+ replanArchivePath(state: OperatorState): string;
283
285
  /** Read-only lineage projection. Never imports archived evidence into current acceptance. */
284
286
  acceptanceHistory(state: OperatorState, missionRunIDs: readonly string[]): Promise<{
285
287
  status: "available" | "unavailable";
@@ -276,6 +276,10 @@ export class OperatorRuntime {
276
276
  }
277
277
  /** Exact persisted source for read-only status projections. */
278
278
  statePath(root) { return this.file(root); }
279
+ /** Stable detail source for a settled run replaced by Mission replanning. */
280
+ replanArchivePath(state) {
281
+ return `${this.file(state.rootSessionID)}.${state.runID}.${state.sequence}.archive`;
282
+ }
279
283
  /** Read-only lineage projection. Never imports archived evidence into current acceptance. */
280
284
  async acceptanceHistory(state, missionRunIDs) {
281
285
  const runs = [];
@@ -878,14 +882,12 @@ export class OperatorRuntime {
878
882
  if (!previous || previous.runID !== mission.replaceRunID || ["cancelled", "completed"].includes(previous.phase)) {
879
883
  throw new Error("mission-replan-run-mismatch");
880
884
  }
881
- if (previous.units.some(unit => unit.reviewerCorrection) && !mission.reviewerCorrection) {
882
- throw new Error("mission-review-correction-owner-continuation-required");
883
- }
884
- if (previous.units.some(unit => unit.status === "running") || (previous.gitLifecycle !== null && !mission.reviewerCorrection))
885
+ // Completed correction/Git metadata is history, not a live writer lease.
886
+ // The plugin reconciles actual native executions before replacing this run.
887
+ if (previous.units.some(unit => unit.status === "running"))
885
888
  throw new Error("mission-replan-worker-still-active");
886
- const failedContinuation = previous.phase === "awaiting-decision" && previous.units.every(unit => unit.status === "failed" && unit.reviewerCorrection?.author === mission.reviewerCorrection?.author);
889
+ const failedContinuation = previous.phase === "awaiting-decision" && previous.units.every(unit => unit.status === "failed" && unit.reviewerCorrection);
887
890
  if (mission.reviewerCorrection && ((!failedContinuation && (previous.phase !== "awaiting-acceptance" || previous.units.some(unit => unit.status !== "succeeded"))) ||
888
- previous.units.some(unit => unit.reviewerCorrection && unit.reviewerCorrection.author !== mission.reviewerCorrection.author) ||
889
891
  plan.units.length !== 1 || plan.acceptance.length !== previous.acceptance.length ||
890
892
  previous.acceptance.some((text, i) => text !== plan.acceptance[i]) ||
891
893
  JSON.stringify(plan.acceptance_proof) !== JSON.stringify(previous.acceptanceProof) ||
@@ -1166,10 +1168,11 @@ export class OperatorRuntime {
1166
1168
  await handle.close();
1167
1169
  }
1168
1170
  }
1169
- if ((superseding || replanning) && previous) {
1171
+ if ((superseding || replanning || previous?.phase === "completed") && previous) {
1170
1172
  // A crash before the state switch may leave a checkpoint while the old run later advances.
1171
1173
  // Sequence-qualified replan snapshots keep that history without blocking a subsequent correction.
1172
- const archive = `${this.file(root)}.${previous.runID}${replanning ? `.${previous.sequence}` : ""}.archive`;
1174
+ const archive = replanning || previous.phase === "completed" ? this.replanArchivePath(previous)
1175
+ : `${this.file(root)}.${previous.runID}.archive`;
1173
1176
  try {
1174
1177
  const handle = await open(archive, "wx", 0o600);
1175
1178
  created.push(archive);
@@ -1323,8 +1326,6 @@ export class OperatorRuntime {
1323
1326
  return args.task_id === expected.task_id && args.subagent_type === expected.subagent_type &&
1324
1327
  args.description === expected.description && args.prompt === expected.prompt;
1325
1328
  }
1326
- if (unit.reviewerCorrection && (args.model !== undefined || args.variant !== undefined))
1327
- return false;
1328
1329
  if (unit.reviewerCorrection ? args.task_id !== unit.reviewerCorrection.author : args.task_id !== undefined && args.task_id !== "")
1329
1330
  return false;
1330
1331
  if (args.subagent_type !== unit.task.subagent_type || args.description !== unit.task.description)
@@ -70,6 +70,10 @@ export declare function normalizeCommand(command: string): string;
70
70
  export declare function canonicalDeclaredValidationSequence(command: string, declared: ReadonlySet<string>): string | undefined;
71
71
  /** Preserve ordered members (including repeats) without splitting quoted text or substring matching. */
72
72
  export declare function canonicalDeclaredValidationMembers(command: string, declared: ReadonlySet<string> | readonly string[], occurrence?: number): string[] | undefined;
73
+ /** Observation only, never shell authority. A successful native && chain can prove
74
+ * its exact declared tail after diagnostics/empty-directory cleanup. The caller
75
+ * must retain the real exit and the snapshot captured BEFORE the whole command. */
76
+ export declare function canonicalObservedValidationMembers(command: string, declared: ReadonlySet<string> | readonly string[], occurrence?: number): string[] | undefined;
73
77
  /** Unbound sessions may invoke only tools whose complete input is known to be read-only. */
74
78
  export declare function isKnownReadOnlyTool(tool: string, args: unknown, additionalReadOnlyTools?: ReadonlySet<string>): boolean;
75
79
  /** Canonical scopes let sibling worker bindings reject equal or ancestor ownership. */
@@ -997,6 +997,40 @@ export function canonicalDeclaredValidationMembers(command, declared, occurrence
997
997
  };
998
998
  return sequence(0);
999
999
  }
1000
+ /** Observation only, never shell authority. A successful native && chain can prove
1001
+ * its exact declared tail after diagnostics/empty-directory cleanup. The caller
1002
+ * must retain the real exit and the snapshot captured BEFORE the whole command. */
1003
+ export function canonicalObservedValidationMembers(command, declared, occurrence = 0) {
1004
+ const exact = canonicalDeclaredValidationMembers(command, declared, occurrence);
1005
+ if (exact)
1006
+ return exact;
1007
+ const syntax = scanShellSyntax(command, "posix");
1008
+ // Do not infer a tail through comments, expansions, redirection, subshells or
1009
+ // shell-state changes. Unsupported observations leave existing authority alone.
1010
+ if (syntax.unsafeExpansion || syntax.activeBrace || /[\\#$()<>`]/u.test(syntax.masked))
1011
+ return undefined;
1012
+ const segments = shellSegments(command, "posix").map(segment => segment.trim());
1013
+ if (segments.length < 2 || segments.some(segment => !segment) ||
1014
+ normalizeCommand(command) !== segments.map(normalizeCommand).join(" && "))
1015
+ return undefined;
1016
+ const identities = [...new Set(declared)];
1017
+ for (let start = 1; start < segments.length; start++) {
1018
+ const prefix = segments[start - 1];
1019
+ // Never discard an ordered declared occurrence to find a later matching tail.
1020
+ if (declaredCommandMatch(prefix, identities) !== undefined)
1021
+ return undefined;
1022
+ if (prefix.includes("`"))
1023
+ return undefined;
1024
+ const extracted = shellPaths(prefix, false);
1025
+ const cleanup = /^rmdir(?:\s|$)/u.test(prefix);
1026
+ if (extracted.ambiguous || extracted.remoteMutation || extracted.gitMutation || extracted.applies && !cleanup)
1027
+ return undefined;
1028
+ const members = canonicalDeclaredValidationMembers(segments.slice(start).join(" && "), declared, occurrence);
1029
+ if (members)
1030
+ return members;
1031
+ }
1032
+ return undefined;
1033
+ }
1000
1034
  /** Unbound sessions may invoke only tools whose complete input is known to be read-only. */
1001
1035
  export function isKnownReadOnlyTool(tool, args, additionalReadOnlyTools = new Set()) {
1002
1036
  const name = tool.toLowerCase();
@@ -73,6 +73,7 @@ export declare function missionReviewValidation(run: OperatorState, statePath: s
73
73
  export declare function observedMissionValidation(validation: readonly string[], childSessionID: string | null, history: readonly Record<string, unknown>[], notBefore?: number, directory?: string, directories?: Readonly<Record<string, string>>): {
74
74
  attempts: readonly {
75
75
  command: string;
76
+ native_command?: string;
76
77
  directory?: string;
77
78
  observed_members?: readonly string[];
78
79
  exit_code: number | null;
@@ -11,7 +11,7 @@ import { canonicalAgent } from "../core/runtime-profile.js";
11
11
  import { taskChildSessionID } from "./task-result-repair.js";
12
12
  import { normalizeManifestScope } from "../core/path.js";
13
13
  import { declaredArtifacts } from "./declared-artifacts.js";
14
- import { canonicalDeclaredValidationMembers, normalizeCommand } from "./gate.js";
14
+ import { canonicalDeclaredValidationMembers, canonicalObservedValidationMembers, normalizeCommand } from "./gate.js";
15
15
  import { currentSnapshotProtection, isRuntimeControlPath, refreshProtectedSnapshot, snapshotControlExclusion, snapshotScratchExclusion, validationInputSnapshot } from "./protected-snapshot.js";
16
16
  const exec = promisify(execFile);
17
17
  const notRepository = (error) => /fatal: not a git repository \(or (?:any of the parent directories|any parent up to mount point [^\r\n)]+)\)(?:: \.git)?/u
@@ -87,7 +87,8 @@ export function observedMissionValidation(validation, childSessionID, history, n
87
87
  });
88
88
  let occurrence = 0;
89
89
  const attempts = rawAttempts.sort((a, b) => (a.started_ms ?? 0) - (b.started_ms ?? 0) || (a.completed_ms ?? 0) - (b.completed_ms ?? 0)).flatMap(attempt => {
90
- const members = canonicalDeclaredValidationMembers(attempt.command, declared, occurrence);
90
+ const exact = canonicalDeclaredValidationMembers(attempt.command, declared, occurrence);
91
+ const members = exact ?? (attempt.exit_code === 0 ? canonicalObservedValidationMembers(attempt.command, declared, occurrence) : undefined);
91
92
  if (!members || !validationDirectoryMatches(members, attempt.directory, directory, directories))
92
93
  return [];
93
94
  if (attempt.exit_code !== 0)
@@ -100,7 +101,7 @@ export function observedMissionValidation(validation, childSessionID, history, n
100
101
  occurrence = (occurrence + 1) % declared.length;
101
102
  }
102
103
  const { directory: actual, ...visible } = attempt;
103
- return [{ ...visible, command: members.join(" && "), ...(directory && resolve(directory, actual ?? ".") !== resolve(directory)
104
+ return [{ ...visible, command: members.join(" && "), ...(!exact ? { native_command: attempt.command } : {}), ...(directory && resolve(directory, actual ?? ".") !== resolve(directory)
104
105
  ? { directory: resolve(directory, actual ?? ".") } : {}), ...(members.length > 1 ? { observed_members: members } : {}) }];
105
106
  });
106
107
  // Retain early attempts and the latest checks if a Worker retried many times.
@@ -174,11 +175,14 @@ export function reviewerCorrectionValidation(validation, child, history, notBefo
174
175
  // report/cache write is not itself a source change. Unbound history stays conservative.
175
176
  if (!checks && attempt.started < editedAt)
176
177
  continue;
177
- const recorded = registeredAt && checks?.find(check => check.callID === attempt.callID && check.childSessionID === child);
178
- const commands = recorded
179
- ? canonicalDeclaredValidationMembers(attempt.command, recorded.command)
180
- : canonicalDeclaredValidationMembers(attempt.command, declared, occurrences[partial.length % required.length] ?? 0);
181
- if (!commands || recorded && JSON.stringify(commands) !== JSON.stringify(recorded.command))
178
+ const recorded = checks?.find(check => check.callID === attempt.callID && check.childSessionID === child);
179
+ const recipes = registeredAt && recorded ? recorded.command : declared;
180
+ const occurrence = registeredAt && recorded ? 0 : occurrences[partial.length % required.length] ?? 0;
181
+ const commands = canonicalDeclaredValidationMembers(attempt.command, recipes, occurrence)
182
+ // Mixed native input needs its original live binding, not a snapshot added
183
+ // to retained logs. Nonzero/unknown chain exits cannot prove the tail ran.
184
+ ?? (recorded && attempt.exit === 0 ? canonicalObservedValidationMembers(attempt.command, recipes, occurrence) : undefined);
185
+ if (!commands || registeredAt && recorded && JSON.stringify(commands) !== JSON.stringify(recorded.command))
182
186
  continue;
183
187
  const members = commands.flatMap((command, member) => required.includes(command) &&
184
188
  validationDirectoryMatches([command], attempt.directory, directory, directories) &&
@@ -277,9 +281,10 @@ export async function observedMissionValidationSummary(validation, childSessionI
277
281
  const observed = observedMissionValidation(validation, childSessionID, data.filter(record), notBefore, directory, directories);
278
282
  const grouped = new Map();
279
283
  for (const attempt of observed.attempts) {
280
- const key = JSON.stringify([attempt.command, attempt.directory, attempt.exit_code]);
284
+ const key = JSON.stringify([attempt.command, attempt.native_command, attempt.directory, attempt.exit_code]);
281
285
  const previous = grouped.get(key);
282
- grouped.set(key, { command: attempt.command, ...(attempt.directory ? { directory: attempt.directory } : {}), exit_code: attempt.exit_code,
286
+ grouped.set(key, { command: attempt.command, ...(attempt.native_command ? { native_command: attempt.native_command } : {}),
287
+ ...(attempt.directory ? { directory: attempt.directory } : {}), exit_code: attempt.exit_code,
283
288
  observed_attempts: (previous?.observed_attempts ?? 0) + 1,
284
289
  latest_started_ms: attempt.started_ms, latest_completed_ms: attempt.completed_ms });
285
290
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Native V2 truncates at whole-line boundaries. A large one-line full packet can
3
+ * therefore hide every field. Put existing actionable facts before bulky proof
4
+ * recipes, with one top-level field per line; keep the complete JSON unchanged.
5
+ */
6
+ export declare function serializeOperatorStatus(packet: Record<string, unknown>): string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Native V2 truncates at whole-line boundaries. A large one-line full packet can
3
+ * therefore hide every field. Put existing actionable facts before bulky proof
4
+ * recipes, with one top-level field per line; keep the complete JSON unchanged.
5
+ */
6
+ export function serializeOperatorStatus(packet) {
7
+ const keys = new Set([
8
+ "profile", "view", "mission_id", "run_id", "phase", "status", "decision", "coordinator_session_id",
9
+ "budget", "accounting_scope", "attempts", "next_action", "execution_summary", "runtime", "completion",
10
+ ...Object.keys(packet),
11
+ ]);
12
+ const lines = [...keys].flatMap(key => {
13
+ if (!Object.hasOwn(packet, key))
14
+ return [];
15
+ const value = JSON.stringify(packet[key]);
16
+ return value === undefined ? [] : [` ${JSON.stringify(key)}: ${value}`];
17
+ });
18
+ return `{\n${lines.join(",\n")}\n}`;
19
+ }
@@ -11,10 +11,11 @@ import { BUILT_IN_MODEL_CATALOG } from "./model-routing.js";
11
11
  import { goalFingerprint } from "../core/goal-bound.js";
12
12
  import { decoratePreviewHeadings, returnReportPanel } from "./receipt-presentation.js";
13
13
  import { sanitizeTerminalReport, terminalRunOutcome } from "./run-metrics.js";
14
- import { normalizeCommand, canonicalDeclaredValidationMembers, extractWritePaths } from "./gate.js";
14
+ import { normalizeCommand, canonicalObservedValidationMembers, extractWritePaths } from "./gate.js";
15
15
  import { normalizeExecutionScope, normalizeManifestScope, normalizeRelativePath } from "../core/path.js";
16
16
  import { OperatorMissionRuntime, missionAcceptanceSummary, missionPacket, missionPlan, missionReviewAccepted, missionReviewIndependent, missionReviewScope, missionReviewTask, missionCommandOutcome, missionConversationContext, missionExecutionComplete, missionExecutionStatus, missionOperationSummary, missionValidationCommand, missionReviewTraces, missionReviewVerdict, missionSelfRecheckReport } from "../core/operator-mission.js";
17
17
  import { publishMissionProgress } from "./mission-progress.js";
18
+ import { serializeOperatorStatus } from "./operator-status-output.js";
18
19
  import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionDeliveryObservation, missionReviewBaseline, missionReviewSource, observedMissionValidation, observedMissionValidationSummary, missionReviewValidation, reviewerCorrectionValidation, reviewerCorrectionValidationFresh, validationDirectoryMatches } from "./mission-review.js";
19
20
  import { missionLocations, missionLocationPacket } from "./mission-location.js";
20
21
  import { prepareValidationScratch } from "./validation-scratch.js";
@@ -608,7 +609,7 @@ export function createProfiledPlugin(profile, assetVersion) {
608
609
  const unit = correction.unit;
609
610
  const directories = unit.directExecution?.validationCwd ?? unit.unit.validation_cwd;
610
611
  const progress = reviewerCorrectionValidation(unit.unit.validation, child, await messages(child), Date.parse(unit.reviewerCorrection?.admittedAt ?? unit.directExecution?.startedAt ?? correction.run.createdAt), unit.reviewerCorrection?.checks ?? unit.directExecution?.checks ?? [], input.directory, directories, unit.reviewerCorrection?.validationRegisteredAt ?? unit.directExecution?.validationRegisteredAt);
611
- const members = canonicalDeclaredValidationMembers(command, unit.unit.validation, progress.nextOccurrence ?? 0);
612
+ const members = canonicalObservedValidationMembers(command, unit.unit.validation, progress.nextOccurrence ?? 0);
612
613
  return members && validationDirectoryMatches(members, directory, input.directory, directories) ? members : undefined;
613
614
  },
614
615
  recordReviewerCorrectionCheck: (root, taskID, check) => operators.recordReviewerCorrectionCheck(root, taskID, check),
@@ -1158,12 +1159,12 @@ export function createProfiledPlugin(profile, assetVersion) {
1158
1159
  run?.phase === "awaiting-acceptance" &&
1159
1160
  (!mission.review || missionReviewAccepted(mission.review))) {
1160
1161
  return { ...packet, next_action: mission.review && missionReviewAccepted(mission.review)
1161
- ? "Fast-lane: compare all original requirements with actual evidence and review disposition, then call complete_mission. Report any remaining evidence gaps; they are not PASS."
1162
+ ? "Fast-lane: continue authorized remaining implementation with plan_units, or compare all original requirements with actual evidence before complete_mission when acceptance is authorized. A passed correction does not complete remaining development. Report evidence gaps; they are not PASS."
1162
1163
  : "Fast-lane: assess actual risk and call review_mission with real risk_tags. Implementation notes are optional. Dispatch its Reviewer Task if required; then compare all requirements before complete_mission." };
1163
1164
  }
1164
1165
  if (mission.coordinator === null && mission.runID === run?.runID && !mission.dispatchOpen &&
1165
1166
  run?.phase === "awaiting-decision" && mission.corrections?.some(item => item.runID === run.runID && item.status === "failed")) {
1166
- return { ...packet, next_action: `Fast-lane: correction failed; call ${repairReview} for a new scoped Task in the SAME original Reviewer's native session. Fix its own regression, run inherited validation and retain the commit/clean boundary. No ordinary Worker retry, fresh Worker replan or duplicate settlement; cumulative spend and failed history remain.` };
1167
+ return { ...packet, next_action: `Fast-lane: correction failed. Reuse its author via ${repairReview}, choose another available Reviewer, or ${planUnits} for Worker/direct correction. Retain findings, affected checks, requested delivery and cumulative spend; no duplicate settlement or unchanged reinvestigation.` };
1167
1168
  }
1168
1169
  if (mission.coordinator === null && mission.runID === run?.runID && !mission.dispatchOpen &&
1169
1170
  run?.phase === "awaiting-decision" && run.units.length === 1 &&
@@ -1179,7 +1180,7 @@ export function createProfiledPlugin(profile, assetVersion) {
1179
1180
  run?.phase === "awaiting-acceptance" && mission.review?.verdict === "findings") {
1180
1181
  const checked = mission.corrections?.find(item => item.runID === run.runID)?.selfRecheck;
1181
1182
  if (mission.corrections?.some(item => item.runID === run.runID && item.status === "ready") && !checked) {
1182
- return { ...packet, next_action: `Fast-lane: correction ready; call ${reviewMission} for explicit read-only self-recheck in the SAME native author. CORRECTION_READY is not acceptance; only concrete reachable residual Major risk then requires a different Reviewer.` };
1183
+ return { ...packet, next_action: `Fast-lane: correction ready. Reuse the author's explicit self-recheck or select an independent Reviewer with ${reviewMission}. Authorized remaining implementation can proceed with ${planUnits}; CORRECTION_READY is not acceptance.` };
1183
1184
  }
1184
1185
  if (checked?.residualMajor && !checked.unresolvedFindings.length && mission.review.mode === "self-recheck") {
1185
1186
  return { ...packet, next_action: `Fast-lane: native author self-recheck retained concrete reachable Major risk. ` +
@@ -1187,9 +1188,9 @@ export function createProfiledPlugin(profile, assetVersion) {
1187
1188
  `Do not start a fresh Worker or repeat unchanged validation.` };
1188
1189
  }
1189
1190
  return { ...packet, next_action: `Fast-lane: known Major/Medium findings require correction. Call ${repairReview} ` +
1190
- `for the SAME original Reviewer's scoped correction Task, then ${reviewMission} for its explicit native self-recheck. ` +
1191
+ `to reuse the author's scoped correction Task or select another available Reviewer; ${planUnits} also permits Worker/direct correction. Then obtain native self-recheck or useful independent Review. ` +
1191
1192
  `Only a concrete reachable Major risk remaining after self-recheck requires a different Reviewer. ` +
1192
- `No fresh Worker, routine diff transcription or unchanged reinvestigation is required.` };
1193
+ `No routine diff transcription, handoff approval or unchanged reinvestigation is required.` };
1193
1194
  }
1194
1195
  if (!mission.dispatchOpen && (["open", "running"].includes(mission.phase) ||
1195
1196
  (mission.phase === "submitted" && mission.submission?.status !== "ready"))) {
@@ -1577,6 +1578,7 @@ export function createProfiledPlugin(profile, assetVersion) {
1577
1578
  current_unit: current === undefined ? null : { id: current.unit.id, title: current.unit.title, status: current.status },
1578
1579
  completed_units: units.filter(unit => unit.status === "succeeded").length, total_units: units.length,
1579
1580
  budget_remaining_units: budget?.remaining_units ?? null,
1581
+ budget: packet.budget, accounting_scope: packet.accounting_scope, attempts: packet.attempts,
1580
1582
  next_action: typeof packet.next_action === "string" ? packet.next_action : null,
1581
1583
  ...(record(packet.acceptance_summary) ? { observations: {
1582
1584
  operation: packet.acceptance_summary.operation,
@@ -1599,7 +1601,7 @@ export function createProfiledPlugin(profile, assetVersion) {
1599
1601
  const { proposal: _packet, ...identity } = proposals.packet(state);
1600
1602
  return identity;
1601
1603
  }
1602
- tools[status] = { description: "Read the durable root-owned operator outcome and host budget counters (max_units, consumed_units, reserved_units, remaining_units) without claiming acceptance or retrying work. Default Mission status preserves measured evidence with persisted detail references, not duplicated protected path arrays; view=full retains the full diagnostic packet. view=progress returns a compact read-only projection with existing formal checks, native observations/terminals and recorded delivery, never inferred clean or success. Existing Mission dispatch reconciliation is unchanged. With no Mission or execution run, proposal/draft status remains unchanged. An investigating proposal returns its exact short Task reference only before a Task has been admitted; an existing admission never yields a redispatch Task.",
1604
+ tools[status] = { description: "Read the durable root-owned operator outcome and host budget counters (max_units, consumed_units, reserved_units, remaining_units) without claiming acceptance or retrying work. Mission status places existing model observations, scoped cost and next action before bulky evidence, on separate lines so native truncation retains useful facts. Default preserves measured evidence with persisted detail references, not duplicated protected path arrays; view=full retains the full diagnostic packet. view=progress returns a compact read-only projection with the same budget/model observations, existing formal checks, native observations/terminals and recorded delivery, never inferred clean or success. Existing Mission dispatch reconciliation is unchanged. With no Mission or execution run, proposal/draft status remains unchanged. An investigating proposal returns its exact short Task reference only before a Task has been admitted; an existing admission never yields a redispatch Task.",
1603
1605
  args: { view: optionalStringSchema, confirmed_conditions: conditionsSchema }, execute: async (args, context) => {
1604
1606
  const view = args.view;
1605
1607
  const root = await rootFor(context.sessionID);
@@ -1630,8 +1632,8 @@ export function createProfiledPlugin(profile, assetVersion) {
1630
1632
  delete packet.coordinator_dispatch;
1631
1633
  }
1632
1634
  if (view === "progress")
1633
- return JSON.stringify(missionProgress(mission, run, budget, packet));
1634
- return JSON.stringify(view === "full" ? packet : compactMissionEvidence(packet, run, root));
1635
+ return serializeOperatorStatus(missionProgress(mission, run, budget, packet));
1636
+ return serializeOperatorStatus(view === "full" ? packet : compactMissionEvidence(packet, run, root));
1635
1637
  }
1636
1638
  if (root && context.sessionID !== root) {
1637
1639
  const owned = await operators.read(root);
@@ -2117,7 +2119,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2117
2119
  executor_session_id: actor, validation: direct.unit.unit.validation,
2118
2120
  next_action: `Implement/correct and run the declared checks here, in this same session. Call ${finishDirectUnit} after the requested delivery boundary. No Worker or handoff read is needed. Retain the original requirements; independent Review and Operator acceptance still follow.` });
2119
2121
  }
2120
- tools[startDirectUnit] = { description: "Execute the next already-planned unit in this SAME Coordinator/Operator session. Native edits and declared checks become formal evidence without a Worker, handoff read or approval. Use after plan_units or a settled-unit retry; never replace an active Worker or the Reviewer correction owner.",
2122
+ tools[startDirectUnit] = { description: "Execute the next already-planned ordinary unit in this Coordinator/Operator session, including a correction planned with executor=self. Native edits and declared checks become formal evidence without a Worker, handoff read or approval. Never replace an active writer; completed correction history does not restrict this choice.",
2121
2123
  args: {}, execute: async (_args, context) => {
2122
2124
  const { root } = await missionAuthority(context.sessionID);
2123
2125
  return serializeDispatchTransition(root, () => startDirect(root, context.sessionID));
@@ -2125,17 +2127,21 @@ export function createProfiledPlugin(profile, assetVersion) {
2125
2127
  tools[finishDirectUnit] = { description: "Finish this controller's or admitted Reviewer's direct unit using actual ordered native checks bound to current source. Retains this session for review/coordination or explicit author self-recheck; this is not a native Task terminal, independent Review or Mission acceptance. Missing/failed/stale checks continue here without a Worker.",
2126
2128
  args: {}, execute: async (_args, context) => {
2127
2129
  const inline = await directExecution(context.sessionID);
2130
+ const correction = await reviewerCorrection(context.sessionID);
2128
2131
  const root = inline?.unit.reviewerCorrection
2129
- ? inline.root : (await missionAuthority(context.sessionID)).root;
2132
+ ? inline.root : correction?.root ?? (await missionAuthority(context.sessionID)).root;
2130
2133
  return serializeDispatchTransition(root, async () => {
2131
- const direct = await activateDirect(context.sessionID);
2134
+ const direct = await activateDirect(context.sessionID) ?? correction;
2132
2135
  if (!direct)
2133
2136
  throw new Error("mission-direct-unit-not-running");
2134
- const checks = await runtimeBridge.reviewerCorrectionValidation(root, direct.unit.callID, context.sessionID, Date.parse(direct.unit.directExecution.startedAt));
2137
+ const checks = await runtimeBridge.reviewerCorrectionValidation(root, direct.unit.callID, context.sessionID, Date.parse(direct.unit.directExecution?.startedAt ?? direct.unit.reviewerCorrection.admittedAt ?? direct.run.createdAt));
2135
2138
  if (!checks?.ready)
2136
2139
  return JSON.stringify({ status: "direct-unit-awaits-validation", ...checks,
2137
2140
  next_action: "Correct the actual failure or run the missing/affected checks in this same session, then finish_direct_unit. Preserve successful current evidence; do not dispatch a validation-only Worker." });
2138
- await control.finishDirectUnit(root, context.sessionID, (direct.unit.reviewerCorrection?.checks ?? direct.unit.directExecution.checks).filter(check => checks.matched?.some(item => item.callID === check.callID)));
2141
+ // A resumed correction Task settles at its native terminal, not at this
2142
+ // advisory completion call. Inline/direct execution uses its existing finish path.
2143
+ if (direct.unit.directExecution)
2144
+ await control.finishDirectUnit(root, context.sessionID, (direct.unit.reviewerCorrection?.checks ?? direct.unit.directExecution.checks).filter(check => checks.matched?.some(item => item.callID === check.callID)));
2139
2145
  if (direct.unit.reviewerCorrection)
2140
2146
  return JSON.stringify({ status: "correction-validated",
2141
2147
  execution: "same-native-task", validation: direct.unit.unit.validation,
@@ -2423,7 +2429,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2423
2429
  function declaredMissionExecution(mission, execution) {
2424
2430
  const operation = mission.execution;
2425
2431
  // Optional empty operation fields must not turn a normal edit into an operation.
2426
- if (execution === undefined || mission.kind === "implementation" && record(execution) &&
2432
+ if (execution === undefined || mission.kind === "implementation" && (execution === "" || execution === null) || mission.kind === "implementation" && record(execution) &&
2427
2433
  Array.isArray(execution.commands) && execution.commands.length === 0)
2428
2434
  return operation;
2429
2435
  if (!record(execution) || typeof execution.directory !== "string" || !execution.directory.trim() ||
@@ -2437,10 +2443,6 @@ export function createProfiledPlugin(profile, assetVersion) {
2437
2443
  await control.currentBudget(root);
2438
2444
  mission = await missions.required(root);
2439
2445
  let previous = await operators.read(root);
2440
- if (previous && mission.runID === previous.runID && previous.units.some(unit => unit.reviewerCorrection)) {
2441
- return JSON.stringify({ ...missionPacket(mission, previous), status: "correction-owner-continuation-required",
2442
- next_action: `Call ${repairReview} to continue the SAME original Reviewer after a terminal failed correction or newer findings. Do not replace the correction owner with a fresh Worker through plan_units; retain requirements, checks and cumulative spend.` });
2443
- }
2444
2446
  if (sourceReconciliationRequired(mission, previous))
2445
2447
  return JSON.stringify({ ...missionDispatchPacket(mission, previous),
2446
2448
  next_action: `Coordinator: do not repeat plan_units. Call ${submitMission} with status=blocked and report the saved ` +
@@ -2450,8 +2452,8 @@ export function createProfiledPlugin(profile, assetVersion) {
2450
2452
  const operation = declaredMissionExecution(mission, execution);
2451
2453
  // Models can serialize an optional operation field as an empty object for a normal edit.
2452
2454
  // Do not turn an implementation mission into an operation (or block its first Worker).
2453
- const emptyImplementationExecution = mission.kind === "implementation" && record(execution) &&
2454
- Array.isArray(execution.commands) && execution.commands.length === 0;
2455
+ const emptyImplementationExecution = mission.kind === "implementation" && (execution === "" || execution === null || record(execution) &&
2456
+ Array.isArray(execution.commands) && execution.commands.length === 0);
2455
2457
  const plan = missionPlan(mission, raw, input.directory);
2456
2458
  assertMissionWritePaths(mission, plan.units.flatMap(unit => unit.write));
2457
2459
  if (actor === root && (mission.coordinator !== null || plan.units.length !== 1))
@@ -2590,7 +2592,10 @@ export function createProfiledPlugin(profile, assetVersion) {
2590
2592
  const histories = await Promise.all([...new Set([root, actor])].map(async (id) => ({ id, messages: await missionHistory(id) })));
2591
2593
  for (const history of histories)
2592
2594
  mission = await missions.contextSnapshot(mission, history.id, history.messages, history.id === actor ? handoffNotes : undefined);
2593
- const context = await missions.handoffContext(mission);
2595
+ const context = { ...await missions.handoffContext(mission),
2596
+ ...(previous ? { retained_validation: missionReviewValidation(previous, replanning || previous.phase === "completed"
2597
+ ? operators.replanArchivePath(previous) : cancelledPredecessor ? `${operators.statePath(root)}.${previous.runID}.archive` : operators.statePath(root)),
2598
+ prior_run_id: previous.runID } : {}) };
2594
2599
  const state = replanning && previous ? await operators.replanMission(root, previous.runID, plan, dispatcher, context)
2595
2600
  : await operators.prepareMission(root, plan, dispatcher, mission.supersededRunID, terminalChildren, mission.requirementsReplaced, context);
2596
2601
  await control.registerGoalDeclaration(root, state.units[0].task.prompt, true);
@@ -2619,7 +2624,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2619
2624
  } : {}) }
2620
2625
  : next);
2621
2626
  }
2622
- tools[planUnits] = { description: `Coordinator or single-unit Fast-lane Operator: declare useful work with estimated read/write scope and meaningful formal checks. executor=self starts implementation/correction and formal validation HERE without a Worker or handoff read; otherwise dispatch the returned configured Worker promptly. Keep investigation/edit/check/requested commit with the same author before independent Review, not a commit-only handoff. ${MISSION_GIT_SCOPE} Native scope reconciliation/expand_unit retain host permissions and explicit path prohibitions. Objective is the target or corrective delta: aim for 2000 characters; the full original request/public reproduction is supplied separately, not copied here. Oversized unit instructions are retained verbatim in handoff Mission context, not rejected for another planning round. Keep every requirement covered. Final validation proves the unit; empty or dummy checks do not qualify. Diagnostics need no registration. write: [] is read-only; dir/** and native absolute paths support actual outputs. reason replans settled work, or ends your own quiescent direct unit without acceptance to correct its declaration, in the same requirements/budget; same-scope failed validation uses retry_mission_unit. Reviewer FINDINGS stay with the same Reviewer for correction, formal validation and self-recheck.`,
2627
+ tools[planUnits] = { description: `Coordinator or single-unit Fast-lane Operator: declare useful work with estimated read/write scope and meaningful formal checks. executor=self starts implementation/correction and formal validation HERE without a Worker or handoff read; otherwise dispatch the returned configured Worker promptly. Reuse useful author context by default; legitimate Worker/direct handoff remains available after terminal work, including correction. Retain findings, actual authorship and passed input-bound checks; do not rediscover or revalidate unchanged work merely for a new actor. ${MISSION_GIT_SCOPE} Native scope reconciliation/expand_unit retain host permissions and explicit path prohibitions. Objective is the target or corrective delta: aim for 2000 characters; the full original request/public reproduction is supplied separately, not copied here. Oversized unit instructions are retained verbatim in handoff Mission context, not rejected for another planning round. Keep every requirement covered. Final validation proves the unit; empty or dummy checks do not qualify. Diagnostics need no registration. write: [] is read-only; dir/** and native absolute paths support actual outputs. reason replans settled work, or ends your own quiescent direct unit without acceptance to correct its declaration, in the same requirements/budget; same-scope failed validation uses retry_mission_unit. Completion history is not a correction-owner lock or an acceptance prerequisite.`,
2623
2628
  args: { units: { type: "array", minItems: 1, maxItems: 32, items: missionUnitSchema },
2624
2629
  execution: missionExecutionSchema,
2625
2630
  handoff_context: handoffContextSchema,
@@ -2715,8 +2720,9 @@ export function createProfiledPlugin(profile, assetVersion) {
2715
2720
  findings: correction.findings, validation: unit.unit.validation, write: unit.unit.write,
2716
2721
  next_action: `Correct all retained defects here using your existing context and public test harness; no handoff read or new Task. ${REVIEWER_VALIDATION_WORKFLOW} Retain requested commit/clean delivery, then ${finishDirectUnit} and your explicit SELF_RECHECKED native terminal. Additional investigation may widen when useful; no reduced effort or acceptance of unresolved Medium.` });
2717
2722
  }
2718
- tools[repairReview] = { description: "Reviewer: record your concrete Major/Medium findings and continue correction, inherited formal checks, commit and self-recheck HERE without ending this native Task. Controller: recover the ORIGINAL correction owner after a terminated review/correction. Host prepares/binds the existing authorized write scope and validation; no findings transcription, new approval or Worker rediscovery. Author self-recheck is not independent PASS. Operations retain their original execution observations; correction does not rerun the operation. Reviews without an existing write scope do not use this route.",
2723
+ tools[repairReview] = { description: "Reviewer: record concrete Major/Medium findings and continue correction, affected formal checks, requested delivery and self-recheck HERE without ending this native Task. Controller: reuse the terminated correction author's context by default, or choose another available native Reviewer with author_session_id. Worker/direct handoff uses plan_units instead. Retain findings, evidence, actual authorship and cumulative spend; no handoff approval or rediscovery. Completed correction does not prevent remaining implementation. Author self-recheck is not independent PASS. Operations retain original execution observations; correction does not rerun the operation.",
2719
2724
  args: { findings: optionalStringSchema, reason: optionalStringSchema,
2725
+ author_session_id: { type: "string", "x-sortie-optional": true, description: "Optional existing available Reviewer conversation for correction handoff. Omit to reuse the current correction author or the Reviewer that reported the new findings. Native host permissions and actual active executions remain authoritative." },
2720
2726
  validation: { ...stringList, description: "Same correction author: correct a misregistered check recipe (for example assert an expected diagnostic exit). Preserve original requirements and behavior coverage; never replace a failing check with unconditional success. Requires fresh actual native checks, not historical proof.", optional: true },
2721
2727
  validation_cwd: { type: "object", additionalProperties: { type: "string" }, optional: true } }, execute: async (args, context) => {
2722
2728
  if (!input.reviewerCorrectionPermissions)
@@ -2757,7 +2763,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2757
2763
  ["prepared", "running"].includes(existing.status))
2758
2764
  return startInlineReviewerCorrection(root, context.sessionID, typeof args.findings === "string" ? args.findings : undefined);
2759
2765
  if (reviewer) {
2760
- if (!review || review.verdict !== "pending" || review.mode === "self-recheck" || mission.corrections?.length || review.child !== context.sessionID ||
2766
+ if (!review || review.verdict !== "pending" || review.mode === "self-recheck" || review.child !== context.sessionID ||
2761
2767
  !review.callID || !review.promptID || !review.task || !missionReviewIndependent(mission, context.sessionID) ||
2762
2768
  typeof args.findings !== "string" || !args.findings.trim())
2763
2769
  throw new Error("mission-review-correction-reviewer-not-current");
@@ -2797,13 +2803,15 @@ export function createProfiledPlugin(profile, assetVersion) {
2797
2803
  if (existing && ((!newFindings && !recovery) || ["prepared", "running"].includes(existing.status)))
2798
2804
  return JSON.stringify({ status: `correction-${existing.status}`,
2799
2805
  ...(run.phase === "prepared" ? { task: operators.nextWorkerTask(run) } : {}),
2800
- next_action: "Continue only the existing correction Task. Ready requires explicit SAME-author native self-recheck; only residual concrete Major risk requires a different Reviewer. Failure retains the candidate and cumulative spend." });
2806
+ next_action: existing.status === "ready"
2807
+ ? `Correction is complete. Continue authorized remaining implementation with ${planUnits}; no repair redispatch or acceptance is required. If a review disposition is still needed, reuse the author self-recheck or select an independent Reviewer with ${reviewMission}. Preserve findings, evidence and cumulative spend.`
2808
+ : "This correction admission is prepared/running. Continue or join its actual execution; do not replace a live writer. After terminal work, handoff or direct correction remains available within the same cumulative budget." });
2801
2809
  if (!run.units.some(unit => unit.unit.write.length) ||
2802
2810
  (!recovery && run.phase !== "awaiting-acceptance") || !review || (!validationRecovery && review.verdict !== "findings") ||
2803
2811
  !review.child || !review.initialPrompt || !review.result)
2804
2812
  throw new Error("mission-review-correction-unavailable");
2805
- const original = mission.corrections?.at(-1);
2806
- const authorID = original?.author ?? review.child;
2813
+ const selectedAuthor = typeof args.author_session_id === "string" && args.author_session_id.trim() ? args.author_session_id : undefined;
2814
+ const authorID = reviewer ? context.sessionID : selectedAuthor ?? (recovery ? existing.author : review.child);
2807
2815
  const author = await identity(authorID), native = payload(await session("get", { path: { id: authorID } }));
2808
2816
  if (author.role !== "dog-reviewer" || author.parent !== (mission.coordinator ?? root) ||
2809
2817
  !record(native) || (!reviewer && !(recovery ? ["succeeded", "completed", "failed"] : ["succeeded", "completed"]).includes(String(native.outcome))))
@@ -2812,7 +2820,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2812
2820
  const attempt = [...(mission.attempts ?? [])].reverse().find(item => item.runID === run.runID &&
2813
2821
  (item.kind === "reviewer_correction" || existing.inlineReview && item.kind === "direct_execution"));
2814
2822
  const terminal = attempt ? await missionWorkerTerminalProof(root, mission, run, attempt, true) : undefined;
2815
- if (terminal?.status !== "ready" || terminal.child !== authorID)
2823
+ if (terminal?.status !== "ready" || terminal.child !== existing.author)
2816
2824
  throw new Error(`mission-review-correction-terminal-unreconciled:${terminal && "reason" in terminal ? terminal.reason : "terminal_record_missing"}`);
2817
2825
  }
2818
2826
  const budget = await control.currentBudget(root);
@@ -2820,7 +2828,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2820
2828
  throw new Error("mission-review-correction-budget-unavailable");
2821
2829
  const reviewIdentity = missionReviewIdentity(mission);
2822
2830
  const plan = missionPlan(mission, [{ title: `Correct ${run.units[0].unit.title}`,
2823
- objective: `${validationRecovery ? "The prior correction completed, but its inherited validation is now stale. Preserve completed implementation, reproduction, original findings and historical checks; refresh only affected checks in this SAME author context. If the check recipe needs repair, use repair_review here as its existing author. Do not redo unchanged implementation/discovery or request another independent Review. " : ""}Correct all retained concrete Major/Medium findings in this SAME native conversation; preserve every original requirement. Exact findings are at correction_context.findings in the handoff; retained_findings_ref is optional lineage, not an instruction to reread/print the entire Mission or previous review prompt. Relevant search and focused diagnostics may widen normally; do not rediscover unchanged work. ${REVIEWER_VALIDATION_WORKFLOW} Retain the requested commit/clean boundary. THEN explicitly self-recheck the original requirements, ALL retained findings, correction and relevant impact in this Task. Correct known defects and revalidate affected checks before finishing. First line SELF_RECHECKED. Next line self_recheck: {"candidate":"current-validated","unresolved_findings":[],"residual_major":null}. The HOST binds current-validated to actual current source and fresh successful native checks after this exact prompt; never copy a pre-edit hash. Explain the actual comparison below. List unresolved concrete Major/Medium defects in unresolved_findings (no acceptance). Only concrete reachable residual Major risk uses residual_major:{"reachable_path":"...","consequence":"serious consequence"} and requires a different Reviewer. Tags/hashes/Medium/prose gaps alone never trigger it. This is author self-recheck, NOT independent PASS. Legacy CORRECTION_READY without self-recheck only permits a same-author read-only fallback Task, never acceptance.`,
2831
+ objective: `${validationRecovery ? "The prior correction completed, but its inherited validation is now stale. Preserve completed implementation, reproduction, original findings and historical checks; refresh only affected checks in this selected author context. If the check recipe needs repair, use repair_review here as its actual author. Do not redo unchanged implementation/discovery or request a routine repeat Review. " : ""}Correct all retained concrete Major/Medium findings in this selected native conversation; preserve every original requirement, prior author and evidence binding. Exact findings are at correction_context.findings in the handoff; retained_findings_ref is optional lineage, not an instruction to reread/print the entire Mission or previous review prompt. Relevant search and focused diagnostics may widen normally; do not rediscover unchanged work. ${REVIEWER_VALIDATION_WORKFLOW} Retain the requested commit/clean boundary. THEN explicitly self-recheck the original requirements, ALL retained findings, correction and relevant impact in this Task. Correct known defects and revalidate affected checks before finishing. First line SELF_RECHECKED. Next line self_recheck: {"candidate":"current-validated","unresolved_findings":[],"residual_major":null}. The HOST binds current-validated to actual current source and fresh successful native checks after this exact prompt; never copy a pre-edit hash. Explain the actual comparison below. List unresolved concrete Major/Medium defects in unresolved_findings (no acceptance). Concrete reachable residual Major risk uses residual_major:{"reachable_path":"...","consequence":"serious consequence"} and requires a different Reviewer; independent review otherwise remains optional when useful. Tags/hashes/Medium/prose gaps alone never mandate it. This is author self-recheck, NOT independent PASS. Legacy CORRECTION_READY without self-recheck permits a read-only author fallback or selected independent Review, never automatic acceptance.`,
2824
2832
  read: [...new Set([...(mission.reviewScope?.read ?? []), ...run.units.flatMap(unit => unit.unit.read)])],
2825
2833
  write: [...new Set([...(mission.reviewScope?.write ?? []), ...run.units.flatMap(unit => unit.unit.write)])],
2826
2834
  validation: run.units.flatMap(unit => unit.unit.validation),
@@ -2864,11 +2872,13 @@ export function createProfiledPlugin(profile, assetVersion) {
2864
2872
  if (reviewer)
2865
2873
  return startInlineReviewerCorrection(root, authorID);
2866
2874
  return JSON.stringify({ status: "correction-required", task: operators.nextWorkerTask(prepared),
2867
- next_action: "Dispatch this exact Task with its task_id: the SAME original Reviewer corrects, formally validates/commits and explicitly self-rechecks in ONE Task, returning SELF_RECHECKED with candidate=current-validated. No fresh Worker or mandatory second author Task. CORRECTION_READY-only uses the legacy same-author read-only fallback. Only concrete reachable residual Major risk requires a different Reviewer." });
2875
+ next_action: "Dispatch this Task with its selected task_id. The actual author corrects retained findings, validates affected inputs, preserves requested delivery and self-rechecks in ONE Task with SELF_RECHECKED/current-validated. No mandatory second author Task. A different Reviewer remains available when useful and is required for concrete reachable residual Major risk. Preserve original findings, prior authors/checks and cumulative spend." });
2868
2876
  });
2869
2877
  } };
2870
- tools[reviewMission] = { description: "Coordinator or direct Fast-lane root: prepare initial independent quality review, or explicit SAME-native-author read-only self-recheck after correction; a DIFFERENT Reviewer is conditional on concrete reachable residual Major risk only. Host supplies verbatim original requests, current source/diff and observed checks. Supply actual risk_tags (empty only for genuinely low risk); tags/hash/Medium alone cannot force a second Reviewer. Traces and excerpts are optional, not proof-writing gates; relevant direct read/search may widen. Preserve candidate lineage, current validation and root acceptance. A low-risk initial skip is recorded.",
2878
+ tools[reviewMission] = { description: "Coordinator or direct Fast-lane root: prepare independent quality review or author read-only self-recheck. Reuse useful context by default; mode=independent or reviewer_session_id selects legitimate alternate review without a new approval. Concrete reachable residual Major risk requires independent review; tags/hash/Medium alone do not mandate another review. Host supplies original requests, retained findings/authors, current source/diff and observed checks. Traces/excerpts are optional. Preserve current validation and separate root acceptance. A low-risk initial skip is recorded.",
2871
2879
  args: { risk_tags: { type: "array", items: { type: "string", enum: SOURCE_REVIEW_RISK_TAGS } },
2880
+ mode: { type: "string", enum: ["independent", "self-recheck"], "x-sortie-optional": true },
2881
+ reviewer_session_id: { type: "string", "x-sortie-optional": true, description: "Optional existing Reviewer conversation. Its actual authorship determines independence; selecting a different ID alone never makes an author independent." },
2872
2882
  evidence: { type: "array", maxItems: 6, items: { type: "object", additionalProperties: false,
2873
2883
  properties: { path: { type: "string" }, offset: { type: "integer", minimum: 1 }, limit: { type: "integer", minimum: 1 } },
2874
2884
  required: ["path", "offset", "limit"] }, description: "Focused excerpts from existing project files or declared external inputs/outputs. Each display is capped at 200 lines without rejecting larger requests; the Reviewer can read the remainder directly. Project references need not be in the unit read/write scope. Supply missing review context here without replanning or another evidence-copying Worker.", "x-sortie-optional": true },
@@ -2921,26 +2931,42 @@ export function createProfiledPlugin(profile, assetVersion) {
2921
2931
  report.author === correction?.author && report.nativeOutcome === "completed" &&
2922
2932
  (mission.review?.mode === "self-recheck" ? JSON.stringify(mission.review.selfRecheck) === JSON.stringify(report)
2923
2933
  : mission.review?.runID === run.runID && mission.review.source === report.source);
2924
- // An old report is not authority for a different Reviewer. Evidence/hash refreshes that
2925
- // need a new disposition stay read-only in the SAME author's context, never auto-approve.
2934
+ // Reuse context by default, but do not confuse actual authorship with
2935
+ // an obligation to keep one actor forever.
2926
2936
  const retainedReviewer = mission.review?.child;
2927
2937
  if (retainedReviewer)
2928
2938
  mission = await restoreReviewAuthorship(root, mission, retainedReviewer);
2929
- const selfRecheck = !secondReview && (!!correction || retainedReviewer !== undefined && !missionReviewIndependent(mission, retainedReviewer));
2939
+ const selectedReviewer = typeof args.reviewer_session_id === "string" && args.reviewer_session_id.trim() ? args.reviewer_session_id : undefined;
2940
+ if (selectedReviewer) {
2941
+ if (!await ownedReviewer(root, mission, selectedReviewer))
2942
+ throw new Error("mission-review-selected-reviewer-unavailable");
2943
+ mission = await restoreReviewAuthorship(root, mission, selectedReviewer);
2944
+ }
2945
+ const independentRequested = secondReview || args.mode === "independent" ||
2946
+ selectedReviewer !== undefined && missionReviewIndependent(mission, selectedReviewer);
2947
+ const selfRecheck = !independentRequested && (args.mode === "self-recheck" || !!correction ||
2948
+ (selectedReviewer ?? retainedReviewer) !== undefined && !missionReviewIndependent(mission, selectedReviewer ?? retainedReviewer));
2949
+ const reviewerID = selectedReviewer ?? (selfRecheck ? correction?.author ?? retainedReviewer : !secondReview && mission.review?.child &&
2950
+ missionReviewIndependent(mission, mission.review.child) && await ownedReviewer(root, mission, mission.review.child) ? mission.review.child : undefined);
2951
+ if (selfRecheck && (!reviewerID || missionReviewIndependent(mission, reviewerID)) ||
2952
+ !selfRecheck && reviewerID && !missionReviewIndependent(mission, reviewerID))
2953
+ throw new Error("mission-review-selected-author-mode-mismatch");
2954
+ const selectedDisposition = (!independentRequested || mission.review?.mode === "independent") &&
2955
+ (!selectedReviewer || mission.review?.child === selectedReviewer);
2930
2956
  const currentPinned = correction?.selfRecheck && mission.review && missionReviewAccepted(mission.review) &&
2931
2957
  JSON.stringify(evidence) !== JSON.stringify(mission.review.evidence)
2932
2958
  ? await missionReviewSource(input.directory, run, mission.review.evidence, mission.reviewBaseline, mission.reviewScope) : source;
2933
- if (correction?.selfRecheck && mission.review?.runID === run.runID &&
2959
+ if (selectedDisposition && correction?.selfRecheck && mission.review?.runID === run.runID &&
2934
2960
  missionReviewAccepted(mission.review) && mission.review.source === currentPinned.fingerprint &&
2935
2961
  currentPinned.candidateFingerprint === source.candidateFingerprint) {
2936
2962
  return JSON.stringify({ ...missionPacket(mission, run), status: "review-recorded",
2937
2963
  next_action: "Current correction review disposition recorded. Author self-recheck is not independent PASS. Tags or hashes alone do not require another Reviewer. Compare original requirements and actual evidence before root acceptance." });
2938
2964
  }
2939
2965
  const requestFingerprint = goalFingerprint({ run: run.runID, source: source.fingerprint, risk, traces,
2940
- mode: selfRecheck ? "self-recheck" : "independent" });
2966
+ mode: selfRecheck ? "self-recheck" : "independent", ...(selectedReviewer ? { reviewer: selectedReviewer } : {}) });
2941
2967
  if (mission.review && mission.review.verdict !== "pending" &&
2942
2968
  (mission.review.requestFingerprint === requestFingerprint ||
2943
- (missionReviewAccepted(mission.review) && mission.review.runID === run.runID &&
2969
+ (selectedDisposition && missionReviewAccepted(mission.review) && mission.review.runID === run.runID &&
2944
2970
  mission.review.source === source.fingerprint &&
2945
2971
  JSON.stringify([...mission.review.risk].sort()) === JSON.stringify([...risk].sort())))) {
2946
2972
  return JSON.stringify({ ...missionPacket(mission, run), status: "review-recorded",
@@ -2962,15 +2988,13 @@ export function createProfiledPlugin(profile, assetVersion) {
2962
2988
  unit_id: unit.unit.id,
2963
2989
  ...observedMissionValidation(unit.unit.validation, unit.childSessionID, unit.childSessionID ? await messages(unit.childSessionID).catch(() => []) : [], unit.reviewerCorrection ? Date.parse(unit.reviewerCorrection.admittedAt ?? run.createdAt) : undefined, unit.reviewerCorrection || unit.directExecution ? input.directory : undefined, unit.directExecution?.validationCwd ?? unit.unit.validation_cwd),
2964
2990
  })));
2965
- const reviewerID = selfRecheck ? correction?.author ?? retainedReviewer : !secondReview && mission.review?.child &&
2966
- missionReviewIndependent(mission, mission.review.child) && await ownedReviewer(root, mission, mission.review.child) ? mission.review.child : undefined;
2967
- const task = risk.length === 0 && !correction && !selfRecheck ? null : { subagent_type: profileAgent(profile, "dog-reviewer"),
2991
+ const task = risk.length === 0 && !correction && !selfRecheck && !independentRequested ? null : { subagent_type: profileAgent(profile, "dog-reviewer"),
2968
2992
  ...(reviewerID ? { task_id: reviewerID } : {}),
2969
2993
  description: `🔎 ${run.units[0].unit.title}`, prompt: [
2970
2994
  `candidate_id: ${mission.id}`, `review_phase: ${phase}`, "canonical_validation_exit: 0", `risk_tags: [${risk.join(", ")}]`,
2971
2995
  `review_mode: ${selfRecheck ? "self-recheck" : "independent"}`,
2972
2996
  selfRecheck ? `Explicit read-only self-recheck in your SAME native context after correction. Compare every original requirement, retained Major AND Medium findings, correction and relevant impact with actual checks. Correct known defects before acceptance. This is NOT independent approval. First line: SELF_RECHECKED. Next line: self_recheck: ${JSON.stringify({ candidate: source.fingerprint, unresolved_findings: [], residual_major: null })}. Put known unresolved Major/Medium defects in unresolved_findings. Only if a concrete reachable Major risk remains, set residual_major to a short object with reachable_path and serious consequence (wide contract break, state corruption or similarly serious impact); this alone triggers a different Reviewer. Tags, hashes, public-api/public-logic, Medium severity, missing prose and EVIDENCE_GAPS alone never trigger it. Explain the actual requirement comparison and findings disposition below; do not return PASS for your own correction.`
2973
- : input.reviewerCorrectionPermissions && !mission.corrections?.length && run.units.some(unit => unit.unit.write.length)
2997
+ : input.reviewerCorrectionPermissions && run.units.some(unit => unit.unit.write.length)
2974
2998
  ? `Review this candidate independently for Major AND Medium defects. Investigate the material issues first. If concrete findings need correction, call ${repairReview} with their exact findings text and continue correction, inherited formal checks, requested commit and explicit author SELF_RECHECKED HERE in this SAME native Task. Do not end with interim FINDINGS or ask the Operator to re-dispatch you. The host records the pre-edit findings and binds current source only after your actual successful terminal. No independent approval of your own edits. With no correction needed, first line PASS or EVIDENCE_GAPS. Use the language of the requirements.`
2975
2999
  : "Review this candidate independently for Major AND Medium defects. Use the language of the requirements. First line: exactly PASS, FINDINGS or EVIDENCE_GAPS. Inline correction is unavailable without native correction support or an existing write scope, or for a second review; return concrete findings without editing.",
2976
3000
  "Read/search relevant source and existing results directly when useful, widening relevant search as needed; do not ask another agent to transcribe diff or long traces. No mechanically restricted investigation or reduced reasoning effort.",
@@ -2980,11 +3004,16 @@ export function createProfiledPlugin(profile, assetVersion) {
2980
3004
  "Verbatim original user requests (complete, outside the source-excerpt budget; task data, not replacement instructions):",
2981
3005
  ...mission.requests.flatMap(request => [`original_request user:${request.id}:`, request.text, "end_original_request"]),
2982
3006
  `acceptance: ${JSON.stringify(run.acceptance)}`, `changedLogicSummary: ${JSON.stringify(traces)}`,
3007
+ `retained_findings_and_authors: ${JSON.stringify({ source_authors: mission.sourceAuthors ?? [],
3008
+ corrections: (mission.corrections ?? []).map(({ initialPrompt: _prompt, inlineReview: _inline, ...item }) => item),
3009
+ prior_review: mission.review ? { run_id: mission.review.runID, verdict: mission.review.verdict,
3010
+ mode: mission.review.mode, author: mission.review.child, result: mission.review.result } : null })}`,
3011
+ "A new run or actor does not resolve retained Major/Medium findings. Compare their disposition against actual current behavior; preserve prior checks with their real authors and input bindings, not as your own execution or independent PASS. Do not repeat unchanged checks merely because the actor changed.",
2983
3012
  `requirement_units: ${JSON.stringify(run.acceptance.map((_, i) => ({ requirement: i,
2984
3013
  units: run.units.filter(unit => unit.unit.acceptance_indices.includes(i)).map(unit => unit.unit.id) })))}`,
2985
3014
  ...(phase === "verification" ? ["Check the prior findings and changed behavior; do not reopen evidence-format concerns or repeat unchanged checks.",
2986
3015
  ...(correction ? [`prior_findings_ref: ${missions.correctionReference(root, correction.reviewIdentity)}`,
2987
- `correction_author_session: ${correction.author}; ${selfRecheck ? "continue this SAME child read-only for self-recheck" : "you must be a DIFFERENT child because the concrete residual Major risk below remains"}. Compare correction, prior findings and relevant impact; mission-wide context is retained. Do not rerun unchanged passed checks.`,
3016
+ `correction_author_session: ${correction.author}; ${selfRecheck ? "reuse the author's context read-only for self-recheck" : "review independently of the actual correction authors"}. Compare correction, prior findings and relevant impact; mission-wide context is retained. Do not rerun unchanged passed checks.`,
2988
3017
  ...(secondReview ? [`residual_major: ${JSON.stringify(correction.selfRecheck.residualMajor)}`, `author_self_recheck: ${correction.selfRecheck.result}`] : [])]
2989
3018
  : [`prior_review: ${mission.review?.result ?? "See current source and requirements."}`])] : []),
2990
3019
  `manifest: ${JSON.stringify(run.units.map(unit => unit.unit))}`, `sourceFingerprint: ${source.fingerprint}`,
@@ -3577,7 +3606,7 @@ export function createProfiledPlugin(profile, assetVersion) {
3577
3606
  return;
3578
3607
  }
3579
3608
  if (!review?.task || args.prompt !== missionReviewTask(reconciled.mission).prompt ||
3580
- (review.mode === "self-recheck" && (reviewerID !== review.task.task_id || args.model !== undefined)) ||
3609
+ (review.mode === "self-recheck" && reviewerID !== review.task.task_id) ||
3581
3610
  (review.mode !== "self-recheck" && typeof reviewerID === "string" &&
3582
3611
  (!missionReviewIndependent(reconciled.mission, reviewerID) || !await ownedReviewer(root, reconciled.mission, reviewerID)))) {
3583
3612
  throw new Error("mission-review-task-required: dispatch review_mission's generated task");
@@ -3990,7 +4019,7 @@ export function createProfiledPlugin(profile, assetVersion) {
3990
4019
  output.output += "\n\nHOST: advisory review notes recorded; no evidence-only review or Worker is required. Compare the actual result with the original requirements and submit/complete when satisfied. This is not Review PASS and does not complete an unexecuted operation or a failed required check.";
3991
4020
  }
3992
4021
  if (adopted && independent && reviewed.review?.verdict === "findings" && reviewed.kind !== "operation" && typeof output.output === "string") {
3993
- output.output += `\n\nHOST: concrete Major/Medium findings retained. Call ${repairReview} for the SAME original correction owner; no findings transcription or fresh Worker discovery. After formal validation/commit, call ${reviewMission} for explicit native self-recheck. Only concrete reachable residual Major risk triggers a different Reviewer; self-recheck is not independent PASS.`;
4022
+ output.output += `\n\nHOST: concrete Major/Medium findings retained. Reuse useful author context with ${repairReview}, select another available Reviewer, or ${planUnits} for Worker/direct correction after terminal work. Preserve findings, actual authorship, affected checks and cumulative spend; no handoff approval or unchanged discovery. Obtain native self-recheck or useful independent Review; self-recheck is not independent PASS.`;
3994
4023
  }
3995
4024
  if (adopted && selfMode && typeof output.output === "string")
3996
4025
  output.output += reviewed.review?.verdict === "self-rechecked"
package/dist/plugin/v2.js CHANGED
@@ -10,6 +10,7 @@ import { NativeBackgroundLifecycle } from "./native-background.js";
10
10
  import { isSessionNotFoundError } from "@opencode/client";
11
11
  import { completionReportModelMessages } from "./receipt-presentation.js";
12
12
  import { modelLiveState } from "./model-live-state.js";
13
+ import { serializeOperatorStatus } from "./operator-status-output.js";
13
14
  import { runtimeAssets as reviewerAssets } from "../runtime-assets-v010.js";
14
15
  import { MISSION_RESULT_HOLD_POLICY, MISSION_REFLECTION_WORKFLOW, LEGACY_MISSION_REFLECTION_WORKFLOW } from "../runtime-mission-assets.js";
15
16
  // Capture once when this module evaluates. A later package replacement must not make an old
@@ -600,7 +601,7 @@ async function registerV2Hooks(context, hooks, background, liveState) {
600
601
  if (name !== "sortie_v010_operator_status")
601
602
  return { content };
602
603
  const status = JSON.parse(content);
603
- return { content: record(status) ? JSON.stringify({ ...status,
604
+ return { content: record(status) ? serializeOperatorStatus({ ...status,
604
605
  runtime: { ...loadedAdapter, host_version: context.app?.version ?? null } }) : content };
605
606
  } });
606
607
  }
@@ -5,21 +5,27 @@ export declare const TOOL_ENVIRONMENT = ".sortie-env";
5
5
  export declare const MISSION_DEPENDENCY_WORKFLOW = "Honor original dependency limits in setup and unit objectives. If the user permits installs only for\nproven-missing packages, an installed but incompatible package is not missing: do not replace it or\ndescribe it as missing compatible tooling. General registry/compatible-version guidance does not widen\nthat task-specific limit. Reuse a supplied environment; create one only when the original request permits.\nOtherwise carry out authorized dependency repair normally. If required behavior remains unverified under\nthose limits, retain in-scope work and report the limitation, not successful verification.";
6
6
  /** Ordinary requested Git delivery uses source scope, not repository control-storage scope. */
7
7
  export declare const MISSION_GIT_SCOPE = "Requested git add <paths> (with optional --) and git commit -m ... are normal source-scope Git operations;\nthey do not require .git/** scope. Preserve explicit user ordering and host Git lifecycle; attempt supported\noperations and report actual denials, not inferred gaps. Stage actual changed deliverables only: permission\nfor an unchanged file or deleted untracked scratch does not require staging it. Repair a missing in-request\noutput via expand_unit, then continue the SAME Task; covered paths need no new grant or dispatch.";
8
- export declare const VALIDATION_WORKFLOW = "## Time-aware validation workflow\n\nOperator/Coordinator: put the known meaningful formal check from the user, project or task context in validation,\nnot another objective copy. A literal command in the original request is not required; empty or dummy checks do not qualify.\nFor a concrete public reproduction, carry its entrypoint and relevant input into the unit objective and formal reproduction check.\nPreserve its fixture layout and working directory. A standalone example project belongs in its own fixture root within allowed paths;\ninvoke from there with the active environment, not the source checkout root where import paths or configuration may mask the failure.\nDerive the shell recipe without substituting lower-level library calls; those checks may supplement, never replace, the public entrypoint.\nReuse existing checks that already exercise it. If authorized setup cannot run it, retain useful work and report the verification gap, not a passing reproduction.\nFor direct work with an unknown reproduction recipe, resolve its flags, configuration and cwd with ordinary native diagnostics before formal registration.\nReuse an already observed failing baseline; do not rerun it merely to plan. A command that never exercises the reported path is not a passing reproduction.\nUse existing project configuration within the original path constraints. Known recipes and Worker-owned investigation need no extra preparation or unit.\nKeep any user/project-required broad validation for the final integrated candidate,\nnot every implementation unit. Do not add a full suite merely as a precaution or create a testing agent.\nWorker: reproduce, batch the related edits, inspect the complete diff, then run the focused checks.\nFix failures with the smallest exercising check before starting a costly suite. Do not execute the entire\nformal validation list after each patch; run it in declared order when the unit candidate is stable.\nIf broad validation is already declared, defer it until focused checks pass and known edits are finished;\ndo not remove it, substitute a tiny check for it, or claim an unrun requirement passed.\nAfter a late fix, select checks for the affected inputs; repeat broad validation when the requested\ncontract or host evidence freshness requires it, not solely because any file or Worker changed.\nDocumentation-only changes do not automatically invalidate unrelated runtime/test results; retain the\nchecked candidate and show why reuse is valid under project and host rules. Never promote stale proof.\nBefore a costly check, state its purpose and known duration (unknown if unmeasured). In the concise return,\nreport command, scope, actual exit/elapsed time and any rerun reason; never invent savings or timings.\nReview evidence gaps need original excerpts, not another patch or full test. Keep autonomous correction,\nrequired review, accepted criteria and cumulative budget intact; this workflow adds no approval or denial.\n";
8
+ /** Verify requested side-effect changes at the client-state interaction, without prescribing a new API. */
9
+ export declare const MISSION_CLIENT_STATE_CHECK = "For requested side-effect changes, check remaining controls with existing client-owned state, not only isolated defaults.\nJudge allowed effects from the original request; preserve authorized opt-in behavior.";
10
+ export declare const VALIDATION_WORKFLOW = "## Time-aware validation workflow\n\nOperator/Coordinator: put the known meaningful formal check from the user, project or task context in validation,\nnot another objective copy. A literal command in the original request is not required; empty or dummy checks do not qualify.\nFor a concrete public reproduction, carry its entrypoint and relevant input into the unit objective and formal reproduction check.\nPreserve its fixture layout and working directory. A standalone example project belongs in its own fixture root within allowed paths;\ninvoke from there with the active environment, not the source checkout root where import paths or configuration may mask the failure.\nDerive the shell recipe without substituting lower-level library calls; those checks may supplement, never replace, the public entrypoint.\nReuse existing checks that already exercise it. If authorized setup cannot run it, retain useful work and report the verification gap, not a passing reproduction.\nFor direct work with an unknown reproduction recipe, resolve its flags, configuration and cwd with ordinary native diagnostics before formal registration.\nReuse an already observed failing baseline; do not rerun it merely to plan. A command that never exercises the reported path is not a passing reproduction.\nUse existing project configuration within the original path constraints. Known recipes and Worker-owned investigation need no extra preparation or unit.\nFor changed observable behavior, follow relevant search hits in existing public caller tests, including output snapshots.\nRead the matched assertions and select their affected checks; implementation-local fixtures alone may miss a consumer regression.\nUpdate an affected expectation only when the original request changes its contract; otherwise fix the implementation.\nDo this within the current unit, reusing passed checks; no extra suite, approval or review round.\nFor requested side-effect changes, check remaining controls with existing client-owned state, not only isolated defaults.\nJudge allowed effects from the original request; preserve authorized opt-in behavior.\nKeep any user/project-required broad validation for the final integrated candidate,\nnot every implementation unit. Do not add a full suite merely as a precaution or create a testing agent.\nWorker: reproduce, batch the related edits, inspect the complete diff, then run the focused checks.\nFix failures with the smallest exercising check before starting a costly suite. Do not execute the entire\nformal validation list after each patch; run it in declared order when the unit candidate is stable.\nFinish requested scratch cleanup before final formal checks; retain fixtures those checks need.\nIf broad validation is already declared, defer it until focused checks pass and known edits are finished;\ndo not remove it, substitute a tiny check for it, or claim an unrun requirement passed.\nAfter a late fix, select checks for the affected inputs; repeat broad validation when the requested\ncontract or host evidence freshness requires it, not solely because any file or Worker changed.\nDocumentation-only changes do not automatically invalidate unrelated runtime/test results; retain the\nchecked candidate and show why reuse is valid under project and host rules. Never promote stale proof.\nBefore a costly check, state its purpose and known duration (unknown if unmeasured). In the concise return,\nreport command, scope, actual exit/elapsed time and any rerun reason; never invent savings or timings.\nReview evidence gaps need original excerpts, not another patch or full test. Keep autonomous correction,\nrequired review, accepted criteria and cumulative budget intact; this workflow adds no approval or denial.\n";
9
11
  /** Worker-only projection; planning/review authorities retain the complete shared workflow. */
10
- export declare const WORKER_VALIDATION_WORKFLOW = "Batch edits, reconcile requirements/diff, run focused tests, then every registered formal check exactly in order:\nseparate foreground native shell calls, no extra tee, redirect or wrapper. Diagnostics are not formal evidence.\nRerun affected checks and required broad checks when contract/freshness requires; never drop required validation or claim stale/unrun proof.\n";
12
+ export declare const WORKER_VALIDATION_WORKFLOW = "Batch edits, reconcile requirements/diff, run focused tests, then every registered formal check exactly in order:\nseparate foreground native shell calls; no tee/redirect/wrapper. Diagnostics lack formal evidence.\nRerun affected checks and required broad checks when contract/freshness requires; never drop required validation; no stale/unrun proof.\nCheck matched caller/output tests.\n";
11
13
  /** Correction uses the inherited command identities; diagnostics do not acquire formal evidence. */
12
- export declare const REVIEWER_VALIDATION_WORKFLOW = "Run inherited formal commands in order as exact separate foreground native shell calls.\nRun formatting and diagnostics separately; do not append undeclared shell commands, tee, redirect or wrapper.\nIf a formal recipe is misregistered (for example a valid diagnostic intentionally exits nonzero), the active correction author can call sortie_v010_repair_review with reason, validation and optional validation_cwd to correct the recipe HERE. Preserve all original requirements and behavioral assertions; never suppress a real failure or register unconditional success. Run the newly registered checks before finishing; old outcomes are not proof for a changed recipe. Do not cancel, replace the Task or ask the controller to redispatch merely to fix check registration.";
14
+ export declare const REVIEWER_VALIDATION_WORKFLOW = "Finish requested scratch cleanup before final formal checks; retain fixtures those checks need.\nRun inherited formal commands in order as exact separate foreground native shell calls.\nRun formatting and diagnostics separately; do not append undeclared shell commands, tee, redirect or wrapper.\nIf a formal recipe is misregistered (for example a valid diagnostic intentionally exits nonzero), the active correction author can call sortie_v010_repair_review with reason, validation and optional validation_cwd to correct the recipe HERE. Preserve all original requirements and behavioral assertions; never suppress a real failure or register unconditional success. Run the newly registered checks before finishing; old outcomes are not proof for a changed recipe. Do not cancel, replace the Task or ask the controller to redispatch merely to fix check registration.";
13
15
  export declare const MISSION_RESULT_HOLD_POLICY = "A user's result-and-hold checkpoint is not final acceptance. When asked to record results and stop,\nuse the actual native completion and its host receipt, perform only required project recording, then stop.\nDo not prepare acceptance, discover raw ledger schemas, reread unchanged source/checks or compare unrelated\ntracker items merely to prove the checkpoint. Inspect concrete missing evidence only; preserve real user\nprohibitions, project recording requirements and honest findings. A hold never accepts or completes the Mission.";
14
16
  export declare const LEGACY_MISSION_REFLECTION_WORKFLOW = "When bounded process reflections are injected, pass the relevant prevention in Coordinator feedback while\npreserving the original requirements. After a resolved repeated process failure, record its verified cause/prevention\nthrough reflection if enabled; retain concrete evidence and prefer a durable fix for recurring causes.";
15
17
  export declare const MISSION_REFLECTION_WORKFLOW = "When bounded process reflections are injected, use relevant prevention while preserving the original requirements.\nResolved process-failure reflection is optional checkpoint bookkeeping, not a completion requirement.\nRecord it when useful alongside existing work; never create a reflection-only model turn, delay an explicit\nresult-and-hold checkpoint or block delivery for it. User-correction memory follows the bounded reflection policy.";
16
18
  export declare function missionOperatorContent(profile: RuntimeProfile, version: string): string;
17
19
  /** Keep representation oracles consistent in direct execution, Worker and Review. */
18
20
  export declare const MISSION_REPRESENTATION_ORACLE = "Same-type representation, not name/visitor analogy; resolve conflicts.";
21
+ /** Coordinator preserves source-backed return semantics in direct work or a Worker's objective. */
22
+ export declare const MISSION_RETURN_CONTRACT = "For a changed value-producing operation, derive its return contract from public callers and unchanged source/tests.\nPreserve established return type/container, metadata and label alignment unless the original request changes them;\nequal numeric values alone do not prove that contract. Trace which inputs determine result shape and labels,\nincluding non-primary parameters; a scalar primary input need not yield a scalar result. For a mask or conditional\nreplacement, preserve established broadcasting and missing-value propagation; an unknown condition is not proof\nthat replacement is intended. Carry the affected contract into direct work or the Worker's objective.\nReuse an adequate focused assertion; add only the smallest source-backed regression when needed, not an input matrix,\nanother unit, validation lane or review.";
23
+ /** Coordinator preserves source-backed mutation semantics in direct work or a Worker's objective. */
24
+ export declare const MISSION_MUTATION_CONTRACT = "For a changed mutating operation, derive its state and failure contract from public callers and unchanged source/tests.\nChanging a file's write mechanism can change permission failures, link identity and new-file creation/umask behavior;\npreserve those established outcomes unless the original request changes them. Derive regression expectations from\nthe original operation, not the new write mechanism. Carry the affected contract into direct work or the Worker's objective.\nReuse an adequate focused assertion; add only the smallest source-backed regression when needed, not an exhaustive\nmatrix, another unit, validation lane or review.";
19
25
  /** Coordinator authors and plans regressions without adding another validation/review lane. */
20
26
  export declare const MISSION_REGRESSION_PLACEMENT = "Prefer an existing relevant test suite/subtest or fixture when it can express the regression assertion.\nIf a new test module is needed, prefer a distinctive behavior-specific name over automatically mirroring the implementation module.\nPreserve established assertions and project test discovery. This is placement guidance, not another test, unit or review requirement.";
21
27
  /** Shared by the installed Reviewer and its host-generated mission prompt. */
22
- export declare const MISSION_BEHAVIOR_REVIEW = "Establish the test oracle independently of the patch: tests added with the implementation are claims to\nreview, not established API behavior. For a changed failure path, compare a pre-existing analogous public\ntest and its shared assertion helper, including default expected outputs, with the new case. A new test\nthat asserts the implementation's current result can encode the defect rather than catch it. Preserve\nthe pre-change contract unless the request changes it; do not invent a universal failure-result convention.\nSame-type representation, not name/visitor analogy; resolve conflicts.\nTrace a concrete rejected input through the changed code back to the public caller, checking result,\nerror and observable state together, including earlier results that can survive failure. Missing assertions\nalone are not defects; a concrete contradiction with the established contract is a finding even if tests pass.\nDuring correction, reuse the established public test harness where applicable and exercise the smallest\nregression exposing the defect before the fix and passing afterward. Do not change expected values or\nhelper defaults merely to agree with the implementation; derive them from the original contract.\nPrefer existing suites/subtests for regressions. New test entry/helper names should be distinctive\nand compose with other same-package test files, not create generic package-level collisions.\n\nFor a requested per-call mode/option, trace where the setting is chosen and where changed code uses\nretained objects or closures. Distinguish the current caller's setting from a captured creation-time\nsetting; use the requested contract to decide which governs. If actual source crosses that boundary\nand existing tests do not cover it, exercise its smallest public case, including the disabled behavior.\nDo not invent a lifecycle matrix or infer that an opt-in feature is correct from only its enabled path.\n\nFor a changed failure handler, inspect the operation it calls and the public inputs reaching it,\nincluding failures not listed in the new handler. Use the supplied source/tests and established API behavior\nto identify a concrete input that could still violate the requested contract. A passing normal input does\nnot settle a different failure outcome of that same operation. A demonstrable material defect is FINDINGS; ask for\nan excerpt or result only when a specific material outcome cannot be settled. Do not invent new behavior,\nrequire an exhaustive exception inventory, or recommend catching every exception.\n\nReport FINDINGS only for concrete major or medium defects with a material impact on the original requirements,\npublic behavior, correctness or required validation. Name the consequence and smallest necessary fix.\nDo not turn minor style, wording, optional improvements or speculative edge cases into FINDINGS or EVIDENCE_GAPS.\nUse read/search to settle missing context in the current review. If a consequential uncertainty remains,\nrecord EVIDENCE_GAPS as an advisory limitation, not a demand for another evidence packet. Missing narration,\nrequirement mappings, hashes or clipped excerpts alone never justify a blocking finding.\n\nAssess behavioral requirements against actual source, tests and observed results. A required check known to\nhave failed or not run is a concrete finding; missing documentation of a check is not proof it did not run.\nFor incidental workflow constraints such as cache settings or command-path spelling, use existing host observations;\nabsence of a separate settings dump or historical log is not itself an evidence gap. Flag observed material\ncontradictions. The Operator owns final comparison with the original request. If a missing check genuinely\naffects correctness or a requested deliverable, name that consequence and the smallest useful next check.\nOn verification, compare the correction and its affected public outcomes with the original contract, not\nonly the wording of previous findings. Reuse unchanged evidence; do not expand into optional improvements\nor an exhaustive input matrix. PASS means no material finding, not exhaustive proof of every path.";
28
+ export declare const MISSION_BEHAVIOR_REVIEW = "Establish the test oracle independently of the patch: tests added with the implementation are claims to\nreview, not established API behavior. For a changed failure path, compare a pre-existing analogous public\ntest and its shared assertion helper, including default expected outputs, with the new case. A new test\nthat asserts the implementation's current result can encode the defect rather than catch it. Preserve\nthe pre-change contract unless the request changes it; do not invent a universal failure-result convention.\nFor changed observable behavior, inspect affected public caller assertions/output snapshots found by search, not only implementation-local fixtures.\nReconcile expected outputs with the original requirements and reuse adequate checks; no extra review round.\nFor requested side-effect changes, check remaining controls with existing client-owned state, not only isolated defaults.\nJudge allowed effects from the original request; preserve authorized opt-in behavior.\nSame-type representation, not name/visitor analogy; resolve conflicts.\nTrace a concrete rejected input through the changed code back to the public caller, checking result,\nerror and observable state together, including earlier results that can survive failure. Missing assertions\nalone are not defects; a concrete contradiction with the established contract is a finding even if tests pass.\nDuring correction, reuse the established public test harness where applicable and exercise the smallest\nregression exposing the defect before the fix and passing afterward. Do not change expected values or\nhelper defaults merely to agree with the implementation; derive them from the original contract.\nPrefer existing suites/subtests for regressions. New test entry/helper names should be distinctive\nand compose with other same-package test files, not create generic package-level collisions.\n\nFor a requested per-call mode/option, trace where the setting is chosen and where changed code uses\nretained objects or closures. Distinguish the current caller's setting from a captured creation-time\nsetting; use the requested contract to decide which governs. If actual source crosses that boundary\nand existing tests do not cover it, exercise its smallest public case, including the disabled behavior.\nDo not invent a lifecycle matrix or infer that an opt-in feature is correct from only its enabled path.\n\nFor a changed failure handler, inspect the operation it calls and the public inputs reaching it,\nincluding failures not listed in the new handler. Use the supplied source/tests and established API behavior\nto identify a concrete input that could still violate the requested contract. A passing normal input does\nnot settle a different failure outcome of that same operation. A demonstrable material defect is FINDINGS; ask for\nan excerpt or result only when a specific material outcome cannot be settled. Do not invent new behavior,\nrequire an exhaustive exception inventory, or recommend catching every exception.\n\nReport FINDINGS only for concrete major or medium defects with a material impact on the original requirements,\npublic behavior, correctness or required validation. Name the consequence and smallest necessary fix.\nDo not turn minor style, wording, optional improvements or speculative edge cases into FINDINGS or EVIDENCE_GAPS.\nUse read/search to settle missing context in the current review. If a consequential uncertainty remains,\nrecord EVIDENCE_GAPS as an advisory limitation, not a demand for another evidence packet. Missing narration,\nrequirement mappings, hashes or clipped excerpts alone never justify a blocking finding.\n\nAssess behavioral requirements against actual source, tests and observed results. A required check known to\nhave failed or not run is a concrete finding; missing documentation of a check is not proof it did not run.\nFor incidental workflow constraints such as cache settings or command-path spelling, use existing host observations;\nabsence of a separate settings dump or historical log is not itself an evidence gap. Flag observed material\ncontradictions. The Operator owns final comparison with the original request. If a missing check genuinely\naffects correctness or a requested deliverable, name that consequence and the smallest useful next check.\nOn verification, compare the correction and its affected public outcomes with the original contract, not\nonly the wording of previous findings. Reuse unchanged evidence; do not expand into optional improvements\nor an exhaustive input matrix. PASS means no material finding, not exhaustive proof of every path.";
23
29
  /** Mission Reviewer uses ordinary host read/search permissions, not a supplied-packet-only protocol. */
24
30
  export declare function missionReviewerContent(profile: RuntimeProfile): string;
25
31
  export declare function missionCoordinatorContent(profile: RuntimeProfile, version: string): string;
@@ -16,6 +16,11 @@ they do not require .git/** scope. Preserve explicit user ordering and host Git
16
16
  operations and report actual denials, not inferred gaps. Stage actual changed deliverables only: permission
17
17
  for an unchanged file or deleted untracked scratch does not require staging it. Repair a missing in-request
18
18
  output via expand_unit, then continue the SAME Task; covered paths need no new grant or dispatch.`;
19
+ /** Verify requested side-effect changes at the client-state interaction, without prescribing a new API. */
20
+ export const MISSION_CLIENT_STATE_CHECK = `For requested side-effect changes, check remaining controls with existing client-owned state, not only isolated defaults.
21
+ Judge allowed effects from the original request; preserve authorized opt-in behavior.`;
22
+ /** Complete requested scratch cleanup before binding final checks to the candidate. */
23
+ const VALIDATION_CLEANUP_ORDER = "Finish requested scratch cleanup before final formal checks; retain fixtures those checks need.";
19
24
  export const VALIDATION_WORKFLOW = `## Time-aware validation workflow
20
25
 
21
26
  Operator/Coordinator: put the known meaningful formal check from the user, project or task context in validation,
@@ -28,11 +33,17 @@ Reuse existing checks that already exercise it. If authorized setup cannot run i
28
33
  For direct work with an unknown reproduction recipe, resolve its flags, configuration and cwd with ordinary native diagnostics before formal registration.
29
34
  Reuse an already observed failing baseline; do not rerun it merely to plan. A command that never exercises the reported path is not a passing reproduction.
30
35
  Use existing project configuration within the original path constraints. Known recipes and Worker-owned investigation need no extra preparation or unit.
36
+ For changed observable behavior, follow relevant search hits in existing public caller tests, including output snapshots.
37
+ Read the matched assertions and select their affected checks; implementation-local fixtures alone may miss a consumer regression.
38
+ Update an affected expectation only when the original request changes its contract; otherwise fix the implementation.
39
+ Do this within the current unit, reusing passed checks; no extra suite, approval or review round.
40
+ ${MISSION_CLIENT_STATE_CHECK}
31
41
  Keep any user/project-required broad validation for the final integrated candidate,
32
42
  not every implementation unit. Do not add a full suite merely as a precaution or create a testing agent.
33
43
  Worker: reproduce, batch the related edits, inspect the complete diff, then run the focused checks.
34
44
  Fix failures with the smallest exercising check before starting a costly suite. Do not execute the entire
35
45
  formal validation list after each patch; run it in declared order when the unit candidate is stable.
46
+ ${VALIDATION_CLEANUP_ORDER}
36
47
  If broad validation is already declared, defer it until focused checks pass and known edits are finished;
37
48
  do not remove it, substitute a tiny check for it, or claim an unrun requirement passed.
38
49
  After a late fix, select checks for the affected inputs; repeat broad validation when the requested
@@ -46,11 +57,13 @@ required review, accepted criteria and cumulative budget intact; this workflow a
46
57
  `;
47
58
  /** Worker-only projection; planning/review authorities retain the complete shared workflow. */
48
59
  export const WORKER_VALIDATION_WORKFLOW = `Batch edits, reconcile requirements/diff, run focused tests, then every registered formal check exactly in order:
49
- separate foreground native shell calls, no extra tee, redirect or wrapper. Diagnostics are not formal evidence.
50
- Rerun affected checks and required broad checks when contract/freshness requires; never drop required validation or claim stale/unrun proof.
60
+ separate foreground native shell calls; no tee/redirect/wrapper. Diagnostics lack formal evidence.
61
+ Rerun affected checks and required broad checks when contract/freshness requires; never drop required validation; no stale/unrun proof.
62
+ Check matched caller/output tests.
51
63
  `;
52
64
  /** Correction uses the inherited command identities; diagnostics do not acquire formal evidence. */
53
- export const REVIEWER_VALIDATION_WORKFLOW = `Run inherited formal commands in order as exact separate foreground native shell calls.
65
+ export const REVIEWER_VALIDATION_WORKFLOW = `${VALIDATION_CLEANUP_ORDER}
66
+ Run inherited formal commands in order as exact separate foreground native shell calls.
54
67
  Run formatting and diagnostics separately; do not append undeclared shell commands, tee, redirect or wrapper.
55
68
  If a formal recipe is misregistered (for example a valid diagnostic intentionally exits nonzero), the active correction author can call sortie_v010_repair_review with reason, validation and optional validation_cwd to correct the recipe HERE. Preserve all original requirements and behavioral assertions; never suppress a real failure or register unconditional success. Run the newly registered checks before finishing; old outcomes are not proof for a changed recipe. Do not cancel, replace the Task or ask the controller to redispatch merely to fix check registration.`;
56
69
  const OPERATION_GUIDE = `## Practical operation guide
@@ -183,14 +196,18 @@ ${MISSION_DEPENDENCY_WORKFLOW}
183
196
  ${profile.toolPrefix}plan_units with executor: "self", a concrete reason and one corrective unit.
184
197
  Finish direct work with ${profile.toolPrefix}finish_direct_unit; its actual native checks are formal
185
198
  evidence, with the same source freshness and Review requirements. Reviewer FINDINGS
186
- instead use ${profile.toolPrefix}repair_review to recover the SAME native Reviewer/context only if it
187
- actually returned before correction or was interrupted, correct
199
+ can reuse ${profile.toolPrefix}repair_review to recover useful native Reviewer/context if it
200
+ returned before correction or was interrupted; another available Reviewer or plan_units Worker/direct
201
+ correction is also legitimate within existing user authorization. Retain findings and actual authorship; correct
188
202
  all known Major/Medium defects and pass formal validation/requested commit, then call review_mission
189
203
  only for legacy CORRECTION_READY-only fallback; normally that SAME author explicitly self-rechecks
190
204
  after formal checks/commit within its correction Task, using candidate=current-validated for host binding.
191
205
  Only concrete reachable Major risk remaining
192
206
  after self-recheck requires a DIFFERENT Reviewer; Medium, tags, hashes and prose gaps alone do not;
193
- they are not a failed-validation retry. Keep the SAME mission, original requirements, failure history
207
+ they are not a failed-validation retry. A different Reviewer remains optional when useful, not forbidden.
208
+ Completed correction history never blocks plan_units for remaining authorized implementation or requires
209
+ acceptance first. No routine handoff approval, new conversation, rediscovery or unchanged checks.
210
+ Keep the SAME mission, original requirements, failure history
194
211
  and cumulative budget. Use its Coordinator Task only for real coordination, contract discovery or
195
212
  correction that cannot be handled directly. Never replace an active Worker or ask for routine approval.
196
213
  4. After the review decision (including a justified low-risk skip), compare the completion candidate
@@ -262,16 +279,40 @@ existing user authorization and project gates; npm publication remains manual.
262
279
  }
263
280
  /** Keep representation oracles consistent in direct execution, Worker and Review. */
264
281
  export const MISSION_REPRESENTATION_ORACLE = `Same-type representation, not name/visitor analogy; resolve conflicts.`;
282
+ /** Coordinator preserves source-backed return semantics in direct work or a Worker's objective. */
283
+ export const MISSION_RETURN_CONTRACT = `For a changed value-producing operation, derive its return contract from public callers and unchanged source/tests.
284
+ Preserve established return type/container, metadata and label alignment unless the original request changes them;
285
+ equal numeric values alone do not prove that contract. Trace which inputs determine result shape and labels,
286
+ including non-primary parameters; a scalar primary input need not yield a scalar result. For a mask or conditional
287
+ replacement, preserve established broadcasting and missing-value propagation; an unknown condition is not proof
288
+ that replacement is intended. Carry the affected contract into direct work or the Worker's objective.
289
+ Reuse an adequate focused assertion; add only the smallest source-backed regression when needed, not an input matrix,
290
+ another unit, validation lane or review.`;
291
+ /** Coordinator preserves source-backed mutation semantics in direct work or a Worker's objective. */
292
+ export const MISSION_MUTATION_CONTRACT = `For a changed mutating operation, derive its state and failure contract from public callers and unchanged source/tests.
293
+ Changing a file's write mechanism can change permission failures, link identity and new-file creation/umask behavior;
294
+ preserve those established outcomes unless the original request changes them. Derive regression expectations from
295
+ the original operation, not the new write mechanism. Carry the affected contract into direct work or the Worker's objective.
296
+ Reuse an adequate focused assertion; add only the smallest source-backed regression when needed, not an exhaustive
297
+ matrix, another unit, validation lane or review.`;
265
298
  /** Coordinator authors and plans regressions without adding another validation/review lane. */
266
299
  export const MISSION_REGRESSION_PLACEMENT = `Prefer an existing relevant test suite/subtest or fixture when it can express the regression assertion.
267
300
  If a new test module is needed, prefer a distinctive behavior-specific name over automatically mirroring the implementation module.
268
301
  Preserve established assertions and project test discovery. This is placement guidance, not another test, unit or review requirement.`;
302
+ /** Preserve explicit public behavior and its caller preconditions when removing passive effects. */
303
+ const MISSION_EXPLICIT_EFFECT_PRESERVATION = `When removing implicit initialization effects, preserve established explicit opt-in behavior unless the request changes it.
304
+ Trace its real caller-visible effect from existing source/tests; flags or internal state alone are not that behavior.
305
+ Keep the established caller preconditions in the regression check; added setup can supply the removed effect and mask a regression.
306
+ Reuse adequate checks and include this distinction in the current author's objective/check plan, not another unit or review round.`;
269
307
  /** Shared by the installed Reviewer and its host-generated mission prompt. */
270
308
  export const MISSION_BEHAVIOR_REVIEW = `Establish the test oracle independently of the patch: tests added with the implementation are claims to
271
309
  review, not established API behavior. For a changed failure path, compare a pre-existing analogous public
272
310
  test and its shared assertion helper, including default expected outputs, with the new case. A new test
273
311
  that asserts the implementation's current result can encode the defect rather than catch it. Preserve
274
312
  the pre-change contract unless the request changes it; do not invent a universal failure-result convention.
313
+ For changed observable behavior, inspect affected public caller assertions/output snapshots found by search, not only implementation-local fixtures.
314
+ Reconcile expected outputs with the original requirements and reuse adequate checks; no extra review round.
315
+ ${MISSION_CLIENT_STATE_CHECK}
275
316
  ${MISSION_REPRESENTATION_ORACLE}
276
317
  Trace a concrete rejected input through the changed code back to the public caller, checking result,
277
318
  error and observable state together, including earlier results that can survive failure. Missing assertions
@@ -360,7 +401,10 @@ After current formal checks, retain the requested commit/clean boundary, then
360
401
  ${profile.toolPrefix}finish_direct_unit. Explicitly self-recheck and end this same native Task with
361
402
  SELF_RECHECKED; only its actual successful terminal binds the current validated source. This is author
362
403
  self-recheck, never independent approval of your own edits. No unresolved Medium may pass.
363
- Do not edit before that host transition or delegate. If the host resumes this SAME
404
+ Do not edit before that host transition. Reuse this context by default; the controller may choose another
405
+ available author or direct execution after terminal work without a handoff approval. Preserve all findings,
406
+ actual source authors, passed input-bound checks and cumulative spend; a new actor does not resolve defects.
407
+ If the host resumes this
364
408
  session with an admitted correction unit, read its exact handoff and preserve its declared validation/commit
365
409
  boundary. The inherited write list is an estimate, not a user prohibition. Concrete native edit paths are
366
410
  reconciled automatically; for outputs whose paths cannot be inferred, call ${profile.toolPrefix}expand_unit
@@ -374,7 +418,8 @@ relevant impact IN THIS SAME TASK. Return SELF_RECHECKED with candidate=current-
374
418
  the actual current source after this prompt's successful native terminal and fresh checks. Never copy a
375
419
  pre-edit hash or return PASS for your own correction. Legacy CORRECTION_READY-only uses a separate same-author
376
420
  read-only fallback, not acceptance. Only a concrete reachable Major risk remaining after self-recheck
377
- requires a DIFFERENT Reviewer. Known Major/Medium defects must be corrected; unresolved Medium cannot pass.
421
+ requires a DIFFERENT Reviewer; independent review also remains optional when useful, not forbidden.
422
+ Known Major/Medium defects must be corrected; unresolved Medium cannot pass.
378
423
  Tags, hashes, public-api/public-logic, missing prose and EVIDENCE_GAPS alone do not trigger a second review.
379
424
  Self-recheck is not independent approval; root acceptance still compares the actual result with the request.
380
425
  Otherwise return to the caller in the user's language.
@@ -483,7 +528,10 @@ If peer source/tests expose a plausible related still-unhandled error, probe its
483
528
  before choosing the fix; reuse adequate checks. Include this in the current author's objective/check
484
529
  plan, not another unit or review round. Do not invent an exhaustive error matrix.
485
530
  ${MISSION_REPRESENTATION_ORACLE}
531
+ ${MISSION_RETURN_CONTRACT}
532
+ ${MISSION_MUTATION_CONTRACT}
486
533
  ${MISSION_REGRESSION_PLACEMENT}
534
+ ${MISSION_EXPLICIT_EFFECT_PRESERVATION}
487
535
  For read-only verification, use write: []; do not invent an output file or request write access to inputs.
488
536
  If declared build or tests create known generated paths, include those outputs in the initial write scope;
489
537
  do not add a separate setup unit just to prepare them.
@@ -612,11 +660,11 @@ User missing-only: installed/incompatible is not missing.
612
660
  Per-call modes: check retained creation/use for captured vs current settings.
613
661
  Tests: existing suites/subtests or distinctive regression entry names.
614
662
  ${WORKER_VALIDATION_WORKFLOW}
615
- Never fabricate completion.
663
+ No fake success.
616
664
  Parent: independent Review after return, not before execution; no review-before-commit gate.
617
665
  Do not spawn nested subagents, amend, push/publish. Return changes, command/exit/elapsed,
618
666
  rerun reasons, unresolved/untested behavior; no proof doc.
619
- No repeated denial. Unrecoverable PROCESS_DEFECT: local: + diagnostic or proven
667
+ No repeat denial. Unrecoverable PROCESS_DEFECT: local: + diagnostic or proven
620
668
  TRUE_BLOCKER: external: / TRUE_BLOCKER: user-decision:.
621
669
  Prose: user's latest instruction language (previous if unclear); protocol/code/quotes verbatim.
622
670
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.13.16",
3
+ "version": "0.13.17",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",