@mstar-harness/engine 3.10.2 → 3.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.js +66 -5
- package/dist/coordination-write.d.ts +5 -3
- package/dist/coordination.d.ts +5 -0
- package/dist/engine.js +402 -23
- package/dist/workflow.d.ts +5 -1
- package/package.json +1 -1
package/dist/audit.js
CHANGED
|
@@ -279,7 +279,7 @@ function validatePlanProgress(value, what = "coordination.progress") {
|
|
|
279
279
|
}
|
|
280
280
|
return violations;
|
|
281
281
|
}
|
|
282
|
-
function validatePlanHandoff(value, what = "coordination.handoff") {
|
|
282
|
+
function validatePlanHandoff(value, what = "coordination.handoff", route = "integration") {
|
|
283
283
|
if (!isPlainObject2(value))
|
|
284
284
|
return [invalid("coordination.row.handoff-shape", `${what} must be an object`)];
|
|
285
285
|
const allowed = [
|
|
@@ -419,7 +419,25 @@ function validatePlanHandoff(value, what = "coordination.handoff") {
|
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
421
|
if ((value.state === "integrating" || value.state === "merged" || value.state === "completed") && value.integration === undefined) {
|
|
422
|
-
|
|
422
|
+
if (route === "standalone-development" && value.state === "completed") {
|
|
423
|
+
if (value.completed_at === undefined) {
|
|
424
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.state completed requires completed_at for a standalone handoff`));
|
|
425
|
+
}
|
|
426
|
+
if (!isNonEmptyString(value.accepted_at)) {
|
|
427
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.accepted_at is required for a standalone completed handoff`));
|
|
428
|
+
}
|
|
429
|
+
if (!isNonEmptyString(value.accepted_by)) {
|
|
430
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.accepted_by is required for a standalone completed handoff`));
|
|
431
|
+
}
|
|
432
|
+
if (value.qc === undefined) {
|
|
433
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qc is required for a standalone completed handoff`));
|
|
434
|
+
}
|
|
435
|
+
if (value.qa === undefined) {
|
|
436
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qa is required for a standalone completed handoff`));
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.state ${String(value.state)} requires integration`));
|
|
440
|
+
}
|
|
423
441
|
}
|
|
424
442
|
return violations;
|
|
425
443
|
}
|
|
@@ -455,7 +473,7 @@ function validatePreparedCoordination(value, what = "coordination.prepared") {
|
|
|
455
473
|
}
|
|
456
474
|
return violations;
|
|
457
475
|
}
|
|
458
|
-
function validateRowCoordination(value, what = "coordination") {
|
|
476
|
+
function validateRowCoordination(value, what = "coordination", route = "integration") {
|
|
459
477
|
if (!isPlainObject2(value))
|
|
460
478
|
return [invalid("coordination.row.shape", `${what} must be an object`)];
|
|
461
479
|
const allowed = ["revision", "prepared", "session", "progress", "handoff"];
|
|
@@ -474,7 +492,7 @@ function validateRowCoordination(value, what = "coordination") {
|
|
|
474
492
|
if (value.progress !== undefined)
|
|
475
493
|
violations.push(...validatePlanProgress(value.progress, `${what}.progress`));
|
|
476
494
|
if (value.handoff !== undefined)
|
|
477
|
-
violations.push(...validatePlanHandoff(value.handoff, `${what}.handoff
|
|
495
|
+
violations.push(...validatePlanHandoff(value.handoff, `${what}.handoff`, route));
|
|
478
496
|
if (value.handoff !== undefined && value.session === undefined) {
|
|
479
497
|
violations.push(invalid("coordination.row.handoff-field", `${what}.handoff requires a bound plan session`));
|
|
480
498
|
}
|
|
@@ -797,6 +815,45 @@ var WORKFLOW_TERMINAL_STATUSES = ["completed", "failed", "stopped"];
|
|
|
797
815
|
var WORKFLOW_LIFECYCLE_TYPES = ["plan", "iteration"];
|
|
798
816
|
var WORKFLOW_DELIVERY_KINDS = ["development", "verification/report-only"];
|
|
799
817
|
var WORKFLOW_COMPOUND_OUTCOMES = ["created", "updated", "skipped"];
|
|
818
|
+
function isStandaloneDevelopmentWorkflow(snapshot) {
|
|
819
|
+
return snapshot.type === "plan" && snapshot.delivery_kind === "development" && Array.isArray(snapshot.plans) && snapshot.plans.length === 1;
|
|
820
|
+
}
|
|
821
|
+
function rowValidationRoute(snapshot, row) {
|
|
822
|
+
if (isStandaloneDevelopmentWorkflow(snapshot) && snapshot.plans[0]?.id === row.id) {
|
|
823
|
+
return "standalone-development";
|
|
824
|
+
}
|
|
825
|
+
return "integration";
|
|
826
|
+
}
|
|
827
|
+
function validateStandaloneCompletedCoherence(snapshot, row) {
|
|
828
|
+
const violations = [];
|
|
829
|
+
if (!isStandaloneDevelopmentWorkflow(snapshot) || row.id !== snapshot.plans[0]?.id)
|
|
830
|
+
return violations;
|
|
831
|
+
const coordination = row.coordination;
|
|
832
|
+
if (!isPlainObject2(coordination) || !isPlainObject2(coordination.handoff))
|
|
833
|
+
return violations;
|
|
834
|
+
const handoff = coordination.handoff;
|
|
835
|
+
if (handoff.state !== "completed" || handoff.integration !== undefined)
|
|
836
|
+
return violations;
|
|
837
|
+
if (row.status !== "Done") {
|
|
838
|
+
violations.push(violation2("high", "coordination.row.handoff-field", `standalone completed handoff requires row ${String(row.id)} to be Done`));
|
|
839
|
+
}
|
|
840
|
+
if (row.execution_lease !== undefined) {
|
|
841
|
+
violations.push(violation2("high", "coordination.row.handoff-field", `standalone completed handoff requires no execution lease on row ${String(row.id)}`));
|
|
842
|
+
}
|
|
843
|
+
if (snapshot.integration_merge_lease !== undefined) {
|
|
844
|
+
violations.push(violation2("high", "coordination.row.handoff-field", "standalone completed handoff requires no integration_merge_lease on the snapshot"));
|
|
845
|
+
}
|
|
846
|
+
const source = snapshot.branch?.source;
|
|
847
|
+
const target = snapshot.branch?.target;
|
|
848
|
+
if (!isNonEmptyString(source) || !isNonEmptyString(target)) {
|
|
849
|
+
violations.push(violation2("high", "coordination.row.handoff-field", "standalone completed handoff requires nonblank branch.source and branch.target"));
|
|
850
|
+
} else {
|
|
851
|
+
if (handoff.source_branch !== source) {
|
|
852
|
+
violations.push(violation2("high", "coordination.row.handoff-field", `standalone completed handoff source_branch ${String(handoff.source_branch)} must equal branch.source ${source}`));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return violations;
|
|
856
|
+
}
|
|
800
857
|
function stableJson(value) {
|
|
801
858
|
if (Array.isArray(value))
|
|
802
859
|
return `[${value.map(stableJson).join(",")}]`;
|
|
@@ -916,13 +973,17 @@ function validateWorkflowSnapshot(doc) {
|
|
|
916
973
|
} else if (!Array.isArray(doc.plans)) {
|
|
917
974
|
violations.push(violation2("high", "workflow.snapshot.invalid-plans", "plans must be an array of legacy plan rows"));
|
|
918
975
|
} else {
|
|
976
|
+
const snapshotDoc = doc;
|
|
919
977
|
for (const row of doc.plans) {
|
|
920
978
|
violations.push(...validatePlanRow(row).violations);
|
|
921
979
|
if (isPlainObject2(row) && row.execution_lease !== undefined) {
|
|
922
980
|
violations.push(...validateExecutionLease(row.execution_lease).violations);
|
|
923
981
|
}
|
|
924
982
|
if (isPlainObject2(row) && row.coordination !== undefined) {
|
|
925
|
-
|
|
983
|
+
const planRow = row;
|
|
984
|
+
const route = rowValidationRoute(snapshotDoc, planRow);
|
|
985
|
+
violations.push(...validateRowCoordination(row.coordination, `plans[${String(row.id)}].coordination`, route));
|
|
986
|
+
violations.push(...validateStandaloneCompletedCoherence(snapshotDoc, planRow));
|
|
926
987
|
}
|
|
927
988
|
}
|
|
928
989
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ValidationResult } from "./core.js";
|
|
2
2
|
/** Stable refusal codes of the scoped coordination surface (spec §C4). */
|
|
3
|
-
export declare const COORDINATION_ERROR_CODES: readonly ["coordination.harness-not-found", "coordination.workflow-not-found", "coordination.plan-not-found", "coordination.scope-mismatch", "coordination.path-mismatch", "coordination.assignment-invalid", "coordination.assignment-stale", "coordination.not-prepared", "coordination.duplicate-holder", "coordination.session-mismatch", "coordination.session-not-found", "coordination.session-role", "coordination.version-conflict", "coordination.expected-version-required", "coordination.invalid-transition", "coordination.invalid-input", "coordination.forbidden-field", "coordination.not-in-git", "coordination.git-unavailable", "coordination.git-proof", "coordination.evidence-stale", "coordination.integration-unresolved", "coordination.integration-diverged", "coordination.local-store-required", "coordination.direct-write-refused", "coordination.scoped-writer-required", "coordination.unknown-operation", "coordination.store", "coordination.prepare-amendment.stale", "coordination.prepare-amendment.invalid-patch", "coordination.prepare-amendment.not-prepare", "coordination.prepare-amendment.execution-started", "coordination.prepare-amendment.duplicate-plan", "coordination.prepare-amendment.invalid-plan", "coordination.prepare-amendment.compass-mismatch", "coordination.prepare-amendment.invalid-worktree"];
|
|
3
|
+
export declare const COORDINATION_ERROR_CODES: readonly ["coordination.harness-not-found", "coordination.workflow-not-found", "coordination.plan-not-found", "coordination.scope-mismatch", "coordination.path-mismatch", "coordination.assignment-invalid", "coordination.assignment-stale", "coordination.not-prepared", "coordination.duplicate-holder", "coordination.session-mismatch", "coordination.session-not-found", "coordination.session-role", "coordination.version-conflict", "coordination.expected-version-required", "coordination.invalid-transition", "coordination.invalid-input", "coordination.forbidden-field", "coordination.not-in-git", "coordination.git-unavailable", "coordination.git-proof", "coordination.evidence-stale", "coordination.integration-unresolved", "coordination.integration-diverged", "coordination.local-store-required", "coordination.direct-write-refused", "coordination.scoped-writer-required", "coordination.unknown-operation", "coordination.store", "coordination.prepare-amendment.stale", "coordination.prepare-amendment.invalid-patch", "coordination.prepare-amendment.not-prepare", "coordination.prepare-amendment.execution-started", "coordination.prepare-amendment.duplicate-plan", "coordination.prepare-amendment.invalid-plan", "coordination.prepare-amendment.compass-mismatch", "coordination.prepare-amendment.invalid-worktree", "coordination.delivery-source-repair.unsupported-workflow", "coordination.delivery-source-repair.terminal", "coordination.delivery-source-repair.no-accepted-handoff", "coordination.delivery-source-repair.already-aligned", "coordination.delivery-source-repair.not-legacy-shape", "coordination.delivery-source-repair.pr-conflict"];
|
|
4
4
|
export type CoordinationErrorCode = (typeof COORDINATION_ERROR_CODES)[number];
|
|
5
5
|
/**
|
|
6
6
|
* Stable exception of the coordination surface: `code` is the consumer
|
|
@@ -156,12 +156,14 @@ export declare const HANDOFF_STATES: readonly HandoffState[];
|
|
|
156
156
|
export declare const PLAN_PROGRESS_STATUSES: readonly PlanProgressStatus[];
|
|
157
157
|
/** Validate a stored `PlanProgress` (`status`, `summary`, `evidence_paths`, `track_branches`). */
|
|
158
158
|
export declare function validatePlanProgress(value: unknown, what?: string): ValidationResult[];
|
|
159
|
+
/** Route-aware stored handoff validation (spec A5). Default remains strict integration. */
|
|
160
|
+
export type RowValidationRoute = "integration" | "standalone-development";
|
|
159
161
|
/** Validate a stored `PlanHandoff`, including its state/field coherence. */
|
|
160
|
-
export declare function validatePlanHandoff(value: unknown, what?: string): ValidationResult[];
|
|
162
|
+
export declare function validatePlanHandoff(value: unknown, what?: string, route?: RowValidationRoute): ValidationResult[];
|
|
161
163
|
/** Validate a stored `PreparedCoordination`. */
|
|
162
164
|
export declare function validatePreparedCoordination(value: unknown, what?: string): ValidationResult[];
|
|
163
165
|
/** Validate one plan row's `coordination` object (spec §C2). */
|
|
164
|
-
export declare function validateRowCoordination(value: unknown, what?: string): ValidationResult[];
|
|
166
|
+
export declare function validateRowCoordination(value: unknown, what?: string, route?: RowValidationRoute): ValidationResult[];
|
|
165
167
|
/** Validate a snapshot's top `coordination` block (spec §C2). */
|
|
166
168
|
export declare function validateSnapshotCoordination(value: unknown, what?: string): ValidationResult[];
|
|
167
169
|
/** Hash-pinned evidence reference for an absolute path, read from disk. */
|
package/dist/coordination.d.ts
CHANGED
|
@@ -197,6 +197,9 @@ export type PlanCoordinationOperation = {
|
|
|
197
197
|
} | {
|
|
198
198
|
kind: "complete";
|
|
199
199
|
handoffId: string;
|
|
200
|
+
} | {
|
|
201
|
+
kind: "repair-delivery-source";
|
|
202
|
+
handoffId: string;
|
|
200
203
|
} | {
|
|
201
204
|
kind: "reconcile";
|
|
202
205
|
handoffId: string;
|
|
@@ -272,6 +275,8 @@ export declare function bindPlanSession(input: BindPlanSessionInput): Promise<Co
|
|
|
272
275
|
* an unknown key anywhere is rejected before any state is touched.
|
|
273
276
|
*/
|
|
274
277
|
export declare function mutatePlanCoordination(request: CoordinationRequest): Promise<CoordinationResult>;
|
|
278
|
+
/** Test-only hook to observe the precheck→mutate gap in standalone complete. */
|
|
279
|
+
export declare function setCompleteStandaloneMutateGapForTest(callback: (() => void) | undefined): void;
|
|
275
280
|
/**
|
|
276
281
|
* Replace a coordinated artifact with an exact-version precondition (spec §B,
|
|
277
282
|
* §C4 line 156). Snapshot replacement goes through the canonical snapshot
|
package/dist/engine.js
CHANGED
|
@@ -623,7 +623,7 @@ function validatePlanProgress(value, what = "coordination.progress") {
|
|
|
623
623
|
}
|
|
624
624
|
return violations;
|
|
625
625
|
}
|
|
626
|
-
function validatePlanHandoff(value, what = "coordination.handoff") {
|
|
626
|
+
function validatePlanHandoff(value, what = "coordination.handoff", route = "integration") {
|
|
627
627
|
if (!isPlainObject2(value))
|
|
628
628
|
return [invalid("coordination.row.handoff-shape", `${what} must be an object`)];
|
|
629
629
|
const allowed = [
|
|
@@ -763,7 +763,25 @@ function validatePlanHandoff(value, what = "coordination.handoff") {
|
|
|
763
763
|
}
|
|
764
764
|
}
|
|
765
765
|
if ((value.state === "integrating" || value.state === "merged" || value.state === "completed") && value.integration === undefined) {
|
|
766
|
-
|
|
766
|
+
if (route === "standalone-development" && value.state === "completed") {
|
|
767
|
+
if (value.completed_at === undefined) {
|
|
768
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.state completed requires completed_at for a standalone handoff`));
|
|
769
|
+
}
|
|
770
|
+
if (!isNonEmptyString(value.accepted_at)) {
|
|
771
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.accepted_at is required for a standalone completed handoff`));
|
|
772
|
+
}
|
|
773
|
+
if (!isNonEmptyString(value.accepted_by)) {
|
|
774
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.accepted_by is required for a standalone completed handoff`));
|
|
775
|
+
}
|
|
776
|
+
if (value.qc === undefined) {
|
|
777
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qc is required for a standalone completed handoff`));
|
|
778
|
+
}
|
|
779
|
+
if (value.qa === undefined) {
|
|
780
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qa is required for a standalone completed handoff`));
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.state ${String(value.state)} requires integration`));
|
|
784
|
+
}
|
|
767
785
|
}
|
|
768
786
|
return violations;
|
|
769
787
|
}
|
|
@@ -799,7 +817,7 @@ function validatePreparedCoordination(value, what = "coordination.prepared") {
|
|
|
799
817
|
}
|
|
800
818
|
return violations;
|
|
801
819
|
}
|
|
802
|
-
function validateRowCoordination(value, what = "coordination") {
|
|
820
|
+
function validateRowCoordination(value, what = "coordination", route = "integration") {
|
|
803
821
|
if (!isPlainObject2(value))
|
|
804
822
|
return [invalid("coordination.row.shape", `${what} must be an object`)];
|
|
805
823
|
const allowed = ["revision", "prepared", "session", "progress", "handoff"];
|
|
@@ -818,7 +836,7 @@ function validateRowCoordination(value, what = "coordination") {
|
|
|
818
836
|
if (value.progress !== undefined)
|
|
819
837
|
violations.push(...validatePlanProgress(value.progress, `${what}.progress`));
|
|
820
838
|
if (value.handoff !== undefined)
|
|
821
|
-
violations.push(...validatePlanHandoff(value.handoff, `${what}.handoff
|
|
839
|
+
violations.push(...validatePlanHandoff(value.handoff, `${what}.handoff`, route));
|
|
822
840
|
if (value.handoff !== undefined && value.session === undefined) {
|
|
823
841
|
violations.push(invalid("coordination.row.handoff-field", `${what}.handoff requires a bound plan session`));
|
|
824
842
|
}
|
|
@@ -1331,6 +1349,45 @@ var WORKFLOW_TERMINAL_STATUSES = ["completed", "failed", "stopped"];
|
|
|
1331
1349
|
var WORKFLOW_LIFECYCLE_TYPES = ["plan", "iteration"];
|
|
1332
1350
|
var WORKFLOW_DELIVERY_KINDS = ["development", "verification/report-only"];
|
|
1333
1351
|
var WORKFLOW_COMPOUND_OUTCOMES = ["created", "updated", "skipped"];
|
|
1352
|
+
function isStandaloneDevelopmentWorkflow(snapshot) {
|
|
1353
|
+
return snapshot.type === "plan" && snapshot.delivery_kind === "development" && Array.isArray(snapshot.plans) && snapshot.plans.length === 1;
|
|
1354
|
+
}
|
|
1355
|
+
function rowValidationRoute(snapshot, row) {
|
|
1356
|
+
if (isStandaloneDevelopmentWorkflow(snapshot) && snapshot.plans[0]?.id === row.id) {
|
|
1357
|
+
return "standalone-development";
|
|
1358
|
+
}
|
|
1359
|
+
return "integration";
|
|
1360
|
+
}
|
|
1361
|
+
function validateStandaloneCompletedCoherence(snapshot, row) {
|
|
1362
|
+
const violations = [];
|
|
1363
|
+
if (!isStandaloneDevelopmentWorkflow(snapshot) || row.id !== snapshot.plans[0]?.id)
|
|
1364
|
+
return violations;
|
|
1365
|
+
const coordination = row.coordination;
|
|
1366
|
+
if (!isPlainObject2(coordination) || !isPlainObject2(coordination.handoff))
|
|
1367
|
+
return violations;
|
|
1368
|
+
const handoff = coordination.handoff;
|
|
1369
|
+
if (handoff.state !== "completed" || handoff.integration !== undefined)
|
|
1370
|
+
return violations;
|
|
1371
|
+
if (row.status !== "Done") {
|
|
1372
|
+
violations.push(violation3("high", "coordination.row.handoff-field", `standalone completed handoff requires row ${String(row.id)} to be Done`));
|
|
1373
|
+
}
|
|
1374
|
+
if (row.execution_lease !== undefined) {
|
|
1375
|
+
violations.push(violation3("high", "coordination.row.handoff-field", `standalone completed handoff requires no execution lease on row ${String(row.id)}`));
|
|
1376
|
+
}
|
|
1377
|
+
if (snapshot.integration_merge_lease !== undefined) {
|
|
1378
|
+
violations.push(violation3("high", "coordination.row.handoff-field", "standalone completed handoff requires no integration_merge_lease on the snapshot"));
|
|
1379
|
+
}
|
|
1380
|
+
const source = snapshot.branch?.source;
|
|
1381
|
+
const target = snapshot.branch?.target;
|
|
1382
|
+
if (!isNonEmptyString(source) || !isNonEmptyString(target)) {
|
|
1383
|
+
violations.push(violation3("high", "coordination.row.handoff-field", "standalone completed handoff requires nonblank branch.source and branch.target"));
|
|
1384
|
+
} else {
|
|
1385
|
+
if (handoff.source_branch !== source) {
|
|
1386
|
+
violations.push(violation3("high", "coordination.row.handoff-field", `standalone completed handoff source_branch ${String(handoff.source_branch)} must equal branch.source ${source}`));
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return violations;
|
|
1390
|
+
}
|
|
1334
1391
|
function stableJson(value) {
|
|
1335
1392
|
if (Array.isArray(value))
|
|
1336
1393
|
return `[${value.map(stableJson).join(",")}]`;
|
|
@@ -1450,13 +1507,17 @@ function validateWorkflowSnapshot(doc) {
|
|
|
1450
1507
|
} else if (!Array.isArray(doc.plans)) {
|
|
1451
1508
|
violations.push(violation3("high", "workflow.snapshot.invalid-plans", "plans must be an array of legacy plan rows"));
|
|
1452
1509
|
} else {
|
|
1510
|
+
const snapshotDoc = doc;
|
|
1453
1511
|
for (const row of doc.plans) {
|
|
1454
1512
|
violations.push(...validatePlanRow(row).violations);
|
|
1455
1513
|
if (isPlainObject2(row) && row.execution_lease !== undefined) {
|
|
1456
1514
|
violations.push(...validateExecutionLease(row.execution_lease).violations);
|
|
1457
1515
|
}
|
|
1458
1516
|
if (isPlainObject2(row) && row.coordination !== undefined) {
|
|
1459
|
-
|
|
1517
|
+
const planRow = row;
|
|
1518
|
+
const route = rowValidationRoute(snapshotDoc, planRow);
|
|
1519
|
+
violations.push(...validateRowCoordination(row.coordination, `plans[${String(row.id)}].coordination`, route));
|
|
1520
|
+
violations.push(...validateStandaloneCompletedCoherence(snapshotDoc, planRow));
|
|
1460
1521
|
}
|
|
1461
1522
|
}
|
|
1462
1523
|
}
|
|
@@ -9906,6 +9967,7 @@ var IMPLEMENTED_OPERATIONS = {
|
|
|
9906
9967
|
"integration-start": true,
|
|
9907
9968
|
"integration-accept": true,
|
|
9908
9969
|
complete: true,
|
|
9970
|
+
"repair-delivery-source": true,
|
|
9909
9971
|
reconcile: true
|
|
9910
9972
|
};
|
|
9911
9973
|
function nowIso() {
|
|
@@ -10465,7 +10527,11 @@ function allowedOperations(role, sessionId, snapshot, row) {
|
|
|
10465
10527
|
out.push("accept", "return");
|
|
10466
10528
|
break;
|
|
10467
10529
|
case "accepted":
|
|
10468
|
-
|
|
10530
|
+
if (isStandaloneDevelopmentWorkflow(snapshot)) {
|
|
10531
|
+
out.push("return", "complete", "repair-delivery-source");
|
|
10532
|
+
} else {
|
|
10533
|
+
out.push("return", "integration-start");
|
|
10534
|
+
}
|
|
10469
10535
|
break;
|
|
10470
10536
|
case "integrating":
|
|
10471
10537
|
out.push("integration-accept", "complete", "reconcile");
|
|
@@ -11240,6 +11306,13 @@ async function mutatePlanCoordination(request) {
|
|
|
11240
11306
|
expectedRevision
|
|
11241
11307
|
});
|
|
11242
11308
|
}
|
|
11309
|
+
case "repair-delivery-source": {
|
|
11310
|
+
assertExactKeys(operation, ["kind", "handoffId"], "repair-delivery-source operation");
|
|
11311
|
+
return mutateRepairDeliverySource(await coordinatorScope(session, request.planId, kind), session, sessionAbs, {
|
|
11312
|
+
handoffId: namedHandoffId(operation),
|
|
11313
|
+
expectedRevision
|
|
11314
|
+
});
|
|
11315
|
+
}
|
|
11243
11316
|
case "reconcile": {
|
|
11244
11317
|
assertExactKeys(operation, ["kind", "handoffId"], "reconcile operation");
|
|
11245
11318
|
return mutateReconcile(await coordinatorScope(session, request.planId, kind), session, sessionAbs, {
|
|
@@ -11270,6 +11343,7 @@ var COORDINATOR_OPERATIONS = [
|
|
|
11270
11343
|
"integration-start",
|
|
11271
11344
|
"integration-accept",
|
|
11272
11345
|
"complete",
|
|
11346
|
+
"repair-delivery-source",
|
|
11273
11347
|
"reconcile"
|
|
11274
11348
|
];
|
|
11275
11349
|
function assertSessionRole(session, kind) {
|
|
@@ -11910,6 +11984,313 @@ async function mutateIntegrationAccept(scope, session, sessionPath, request) {
|
|
|
11910
11984
|
view: buildView(scope.harnessRoot, scope.workflowId, scope.projectId, scope, result.snapshot, result.row, session, sessionPath)
|
|
11911
11985
|
};
|
|
11912
11986
|
}
|
|
11987
|
+
function validateRowCoordinationInContext(context, coordination, what) {
|
|
11988
|
+
assertViolationFree(validateRowCoordination(coordination, what, rowValidationRoute(context.snapshot, context.row)), what);
|
|
11989
|
+
}
|
|
11990
|
+
function standaloneDeliveryAnchors(snapshot, planId) {
|
|
11991
|
+
const source = snapshot.branch?.source;
|
|
11992
|
+
const target = snapshot.branch?.target;
|
|
11993
|
+
if (!isNonEmptyString(source) || !isNonEmptyString(target)) {
|
|
11994
|
+
throw new CoordinationError("coordination.invalid-transition", `plan ${planId} has no delivery anchors — the snapshot must name branch.source and branch.target`, { plan_id: planId });
|
|
11995
|
+
}
|
|
11996
|
+
return { source, target };
|
|
11997
|
+
}
|
|
11998
|
+
function assertStandaloneRoute(snapshot, planId, what) {
|
|
11999
|
+
if (!isStandaloneDevelopmentWorkflow(snapshot)) {
|
|
12000
|
+
throw new CoordinationError("coordination.invalid-transition", `${what} requires a standalone development workflow for plan ${planId}`, { plan_id: planId });
|
|
12001
|
+
}
|
|
12002
|
+
}
|
|
12003
|
+
function assertNoIntegrationContamination(context, planId, handoff, what, code = "coordination.invalid-transition") {
|
|
12004
|
+
if (handoff.integration !== undefined) {
|
|
12005
|
+
throw new CoordinationError(code, `${what} refuses plan ${planId} because the handoff already carries an integration record`, { plan_id: planId });
|
|
12006
|
+
}
|
|
12007
|
+
if (context.snapshot.integration_worktree_path !== undefined) {
|
|
12008
|
+
throw new CoordinationError(code, `${what} refuses plan ${planId} because the snapshot names integration_worktree_path`, { plan_id: planId });
|
|
12009
|
+
}
|
|
12010
|
+
if (isNonEmptyString(context.snapshot.branch?.integration)) {
|
|
12011
|
+
throw new CoordinationError(code, `${what} refuses plan ${planId} because the snapshot names branch.integration`, { plan_id: planId, integration: context.snapshot.branch?.integration });
|
|
12012
|
+
}
|
|
12013
|
+
if (context.snapshot.integration_merge_lease !== undefined) {
|
|
12014
|
+
throw new CoordinationError(code, `${what} refuses plan ${planId} because the snapshot carries an integration merge lease`, { plan_id: planId });
|
|
12015
|
+
}
|
|
12016
|
+
}
|
|
12017
|
+
function assertAcceptedReviewDecision(handoff, planId, what) {
|
|
12018
|
+
if (handoff.qc.decision !== "Approve" && handoff.qc.decision !== "Approve with residuals") {
|
|
12019
|
+
throw new CoordinationError("coordination.invalid-transition", `${what} requires an accepted QC decision for plan ${planId} — got ${handoff.qc.decision}`, { plan_id: planId, decision: handoff.qc.decision });
|
|
12020
|
+
}
|
|
12021
|
+
if (handoff.qa.decision !== "pass") {
|
|
12022
|
+
throw new CoordinationError("coordination.invalid-transition", `${what} requires QA decision pass for plan ${planId} — got ${handoff.qa.decision}`, { plan_id: planId, decision: handoff.qa.decision });
|
|
12023
|
+
}
|
|
12024
|
+
}
|
|
12025
|
+
function assertStandaloneBranchIdentity(context, scope, handoff, anchors, what, requireLease = true) {
|
|
12026
|
+
const worktree = canonicalTarget(scope.worktreePath);
|
|
12027
|
+
if (handoff.source_branch !== anchors.source) {
|
|
12028
|
+
throw new CoordinationError("coordination.scope-mismatch", `${what} requires handoff.source_branch ${handoff.source_branch} to equal the registered delivery source ${anchors.source}`, { plan_id: scope.planId, expected: anchors.source, actual: handoff.source_branch });
|
|
12029
|
+
}
|
|
12030
|
+
if (requireLease) {
|
|
12031
|
+
const lease = requireExecutionLease(context.row, scope.planId, what);
|
|
12032
|
+
if (scope.workingBranch !== anchors.source) {
|
|
12033
|
+
throw new CoordinationError("coordination.scope-mismatch", `${what} requires the prepared working branch ${scope.workingBranch} to equal the registered delivery source ${anchors.source}`, { plan_id: scope.planId, expected: anchors.source, actual: scope.workingBranch });
|
|
12034
|
+
}
|
|
12035
|
+
if (lease.working_branch !== anchors.source) {
|
|
12036
|
+
throw new CoordinationError("coordination.scope-mismatch", `${what} requires the execution lease working branch ${lease.working_branch} to equal the registered delivery source ${anchors.source}`, { plan_id: scope.planId, expected: anchors.source, actual: lease.working_branch });
|
|
12037
|
+
}
|
|
12038
|
+
if (canonicalTarget(lease.worktree_path) !== worktree) {
|
|
12039
|
+
throw new CoordinationError("coordination.path-mismatch", `${what} requires the execution lease worktree ${lease.worktree_path} to equal the prepared scope ${worktree}`, { plan_id: scope.planId, expected: worktree, actual: lease.worktree_path });
|
|
12040
|
+
}
|
|
12041
|
+
}
|
|
12042
|
+
if (canonicalTarget(handoff.worktree_path) !== worktree) {
|
|
12043
|
+
throw new CoordinationError("coordination.path-mismatch", `${what} requires the handoff worktree ${handoff.worktree_path} to equal the prepared scope ${worktree}`, { plan_id: scope.planId, expected: worktree, actual: handoff.worktree_path });
|
|
12044
|
+
}
|
|
12045
|
+
const metadata = context.row.metadata;
|
|
12046
|
+
if (isPlainObject2(metadata) && metadata.working_branch !== undefined && metadata.working_branch !== anchors.source) {
|
|
12047
|
+
throw new CoordinationError("coordination.scope-mismatch", `${what} requires row metadata.working_branch to equal the registered delivery source ${anchors.source}`, { plan_id: scope.planId, expected: anchors.source, actual: metadata.working_branch });
|
|
12048
|
+
}
|
|
12049
|
+
if (isPlainObject2(metadata) && metadata.worktree_path !== undefined && canonicalTarget(String(metadata.worktree_path)) !== worktree) {
|
|
12050
|
+
throw new CoordinationError("coordination.path-mismatch", `${what} requires row metadata.worktree_path to equal the prepared scope ${worktree}`, { plan_id: scope.planId, expected: worktree, actual: metadata.worktree_path });
|
|
12051
|
+
}
|
|
12052
|
+
}
|
|
12053
|
+
function assertStandaloneSourceGitProof(scope, handoff, sourceBranch, what) {
|
|
12054
|
+
assertFeatureCheckout(scope, handoff.source_sha, what);
|
|
12055
|
+
const branch = gitRead(scope.worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
12056
|
+
if (branch !== sourceBranch) {
|
|
12057
|
+
throw gitProof(`${what} requires the plan worktree ${scope.worktreePath} to be on ${sourceBranch} — got ${branch || "a detached HEAD"}`, { plan_id: scope.planId, expected: sourceBranch, actual: branch });
|
|
12058
|
+
}
|
|
12059
|
+
const refTip = gitRead(scope.worktreePath, ["rev-parse", `refs/heads/${sourceBranch}`]);
|
|
12060
|
+
if (refTip !== handoff.source_sha) {
|
|
12061
|
+
throw gitProof(`${what} requires refs/heads/${sourceBranch} to resolve to the pinned source ${handoff.source_sha} — got ${refTip || "missing"}`, { plan_id: scope.planId, expected: handoff.source_sha, actual: refTip });
|
|
12062
|
+
}
|
|
12063
|
+
if (handoff.review_head !== handoff.source_sha) {
|
|
12064
|
+
throw gitProof(`${what} requires review_head to be the pinned source ${handoff.source_sha} — got ${handoff.review_head}`, { plan_id: scope.planId, source_sha: handoff.source_sha, review_head: handoff.review_head });
|
|
12065
|
+
}
|
|
12066
|
+
if (!gitObjectExists(scope.worktreePath, handoff.review_base)) {
|
|
12067
|
+
throw gitProof(`${what} review base ${handoff.review_base} is not a commit of ${scope.worktreePath}`, {
|
|
12068
|
+
plan_id: scope.planId,
|
|
12069
|
+
review_base: handoff.review_base
|
|
12070
|
+
});
|
|
12071
|
+
}
|
|
12072
|
+
if (!gitIsAncestor(scope.worktreePath, handoff.review_base, handoff.review_head)) {
|
|
12073
|
+
throw gitProof(`${what} review range ${handoff.review_base}..${handoff.review_head} is not an ancestry`, { plan_id: scope.planId, review_base: handoff.review_base, review_head: handoff.review_head });
|
|
12074
|
+
}
|
|
12075
|
+
}
|
|
12076
|
+
async function assertStandaloneCompletionPrecheck(context, scope, session, handoff) {
|
|
12077
|
+
assertStandaloneRoute(context.snapshot, scope.planId, "complete");
|
|
12078
|
+
if (context.snapshot.status !== "running") {
|
|
12079
|
+
throw new CoordinationError("coordination.invalid-transition", `complete requires workflow ${context.snapshot.id} to still be running — got ${context.snapshot.status}`, { workflow_id: context.snapshot.id, status: context.snapshot.status });
|
|
12080
|
+
}
|
|
12081
|
+
if (handoff.state !== "accepted") {
|
|
12082
|
+
throw new CoordinationError("coordination.invalid-transition", `plan ${scope.planId} handoff is ${handoff.state} — standalone complete requires an accepted handoff`, { plan_id: scope.planId, state: handoff.state });
|
|
12083
|
+
}
|
|
12084
|
+
if (rowStatusOf(context.row) !== "InReview") {
|
|
12085
|
+
throw new CoordinationError("coordination.invalid-transition", `complete requires ${scope.planId} to still be InReview`, { plan_id: scope.planId, status: context.row.status });
|
|
12086
|
+
}
|
|
12087
|
+
const prepared = context.coordination?.prepared;
|
|
12088
|
+
if (prepared === undefined) {
|
|
12089
|
+
throw new CoordinationError("coordination.not-prepared", `plan ${scope.planId} is not prepared in this workflow`, { plan_id: scope.planId });
|
|
12090
|
+
}
|
|
12091
|
+
assertNoIntegrationContamination(context, scope.planId, handoff, "complete");
|
|
12092
|
+
assertEvidenceDigests(handoff);
|
|
12093
|
+
assertAcceptedReviewDecision(handoff, scope.planId, "complete");
|
|
12094
|
+
if (handoff.qa.gate !== prepared.qa_gate) {
|
|
12095
|
+
throw new CoordinationError("coordination.assignment-stale", `complete qa.gate ${handoff.qa.gate} is not the Assignment's QA gate ${prepared.qa_gate}`, { plan_id: scope.planId, expected: prepared.qa_gate, actual: handoff.qa.gate });
|
|
12096
|
+
}
|
|
12097
|
+
await assertFindingsClosed(scope, prepared, "complete");
|
|
12098
|
+
assertExecutionHolder(context.row, session.session_id, scope.planId, "complete");
|
|
12099
|
+
const anchors = standaloneDeliveryAnchors(context.snapshot, scope.planId);
|
|
12100
|
+
assertStandaloneBranchIdentity(context, scope, handoff, anchors, "complete");
|
|
12101
|
+
assertStandaloneSourceGitProof(scope, handoff, anchors.source, "complete");
|
|
12102
|
+
}
|
|
12103
|
+
function requireAcceptedHandoffForRepair(context, planId, namedHandoffId2) {
|
|
12104
|
+
const handoff = context.coordination?.handoff;
|
|
12105
|
+
if (handoff === undefined || handoff.id !== namedHandoffId2 || handoff.state !== "accepted" || rowStatusOf(context.row) !== "InReview") {
|
|
12106
|
+
throw new CoordinationError("coordination.delivery-source-repair.no-accepted-handoff", `repair-delivery-source requires plan ${planId} to carry the named accepted handoff while InReview`, { plan_id: planId, handoff_id: namedHandoffId2, state: handoff?.state, status: context.row.status });
|
|
12107
|
+
}
|
|
12108
|
+
return handoff;
|
|
12109
|
+
}
|
|
12110
|
+
function assertRepairNotTerminal(snapshot) {
|
|
12111
|
+
if (WORKFLOW_TERMINAL_STATUSES.includes(snapshot.status)) {
|
|
12112
|
+
throw new CoordinationError("coordination.delivery-source-repair.terminal", `repair-delivery-source refuses workflow ${snapshot.id} in terminal status ${snapshot.status}`, { workflow_id: snapshot.id, status: snapshot.status });
|
|
12113
|
+
}
|
|
12114
|
+
}
|
|
12115
|
+
function assertLegacyRepairShape(snapshot, planId, handoff) {
|
|
12116
|
+
const source = snapshot.branch?.source;
|
|
12117
|
+
const target = snapshot.branch?.target;
|
|
12118
|
+
if (!isNonEmptyString(source) || !isNonEmptyString(target)) {
|
|
12119
|
+
throw new CoordinationError("coordination.delivery-source-repair.not-legacy-shape", `repair-delivery-source requires nonblank delivery anchors on plan ${planId}`, { plan_id: planId });
|
|
12120
|
+
}
|
|
12121
|
+
const candidateSource = handoff.source_branch;
|
|
12122
|
+
if (!isNonEmptyString(candidateSource)) {
|
|
12123
|
+
throw new CoordinationError("coordination.delivery-source-repair.not-legacy-shape", `repair-delivery-source requires the accepted handoff to name a nonblank source branch for plan ${planId}`, { plan_id: planId });
|
|
12124
|
+
}
|
|
12125
|
+
if (source === candidateSource) {
|
|
12126
|
+
throw new CoordinationError("coordination.delivery-source-repair.already-aligned", `repair-delivery-source refuses plan ${planId} because branch.source already equals the accepted handoff source ${candidateSource}`, { plan_id: planId, source, candidate: candidateSource });
|
|
12127
|
+
}
|
|
12128
|
+
if (source !== target) {
|
|
12129
|
+
throw new CoordinationError("coordination.delivery-source-repair.not-legacy-shape", `repair-delivery-source requires the legacy shape branch.source === branch.target for plan ${planId} — got source ${source} and target ${target}`, { plan_id: planId, source, target });
|
|
12130
|
+
}
|
|
12131
|
+
return { target, candidateSource };
|
|
12132
|
+
}
|
|
12133
|
+
function assertRepairBranchIdentity(context, scope, handoff, candidateSource, what) {
|
|
12134
|
+
const worktree = canonicalTarget(scope.worktreePath);
|
|
12135
|
+
if (handoff.source_branch !== candidateSource) {
|
|
12136
|
+
throw new CoordinationError("coordination.scope-mismatch", `${what} requires handoff.source_branch ${handoff.source_branch} to equal the candidate delivery source ${candidateSource}`, { plan_id: scope.planId, expected: candidateSource, actual: handoff.source_branch });
|
|
12137
|
+
}
|
|
12138
|
+
const lease = requireExecutionLease(context.row, scope.planId, what);
|
|
12139
|
+
if (scope.workingBranch !== candidateSource) {
|
|
12140
|
+
throw new CoordinationError("coordination.scope-mismatch", `${what} requires the prepared working branch ${scope.workingBranch} to equal the candidate delivery source ${candidateSource}`, { plan_id: scope.planId, expected: candidateSource, actual: scope.workingBranch });
|
|
12141
|
+
}
|
|
12142
|
+
if (lease.working_branch !== candidateSource) {
|
|
12143
|
+
throw new CoordinationError("coordination.scope-mismatch", `${what} requires the execution lease working branch ${lease.working_branch} to equal the candidate delivery source ${candidateSource}`, { plan_id: scope.planId, expected: candidateSource, actual: lease.working_branch });
|
|
12144
|
+
}
|
|
12145
|
+
if (canonicalTarget(lease.worktree_path) !== worktree) {
|
|
12146
|
+
throw new CoordinationError("coordination.path-mismatch", `${what} requires the execution lease worktree ${lease.worktree_path} to equal the prepared scope ${worktree}`, { plan_id: scope.planId, expected: worktree, actual: lease.worktree_path });
|
|
12147
|
+
}
|
|
12148
|
+
if (canonicalTarget(handoff.worktree_path) !== worktree) {
|
|
12149
|
+
throw new CoordinationError("coordination.path-mismatch", `${what} requires the handoff worktree ${handoff.worktree_path} to equal the prepared scope ${worktree}`, { plan_id: scope.planId, expected: worktree, actual: handoff.worktree_path });
|
|
12150
|
+
}
|
|
12151
|
+
const metadata = context.row.metadata;
|
|
12152
|
+
if (isPlainObject2(metadata) && metadata.working_branch !== undefined && metadata.working_branch !== candidateSource) {
|
|
12153
|
+
throw new CoordinationError("coordination.scope-mismatch", `${what} requires row metadata.working_branch to equal the candidate delivery source ${candidateSource}`, { plan_id: scope.planId, expected: candidateSource, actual: metadata.working_branch });
|
|
12154
|
+
}
|
|
12155
|
+
if (isPlainObject2(metadata) && metadata.worktree_path !== undefined && canonicalTarget(String(metadata.worktree_path)) !== worktree) {
|
|
12156
|
+
throw new CoordinationError("coordination.path-mismatch", `${what} requires row metadata.worktree_path to equal the prepared scope ${worktree}`, { plan_id: scope.planId, expected: worktree, actual: metadata.worktree_path });
|
|
12157
|
+
}
|
|
12158
|
+
}
|
|
12159
|
+
function assertDeliveryPrCompatible(snapshot, candidateSource, registeredTarget, planId) {
|
|
12160
|
+
const pr = snapshot.delivery?.pr;
|
|
12161
|
+
if (pr === undefined)
|
|
12162
|
+
return;
|
|
12163
|
+
if (pr.head !== candidateSource || pr.target !== registeredTarget) {
|
|
12164
|
+
throw new CoordinationError("coordination.delivery-source-repair.pr-conflict", `repair-delivery-source refuses plan ${planId} because stored PR identity head ${JSON.stringify(pr.head)} target ${JSON.stringify(pr.target)} conflicts with candidate source ${candidateSource} and registered target ${registeredTarget}`, { plan_id: planId, pr_head: pr.head, pr_target: pr.target, candidate: candidateSource, target: registeredTarget });
|
|
12165
|
+
}
|
|
12166
|
+
}
|
|
12167
|
+
function assertRepairDeliverySourceAdmission(context, scope) {
|
|
12168
|
+
if (!isStandaloneDevelopmentWorkflow(context.snapshot)) {
|
|
12169
|
+
throw new CoordinationError("coordination.delivery-source-repair.unsupported-workflow", `repair-delivery-source requires a single-row standalone development workflow for plan ${scope.planId}`, { plan_id: scope.planId, workflow_id: context.snapshot.id });
|
|
12170
|
+
}
|
|
12171
|
+
assertRepairNotTerminal(context.snapshot);
|
|
12172
|
+
if (context.snapshot.status === "paused") {
|
|
12173
|
+
throw new CoordinationError("coordination.invalid-transition", `repair-delivery-source refuses paused workflow ${context.snapshot.id}`, { workflow_id: context.snapshot.id, status: context.snapshot.status });
|
|
12174
|
+
}
|
|
12175
|
+
if (context.snapshot.status !== "running") {
|
|
12176
|
+
throw new CoordinationError("coordination.invalid-transition", `repair-delivery-source requires workflow ${context.snapshot.id} to still be running — got ${context.snapshot.status}`, { workflow_id: context.snapshot.id, status: context.snapshot.status });
|
|
12177
|
+
}
|
|
12178
|
+
}
|
|
12179
|
+
async function assertRepairDeliverySourcePrecheck(context, scope, session, handoff) {
|
|
12180
|
+
assertNoIntegrationContamination(context, scope.planId, handoff, "repair-delivery-source", "coordination.delivery-source-repair.not-legacy-shape");
|
|
12181
|
+
const prepared = context.coordination?.prepared;
|
|
12182
|
+
if (prepared === undefined) {
|
|
12183
|
+
throw new CoordinationError("coordination.not-prepared", `plan ${scope.planId} is not prepared in this workflow`, { plan_id: scope.planId });
|
|
12184
|
+
}
|
|
12185
|
+
assertEvidenceDigests(handoff);
|
|
12186
|
+
assertAcceptedReviewDecision(handoff, scope.planId, "repair-delivery-source");
|
|
12187
|
+
if (handoff.qa.gate !== prepared.qa_gate) {
|
|
12188
|
+
throw new CoordinationError("coordination.assignment-stale", `repair-delivery-source qa.gate ${handoff.qa.gate} is not the Assignment's QA gate ${prepared.qa_gate}`, { plan_id: scope.planId, expected: prepared.qa_gate, actual: handoff.qa.gate });
|
|
12189
|
+
}
|
|
12190
|
+
await assertFindingsClosed(scope, prepared, "repair-delivery-source");
|
|
12191
|
+
assertExecutionHolder(context.row, session.session_id, scope.planId, "repair-delivery-source");
|
|
12192
|
+
const { target, candidateSource } = assertLegacyRepairShape(context.snapshot, scope.planId, handoff);
|
|
12193
|
+
assertRepairBranchIdentity(context, scope, handoff, candidateSource, "repair-delivery-source");
|
|
12194
|
+
assertDeliveryPrCompatible(context.snapshot, candidateSource, target, scope.planId);
|
|
12195
|
+
assertStandaloneSourceGitProof(scope, handoff, candidateSource, "repair-delivery-source");
|
|
12196
|
+
}
|
|
12197
|
+
function repairDeliverySourceRow(context, scope, candidateSource) {
|
|
12198
|
+
const nextCoordination = {
|
|
12199
|
+
...context.coordination ?? { revision: 0 },
|
|
12200
|
+
revision: context.revision + 1
|
|
12201
|
+
};
|
|
12202
|
+
validateRowCoordinationInContext(context, nextCoordination, `plan ${scope.planId} coordination`);
|
|
12203
|
+
const currentBranch = context.snapshot.branch ?? {};
|
|
12204
|
+
return {
|
|
12205
|
+
row: { ...context.row, coordination: nextCoordination },
|
|
12206
|
+
coordination: nextCoordination,
|
|
12207
|
+
topLevel: {
|
|
12208
|
+
branch: { ...currentBranch, source: candidateSource }
|
|
12209
|
+
}
|
|
12210
|
+
};
|
|
12211
|
+
}
|
|
12212
|
+
async function mutateRepairDeliverySource(scope, session, sessionPath, request) {
|
|
12213
|
+
const result = await withRowCommit(scope, {
|
|
12214
|
+
expectedRevision: request.expectedRevision,
|
|
12215
|
+
precheck: async (context) => {
|
|
12216
|
+
assertCoordinatorBinding(session, sessionPath, context.snapshot);
|
|
12217
|
+
assertRepairDeliverySourceAdmission(context, scope);
|
|
12218
|
+
const handoff = requireAcceptedHandoffForRepair(context, scope.planId, request.handoffId);
|
|
12219
|
+
await assertRepairDeliverySourcePrecheck(context, scope, session, handoff);
|
|
12220
|
+
},
|
|
12221
|
+
mutate: (context) => {
|
|
12222
|
+
const handoff = requireAcceptedHandoffForRepair(context, scope.planId, request.handoffId);
|
|
12223
|
+
const { candidateSource } = assertLegacyRepairShape(context.snapshot, scope.planId, handoff);
|
|
12224
|
+
return repairDeliverySourceRow(context, scope, candidateSource);
|
|
12225
|
+
}
|
|
12226
|
+
});
|
|
12227
|
+
return {
|
|
12228
|
+
ok: true,
|
|
12229
|
+
operation: "repair-delivery-source",
|
|
12230
|
+
session,
|
|
12231
|
+
session_file: sessionPath,
|
|
12232
|
+
outcome: "delivery-source-repaired",
|
|
12233
|
+
view: buildView(scope.harnessRoot, scope.workflowId, scope.projectId, scope, result.snapshot, result.row, session, sessionPath)
|
|
12234
|
+
};
|
|
12235
|
+
}
|
|
12236
|
+
function completeStandaloneRow(context, scope, handoff) {
|
|
12237
|
+
const metadata = isPlainObject2(context.row.metadata) ? { ...context.row.metadata } : {};
|
|
12238
|
+
metadata.working_branch = handoff.source_branch;
|
|
12239
|
+
metadata.worktree_path = handoff.worktree_path;
|
|
12240
|
+
const nextCoordination = {
|
|
12241
|
+
...context.coordination ?? { revision: 0 },
|
|
12242
|
+
revision: context.revision + 1,
|
|
12243
|
+
handoff: { ...handoff, state: "completed", completed_at: nowIso() }
|
|
12244
|
+
};
|
|
12245
|
+
validateRowCoordinationInContext(context, nextCoordination, `plan ${scope.planId} coordination`);
|
|
12246
|
+
const nextRow = { ...context.row, status: "Done", metadata, coordination: nextCoordination };
|
|
12247
|
+
delete nextRow.execution_lease;
|
|
12248
|
+
return { row: nextRow, coordination: nextCoordination };
|
|
12249
|
+
}
|
|
12250
|
+
function assertStandaloneCompletedReplay(context, scope, handoff) {
|
|
12251
|
+
assertStandaloneRoute(context.snapshot, scope.planId, "reconcile");
|
|
12252
|
+
if (rowStatusOf(context.row) !== "Done") {
|
|
12253
|
+
throw new CoordinationError("coordination.invalid-transition", `reconcile requires ${scope.planId} to be Done for a standalone completed replay`, { plan_id: scope.planId, status: context.row.status });
|
|
12254
|
+
}
|
|
12255
|
+
if (context.row.execution_lease !== undefined) {
|
|
12256
|
+
throw new CoordinationError("coordination.invalid-transition", `reconcile requires no execution lease on ${scope.planId} for a standalone completed replay`, { plan_id: scope.planId });
|
|
12257
|
+
}
|
|
12258
|
+
if (context.snapshot.integration_merge_lease !== undefined) {
|
|
12259
|
+
throw new CoordinationError("coordination.invalid-transition", `reconcile requires no integration merge lease for a standalone completed replay of ${scope.planId}`, { plan_id: scope.planId });
|
|
12260
|
+
}
|
|
12261
|
+
assertNoIntegrationContamination(context, scope.planId, handoff, "reconcile");
|
|
12262
|
+
const storedHandoffViolations = validatePlanHandoff(handoff, `plan ${scope.planId} coordination.handoff`, "standalone-development");
|
|
12263
|
+
const storedHandoffFailure = storedHandoffViolations.find((entry) => !entry.ok);
|
|
12264
|
+
if (storedHandoffFailure !== undefined) {
|
|
12265
|
+
throw new CoordinationError(storedHandoffFailure.code, storedHandoffFailure.message, { plan_id: scope.planId });
|
|
12266
|
+
}
|
|
12267
|
+
const anchors = standaloneDeliveryAnchors(context.snapshot, scope.planId);
|
|
12268
|
+
assertStandaloneBranchIdentity(context, scope, handoff, anchors, "reconcile", false);
|
|
12269
|
+
const repository = proofRepository([handoff.worktree_path, scope.worktreePath, scope.harnessRoot]);
|
|
12270
|
+
if (repository !== undefined && !gitObjectExists(repository, handoff.source_sha)) {
|
|
12271
|
+
throw gitProof(`reconcile cannot re-verify the pinned standalone source ${handoff.source_sha} for plan ${scope.planId}`, { plan_id: scope.planId, source_sha: handoff.source_sha });
|
|
12272
|
+
}
|
|
12273
|
+
}
|
|
12274
|
+
async function assertIterationCompletionPrecheck(context, scope, session, handoff) {
|
|
12275
|
+
if (handoff.state !== "merged") {
|
|
12276
|
+
throw new CoordinationError("coordination.invalid-transition", `plan ${scope.planId} handoff is ${handoff.state} — complete requires a merged attempt`, { plan_id: scope.planId, state: handoff.state });
|
|
12277
|
+
}
|
|
12278
|
+
if (rowStatusOf(context.row) !== "InReview") {
|
|
12279
|
+
throw new CoordinationError("coordination.invalid-transition", `complete requires ${scope.planId} to still be InReview`, { plan_id: scope.planId, status: context.row.status });
|
|
12280
|
+
}
|
|
12281
|
+
const prepared = context.coordination?.prepared;
|
|
12282
|
+
if (prepared === undefined) {
|
|
12283
|
+
throw new CoordinationError("coordination.not-prepared", `plan ${scope.planId} is not prepared in this workflow`, { plan_id: scope.planId });
|
|
12284
|
+
}
|
|
12285
|
+
assertEvidenceDigests(handoff);
|
|
12286
|
+
await assertFindingsClosed(scope, prepared, "complete");
|
|
12287
|
+
assertExecutionHolder(context.row, session.session_id, scope.planId, "complete");
|
|
12288
|
+
assertMergeLease(context.snapshot, session, scope.planId, handoff);
|
|
12289
|
+
const integration = requireIntegration(handoff, scope.planId);
|
|
12290
|
+
const anchors = integrationAnchors(context.snapshot, scope.planId);
|
|
12291
|
+
const checkout = assertIntegrationCheckout(anchors, scope.planId);
|
|
12292
|
+
assertRecordedResult(anchors.worktreePath, scope.planId, integration, handoff.source_sha, checkout.head);
|
|
12293
|
+
}
|
|
11913
12294
|
function completeRow(context, scope, handoff, resultSha) {
|
|
11914
12295
|
const integration = requireIntegration(handoff, scope.planId);
|
|
11915
12296
|
const completed = { ...integration, result_sha: resultSha, verified_at: nowIso() };
|
|
@@ -11928,33 +12309,27 @@ function completeRow(context, scope, handoff, resultSha) {
|
|
|
11928
12309
|
dropTopLevel: release === undefined ? [] : ["integration_merge_lease"]
|
|
11929
12310
|
};
|
|
11930
12311
|
}
|
|
12312
|
+
var completeStandaloneMutateGapForTest;
|
|
11931
12313
|
async function mutateComplete(scope, session, sessionPath, request) {
|
|
11932
12314
|
const result = await withRowCommit(scope, {
|
|
11933
12315
|
expectedRevision: request.expectedRevision,
|
|
11934
12316
|
precheck: async (context) => {
|
|
11935
12317
|
assertCoordinatorBinding(session, sessionPath, context.snapshot);
|
|
11936
12318
|
const handoff = requireHandoff(context, scope.planId, request.handoffId);
|
|
11937
|
-
if (
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
if (rowStatusOf(context.row) !== "InReview") {
|
|
11941
|
-
throw new CoordinationError("coordination.invalid-transition", `complete requires ${scope.planId} to still be InReview`, { plan_id: scope.planId, status: context.row.status });
|
|
11942
|
-
}
|
|
11943
|
-
const prepared = context.coordination?.prepared;
|
|
11944
|
-
if (prepared === undefined) {
|
|
11945
|
-
throw new CoordinationError("coordination.not-prepared", `plan ${scope.planId} is not prepared in this workflow`, { plan_id: scope.planId });
|
|
12319
|
+
if (isStandaloneDevelopmentWorkflow(context.snapshot)) {
|
|
12320
|
+
await assertStandaloneCompletionPrecheck(context, scope, session, handoff);
|
|
12321
|
+
return;
|
|
11946
12322
|
}
|
|
11947
|
-
|
|
11948
|
-
await assertFindingsClosed(scope, prepared, "complete");
|
|
11949
|
-
assertExecutionHolder(context.row, session.session_id, scope.planId, "complete");
|
|
11950
|
-
assertMergeLease(context.snapshot, session, scope.planId, handoff);
|
|
11951
|
-
const integration = requireIntegration(handoff, scope.planId);
|
|
11952
|
-
const anchors = integrationAnchors(context.snapshot, scope.planId);
|
|
11953
|
-
const checkout = assertIntegrationCheckout(anchors, scope.planId);
|
|
11954
|
-
assertRecordedResult(anchors.worktreePath, scope.planId, integration, handoff.source_sha, checkout.head);
|
|
12323
|
+
await assertIterationCompletionPrecheck(context, scope, session, handoff);
|
|
11955
12324
|
},
|
|
11956
12325
|
mutate: (context) => {
|
|
11957
12326
|
const handoff = requireHandoff(context, scope.planId, request.handoffId);
|
|
12327
|
+
if (isStandaloneDevelopmentWorkflow(context.snapshot)) {
|
|
12328
|
+
completeStandaloneMutateGapForTest?.();
|
|
12329
|
+
const anchors = standaloneDeliveryAnchors(context.snapshot, scope.planId);
|
|
12330
|
+
assertStandaloneSourceGitProof(scope, handoff, anchors.source, "complete");
|
|
12331
|
+
return completeStandaloneRow(context, scope, handoff);
|
|
12332
|
+
}
|
|
11958
12333
|
const integration = requireIntegration(handoff, scope.planId);
|
|
11959
12334
|
const resultSha = assertRecordedResult(integration.worktree_path, scope.planId, integration, handoff.source_sha, undefined);
|
|
11960
12335
|
return completeRow(context, scope, handoff, resultSha);
|
|
@@ -11978,6 +12353,10 @@ async function classifyReconcile(context, scope, session, handoff, expectedHando
|
|
|
11978
12353
|
});
|
|
11979
12354
|
}
|
|
11980
12355
|
if (handoff.state === "completed") {
|
|
12356
|
+
if (isStandaloneDevelopmentWorkflow(context.snapshot)) {
|
|
12357
|
+
assertStandaloneCompletedReplay(context, scope, handoff);
|
|
12358
|
+
return { outcome: "already-completed", apply: () => null };
|
|
12359
|
+
}
|
|
11981
12360
|
const integration = requireIntegration(handoff, planId);
|
|
11982
12361
|
const repository = proofRepository([integration.worktree_path, handoff.worktree_path, session.harness_root]);
|
|
11983
12362
|
if (repository === undefined) {
|
package/dist/workflow.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type GateResult, type ValidationResult } from "./core.js";
|
|
2
|
-
import { type SnapshotCoordination } from "./coordination-write.js";
|
|
2
|
+
import { type RowValidationRoute, type SnapshotCoordination } from "./coordination-write.js";
|
|
3
3
|
import { type IntegrationMergeLease } from "./lease.js";
|
|
4
4
|
import { type PlanRow } from "./status.js";
|
|
5
5
|
/** Snapshot file name inside `workflows/<id>/` ( — writer contract). */
|
|
@@ -159,6 +159,10 @@ export type WorkflowSnapshot = {
|
|
|
159
159
|
*/
|
|
160
160
|
delivery?: WorkflowDeliveryEvidence;
|
|
161
161
|
};
|
|
162
|
+
/** True exactly for a single-row standalone development plan workflow (spec A1). */
|
|
163
|
+
export declare function isStandaloneDevelopmentWorkflow(snapshot: WorkflowSnapshot): boolean;
|
|
164
|
+
/** Classify row coordination validation: standalone development vs integration delivery. */
|
|
165
|
+
export declare function rowValidationRoute(snapshot: WorkflowSnapshot, row: PlanRow): RowValidationRoute;
|
|
162
166
|
/** Stable JSON for change detection (sorted keys, recursive). */
|
|
163
167
|
export declare function stableJson(value: unknown): string;
|
|
164
168
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mstar-harness/engine",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.3",
|
|
4
4
|
"description": "Morning Star Harness Workflow Engine — deterministic workflow enforcement library (path, status, lease, dispatch, sdd, iteration, lint gates).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|