sortie-dogs 0.16.1 → 0.16.2
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/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/goal-bound.d.ts +16 -1
- package/dist/core/goal-bound.js +15 -3
- package/dist/core/observed-goal-evidence.d.ts +1 -1
- package/dist/core/observed-goal-evidence.js +2 -2
- package/dist/core/operator-mission.js +4 -4
- package/dist/core/operator-runtime.d.ts +12 -0
- package/dist/core/operator-runtime.js +38 -24
- package/dist/plugin/index.js +22 -5
- package/dist/plugin/mission-review.d.ts +3 -3
- package/dist/plugin/mission-review.js +9 -4
- package/dist/plugin/profiled.js +112 -66
- package/dist/plugin/runtime-bridge.d.ts +11 -1
- package/dist/runtime-mission-assets.js +6 -3
- package/package.json +1 -1
package/dist/asset-version.d.ts
CHANGED
|
@@ -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.16.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.16.2-direct-validation-registration-v1";
|
|
7
7
|
export declare const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
|
|
8
8
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -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.16.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.16.2-direct-validation-registration-v1";
|
|
7
7
|
export const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
|
|
@@ -273,6 +273,14 @@ export type GoalFlightEvent = (GoalEventBase & {
|
|
|
273
273
|
readonly goal_id: string;
|
|
274
274
|
readonly revision: number;
|
|
275
275
|
readonly reason: "no-progress";
|
|
276
|
+
}) | (GoalEventBase & {
|
|
277
|
+
readonly kind: "goal.validation-registered";
|
|
278
|
+
readonly goal_id: string;
|
|
279
|
+
readonly reservation_id: string;
|
|
280
|
+
readonly unit_id: string;
|
|
281
|
+
readonly criterion_id: string;
|
|
282
|
+
readonly command: string;
|
|
283
|
+
readonly registered_at: string;
|
|
276
284
|
}) | (GoalEventBase & {
|
|
277
285
|
readonly kind: "goal.terminal";
|
|
278
286
|
readonly goal_id: string;
|
|
@@ -309,6 +317,12 @@ export interface GoalFlightState {
|
|
|
309
317
|
readonly delivery: GoalDeliveryMode | null;
|
|
310
318
|
readonly budget: GoalBudget | null;
|
|
311
319
|
readonly acceptance_contract: GoalAcceptanceContract | null;
|
|
320
|
+
/** Native registration metadata only; the acceptance contract and its fingerprint stay frozen. */
|
|
321
|
+
readonly validation_registrations?: Readonly<Record<string, {
|
|
322
|
+
readonly unit_id: string;
|
|
323
|
+
readonly command: string;
|
|
324
|
+
readonly registered_at: string;
|
|
325
|
+
}>>;
|
|
312
326
|
readonly consumed_units: number;
|
|
313
327
|
readonly consumed_time_ms: number | null;
|
|
314
328
|
readonly consumed_cost_usd: number | null;
|
|
@@ -355,6 +369,7 @@ export declare function selectGoalDelivery(input: {
|
|
|
355
369
|
readonly irreversible_or_major_scope: boolean;
|
|
356
370
|
readonly explicit_mode?: GoalDeliveryMode;
|
|
357
371
|
}): GoalDeliveryMode;
|
|
358
|
-
export declare
|
|
372
|
+
export declare const goalValidationCommand: (state: Pick<GoalFlightState, "validation_registrations">, criterion: GoalAcceptanceCriterion) => string | undefined;
|
|
373
|
+
export declare function validGoalEvidence(value: GoalEvidence, state: Pick<GoalFlightState, "goal_id" | "revision" | "scope_epoch" | "acceptance_fingerprint" | "acceptance_contract" | "validation_registrations">): boolean;
|
|
359
374
|
export declare function reduceGoalFlight(records: readonly GoalFlightEventRecord[]): GoalFlightState;
|
|
360
375
|
export {};
|
package/dist/core/goal-bound.js
CHANGED
|
@@ -51,6 +51,7 @@ function validAcceptanceContract(value) {
|
|
|
51
51
|
function sameStrings(left, right) {
|
|
52
52
|
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
53
53
|
}
|
|
54
|
+
export const goalValidationCommand = (state, criterion) => state.validation_registrations?.[criterion.criterion_id]?.command ?? criterion.validation_command;
|
|
54
55
|
export function validGoalEvidence(value, state) {
|
|
55
56
|
if (typeof value !== "object" || value === null || typeof value.measurement !== "object" || value.measurement === null ||
|
|
56
57
|
typeof value.identity !== "object" || value.identity === null || typeof value.execution !== "object" || value.execution === null ||
|
|
@@ -100,8 +101,11 @@ export function validGoalEvidence(value, state) {
|
|
|
100
101
|
(criterion.candidate_binding === "current-protected"
|
|
101
102
|
? protectedBindingValid && HASH.test(value.identity.candidate) && (dockerWorkspace === undefined || dockerWorkspace.candidate === value.identity.candidate)
|
|
102
103
|
: criterion.candidate === value.identity.candidate) &&
|
|
103
|
-
(criterion
|
|
104
|
-
(value.execution.command.length === 1 && value.execution.command[0] === criterion
|
|
104
|
+
(goalValidationCommand(state, criterion) === undefined ||
|
|
105
|
+
(value.execution.command.length === 1 && value.execution.command[0] === goalValidationCommand(state, criterion))) &&
|
|
106
|
+
(!state.validation_registrations?.[criterion.criterion_id] ||
|
|
107
|
+
value.execution.units.includes(state.validation_registrations[criterion.criterion_id].unit_id) &&
|
|
108
|
+
Date.parse(value.execution.started_at) >= Date.parse(state.validation_registrations[criterion.criterion_id].registered_at)) &&
|
|
105
109
|
criterion.fixture === value.identity.fixture &&
|
|
106
110
|
criterion.proof_scope === value.proof_scope && criterion.expected_outcome === value.execution.outcome);
|
|
107
111
|
return text(value.evidence_id) && value.goal_id === state.goal_id && value.goal_revision === state.revision &&
|
|
@@ -193,7 +197,7 @@ export function reduceGoalFlight(records) {
|
|
|
193
197
|
selected_agent: event.selected_agent, delivery: event.delivery, budget: event.budget,
|
|
194
198
|
validation_budget: { ...state.validation_budget,
|
|
195
199
|
limit: state.validation_budget.limit === null ? null : Math.max(state.validation_budget.limit, state.validation_budget.consumed, event.budget.max_units) },
|
|
196
|
-
acceptance_contract: event.acceptance_contract,
|
|
200
|
+
acceptance_contract: event.acceptance_contract, validation_registrations: undefined,
|
|
197
201
|
session_ids: addUnique(state.session_ids, event.session_id), phase: "active", stop_reason: null,
|
|
198
202
|
receipt: null, tickets: [], ...(event.reset_no_progress === true
|
|
199
203
|
? { no_progress_results: 0, replan_required: false, replan_used: false }
|
|
@@ -204,6 +208,14 @@ export function reduceGoalFlight(records) {
|
|
|
204
208
|
if (validationAdmissionLimit !== null)
|
|
205
209
|
validationAdmissionLimit = Math.max(event.budget.max_units, state.validation_budget.consumed);
|
|
206
210
|
}
|
|
211
|
+
else if (event.kind === "goal.validation-registered") {
|
|
212
|
+
requireState(state.phase === "active" && state.outstanding_reservations.some(item => item.reservation_id === event.reservation_id && item.unit_id === event.unit_id) &&
|
|
213
|
+
state.acceptance_contract?.criteria.some(item => item.criterion_id === event.criterion_id) === true &&
|
|
214
|
+
commandText(event.command) && instant(event.registered_at) && Date.parse(event.registered_at) <= Date.parse(event.at), "transition", "Validation registration requires the same active reserved unit and a current criterion.");
|
|
215
|
+
state = { ...state, validation_registrations: { ...state.validation_registrations,
|
|
216
|
+
[event.criterion_id]: { unit_id: event.unit_id, command: event.command, registered_at: event.registered_at } },
|
|
217
|
+
satisfied_criteria: state.satisfied_criteria.filter(id => id !== event.criterion_id) };
|
|
218
|
+
}
|
|
207
219
|
else if (event.kind === "ticket.issued") {
|
|
208
220
|
requireState(state.phase === "active" && event.revision === state.revision && event.scope_epoch === state.scope_epoch &&
|
|
209
221
|
event.origin_user_message_id === state.latest_user_message_id && event.sequence === state.tickets.length + 1 &&
|
|
@@ -15,4 +15,4 @@ export interface ObservedGoalExecution {
|
|
|
15
15
|
}
|
|
16
16
|
/** One host execution can exercise several independently declared criteria.
|
|
17
17
|
* Preserve each measurement identity instead of dropping all heterogeneous ones. */
|
|
18
|
-
export declare function evidenceFromObservedExecution(execution: ObservedGoalExecution, state: Pick<GoalFlightState, "goal_id" | "revision" | "scope_epoch" | "acceptance_fingerprint" | "acceptance_contract">, unitID: string): GoalEvidence[];
|
|
18
|
+
export declare function evidenceFromObservedExecution(execution: ObservedGoalExecution, state: Pick<GoalFlightState, "goal_id" | "revision" | "scope_epoch" | "acceptance_fingerprint" | "acceptance_contract" | "validation_registrations">, unitID: string): GoalEvidence[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { goalFingerprint } from "./goal-bound.js";
|
|
1
|
+
import { goalFingerprint, goalValidationCommand } from "./goal-bound.js";
|
|
2
2
|
/** One host execution can exercise several independently declared criteria.
|
|
3
3
|
* Preserve each measurement identity instead of dropping all heterogeneous ones. */
|
|
4
4
|
export function evidenceFromObservedExecution(execution, state, unitID) {
|
|
@@ -6,7 +6,7 @@ export function evidenceFromObservedExecution(execution, state, unitID) {
|
|
|
6
6
|
return [];
|
|
7
7
|
// Canonical requested-full proof is coordinator-owned and must never be manufactured from a
|
|
8
8
|
// child shell result. The caller derives owner from the observed host session lineage.
|
|
9
|
-
const matching = state.acceptance_contract?.criteria.filter(criterion => criterion
|
|
9
|
+
const matching = state.acceptance_contract?.criteria.filter(criterion => goalValidationCommand(state, criterion) === execution.command[0] && criterion.expected_outcome === "pass" &&
|
|
10
10
|
(criterion.proof_scope !== "requested-full" || execution.owner === "coordinator")) ?? [];
|
|
11
11
|
const groups = new Map();
|
|
12
12
|
for (const criterion of matching) {
|
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdir, open, readFile, readdir, rename, rm, rmdir, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join, resolve, win32 } from "node:path";
|
|
4
4
|
import { normalizeExecutionScope } from "./path.js";
|
|
5
|
-
import { parseOperatorPlan } from "./operator-runtime.js";
|
|
5
|
+
import { parseOperatorPlan, unitValidation } from "./operator-runtime.js";
|
|
6
6
|
import { profileAgent } from "./runtime-profile.js";
|
|
7
7
|
import { normalizeCommand, shellCommandBase, shellCommandWords } from "../plugin/gate.js";
|
|
8
8
|
import { missionValidationCommand } from "./mission-validation-command.js";
|
|
@@ -859,7 +859,7 @@ export async function missionAcceptanceSummary(mission, run, operators, observe)
|
|
|
859
859
|
// Settled proof already records these commands. Do not fetch the same native
|
|
860
860
|
// history again merely to render acceptance; freshness is checked separately.
|
|
861
861
|
const proofs = unit.evidence ?? [];
|
|
862
|
-
if (unit.status === "succeeded" && unit.
|
|
862
|
+
if (unit.status === "succeeded" && unitValidation(unit).every(command => proofs.some(proof => proof.execution.exit_code === 0 && proof.execution.outcome === "pass" &&
|
|
863
863
|
proof.execution.command.includes(command)))) {
|
|
864
864
|
return { ...provenance, status: "recorded-formal-evidence",
|
|
865
865
|
evidence_ids: proofs.map(proof => proof.evidence_id),
|
|
@@ -868,7 +868,7 @@ export async function missionAcceptanceSummary(mission, run, operators, observe)
|
|
|
868
868
|
try {
|
|
869
869
|
if (!observe || !unit.childSessionID)
|
|
870
870
|
throw new Error("native-worker-history-unavailable");
|
|
871
|
-
return { ...provenance, status: "available", observations: await observe(unit
|
|
871
|
+
return { ...provenance, status: "available", observations: await observe(unitValidation(unit), unit.childSessionID, unit.directExecution ? Date.parse(unit.directExecution.startedAt) :
|
|
872
872
|
unit.reviewerCorrection ? Date.parse(unit.reviewerCorrection.admittedAt ?? item.state.createdAt) : undefined) };
|
|
873
873
|
}
|
|
874
874
|
catch (error) {
|
|
@@ -942,7 +942,7 @@ export function missionPacket(mission, run, includeWorkQuantity = true) {
|
|
|
942
942
|
units: run.units.map(unit => ({ id: unit.unit.id, title: unit.unit.title, status: unit.status,
|
|
943
943
|
child_session_id: unit.childSessionID, result_class: unit.resultClass, evidence: unit.evidence,
|
|
944
944
|
handoff_path: unit.handoffPath, operation_manifest_path: unit.manifestPath,
|
|
945
|
-
scope_read: unit.unit.read, scope_write: unit.unit.write, validation: unit
|
|
945
|
+
scope_read: unit.unit.read, scope_write: unit.unit.write, validation: unitValidation(unit),
|
|
946
946
|
normal_remediation_used: unit.normalRemediationUsed ?? false,
|
|
947
947
|
...(unit.failure ? { failure: { outcome: unit.failure.outcome, exit_code: unit.failure.exitCode } } : {}),
|
|
948
948
|
...(unit.dispatchDenial ? { dispatch_denial: unit.dispatchDenial } : {}) })) } : {}),
|
|
@@ -127,6 +127,13 @@ interface UnitState {
|
|
|
127
127
|
validationRegisteredAt?: Readonly<Record<string, string>>;
|
|
128
128
|
};
|
|
129
129
|
evidence: readonly GoalEvidence[];
|
|
130
|
+
/** Effective check registration, separate from the immutable admitted contract. */
|
|
131
|
+
validationRegistration?: {
|
|
132
|
+
validation: readonly string[];
|
|
133
|
+
cwd: Readonly<Record<string, string>>;
|
|
134
|
+
registeredAt: Readonly<Record<string, string>>;
|
|
135
|
+
checks: import("../plugin/runtime-bridge.js").ReviewerCorrectionCheck[];
|
|
136
|
+
};
|
|
130
137
|
/** Implementation in the existing controller session, without a native child Task. */
|
|
131
138
|
directExecution?: {
|
|
132
139
|
actor: string;
|
|
@@ -138,6 +145,7 @@ interface UnitState {
|
|
|
138
145
|
previousResultClass: string | null;
|
|
139
146
|
};
|
|
140
147
|
checks: import("../plugin/runtime-bridge.js").ReviewerCorrectionCheck[];
|
|
148
|
+
validation?: readonly string[];
|
|
141
149
|
validationCwd?: Readonly<Record<string, string>>;
|
|
142
150
|
validationRegisteredAt?: Readonly<Record<string, string>>;
|
|
143
151
|
};
|
|
@@ -283,6 +291,10 @@ export interface OperatorProgress {
|
|
|
283
291
|
export declare function cancelledMissionRetainsAcceptance(state: OperatorState): boolean;
|
|
284
292
|
export declare function operatorGitPathAuthorized(path: string, scopes: readonly string[], platform?: NodeJS.Platform): boolean;
|
|
285
293
|
export declare function parseOperatorPlan(value: unknown, scopeFormat?: "repository" | "execution"): OperatorPlan;
|
|
294
|
+
export declare const unitValidation: (unit: OperatorState["units"][number]) => readonly string[];
|
|
295
|
+
export declare const unitValidationCwd: (unit: OperatorState["units"][number]) => Readonly<Record<string, string>> | undefined;
|
|
296
|
+
/** Strip only the owning unit's registration fields when comparing its frozen plan. */
|
|
297
|
+
export declare function directValidationRegistrationPlanHash(state: OperatorState, plan: OperatorPlan, unitID: string): string;
|
|
286
298
|
/** Root-owned durable queue. It never executes code, changes acceptance, or accepts a candidate. */
|
|
287
299
|
export declare class OperatorRuntime {
|
|
288
300
|
readonly profile: RuntimeProfile;
|
|
@@ -260,6 +260,23 @@ export function parseOperatorPlan(value, scopeFormat = "repository") {
|
|
|
260
260
|
expandGoalDeclaration(value.goal_declaration);
|
|
261
261
|
return { ...value, acceptance: normalizeAcceptanceCriteria(value.acceptance) };
|
|
262
262
|
}
|
|
263
|
+
export const unitValidation = (unit) => unit.validationRegistration?.validation ?? unit.unit.validation;
|
|
264
|
+
export const unitValidationCwd = (unit) => unit.validationRegistration?.cwd ?? unit.directExecution?.validationCwd ?? unit.unit.validation_cwd;
|
|
265
|
+
/** Strip only the owning unit's registration fields when comparing its frozen plan. */
|
|
266
|
+
export function directValidationRegistrationPlanHash(state, plan, unitID) {
|
|
267
|
+
const original = state.units.find(item => item.unit.id === unitID)?.unit;
|
|
268
|
+
const proposed = plan.units.find(item => item.id === unitID);
|
|
269
|
+
const criteria = plan.goal_declaration.criteria;
|
|
270
|
+
const goal = Array.isArray(criteria) ? { ...plan.goal_declaration, criteria: criteria.map(criterion => record(criterion) && criterion.criterion_id === unitID && criterion.validation_command === proposed?.validation.at(-1)
|
|
271
|
+
? { ...criterion, validation_command: original?.validation.at(-1) } : criterion) } : plan.goal_declaration;
|
|
272
|
+
return hash(JSON.stringify({ ...plan, goal_declaration: goal, units: plan.units.map((next, index) => {
|
|
273
|
+
const prior = state.units[index]?.unit;
|
|
274
|
+
if (!prior || prior.id !== unitID || next.id !== unitID)
|
|
275
|
+
return next;
|
|
276
|
+
const { validation_cwd: _cwd, ...rest } = next;
|
|
277
|
+
return { ...rest, validation: prior.validation, ...(prior.validation_cwd ? { validation_cwd: prior.validation_cwd } : {}) };
|
|
278
|
+
}) }));
|
|
279
|
+
}
|
|
263
280
|
/** Root-owned durable queue. It never executes code, changes acceptance, or accepts a candidate. */
|
|
264
281
|
export class OperatorRuntime {
|
|
265
282
|
profile;
|
|
@@ -766,26 +783,22 @@ export class OperatorRuntime {
|
|
|
766
783
|
unit.directExecution.actor !== actor || unit.directExecution.finishedAt || unit.reviewerCorrection ||
|
|
767
784
|
plan.units.length !== state.units.length)
|
|
768
785
|
throw new Error("mission-validation-registration-owner-mismatch");
|
|
769
|
-
|
|
770
|
-
const prior = state.units[i].unit;
|
|
771
|
-
if (prior.validation_cwd)
|
|
772
|
-
return { ...next, validation_cwd: prior.validation_cwd };
|
|
773
|
-
const { validation_cwd: _cwd, ...rest } = next;
|
|
774
|
-
return rest;
|
|
775
|
-
}) };
|
|
776
|
-
if (hash(JSON.stringify(original)) !== state.planHash || plan.units.some((next, i) => next.id !== unit.unit.id && JSON.stringify(next.validation_cwd) !== JSON.stringify(state.units[i].unit.validation_cwd))) {
|
|
786
|
+
if (directValidationRegistrationPlanHash(state, plan, unit.unit.id) !== state.planHash) {
|
|
777
787
|
throw new Error("mission-validation-registration-contract-mismatch");
|
|
778
788
|
}
|
|
779
|
-
const
|
|
780
|
-
const
|
|
781
|
-
|
|
789
|
+
const next = plan.units.find(next => next.id === unit.unit.id);
|
|
790
|
+
const validation = next.validation.map(normalizeCommand), cwd = next.validation_cwd ?? {};
|
|
791
|
+
const previousValidation = unitValidation(unit), previousCwd = unitValidationCwd(unit) ?? {};
|
|
792
|
+
if (JSON.stringify(validation) !== JSON.stringify(previousValidation) || JSON.stringify(cwd) !== JSON.stringify(previousCwd)) {
|
|
782
793
|
// Keep old checks as history, never attach a new recipe to their old successes.
|
|
783
794
|
const registeredAt = { ...unit.directExecution.validationRegisteredAt }, now = new Date().toISOString();
|
|
784
|
-
for (const command of
|
|
785
|
-
if (
|
|
795
|
+
for (const command of validation) {
|
|
796
|
+
if (previousValidation.filter(item => item === command).length !== validation.filter(item => item === command).length ||
|
|
797
|
+
resolve(this.projectRoot, cwd[command] ?? ".") !== resolve(this.projectRoot, previousCwd[command] ?? ".")) {
|
|
786
798
|
registeredAt[command] = now;
|
|
787
799
|
}
|
|
788
800
|
}
|
|
801
|
+
unit.validationRegistration = { validation, cwd, registeredAt, checks: unit.validationRegistration?.checks ?? [] };
|
|
789
802
|
unit.directExecution.validationCwd = cwd;
|
|
790
803
|
unit.directExecution.validationRegisteredAt = registeredAt;
|
|
791
804
|
await this.save(state);
|
|
@@ -837,9 +850,9 @@ export class OperatorRuntime {
|
|
|
837
850
|
return this.serial(root, async () => {
|
|
838
851
|
const state = await this.required(root);
|
|
839
852
|
const unit = state.units.find(item => /^task_id: (.+)$/mu.exec(item.task.prompt)?.[1] === taskID);
|
|
840
|
-
const execution = unit?.reviewerCorrection ?? unit?.directExecution;
|
|
853
|
+
const execution = unit?.reviewerCorrection ?? unit?.directExecution ?? unit?.validationRegistration;
|
|
841
854
|
if (!unit || !execution || unit.callID !== check.dispatchCallID || unit.childSessionID !== check.childSessionID ||
|
|
842
|
-
(unit.reviewerCorrection?.author ?? unit.directExecution?.actor) !== check.childSessionID)
|
|
855
|
+
(unit.reviewerCorrection?.author ?? unit.directExecution?.actor ?? unit.childSessionID) !== check.childSessionID)
|
|
843
856
|
throw new Error("mission-review-correction-check-owner-mismatch");
|
|
844
857
|
const checks = execution.checks ??= [];
|
|
845
858
|
if (checks.some(item => item.callID === check.callID))
|
|
@@ -906,8 +919,8 @@ export class OperatorRuntime {
|
|
|
906
919
|
plan.units.length !== 1 || plan.acceptance.length !== previous.acceptance.length ||
|
|
907
920
|
previous.acceptance.some((text, i) => text !== plan.acceptance[i]) ||
|
|
908
921
|
JSON.stringify(plan.acceptance_proof) !== JSON.stringify(previous.acceptanceProof) ||
|
|
909
|
-
JSON.stringify(plan.units[0].validation) !== JSON.stringify(previous.units.flatMap(
|
|
910
|
-
JSON.stringify(plan.units[0].validation_cwd ?? {}) !== JSON.stringify(Object.assign({}, ...previous.units.map(item => item
|
|
922
|
+
JSON.stringify(plan.units[0].validation) !== JSON.stringify(previous.units.flatMap(unitValidation)) ||
|
|
923
|
+
JSON.stringify(plan.units[0].validation_cwd ?? {}) !== JSON.stringify(Object.assign({}, ...previous.units.map(item => unitValidationCwd(item) ?? {}))) ||
|
|
911
924
|
JSON.stringify(plan.source_refs) !== JSON.stringify(previous.sourceRefs) ||
|
|
912
925
|
plan.units.some(unit => unit.write.some(path => !this.pathAuthorized(path, mission.reviewerCorrection.writeUnion))))) {
|
|
913
926
|
throw new Error("mission-review-correction-contract-mismatch");
|
|
@@ -1467,14 +1480,15 @@ export class OperatorRuntime {
|
|
|
1467
1480
|
throw new Error("mission-direct-handoff-unavailable");
|
|
1468
1481
|
}
|
|
1469
1482
|
await this.verifyControls(unit);
|
|
1470
|
-
const segment = { ...structuredClone(unit.directExecution),
|
|
1483
|
+
const segment = { ...structuredClone(unit.directExecution), validation: unitValidation(unit),
|
|
1484
|
+
callID: unit.callID, handedOffAt: new Date().toISOString() };
|
|
1471
1485
|
// Keep the original controls immutable. Publish continuation context and the new
|
|
1472
1486
|
// Task reference together in the atomic run save, so a crash has no half-updated pins.
|
|
1473
1487
|
const continuation = { actor, unit_id: unit.unit.id, at: segment.handedOffAt,
|
|
1474
|
-
instruction: "Continue existing changes and remaining action. The authoritative handoff retains the exact original requirements. Reuse current source-bound checks; inspect missing facts only. Handoff itself is not validation.",
|
|
1488
|
+
instruction: "Continue existing changes and remaining action. The authoritative handoff retains the exact original requirements. This continuation's validation/validation_cwd is the effective registration, superseding only the frozen check recipe. Reuse current source-bound checks; inspect missing facts only. Handoff itself is not validation.",
|
|
1475
1489
|
continuity: context.continuity,
|
|
1476
1490
|
checks: segment.checks.map(({ callID, command, directory, exitCode, startedAt, endedAt }) => ({ callID, command, directory, exitCode, startedAt, endedAt })),
|
|
1477
|
-
|
|
1491
|
+
validation: segment.validation, validation_cwd: unitValidationCwd(unit), details_ref: this.statePath(root) };
|
|
1478
1492
|
unit.task = { ...unit.task, prompt: `${unit.task.prompt.replace(/\ndirect_handoff: .+$/mu, "")}\ndirect_handoff: ${JSON.stringify(continuation)}` };
|
|
1479
1493
|
(unit.directHandoffs ??= []).push(segment);
|
|
1480
1494
|
delete unit.directExecution;
|
|
@@ -1610,7 +1624,7 @@ export class OperatorRuntime {
|
|
|
1610
1624
|
}
|
|
1611
1625
|
async captureContractRepair(state, unit, error, command) {
|
|
1612
1626
|
const normalized = normalizeCommand(command);
|
|
1613
|
-
const commandIndex = unit.
|
|
1627
|
+
const commandIndex = unitValidation(unit).map(normalizeCommand).indexOf(normalized);
|
|
1614
1628
|
if (commandIndex < 0)
|
|
1615
1629
|
throw new Error("operator-contract-repair-validation-command-missing");
|
|
1616
1630
|
const paths = [...new Set(error.diagnostics.flatMap(item => item.repair_paths ?? []))].sort();
|
|
@@ -1630,7 +1644,7 @@ export class OperatorRuntime {
|
|
|
1630
1644
|
return { code: "operator-git-change-outside-write-union", unit_id: unit.unit.id,
|
|
1631
1645
|
diagnostics: structuredClone(error.diagnostics), diagnostics_truncated: error.diagnostics_truncated,
|
|
1632
1646
|
repair_generation: 0, mode, repair_fingerprint: repairFingerprint, files,
|
|
1633
|
-
remaining_validation: unit.
|
|
1647
|
+
remaining_validation: unitValidation(unit).slice(commandIndex).map(normalizeCommand) };
|
|
1634
1648
|
}
|
|
1635
1649
|
recordContractRepair(root, actor, error, command) {
|
|
1636
1650
|
return this.serial(root, async () => {
|
|
@@ -2368,7 +2382,7 @@ export class OperatorRuntime {
|
|
|
2368
2382
|
id: unit.unit.id, status: unit.status, child_session_id: unit.childSessionID, result_class: unit.resultClass,
|
|
2369
2383
|
task_ref: unit.status === "pending" ? (unit.repairValidation === null ? this.workerTask(state, unit).prompt : this.repairWorkerTask(state, unit).prompt) : null,
|
|
2370
2384
|
handoff_path: unit.handoffPath, operation_manifest_path: unit.manifestPath,
|
|
2371
|
-
scope_read: unit.unit.read, scope_write: unit.unit.write, validation: unit
|
|
2385
|
+
scope_read: unit.unit.read, scope_write: unit.unit.write, validation: unitValidation(unit),
|
|
2372
2386
|
acceptance_indices: unit.unit.acceptance_indices,
|
|
2373
2387
|
normal_remediation_used: unit.normalRemediationUsed ?? false,
|
|
2374
2388
|
...(unit.terminalRescue ? { terminal_rescue: { attempt_id: unit.terminalRescue.attempt_id,
|
|
@@ -2398,7 +2412,7 @@ export class OperatorRuntime {
|
|
|
2398
2412
|
unit_id: unit.unit.id, task_id: /^task_id: (.+)$/m.exec(unit.task.prompt)?.[1],
|
|
2399
2413
|
project_root: this.projectRoot, handoff_path: this.controlReference(unit.handoffPath), operation_manifest: this.controlReference(unit.manifestPath),
|
|
2400
2414
|
objective: unit.unit.objective, read: unit.unit.read, write: unit.unit.write,
|
|
2401
|
-
validation: unit
|
|
2415
|
+
validation: unitValidation(unit), acceptance: state.acceptance,
|
|
2402
2416
|
...(unit.repairValidation === null ? {} : { validation_only: true }),
|
|
2403
2417
|
})}\nThis is the durable assignment for this same Worker, including after compaction or reload. ` +
|
|
2404
2418
|
"A missing path in a summary does not mean the handoff is missing. Read the retained handoff/manifest using these project-relative paths without prepending project_root, and inspect the actual diff and outputs before claiming no edits or replaying work. " +
|
package/dist/plugin/index.js
CHANGED
|
@@ -41,7 +41,7 @@ import { CancellableChildLifecycle, DEFAULT_CHILD_DEADLINE_MS } from "../core/ch
|
|
|
41
41
|
import { FailureSwarmRuntime } from "../core/failure-swarm-runtime.js";
|
|
42
42
|
import { EvidenceCapsuleStore } from "../core/evidence-capsule.js";
|
|
43
43
|
import { RunFlightLedger, diagnosisContractHash } from "../core/run-flight-ledger.js";
|
|
44
|
-
import { GOAL_BOUND_METADATA_KEY, goalFingerprint, selectGoalDelivery, validGoalEvidence } from "../core/goal-bound.js";
|
|
44
|
+
import { GOAL_BOUND_METADATA_KEY, goalFingerprint, goalValidationCommand, selectGoalDelivery, validGoalEvidence } from "../core/goal-bound.js";
|
|
45
45
|
import { LUNA_FABRIC_MAX_ACTIVE } from "../core/luna-fabric-scheduler.js";
|
|
46
46
|
import { terminalRescueModel } from "./terminal-rescue-binding.js";
|
|
47
47
|
import { TerminalRescueRuntime } from "../core/terminal-rescue-runtime.js";
|
|
@@ -1381,7 +1381,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1381
1381
|
const rawCommand = args !== undefined && typeof args.command === "string" ? normalizeCommand(args.command) : undefined;
|
|
1382
1382
|
if (rawCommand === undefined || rawCommand.length === 0)
|
|
1383
1383
|
return;
|
|
1384
|
-
const correcting = !!direct || await input.runtimeBridge?.ownsReviewerCorrection?.(toolInput.sessionID) === true
|
|
1384
|
+
const correcting = !!direct || await input.runtimeBridge?.ownsReviewerCorrection?.(toolInput.sessionID) === true ||
|
|
1385
|
+
await input.runtimeBridge?.ownsValidationRegistration?.(toolInput.sessionID) === true;
|
|
1385
1386
|
const directory = dockerRequest?.cwd ?? resolve(input.directory, typeof args?.workdir === "string" ? args.workdir : ".");
|
|
1386
1387
|
// A check is proof only in its registered cwd. Snapshot the same admitted source
|
|
1387
1388
|
// recipe at actual native start; never retrofit it to older external-cwd history.
|
|
@@ -1413,7 +1414,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1413
1414
|
let reusedEvidence;
|
|
1414
1415
|
const unitID = authorization.taskID;
|
|
1415
1416
|
const repairResume = operatorContractRepairResumes.get(root);
|
|
1416
|
-
const criteria = goal.acceptance_contract?.criteria.filter((criterion) => (correction ? criterion
|
|
1417
|
+
const criteria = goal.acceptance_contract?.criteria.filter((criterion) => (correction ? goalValidationCommand(goal, criterion) !== undefined && correction.commands.includes(goalValidationCommand(goal, criterion)) : goalValidationCommand(goal, criterion) === rawCommand) &&
|
|
1417
1418
|
criterion.expected_outcome === "pass") ?? [];
|
|
1418
1419
|
const denyValidation = (reason) => {
|
|
1419
1420
|
goalValidationDefects.add(toolInput.sessionID);
|
|
@@ -1438,7 +1439,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1438
1439
|
if (goal.goal_id !== null && criteria.length > 0) {
|
|
1439
1440
|
const requestedFull = criteria.some((criterion) => criterion.proof_scope === "requested-full");
|
|
1440
1441
|
const coordinatorOwned = requestedFull && unitID !== undefined &&
|
|
1441
|
-
await input.runtimeBridge?.ownsCanonicalValidation?.(root, unitID, toolInput.sessionID, correction ? criteria[0]
|
|
1442
|
+
await input.runtimeBridge?.ownsCanonicalValidation?.(root, unitID, toolInput.sessionID, correction ? goalValidationCommand(goal, criteria[0]) : rawCommand) === true;
|
|
1442
1443
|
if (requestedFull && !coordinatorOwned)
|
|
1443
1444
|
throw denyValidation("owner-mismatch: coordinator-routing-unavailable");
|
|
1444
1445
|
const profile = loaded?.validationProfile ?? DEFAULT_PLUGIN_OPTIONS.validationProfile;
|
|
@@ -2485,7 +2486,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2485
2486
|
Date.parse(execution.startedAt) >= Date.parse(check.startedAt));
|
|
2486
2487
|
return laterFailure ? [] : observedHostGoalEvidence({ ...check, owner: "coordinator", outcome: "pass",
|
|
2487
2488
|
immutableRef: goalFingerprint({ direct_check: check }),
|
|
2488
|
-
correction: { taskID: reservation.unitID, dispatchCallID: check.dispatchCallID,
|
|
2489
|
+
correction: { taskID: reservation.unitID, dispatchCallID: check.dispatchCallID,
|
|
2490
|
+
commands: check.matchedOccurrences.map(item => check.command[item.member]) } }, state, reservation.unitID);
|
|
2489
2491
|
});
|
|
2490
2492
|
const hostEvidence = [...new Map((directEvidence ?? [...observedEvidence, ...inherited]).map(entry => [entry.evidence_id, entry])).values()];
|
|
2491
2493
|
// A worker may return after a user scope epoch changed. Settle its spend/reservation, but only
|
|
@@ -8426,6 +8428,21 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
8426
8428
|
sessionTaskIDs.set(child, taskID);
|
|
8427
8429
|
activateSession(child);
|
|
8428
8430
|
},
|
|
8431
|
+
registerDirectValidation: async (root, actor, criterionID, command, registeredAt) => {
|
|
8432
|
+
const direct = await input.runtimeBridge?.directMissionExecution?.(actor);
|
|
8433
|
+
if (!direct || direct.root !== root || !await input.runtimeBridge?.ownsCanonicalValidation?.(root, direct.taskID, actor, command)) {
|
|
8434
|
+
throw new Error("mission-validation-registration-owner-mismatch");
|
|
8435
|
+
}
|
|
8436
|
+
const ledger = await goalLedger(root), state = (await ledger.readGoal()).state;
|
|
8437
|
+
const reservation = state.outstanding_reservations.find(item => item.unit_id === direct.taskID && item.session_id === root);
|
|
8438
|
+
if (!reservation || state.phase !== "active" || !state.goal_id)
|
|
8439
|
+
throw new Error("mission-validation-registration-reservation-missing");
|
|
8440
|
+
const prior = state.validation_registrations?.[criterionID];
|
|
8441
|
+
if (prior?.unit_id === direct.taskID && prior.command === command && prior.registered_at === registeredAt)
|
|
8442
|
+
return;
|
|
8443
|
+
await ledger.appendGoal({ kind: "goal.validation-registered", at: new Date().toISOString(), goal_id: state.goal_id,
|
|
8444
|
+
reservation_id: reservation.reservation_id, unit_id: direct.taskID, criterion_id: criterionID, command, registered_at: registeredAt });
|
|
8445
|
+
},
|
|
8429
8446
|
activateDirectUnit: (root, actor, prompt, handoffPath) => serializeChatTransition(actor, async () => {
|
|
8430
8447
|
const direct = await input.runtimeBridge?.directMissionExecution?.(actor);
|
|
8431
8448
|
if (!direct || direct.root !== root)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type OperatorState } from "../core/operator-runtime.js";
|
|
2
2
|
import { type MissionEvidenceExcerpt, type MissionReviewScope, type OperatorMission } from "../core/operator-mission.js";
|
|
3
3
|
import { type RuntimeProfile } from "../core/runtime-profile.js";
|
|
4
|
-
import type { ReviewerCorrectionCheck } from "./runtime-bridge.js";
|
|
4
|
+
import type { DirectHandoffValidationCheck, ReviewerCorrectionCheck } from "./runtime-bridge.js";
|
|
5
5
|
export declare const validationDirectoryMatches: (commands: readonly string[], actual: string | undefined, directory?: string, directories?: Readonly<Record<string, string>>) => boolean;
|
|
6
6
|
/** Reviewer-facing evidence preserves execution/coverage; the host retains the full snapshot recipe. */
|
|
7
7
|
export declare function missionReviewValidation(run: OperatorState, statePath: string): {
|
|
@@ -125,7 +125,7 @@ export declare function reviewerCorrectionValidationFresh(validation: readonly s
|
|
|
125
125
|
nextOccurrence?: number;
|
|
126
126
|
}>;
|
|
127
127
|
/** Preserve a current ordered prefix while the next author executes the remaining recipe. */
|
|
128
|
-
export declare function directHandoffValidationChecks(...args: Parameters<typeof reviewerCorrectionValidationFresh>): Promise<readonly
|
|
128
|
+
export declare function directHandoffValidationChecks(...args: Parameters<typeof reviewerCorrectionValidationFresh>): Promise<readonly DirectHandoffValidationCheck[]>;
|
|
129
129
|
/** Summary-only native reader: unavailable API/error is not a successfully observed empty history. */
|
|
130
130
|
export declare function observedMissionValidationSummary(validation: readonly string[], childSessionID: string, read?: () => Promise<unknown>, notBefore?: number, directory?: string, directories?: Readonly<Record<string, string>>): Promise<Record<string, unknown>>;
|
|
131
131
|
export declare function missionReviewBaseline(directory: string): Promise<string | undefined>;
|
|
@@ -5,6 +5,7 @@ import { lstat, readlink, readdir } from "node:fs/promises";
|
|
|
5
5
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
8
|
+
import { unitValidation, unitValidationCwd } from "../core/operator-runtime.js";
|
|
8
9
|
import { TOOL_ENVIRONMENT } from "../runtime-mission-assets.js";
|
|
9
10
|
import { MISSION_REVIEW_REFERENCE, missionReviewScope } from "../core/operator-mission.js";
|
|
10
11
|
import { canonicalAgent } from "../core/runtime-profile.js";
|
|
@@ -39,9 +40,9 @@ export const validationDirectoryMatches = (commands, actual, directory, director
|
|
|
39
40
|
export function missionReviewValidation(run, statePath) {
|
|
40
41
|
return run.units.map(unit => ({
|
|
41
42
|
unit_id: unit.unit.id,
|
|
42
|
-
command: unit
|
|
43
|
-
...(unit
|
|
44
|
-
validation_cwd: unit
|
|
43
|
+
command: unitValidation(unit),
|
|
44
|
+
...(unitValidationCwd(unit) ? {
|
|
45
|
+
validation_cwd: unitValidationCwd(unit),
|
|
45
46
|
} : {}),
|
|
46
47
|
evidence: unit.evidence.map(({ protected_binding, ...evidence }) => ({
|
|
47
48
|
...evidence,
|
|
@@ -381,7 +382,11 @@ export async function directHandoffValidationChecks(...args) {
|
|
|
381
382
|
for (let count = validation.length; count > 0; count--) {
|
|
382
383
|
const result = await reviewerCorrectionValidationFresh(validation.slice(0, count), child, history, started, checks, root, directories, registeredAt);
|
|
383
384
|
if (result.ready)
|
|
384
|
-
return checks.
|
|
385
|
+
return checks.flatMap(check => {
|
|
386
|
+
const matchedOccurrences = result.matched?.filter(item => item.callID === check.callID)
|
|
387
|
+
.map(({ member, occurrence }) => ({ member, occurrence })) ?? [];
|
|
388
|
+
return matchedOccurrences.length ? [{ ...check, matchedOccurrences }] : [];
|
|
389
|
+
});
|
|
385
390
|
}
|
|
386
391
|
return [];
|
|
387
392
|
}
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { V010_RUNTIME_ASSET_VERSION } from "../asset-version.js";
|
|
2
2
|
import { MISSION_BEHAVIOR_REVIEW, MISSION_GIT_SCOPE, REVIEWER_VALIDATION_WORKFLOW } from "../runtime-mission-assets.js";
|
|
3
|
-
import { cancelledMissionRetainsAcceptance, operatorGitPathAuthorized, OperatorContractError, OperatorRuntime } from "../core/operator-runtime.js";
|
|
3
|
+
import { cancelledMissionRetainsAcceptance, directValidationRegistrationPlanHash, unitValidation, unitValidationCwd, operatorGitPathAuthorized, OperatorContractError, OperatorRuntime } from "../core/operator-runtime.js";
|
|
4
4
|
import { DEFAULT_OPERATOR_PROPOSAL_BUDGET, OPERATOR_APPROVAL_CONTRACT, OPERATOR_PROPOSAL_BUDGET_CAPS, OPERATOR_PROPOSAL_REVISION_CONTRACT, OperatorProposalBudgetError, OperatorProposalRuntime } from "../core/operator-proposal.js";
|
|
5
5
|
import { CANONICAL_AGENT_ROLES, canonicalAgent, profileAgent, profileTool, V010_RUNTIME_PROFILE } from "../core/runtime-profile.js";
|
|
6
6
|
import { SortieDogsPlugin as canonicalPlugin, realAttachmentParts } from "./index.js";
|
|
@@ -256,7 +256,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
256
256
|
const method = typeof nativeSession?.reviewMessages === "function" ? "reviewMessages" : "messages";
|
|
257
257
|
return observedMissionValidationSummary(validation, child, typeof nativeSession?.[method] === "function"
|
|
258
258
|
? () => session(method, { path: { id: child }, query: { directory: input.directory,
|
|
259
|
-
...(notBefore === undefined ? {} : { notBefore: new Date(notBefore).toISOString() }) } }) : undefined, notBefore, notBefore === undefined ? undefined : input.directory, direct
|
|
259
|
+
...(notBefore === undefined ? {} : { notBefore: new Date(notBefore).toISOString() }) } }) : undefined, notBefore, notBefore === undefined ? undefined : input.directory, direct ? unitValidationCwd(direct.unit) : undefined);
|
|
260
260
|
}
|
|
261
261
|
async function reviewMessages(id, notBefore, requireAvailable = false) {
|
|
262
262
|
const method = typeof nativeSession?.reviewMessages === "function" ? "reviewMessages" : "messages";
|
|
@@ -498,6 +498,34 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
498
498
|
return undefined;
|
|
499
499
|
return unit ? { root, mission, run, unit } : undefined;
|
|
500
500
|
}
|
|
501
|
+
async function registeredValidation(id) {
|
|
502
|
+
const root = await rootFor(id);
|
|
503
|
+
if (!root)
|
|
504
|
+
return undefined;
|
|
505
|
+
const mission = await missions.read(root), run = await operators.read(root);
|
|
506
|
+
if (!mission || !run || mission.runID !== run.runID || run.phase !== "running" ||
|
|
507
|
+
["completed", "cancelled"].includes(mission.phase))
|
|
508
|
+
return undefined;
|
|
509
|
+
const unit = run.units.find(item => item.status === "running" && item.childSessionID === id && item.validationRegistration);
|
|
510
|
+
return unit ? { root, mission, run, unit } : undefined;
|
|
511
|
+
}
|
|
512
|
+
async function remainingRegisteredValidation(root, unit) {
|
|
513
|
+
const validation = unitValidation(unit);
|
|
514
|
+
if (unit.directExecution || !unit.validationRegistration || !unit.callID)
|
|
515
|
+
return validation;
|
|
516
|
+
const taskID = /^task_id: (.+)$/mu.exec(unit.task.prompt)?.[1];
|
|
517
|
+
const inherited = taskID ? await runtimeBridge.missionHandoffChecks(root, taskID, unit.callID) : [];
|
|
518
|
+
let prefix = inherited.reduce((count, check) => count + check.matchedOccurrences.length, 0);
|
|
519
|
+
// A new author's actual retry supersedes that inherited occurrence, including
|
|
520
|
+
// a later failure. Never let an earlier handed-off PASS hide this native result.
|
|
521
|
+
for (const check of unit.validationRegistration.checks)
|
|
522
|
+
for (const command of check.command) {
|
|
523
|
+
const occurrence = validation.indexOf(command);
|
|
524
|
+
if (occurrence >= 0)
|
|
525
|
+
prefix = Math.min(prefix, occurrence);
|
|
526
|
+
}
|
|
527
|
+
return validation.slice(prefix);
|
|
528
|
+
}
|
|
501
529
|
/**
|
|
502
530
|
* Resolve the owning root of a proposal investigation child for prompt assembly only.
|
|
503
531
|
*
|
|
@@ -644,14 +672,14 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
644
672
|
const unit = state.units.find(candidate => /^task_id: (.+)$/m.exec(candidate.task.prompt)?.[1] === taskID);
|
|
645
673
|
const active = unit?.status === "running" || (unit?.status === "failed" && unit.repairValidation !== null);
|
|
646
674
|
return active && unit.childSessionID === child &&
|
|
647
|
-
unit.
|
|
675
|
+
unitValidation(unit).some(candidate => normalizeCommand(candidate) === command);
|
|
648
676
|
},
|
|
649
677
|
beforeValidationSnapshot: (root, child, command) => operators.beforePostCommitValidation(root, child, command),
|
|
650
678
|
requiresValidationExecution: async (root, taskID, child, commands) => {
|
|
651
679
|
const run = await operators.read(root);
|
|
652
680
|
const unit = run?.units.find(item => item.status === "running" && item.childSessionID === child &&
|
|
653
681
|
/^task_id: (.+)$/mu.exec(item.task.prompt)?.[1] === taskID);
|
|
654
|
-
return !!unit && commands.some(command => unit.
|
|
682
|
+
return !!unit && commands.some(command => unitValidation(unit).filter(item => normalizeCommand(item) === command).length > 1);
|
|
655
683
|
},
|
|
656
684
|
ownsMissionDispatch: async (root, callID, taskID) => {
|
|
657
685
|
const mission = await missions.read(root), state = await operators.read(root);
|
|
@@ -661,6 +689,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
661
689
|
/^task_id: (.+)$/m.exec(unit.task.prompt)?.[1] === taskID);
|
|
662
690
|
},
|
|
663
691
|
ownsReviewerCorrection: async (child) => !!await reviewerCorrection(child),
|
|
692
|
+
ownsValidationRegistration: async (child) => !!await registeredValidation(child),
|
|
664
693
|
directMissionExecution: async (actor) => {
|
|
665
694
|
const direct = await directExecution(actor);
|
|
666
695
|
const taskID = direct && /^task_id: (.+)$/mu.exec(direct.unit.task.prompt)?.[1];
|
|
@@ -683,21 +712,24 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
683
712
|
return [];
|
|
684
713
|
try {
|
|
685
714
|
const history = await reviewMessages(prior.actor, prior.startedAt, true);
|
|
686
|
-
return await directHandoffValidationChecks(unit
|
|
715
|
+
return await directHandoffValidationChecks(unitValidation(unit), prior.actor, history, Date.parse(prior.startedAt), prior.checks, input.directory, prior.validationCwd ?? unit.unit.validation_cwd, prior.validationRegisteredAt);
|
|
687
716
|
}
|
|
688
717
|
catch {
|
|
689
718
|
return [];
|
|
690
719
|
}
|
|
691
720
|
},
|
|
692
721
|
reviewerCorrectionValidationMembers: async (child, command, directory) => {
|
|
693
|
-
const correction = await reviewerCorrection(child) ?? await directExecution(child);
|
|
722
|
+
const correction = await reviewerCorrection(child) ?? await directExecution(child) ?? await registeredValidation(child);
|
|
694
723
|
if (!correction)
|
|
695
724
|
return undefined;
|
|
696
725
|
const unit = correction.unit;
|
|
697
|
-
const directories = unit
|
|
726
|
+
const directories = unitValidationCwd(unit);
|
|
698
727
|
const admittedAt = unit.reviewerCorrection?.admittedAt ?? unit.directExecution?.startedAt ?? correction.run.createdAt;
|
|
699
|
-
const
|
|
700
|
-
const
|
|
728
|
+
const validation = await remainingRegisteredValidation(correction.root, unit);
|
|
729
|
+
const progress = reviewerCorrectionValidation(validation, child, await reviewMessages(child, admittedAt), Date.parse(admittedAt), unit.reviewerCorrection?.checks ?? unit.directExecution?.checks ?? unit.validationRegistration?.checks ?? [], input.directory, directories, unit.reviewerCorrection?.validationRegisteredAt ?? unit.directExecution?.validationRegisteredAt ?? unit.validationRegistration?.registeredAt);
|
|
730
|
+
// Progress is relative to the remaining suffix; native matching uses the
|
|
731
|
+
// full declaration so a retry of an inherited occurrence is captured too.
|
|
732
|
+
const members = canonicalObservedValidationMembers(command, unitValidation(unit), unitValidation(unit).length - validation.length + (progress.nextOccurrence ?? 0));
|
|
701
733
|
return members && validationDirectoryMatches(members, directory, input.directory, directories) ? members : undefined;
|
|
702
734
|
},
|
|
703
735
|
recordReviewerCorrectionCheck: (root, taskID, check) => operators.recordReviewerCorrectionCheck(root, taskID, check),
|
|
@@ -706,18 +738,21 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
706
738
|
// Recovery also compares failed units after their native Task has ended.
|
|
707
739
|
const unit = run?.units.find(item => item.childSessionID === child &&
|
|
708
740
|
/^task_id: (.+)$/mu.exec(item.task.prompt)?.[1] === taskID);
|
|
709
|
-
return !unit || validationDirectoryMatches(commands, directory, input.directory, unit
|
|
741
|
+
return !unit || validationDirectoryMatches(commands, directory, input.directory, unitValidationCwd(unit));
|
|
710
742
|
},
|
|
711
743
|
reviewerCorrectionValidation: async (root, callID, child, startedAt) => {
|
|
712
744
|
const run = await operators.read(root);
|
|
713
745
|
const unit = run?.units.find(item => item.callID === callID && item.childSessionID === child &&
|
|
714
|
-
(item.reviewerCorrection?.author === child || item.directExecution?.actor === child));
|
|
746
|
+
(item.reviewerCorrection?.author === child || item.directExecution?.actor === child || item.validationRegistration));
|
|
715
747
|
if (!unit)
|
|
716
748
|
return undefined;
|
|
717
749
|
// Compaction removes earlier tools from session.context, not from native history.
|
|
718
750
|
// Reuse the admission-window reader; saved checks still require their live input binding.
|
|
719
751
|
try {
|
|
720
|
-
|
|
752
|
+
const validation = await remainingRegisteredValidation(root, unit);
|
|
753
|
+
if (!validation.length && unitValidation(unit).length)
|
|
754
|
+
return { ready: true, matched: [] };
|
|
755
|
+
return await reviewerCorrectionValidationFresh(validation, child, await reviewMessages(child, new Date(startedAt).toISOString()), startedAt, unit.reviewerCorrection?.checks ?? unit.directExecution?.checks ?? unit.validationRegistration?.checks ?? [], input.directory, unitValidationCwd(unit), unit.reviewerCorrection?.validationRegisteredAt ?? unit.directExecution?.validationRegisteredAt ?? unit.validationRegistration?.registeredAt);
|
|
721
756
|
}
|
|
722
757
|
catch {
|
|
723
758
|
return { ready: false, reason: "mission-review-correction-validation-history-unavailable" };
|
|
@@ -1920,7 +1955,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1920
1955
|
const gapSummary = reviewGaps?.replace(/^EVIDENCE_GAPS\s*/u, "").replace(/\s+/gu, " ").slice(0, 500);
|
|
1921
1956
|
const text = withMissionOperationOutcome(`✅ **DONE** \`${state.runID}\` — declared checks passed; Operator accepted the result.`, mission) + `\n\n` +
|
|
1922
1957
|
`**${t("変更点", "Changes")}:** ${state.units.map(unit => unit.unit.title).join("; ")}\n\n` +
|
|
1923
|
-
`**${t("確認結果", "Validation")}:** ${t("宣言検証合格", "Declared checks passed")} — ${[...new Set(state.units.flatMap(
|
|
1958
|
+
`**${t("確認結果", "Validation")}:** ${t("宣言検証合格", "Declared checks passed")} — ${[...new Set(state.units.flatMap(unitValidation))].join("; ")}` +
|
|
1924
1959
|
(mission?.review ? `\n${t("レビュー", "Review")}: ${mission.review.verdict === "self-rechecked" ? t("修正著者の自己再確認(独立PASSではない)", "Author self-rechecked (not independent PASS)") : mission.review.verdict === "evidence-gaps" ? t("証拠不足を残して受入れ(レビューPASSではない)", "Accepted with evidence gaps (not Review PASS)") : mission.review.verdict}.` : "") +
|
|
1925
1960
|
(mission?.deliveryObservation?.head ? `\nCommit: ${mission.deliveryObservation.head} (${mission.deliveryObservation.branch ?? "detached"}; clean=${mission.deliveryObservation.clean})` : "") +
|
|
1926
1961
|
(reviewGaps ? `\n\n**${t("レビュー補足", "Review note")}:** ${gapSummary}\n\n**${t("次", "Next")}:** ${t("なし(補足だけを理由に再レビュー不要)", "None (no repeat review solely for the advisory)")}`
|
|
@@ -2008,8 +2043,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2008
2043
|
recovered.set(unit.unit.id, await control.recoverUnitEvidence(context.sessionID, { unitID: taskID,
|
|
2009
2044
|
childSessionID: direct.actor, manifestPath: unit.manifestPath, manifestHash: unit.hashes[1], goalFingerprint,
|
|
2010
2045
|
direct: { callID: unit.callID, startedAt: direct.startedAt, finishedAt: direct.finishedAt,
|
|
2011
|
-
checks: unit.reviewerCorrection?.checks ?? direct.checks, validation: unit
|
|
2012
|
-
directories:
|
|
2046
|
+
checks: unit.reviewerCorrection?.checks ?? direct.checks, validation: unitValidation(unit),
|
|
2047
|
+
directories: unitValidationCwd(unit),
|
|
2013
2048
|
registeredAt: unit.reviewerCorrection?.validationRegisteredAt ?? direct.validationRegisteredAt } }));
|
|
2014
2049
|
recoveredDirect.add(unit.unit.id);
|
|
2015
2050
|
continue;
|
|
@@ -2284,9 +2319,18 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2284
2319
|
} };
|
|
2285
2320
|
const startMission = `${profile.toolPrefix}start_mission`, planUnits = `${profile.toolPrefix}plan_units`, reviewMission = `${profile.toolPrefix}review_mission`, repairReview = `${profile.toolPrefix}repair_review`, submitMission = `${profile.toolPrefix}submit_mission`, completeMission = `${profile.toolPrefix}complete_mission`, expandUnit = `${profile.toolPrefix}expand_unit`, skipMissionConsultation = `${profile.toolPrefix}skip_mission_consultation`, extendMissionBudget = `${profile.toolPrefix}extend_mission_budget`;
|
|
2286
2321
|
const startDirectUnit = `${profile.toolPrefix}start_direct_unit`, finishDirectUnit = `${profile.toolPrefix}finish_direct_unit`;
|
|
2322
|
+
async function syncDirectValidation(direct) {
|
|
2323
|
+
if (!direct.unit.validationRegistration)
|
|
2324
|
+
return;
|
|
2325
|
+
const last = unitValidation(direct.unit).at(-1);
|
|
2326
|
+
await control.registerDirectValidation(direct.root, direct.unit.directExecution.actor, direct.unit.unit.id, last, direct.unit.directExecution.validationRegisteredAt?.[last] ?? direct.unit.directExecution.startedAt);
|
|
2327
|
+
}
|
|
2287
2328
|
async function activateDirect(actor) {
|
|
2288
2329
|
const direct = await directExecution(actor);
|
|
2289
2330
|
if (direct) {
|
|
2331
|
+
// Recover a crash between durable registration and its goal-ledger projection
|
|
2332
|
+
// before admitting another native check or selecting finish evidence.
|
|
2333
|
+
await syncDirectValidation(direct);
|
|
2290
2334
|
await control.activateDirectUnit(direct.root, actor, direct.unit.task.prompt, direct.unit.handoffPath);
|
|
2291
2335
|
}
|
|
2292
2336
|
return direct;
|
|
@@ -2308,7 +2352,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2308
2352
|
throw new Error("mission-direct-binding-unavailable: retain the admitted unit and restore its Mission binding");
|
|
2309
2353
|
await activateDirect(actor);
|
|
2310
2354
|
return JSON.stringify({ status: "direct-unit-running", run_id: direct.run.runID, unit_id: direct.unit.unit.id,
|
|
2311
|
-
executor_session_id: actor, validation: direct.unit
|
|
2355
|
+
executor_session_id: actor, validation: unitValidation(direct.unit),
|
|
2312
2356
|
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.` });
|
|
2313
2357
|
}
|
|
2314
2358
|
async function assertMissionRunBound(root) {
|
|
@@ -2317,10 +2361,45 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2317
2361
|
throw new Error("mission-run-registration-incomplete: retry plan_units with the same plan to finish goal registration and Mission binding; no unit admitted");
|
|
2318
2362
|
await operators.verifyContinuityControls(run);
|
|
2319
2363
|
}
|
|
2364
|
+
async function assertDirectPlanningQuiescent(actor, planningCallID) {
|
|
2365
|
+
await control.assertDirectUnitQuiescent(actor);
|
|
2366
|
+
// Cold hook state is not proof. Only the exact current planner may be pending;
|
|
2367
|
+
// a completed background launch or another Task remains an active/unproven writer.
|
|
2368
|
+
const history = payload(await session("messages", { path: { id: actor }, query: { directory: input.directory } }));
|
|
2369
|
+
if (!Array.isArray(history))
|
|
2370
|
+
throw new Error("mission-replan-terminal-unreconciled:tool_terminal_records_unavailable");
|
|
2371
|
+
const calls = new Map();
|
|
2372
|
+
for (const message of history) {
|
|
2373
|
+
if (!record(message) || !record(message.info) || message.info.sessionID !== actor || !Array.isArray(message.parts)) {
|
|
2374
|
+
throw new Error("mission-replan-terminal-unreconciled:tool_terminal_records_unavailable");
|
|
2375
|
+
}
|
|
2376
|
+
for (const part of message.parts) {
|
|
2377
|
+
if (!record(part) || typeof part.type !== "string")
|
|
2378
|
+
throw new Error("mission-replan-terminal-unreconciled:tool_terminal_records_unavailable");
|
|
2379
|
+
if (part.type !== "tool")
|
|
2380
|
+
continue;
|
|
2381
|
+
if (part.callID === planningCallID && planningCallID && part.tool === planUnits)
|
|
2382
|
+
continue;
|
|
2383
|
+
if (typeof part.callID !== "string")
|
|
2384
|
+
throw new Error("mission-replan-terminal-unreconciled:tool_identity_unavailable");
|
|
2385
|
+
const prior = calls.get(part.callID);
|
|
2386
|
+
if (prior && prior.tool !== part.tool)
|
|
2387
|
+
throw new Error("mission-replan-terminal-unreconciled:tool_identity_conflict");
|
|
2388
|
+
calls.set(part.callID, part);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
for (const part of calls.values()) {
|
|
2392
|
+
if (!record(part.state) || !["completed", "error"].includes(String(part.state.status)) ||
|
|
2393
|
+
record(part.state.metadata) && ["running", "pending", "unknown"].includes(String(part.state.metadata.status))) {
|
|
2394
|
+
throw new Error("mission-replan-terminal-unreconciled:tool_dispatch_active_or_unproven");
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2320
2398
|
async function handoffDirect(root, actor, callID, notes) {
|
|
2321
2399
|
const direct = await directExecution(actor);
|
|
2322
2400
|
if (direct) {
|
|
2323
2401
|
await control.assertDirectUnitQuiescent(actor);
|
|
2402
|
+
await syncDirectValidation(direct);
|
|
2324
2403
|
const history = await reviewMessages(actor, direct.unit.directExecution.startedAt, true);
|
|
2325
2404
|
const calls = new Map();
|
|
2326
2405
|
for (const message of history)
|
|
@@ -2390,7 +2469,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2390
2469
|
}
|
|
2391
2470
|
if (direct.unit.reviewerCorrection)
|
|
2392
2471
|
return JSON.stringify({ status: "correction-validated",
|
|
2393
|
-
execution: "same-native-task", validation: direct.unit
|
|
2472
|
+
execution: "same-native-task", validation: unitValidation(direct.unit),
|
|
2394
2473
|
next_action: 'Compare the original requirements, retained findings and affected public behavior in this same context, then finish THIS native Task with SELF_RECHECKED and self_recheck: {"candidate":"current-validated","unresolved_findings":[],"residual_major":null}. No handoff reread, second Task or Operator round trip. This is author self-recheck; only the actual successful native terminal can bind it, never this tool result.' });
|
|
2395
2474
|
const run = await operators.required(root);
|
|
2396
2475
|
// Full protected path arrays can make V2 truncate this one-line JSON to zero lines,
|
|
@@ -2975,25 +3054,23 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2975
3054
|
const same = previous?.planHash === createHash("sha256").update(JSON.stringify(plan)).digest("hex");
|
|
2976
3055
|
const replanning = previous && !["completed", "cancelled"].includes(previous.phase) && !same;
|
|
2977
3056
|
const activeDirect = previous?.units.find(unit => unit.status === "running" && unit.directExecution?.actor === actor);
|
|
2978
|
-
const
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
if (prior?.validation_cwd)
|
|
2984
|
-
return { ...unit, validation_cwd: prior.validation_cwd };
|
|
2985
|
-
const { validation_cwd: _cwd, ...rest } = unit;
|
|
2986
|
-
return rest;
|
|
2987
|
-
}) })).digest("hex");
|
|
3057
|
+
const nextRegistration = plan.units.find(unit => unit.id === activeDirect?.unit.id);
|
|
3058
|
+
const registrationChanged = activeDirect && (JSON.stringify(unitValidationCwd(activeDirect) ?? {}) !==
|
|
3059
|
+
JSON.stringify(nextRegistration?.validation_cwd ?? {}) ||
|
|
3060
|
+
JSON.stringify(unitValidation(activeDirect)) !== JSON.stringify(nextRegistration?.validation));
|
|
3061
|
+
const registrationPlanHash = previous && activeDirect && directValidationRegistrationPlanHash(previous, plan, activeDirect.unit.id);
|
|
2988
3062
|
if (previous && activeDirect && (registrationChanged || replanning && registrationPlanHash === previous.planHash)) {
|
|
2989
3063
|
if (!reason?.trim())
|
|
2990
3064
|
throw new Error("mission-replan-reason-required: name the validation registration correction");
|
|
2991
3065
|
if (execution !== undefined && !emptyImplementationExecution)
|
|
2992
3066
|
throw new Error("mission-validation-registration-operation-change-forbidden");
|
|
3067
|
+
await assertDirectPlanningQuiescent(actor, planningCallID);
|
|
2993
3068
|
const corrected = await operators.correctDirectValidationRegistration(root, actor, previous.runID, plan);
|
|
2994
3069
|
const unit = corrected.units.find(unit => unit.status === "running" && unit.directExecution?.actor === actor);
|
|
3070
|
+
const last = unitValidation(unit).at(-1);
|
|
3071
|
+
await control.registerDirectValidation(root, actor, unit.unit.id, last, unit.directExecution.validationRegisteredAt?.[last] ?? unit.directExecution.startedAt);
|
|
2995
3072
|
return JSON.stringify({ status: "direct-unit-registration-corrected", run_id: corrected.runID, unit_id: unit.unit.id,
|
|
2996
|
-
executor_session_id: actor, validation: unit
|
|
3073
|
+
executor_session_id: actor, validation: unitValidation(unit), validation_cwd: unitValidationCwd(unit),
|
|
2997
3074
|
registered_at: unit.directExecution.validationRegisteredAt,
|
|
2998
3075
|
next_action: `Run the affected declared checks in their registered cwd here, then ${finishDirectUnit}. Old observations remain historical; no new Task, terminal, reservation or benchmark attempt was created.` });
|
|
2999
3076
|
}
|
|
@@ -3019,38 +3096,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3019
3096
|
throw new Error(`mission-replan-terminal-unreconciled:${"reason" in terminal ? terminal.reason : "unknown"}`);
|
|
3020
3097
|
}
|
|
3021
3098
|
if (ownDirect) {
|
|
3022
|
-
|
|
3023
|
-
// a completed background launch still owns a running process. Only this exact planner
|
|
3024
|
-
// call may be pending while it ends its own admission.
|
|
3025
|
-
const history = payload(await session("messages", { path: { id: actor }, query: { directory: input.directory } }));
|
|
3026
|
-
if (!Array.isArray(history))
|
|
3027
|
-
throw new Error("mission-replan-terminal-unreconciled:tool_terminal_records_unavailable");
|
|
3028
|
-
const calls = new Map();
|
|
3029
|
-
for (const message of history) {
|
|
3030
|
-
if (!record(message) || !record(message.info) || message.info.sessionID !== actor || !Array.isArray(message.parts)) {
|
|
3031
|
-
throw new Error("mission-replan-terminal-unreconciled:tool_terminal_records_unavailable");
|
|
3032
|
-
}
|
|
3033
|
-
for (const part of message.parts) {
|
|
3034
|
-
if (!record(part) || typeof part.type !== "string")
|
|
3035
|
-
throw new Error("mission-replan-terminal-unreconciled:tool_terminal_records_unavailable");
|
|
3036
|
-
if (part.type !== "tool")
|
|
3037
|
-
continue;
|
|
3038
|
-
if (part.callID === planningCallID && planningCallID && part.tool === planUnits)
|
|
3039
|
-
continue;
|
|
3040
|
-
if (typeof part.callID !== "string")
|
|
3041
|
-
throw new Error("mission-replan-terminal-unreconciled:tool_identity_unavailable");
|
|
3042
|
-
const prior = calls.get(part.callID);
|
|
3043
|
-
if (prior && prior.tool !== part.tool)
|
|
3044
|
-
throw new Error("mission-replan-terminal-unreconciled:tool_identity_conflict");
|
|
3045
|
-
calls.set(part.callID, part);
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
for (const part of calls.values()) {
|
|
3049
|
-
if (!record(part.state) || !["completed", "error"].includes(String(part.state.status)) ||
|
|
3050
|
-
(record(part.state.metadata) && ["running", "pending", "unknown"].includes(String(part.state.metadata.status)))) {
|
|
3051
|
-
throw new Error("mission-replan-terminal-unreconciled:tool_dispatch_active_or_unproven");
|
|
3052
|
-
}
|
|
3053
|
-
}
|
|
3099
|
+
await assertDirectPlanningQuiescent(actor, planningCallID);
|
|
3054
3100
|
await activateDirect(actor);
|
|
3055
3101
|
await control.finishDirectUnit(root, actor, []);
|
|
3056
3102
|
previous = await operators.required(root);
|
|
@@ -3151,7 +3197,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3151
3197
|
handoff_context: handoffContextSchema,
|
|
3152
3198
|
reason: { type: "string", "x-sortie-optional": true },
|
|
3153
3199
|
purpose_decision: purposeDecisionSchema,
|
|
3154
|
-
executor: { type: "string", enum: ["worker", "self"], description: "Use self to implement/correct and formally validate here. Use worker
|
|
3200
|
+
executor: { type: "string", enum: ["worker", "self"], description: "Use self to implement/correct and formally validate here. Use executor=worker with units=\"\" in V2 (omit units in legacy; never units=[]) to yield the current direct unit, carrying remaining action in handoff_context; the same unit, edits, checks and budget continue. With units, worker/default retains configured Worker routing.", "x-sortie-optional": true } }, execute: async (args, context) => {
|
|
3155
3201
|
const { root, mission } = await missionAuthority(context.sessionID);
|
|
3156
3202
|
if (args.executor !== undefined && !["self", "worker"].includes(args.executor))
|
|
3157
3203
|
throw new Error("mission-executor-invalid");
|
|
@@ -3360,8 +3406,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3360
3406
|
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.`,
|
|
3361
3407
|
read: [...new Set([...(mission.reviewScope?.read ?? []), ...run.units.flatMap(unit => unit.unit.read)])],
|
|
3362
3408
|
write: [...new Set([...(mission.reviewScope?.write ?? []), ...run.units.flatMap(unit => unit.unit.write)])],
|
|
3363
|
-
validation: run.units.flatMap(
|
|
3364
|
-
validation_cwd: Object.assign({}, ...run.units.map(unit => unit
|
|
3409
|
+
validation: run.units.flatMap(unitValidation),
|
|
3410
|
+
validation_cwd: Object.assign({}, ...run.units.map(unit => unitValidationCwd(unit) ?? {})) }], input.directory);
|
|
3365
3411
|
// Inherit fixed declaration budgets and Git authority, never infer fresh capacity.
|
|
3366
3412
|
const goalPath = /^goal_declaration_path: (.+)$/mu.exec(run.units[0].task.prompt)?.[1];
|
|
3367
3413
|
if (!goalPath)
|
|
@@ -3369,7 +3415,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3369
3415
|
const declaration = JSON.parse(await readFile(goalPath, "utf8"));
|
|
3370
3416
|
const retainedPlan = { ...plan, acceptance: run.acceptance, acceptance_proof: run.acceptanceProof,
|
|
3371
3417
|
source_refs: run.sourceRefs, goal_declaration: declaration,
|
|
3372
|
-
units: [{ ...plan.units[0], validation: run.units.flatMap(
|
|
3418
|
+
units: [{ ...plan.units[0], validation: run.units.flatMap(unitValidation) }] };
|
|
3373
3419
|
const correctedPlan = run.gitLifecycle ? { ...retainedPlan, git_lifecycle: {
|
|
3374
3420
|
branch_create: { branch: run.gitLifecycle.branch, start_ref: run.gitLifecycle.startRef },
|
|
3375
3421
|
commit: { message: run.gitLifecycle.commitMessage }, post_commit_validation: run.gitLifecycle.postCommitValidation,
|
|
@@ -3520,9 +3566,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3520
3566
|
}).catch(() => []) : [];
|
|
3521
3567
|
const initialPrompt = initialMissionReviewPrompt(mission, completed);
|
|
3522
3568
|
const phase = initialPrompt ? "verification" : "initial";
|
|
3523
|
-
const observedValidation = risk.length === 0 && !correction ? [] : await Promise.all(run.units.filter(unit => unit.
|
|
3569
|
+
const observedValidation = risk.length === 0 && !correction ? [] : await Promise.all(run.units.filter(unit => unitValidation(unit).length > 0).map(async (unit) => ({
|
|
3524
3570
|
unit_id: unit.unit.id,
|
|
3525
|
-
...observedMissionValidation(unit
|
|
3571
|
+
...observedMissionValidation(unitValidation(unit), 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, unitValidationCwd(unit)),
|
|
3526
3572
|
})));
|
|
3527
3573
|
const task = risk.length === 0 && !correction && !selfRecheck && !independentRequested ? null : { subagent_type: profileAgent(profile, "dog-reviewer"),
|
|
3528
3574
|
...(reviewerID ? { task_id: reviewerID } : {}),
|
|
@@ -47,6 +47,13 @@ export interface ReviewerCorrectionCheck {
|
|
|
47
47
|
/** A command that actually generated/formatted declared outputs retains its stable input digest. */
|
|
48
48
|
readonly generatedInputs?: string;
|
|
49
49
|
}
|
|
50
|
+
/** Live projection of reusable occurrences; never rewrites the saved native check. */
|
|
51
|
+
export interface DirectHandoffValidationCheck extends ReviewerCorrectionCheck {
|
|
52
|
+
readonly matchedOccurrences: readonly {
|
|
53
|
+
readonly member: number;
|
|
54
|
+
readonly occurrence: number;
|
|
55
|
+
}[];
|
|
56
|
+
}
|
|
50
57
|
/** Exact durable Mission Task claim, not authority supplied by a tool argument or Read. */
|
|
51
58
|
export interface MissionImplementationAdmission {
|
|
52
59
|
readonly runID: string;
|
|
@@ -82,6 +89,8 @@ export interface RuntimeBridge {
|
|
|
82
89
|
/** Only an exact admitted correction Task is an implementation dispatch continuing the original Reviewer child. */
|
|
83
90
|
ownsReviewerCorrectionDispatch?(rootSessionID: string, callID: string, taskID: string): Promise<boolean>;
|
|
84
91
|
ownsReviewerCorrection?(childSessionID: string): Promise<boolean>;
|
|
92
|
+
/** Native check capture only; does not grant Reviewer correction or artifact authority. */
|
|
93
|
+
ownsValidationRegistration?(childSessionID: string): Promise<boolean>;
|
|
85
94
|
/** Existing controller executing a declared unit itself; never a fictitious native Task. */
|
|
86
95
|
directMissionExecution?(sessionID: string): Promise<{
|
|
87
96
|
root: string;
|
|
@@ -99,7 +108,7 @@ export interface RuntimeBridge {
|
|
|
99
108
|
endedAt: string;
|
|
100
109
|
}[];
|
|
101
110
|
} | undefined>;
|
|
102
|
-
missionHandoffChecks?(rootSessionID: string, taskID: string, callID: string): Promise<readonly
|
|
111
|
+
missionHandoffChecks?(rootSessionID: string, taskID: string, callID: string): Promise<readonly DirectHandoffValidationCheck[]>;
|
|
103
112
|
reviewerCorrectionValidationMembers?(childSessionID: string, command: string, directory?: string): Promise<string[] | undefined>;
|
|
104
113
|
missionValidationDirectoryMatches?(rootSessionID: string, taskID: string, childSessionID: string, commands: readonly string[], directory: string): Promise<boolean>;
|
|
105
114
|
recordReviewerCorrectionCheck?(rootSessionID: string, taskID: string, check: ReviewerCorrectionCheck): Promise<void>;
|
|
@@ -156,6 +165,7 @@ export interface RuntimeBridge {
|
|
|
156
165
|
currentTaskIDs: readonly string[];
|
|
157
166
|
}): Promise<void>;
|
|
158
167
|
registerGoalDeclaration(rootSessionID: string, prompt: string, missionRevision?: boolean): Promise<void>;
|
|
168
|
+
registerDirectValidation(rootSessionID: string, actor: string, criterionID: string, command: string, registeredAt: string): Promise<void>;
|
|
159
169
|
relinkRegisteredGoal(rootSessionID: string, request: {
|
|
160
170
|
prompt: string;
|
|
161
171
|
expectedFingerprint: string;
|
|
@@ -195,7 +195,8 @@ ${MISSION_DEPENDENCY_WORKFLOW}
|
|
|
195
195
|
an ordinary affirmative reply or a request to self-recover is enough. No hidden command or ID is
|
|
196
196
|
needed from the user, and ordinary autonomous recovery does not require a new approval.
|
|
197
197
|
Inspect operator_status to reconcile only native-proven ended work. If this session owns a quiescent
|
|
198
|
-
direct admission that cannot bind, plan_units with executor: "worker" and
|
|
198
|
+
direct admission that cannot bind, plan_units with executor: "worker" and units: "" in V2
|
|
199
|
+
(omit units on the legacy API; never units: []) yields that same
|
|
199
200
|
unit and reservation without acquiring the contested writer. Explain the result and continue the
|
|
200
201
|
returned Task when its write scope is available. A genuinely live or unproven other owner is not
|
|
201
202
|
released: wait or separate work, and do not dispatch repeated conflicting attempts. Do not invent
|
|
@@ -545,14 +546,16 @@ Use the Operator's preferred_executor as a starting recommendation. Start useful
|
|
|
545
546
|
do not investigate merely to confirm the recommendation. Adapt through actual work, honoring explicit user choices.
|
|
546
547
|
Use plan_units with executor: "self" for direct work, or start_direct_unit for an already prepared unit.
|
|
547
548
|
If a direct admission cannot bind because of manifest-overlap, operator_status and plan_units with
|
|
548
|
-
executor: "worker" and
|
|
549
|
+
executor: "worker" and units: "" in V2 (omit units on the legacy API; never units: []) remain
|
|
550
|
+
management operations: yield only this quiescent owner, retain
|
|
549
551
|
its checks and reservation, and wait for a real conflicting writer rather than evicting it. Continue
|
|
550
552
|
the same Mission and actual remaining work; do not create another Mission to clear the conflict.
|
|
551
553
|
The host binds your native check results to the candidate; finish_direct_unit closes that work without
|
|
552
554
|
a Worker handoff. Independent Review and Operator final acceptance still follow. This is not limited to
|
|
553
555
|
micro-edits. Delegate to the configured Worker when that reduces work/cost or provides useful separation.
|
|
554
556
|
There is no proposal/approval/contract-repair round trip in this route.
|
|
555
|
-
To hand unfinished direct work to a Worker, call plan_units with executor: "worker",
|
|
557
|
+
To hand unfinished direct work to a Worker, call plan_units with executor: "worker", units: "" in V2
|
|
558
|
+
(omit units on the legacy API; never units: []), and put
|
|
556
559
|
the remaining action/useful observations in handoff_context. The host transfers this same unit after current
|
|
557
560
|
native commands settle, retaining changes, checks and budget. Do not finish it as successful or replan just
|
|
558
561
|
to change its author. Dispatch the returned Task once; Operator recommendation does not prevent this choice.
|