@mstar-harness/engine 3.10.1 → 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 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
- violations.push(invalid("coordination.row.handoff-field", `${what}.state ${String(value.state)} requires integration`));
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
- violations.push(...validateRowCoordination(row.coordination, `plans[${String(row.id)}].coordination`));
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. */
@@ -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
- violations.push(invalid("coordination.row.handoff-field", `${what}.state ${String(value.state)} requires integration`));
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
- violations.push(...validateRowCoordination(row.coordination, `plans[${String(row.id)}].coordination`));
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
  }
@@ -1812,6 +1873,15 @@ async function recordWorkflowDelivery(workflowId, dir, opts) {
1812
1873
  if (stableJson(snapshot.delivery ?? null) === stableJson(merged)) {
1813
1874
  return { snapshot, written: false };
1814
1875
  }
1876
+ const tailMembers = members.filter((member) => member === "compound" || member === "pr" || member === "merge");
1877
+ if (tailMembers.length > 0) {
1878
+ const notDone = snapshot.plans.filter((row) => row.status !== "Done");
1879
+ if (notDone.length > 0) {
1880
+ const rowDetail = notDone.map((row) => `${row.id} (${JSON.stringify(row.status)})`).join(", ");
1881
+ const detail = violation3("high", "PHASE6_PLAN_ROW_NOT_DONE", `owned plan row(s) ${rowDetail} are not Done - every plan row must be Done before the delivery tail (${tailMembers.join(", ")}) is recorded (mstar-artifacts/references/plan-workflow-lifecycle-contract.md section 3: row Done -> compound disposition -> PR identity -> verified-merge record)`, `Bring every owned plan row to Done, then record delivery evidence with 'mstar workflow evidence --workflow ${workflowId} --file <payload.json>'`);
1882
+ throw new Error(`refusing to record delivery evidence: ${detail.code}: ${detail.message}${detail.fix !== undefined ? ` (fix: ${detail.fix})` : ""}`);
1883
+ }
1884
+ }
1815
1885
  const next = { ...snapshot, delivery: merged, updated_at: at };
1816
1886
  await validateAndPutWorkflowSnapshot(store, next, snapshotPath);
1817
1887
  return { snapshot: next, written: true };
@@ -9897,6 +9967,7 @@ var IMPLEMENTED_OPERATIONS = {
9897
9967
  "integration-start": true,
9898
9968
  "integration-accept": true,
9899
9969
  complete: true,
9970
+ "repair-delivery-source": true,
9900
9971
  reconcile: true
9901
9972
  };
9902
9973
  function nowIso() {
@@ -10456,7 +10527,11 @@ function allowedOperations(role, sessionId, snapshot, row) {
10456
10527
  out.push("accept", "return");
10457
10528
  break;
10458
10529
  case "accepted":
10459
- out.push("return", "integration-start");
10530
+ if (isStandaloneDevelopmentWorkflow(snapshot)) {
10531
+ out.push("return", "complete", "repair-delivery-source");
10532
+ } else {
10533
+ out.push("return", "integration-start");
10534
+ }
10460
10535
  break;
10461
10536
  case "integrating":
10462
10537
  out.push("integration-accept", "complete", "reconcile");
@@ -11231,6 +11306,13 @@ async function mutatePlanCoordination(request) {
11231
11306
  expectedRevision
11232
11307
  });
11233
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
+ }
11234
11316
  case "reconcile": {
11235
11317
  assertExactKeys(operation, ["kind", "handoffId"], "reconcile operation");
11236
11318
  return mutateReconcile(await coordinatorScope(session, request.planId, kind), session, sessionAbs, {
@@ -11261,6 +11343,7 @@ var COORDINATOR_OPERATIONS = [
11261
11343
  "integration-start",
11262
11344
  "integration-accept",
11263
11345
  "complete",
11346
+ "repair-delivery-source",
11264
11347
  "reconcile"
11265
11348
  ];
11266
11349
  function assertSessionRole(session, kind) {
@@ -11901,6 +11984,313 @@ async function mutateIntegrationAccept(scope, session, sessionPath, request) {
11901
11984
  view: buildView(scope.harnessRoot, scope.workflowId, scope.projectId, scope, result.snapshot, result.row, session, sessionPath)
11902
11985
  };
11903
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
+ }
11904
12294
  function completeRow(context, scope, handoff, resultSha) {
11905
12295
  const integration = requireIntegration(handoff, scope.planId);
11906
12296
  const completed = { ...integration, result_sha: resultSha, verified_at: nowIso() };
@@ -11919,33 +12309,27 @@ function completeRow(context, scope, handoff, resultSha) {
11919
12309
  dropTopLevel: release === undefined ? [] : ["integration_merge_lease"]
11920
12310
  };
11921
12311
  }
12312
+ var completeStandaloneMutateGapForTest;
11922
12313
  async function mutateComplete(scope, session, sessionPath, request) {
11923
12314
  const result = await withRowCommit(scope, {
11924
12315
  expectedRevision: request.expectedRevision,
11925
12316
  precheck: async (context) => {
11926
12317
  assertCoordinatorBinding(session, sessionPath, context.snapshot);
11927
12318
  const handoff = requireHandoff(context, scope.planId, request.handoffId);
11928
- if (handoff.state !== "merged") {
11929
- 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 });
11930
- }
11931
- if (rowStatusOf(context.row) !== "InReview") {
11932
- throw new CoordinationError("coordination.invalid-transition", `complete requires ${scope.planId} to still be InReview`, { plan_id: scope.planId, status: context.row.status });
11933
- }
11934
- const prepared = context.coordination?.prepared;
11935
- if (prepared === undefined) {
11936
- 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;
11937
12322
  }
11938
- assertEvidenceDigests(handoff);
11939
- await assertFindingsClosed(scope, prepared, "complete");
11940
- assertExecutionHolder(context.row, session.session_id, scope.planId, "complete");
11941
- assertMergeLease(context.snapshot, session, scope.planId, handoff);
11942
- const integration = requireIntegration(handoff, scope.planId);
11943
- const anchors = integrationAnchors(context.snapshot, scope.planId);
11944
- const checkout = assertIntegrationCheckout(anchors, scope.planId);
11945
- assertRecordedResult(anchors.worktreePath, scope.planId, integration, handoff.source_sha, checkout.head);
12323
+ await assertIterationCompletionPrecheck(context, scope, session, handoff);
11946
12324
  },
11947
12325
  mutate: (context) => {
11948
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
+ }
11949
12333
  const integration = requireIntegration(handoff, scope.planId);
11950
12334
  const resultSha = assertRecordedResult(integration.worktree_path, scope.planId, integration, handoff.source_sha, undefined);
11951
12335
  return completeRow(context, scope, handoff, resultSha);
@@ -11969,6 +12353,10 @@ async function classifyReconcile(context, scope, session, handoff, expectedHando
11969
12353
  });
11970
12354
  }
11971
12355
  if (handoff.state === "completed") {
12356
+ if (isStandaloneDevelopmentWorkflow(context.snapshot)) {
12357
+ assertStandaloneCompletedReplay(context, scope, handoff);
12358
+ return { outcome: "already-completed", apply: () => null };
12359
+ }
11972
12360
  const integration = requireIntegration(handoff, planId);
11973
12361
  const repository = proofRepository([integration.worktree_path, handoff.worktree_path, session.harness_root]);
11974
12362
  if (repository === undefined) {
@@ -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
  /**
@@ -375,7 +379,15 @@ export type RecordWorkflowDeliveryResult = {
375
379
  * `development` workflow) — the declared kind is authoritative;
376
380
  * - a rewrite of the recorded PR identity (§4d records it once at submission:
377
381
  * an identical re-record is idempotent, a different pair is refused);
378
- * - an empty patch or a malformed member: nothing is silently dropped.
382
+ * - an empty patch or a malformed member: nothing is silently dropped;
383
+ * - compound / PR identity / verified-merge record while any owned plan row
384
+ * is not `Done` (`PHASE6_PLAN_ROW_NOT_DONE` — contract §3: the delivery
385
+ * tail runs after every row is Done; write-time only, see below).
386
+ *
387
+ * Grandfathering: the row-Done gate is write-time only. Snapshots that
388
+ * already carry delivery evidence while rows are not `Done` are never
389
+ * retro-invalidated; idempotent re-records return without consulting row
390
+ * state; `closeWorkflow` / `evaluatePostMergeClose` are unchanged.
379
391
  *
380
392
  * Idempotent and re-entrant: re-recording the exact stored evidence performs
381
393
  * NO write and returns the snapshot as read (the timestamp is untouched), so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/engine",
3
- "version": "3.10.1",
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": {