@mstar-harness/engine 3.10.2 → 3.11.0

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/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
  }
@@ -2023,6 +2084,158 @@ async function registerPlanWorkflow(workflowId, options) {
2023
2084
  return { workflowId, snapshotPath, recovered: false };
2024
2085
  });
2025
2086
  }
2087
+ function iterationWorkflowRegistrationIdentity(snapshot) {
2088
+ const coordinator = snapshot.coordination?.coordinator;
2089
+ return stableJson({
2090
+ id: snapshot.id,
2091
+ type: snapshot.type,
2092
+ status: snapshot.status,
2093
+ compass_ref: snapshot.compass_ref ?? null,
2094
+ branch: snapshot.branch ?? null,
2095
+ project: snapshot.project ?? null,
2096
+ plans: snapshot.plans,
2097
+ coordinator: coordinator ? { session_id: coordinator.session_id, session_file: coordinator.session_file } : null
2098
+ });
2099
+ }
2100
+ async function registerIterationWorkflow(workflowId, options) {
2101
+ const refuse = (detail) => new Error(`registerIterationWorkflow: ${detail}`);
2102
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
2103
+ throw refuse("options must be an object (harnessDir, compassRef, branch, rows)");
2104
+ }
2105
+ if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
2106
+ throw refuse("options.harnessDir is required (must contain status.json + workflows/)");
2107
+ }
2108
+ assertSafePathComponent(workflowId, "workflow id");
2109
+ if (typeof options.compassRef !== "string" || options.compassRef.trim() === "") {
2110
+ throw refuse("options.compassRef must be a non-empty string");
2111
+ }
2112
+ if (typeof options.branch !== "object" || options.branch === null || Array.isArray(options.branch)) {
2113
+ throw refuse("options.branch must be an object (base, integration, target)");
2114
+ }
2115
+ for (const anchor of ["base", "integration", "target"]) {
2116
+ const value = options.branch[anchor];
2117
+ if (typeof value !== "string" || value.trim() === "") {
2118
+ throw refuse(`options.branch.${anchor} must be a non-empty string`);
2119
+ }
2120
+ }
2121
+ if (options.project !== undefined && (typeof options.project !== "string" || options.project.trim() === "")) {
2122
+ throw refuse("options.project must be a non-empty string when given");
2123
+ }
2124
+ const rows = options.rows;
2125
+ if (!Array.isArray(rows) || rows.length === 0) {
2126
+ throw refuse("options.rows must be a non-empty array of { id, title, file }");
2127
+ }
2128
+ const seenRowIds = new Set;
2129
+ for (const row of rows) {
2130
+ if (typeof row !== "object" || row === null || Array.isArray(row)) {
2131
+ throw refuse("each row must be an object ({ id, title, file })");
2132
+ }
2133
+ for (const field of ["id", "title", "file"]) {
2134
+ if (typeof row[field] !== "string" || row[field].trim() === "") {
2135
+ throw refuse(`each row.${field} must be a non-empty string`);
2136
+ }
2137
+ }
2138
+ if ("status" in row) {
2139
+ throw refuse(`row ${JSON.stringify(row.id)} supplies a status — rows are always written status "Todo"; ` + "a state transition is requested through the lifecycle seams, never at registration");
2140
+ }
2141
+ if (seenRowIds.has(row.id)) {
2142
+ throw refuse(`duplicate row id ${JSON.stringify(row.id)} — plan row ids are unique per workflow`);
2143
+ }
2144
+ seenRowIds.add(row.id);
2145
+ }
2146
+ const startedAt = options.startedAt ?? new Date().toISOString();
2147
+ if (!isCloseTimestamp(startedAt)) {
2148
+ throw refuse("options.startedAt must be a valid YYYY-MM-DD date or RFC3339 timestamp");
2149
+ }
2150
+ const harnessDir = resolve6(options.harnessDir);
2151
+ const statusPath = join6(harnessDir, "status.json");
2152
+ const workflowDir = join6(harnessDir, "workflows", workflowId);
2153
+ const snapshotPath = join6(workflowDir, WORKFLOW_SNAPSHOT_FILE);
2154
+ const store = getArtifactStore();
2155
+ const snapshot = {
2156
+ schema_version: 1,
2157
+ id: workflowId,
2158
+ type: "iteration",
2159
+ status: "running",
2160
+ started_at: startedAt,
2161
+ updated_at: startedAt.slice(0, 10),
2162
+ compass_ref: options.compassRef,
2163
+ branch: { base: options.branch.base, integration: options.branch.integration, target: options.branch.target },
2164
+ plans: rows.map((r) => ({
2165
+ id: r.id,
2166
+ title: r.title,
2167
+ file: r.file,
2168
+ status: "Todo",
2169
+ metadata: {
2170
+ iteration_refs: [options.compassRef],
2171
+ spec_integration_branch: options.branch.integration,
2172
+ merge_target: options.branch.integration
2173
+ }
2174
+ }))
2175
+ };
2176
+ if (options.project !== undefined)
2177
+ snapshot.project = options.project;
2178
+ const entry = {
2179
+ id: workflowId,
2180
+ type: "iteration",
2181
+ started_at: snapshot.started_at,
2182
+ dir: `workflows/${workflowId}`
2183
+ };
2184
+ const entryGate = validateWorkflowEntry(entry);
2185
+ if (!entryGate.ok) {
2186
+ throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
2187
+ }
2188
+ return withStatusWriteLock(statusPath, async () => {
2189
+ const rootDoc = readJson(statusPath);
2190
+ const workflows = Array.isArray(rootDoc.workflows) ? rootDoc.workflows : [];
2191
+ if (workflows.some((candidate) => isPlainObject2(candidate) && candidate.id === workflowId)) {
2192
+ throw new Error(`refusing to register workflow ${JSON.stringify(workflowId)}: it is already registered ` + `(root entry in ${statusPath}) — registration is create-only; ` + "remove that workflow before registering again");
2193
+ }
2194
+ if (existsSync4(snapshotPath)) {
2195
+ const existing = readWorkflowSnapshot(workflowDir);
2196
+ if (existing.snapshot.id !== workflowId || iterationWorkflowRegistrationIdentity(existing.snapshot) !== iterationWorkflowRegistrationIdentity(snapshot)) {
2197
+ throw new Error(`refusing to register workflow ${JSON.stringify(workflowId)}: snapshot ${snapshotPath} already exists ` + "with a different registration identity — remove that workflow or register under a different id");
2198
+ }
2199
+ const recoveryEntry = {
2200
+ id: workflowId,
2201
+ type: "iteration",
2202
+ started_at: existing.snapshot.started_at,
2203
+ dir: `workflows/${workflowId}`
2204
+ };
2205
+ const recoveryGate = validateWorkflowEntry(recoveryEntry);
2206
+ if (!recoveryGate.ok) {
2207
+ throw new Error(`refusing to register invalid workflow entry: ${recoveryGate.violations.map((v) => v.message).join("; ")}`);
2208
+ }
2209
+ await registerWorkflowEntryLocked(statusPath, recoveryEntry);
2210
+ return { workflowId, snapshotPath, recovered: true };
2211
+ }
2212
+ let createdVersion;
2213
+ try {
2214
+ await writeWorkflowSnapshot(snapshot, workflowDir, { createOnly: true });
2215
+ createdVersion = readArtifactBytes(snapshotPath)?.version;
2216
+ await registerWorkflowEntryLocked(statusPath, entry);
2217
+ } catch (error) {
2218
+ if (createdVersion !== undefined) {
2219
+ await withStatusWriteLock(snapshotPath, async () => {
2220
+ const current = readArtifactBytes(snapshotPath);
2221
+ if (current === undefined || current.version !== createdVersion)
2222
+ return;
2223
+ const remove = store.delete?.bind(store);
2224
+ if (remove !== undefined) {
2225
+ await withProtectedWrite(snapshotPath, "delete", () => remove({ kind: "snapshot", key: workflowId }));
2226
+ }
2227
+ });
2228
+ }
2229
+ try {
2230
+ if (readdirSync2(workflowDir).length === 0) {
2231
+ rmdirSync2(workflowDir);
2232
+ }
2233
+ } catch {}
2234
+ throw error;
2235
+ }
2236
+ return { workflowId, snapshotPath, recovered: false };
2237
+ });
2238
+ }
2026
2239
 
2027
2240
  // src/status.ts
2028
2241
  var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
@@ -7566,6 +7779,176 @@ function slugify(title) {
7566
7779
  }
7567
7780
  var escapeCell = (value) => value.replace(/\|/g, "\\|");
7568
7781
  var truncate = (value, max) => value.length > max ? `${value.slice(0, max)}…` : value;
7782
+ var collapseEvidenceWs = (value) => value.replace(/\s*[\r\n]\s*/g, " ");
7783
+ var evidenceText = (item) => typeof item === "string" ? item : `${item.file}${item.line !== undefined ? `:${item.line}` : ""} — ${item.description}`;
7784
+ var hasEnrichedMetadata = (finding) => finding.fingerprint !== undefined || finding.trace !== undefined || finding.severity !== undefined || finding.evidence.some((item) => typeof item !== "string");
7785
+ var AUDIT_FINGERPRINT_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@+-]*(?![\s\S])/;
7786
+ var AUDIT_SEVERITY_ORDER = { informational: 0, low: 1, medium: 2, high: 3, critical: 4 };
7787
+ var DEFAULT_IGNORABLE_RE = /[\u00AD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180B-\u180F\u200B-\u200F\u202A-\u202E\u2060-\u206F\u3164\uFE00-\uFE0F\uFEFF\uFFA0\uFFF0-\uFFF8\u{1BCA0}-\u{1BCA3}\u{1D173}-\u{1D17A}\u{E0000}-\u{E0FFF}]/u;
7788
+ var LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
7789
+ var isPlainObject9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7790
+ function isVisibleText(value) {
7791
+ if (LONE_SURROGATE_RE.test(value))
7792
+ return false;
7793
+ for (const ch of value) {
7794
+ if (!/\p{White_Space}/u.test(ch) && !DEFAULT_IGNORABLE_RE.test(ch))
7795
+ return true;
7796
+ }
7797
+ return false;
7798
+ }
7799
+ function safeAuditPath(value) {
7800
+ if (typeof value !== "string")
7801
+ return false;
7802
+ if (value === "")
7803
+ return false;
7804
+ if (value.includes("\\"))
7805
+ return false;
7806
+ if (/[\u0000-\u001F\u007F-\u009F]/.test(value))
7807
+ return false;
7808
+ if (LONE_SURROGATE_RE.test(value))
7809
+ return false;
7810
+ if (value.startsWith("/"))
7811
+ return false;
7812
+ if (/^[A-Za-z]:/.test(value))
7813
+ return false;
7814
+ for (const segment of value.split("/")) {
7815
+ if (segment === "" || segment === "." || segment === "..")
7816
+ return false;
7817
+ if (segment.endsWith(".") || segment.endsWith(" "))
7818
+ return false;
7819
+ }
7820
+ return true;
7821
+ }
7822
+ function validateAuditFindingGates(findings) {
7823
+ const violations = [];
7824
+ const seen = new Map;
7825
+ let previousFingerprint;
7826
+ if (!Array.isArray(findings)) {
7827
+ violations.push(violation10("high", "audit.finding.shape", "findings — audit.finding.shape"));
7828
+ return { ok: false, violations };
7829
+ }
7830
+ findings.forEach((finding, index) => {
7831
+ const at = (field) => `findings[${index}].${field}`;
7832
+ const push = (code, field) => {
7833
+ violations.push(violation10("high", code, `${at(field)} — ${code}`));
7834
+ };
7835
+ if (!isPlainObject9(finding)) {
7836
+ violations.push(violation10("high", "audit.finding.shape", `findings[${index}] — audit.finding.shape`));
7837
+ return;
7838
+ }
7839
+ if (finding.fingerprint !== undefined) {
7840
+ const fingerprint = finding.fingerprint;
7841
+ if (typeof fingerprint !== "string" || !AUDIT_FINGERPRINT_RE.test(fingerprint))
7842
+ push("audit.finding.fingerprint.grammar", "fingerprint");
7843
+ else if (redactSecrets(fingerprint).text !== fingerprint)
7844
+ push("audit.finding.fingerprint.secret", "fingerprint");
7845
+ else {
7846
+ const firstAt = seen.get(fingerprint);
7847
+ if (firstAt !== undefined)
7848
+ push("audit.finding.fingerprint.duplicate", "fingerprint");
7849
+ else
7850
+ seen.set(fingerprint, index);
7851
+ if (previousFingerprint !== undefined && fingerprint < previousFingerprint) {
7852
+ push("audit.finding.fingerprint.order", "fingerprint");
7853
+ }
7854
+ previousFingerprint = fingerprint;
7855
+ }
7856
+ }
7857
+ if (finding.severity !== undefined) {
7858
+ if (!isPlainObject9(finding.severity)) {
7859
+ push("audit.finding.severity.shape", "severity");
7860
+ } else {
7861
+ const { likelihood, impact, overall } = finding.severity;
7862
+ const rankOf = (r) => AUDIT_SEVERITY_ORDER[r];
7863
+ for (const [field, value] of [
7864
+ ["likelihood", likelihood],
7865
+ ["impact", impact],
7866
+ ["overall", overall]
7867
+ ]) {
7868
+ if (rankOf(value) === undefined)
7869
+ push("audit.finding.severity.rank", `severity.${field}`);
7870
+ }
7871
+ const impactRank = rankOf(impact);
7872
+ const overallRank = rankOf(overall);
7873
+ if (impactRank !== undefined && overallRank !== undefined && overallRank > impactRank) {
7874
+ push("audit.finding.severity.overall-exceeds-impact", "severity.overall");
7875
+ }
7876
+ }
7877
+ }
7878
+ const textViolation = (field, value) => {
7879
+ if (value === undefined)
7880
+ return;
7881
+ if (typeof value !== "string")
7882
+ push("audit.finding.text.type", field);
7883
+ else if (LONE_SURROGATE_RE.test(value))
7884
+ push("audit.finding.text.surrogate", field);
7885
+ else if (!isVisibleText(value))
7886
+ push("audit.finding.text.invisible", field);
7887
+ };
7888
+ if (finding.trace !== undefined) {
7889
+ if (!Array.isArray(finding.trace))
7890
+ push("audit.finding.trace.shape", "trace");
7891
+ else if (finding.trace.length === 0)
7892
+ push("audit.finding.trace.empty", "trace");
7893
+ else {
7894
+ const kinds = finding.trace.map((s) => isPlainObject9(s) ? s.kind : undefined);
7895
+ const topologyOk = finding.trace.length === 1 ? kinds[0] === "entrypoint" || kinds[0] === "sink" : kinds[0] === "entrypoint" && kinds[kinds.length - 1] === "sink" && kinds.slice(1, -1).every((k) => k === "propagation");
7896
+ if (!topologyOk)
7897
+ push("audit.finding.trace.topology", "trace");
7898
+ finding.trace.forEach((step, stepIndex) => {
7899
+ const stepAt = `trace[${stepIndex}]`;
7900
+ if (!isPlainObject9(step)) {
7901
+ push("audit.finding.trace.shape", stepAt);
7902
+ return;
7903
+ }
7904
+ if (typeof step.line !== "number" || !Number.isSafeInteger(step.line) || step.line <= 0) {
7905
+ push("audit.finding.trace.line", `${stepAt}.line`);
7906
+ }
7907
+ const stepFile = step.file;
7908
+ if (typeof stepFile !== "string" || !safeAuditPath(stepFile))
7909
+ push("audit.finding.path.unsafe", `${stepAt}.file`);
7910
+ else if (redactSecrets(stepFile).text !== stepFile)
7911
+ push("audit.finding.path.secret", `${stepAt}.file`);
7912
+ if (step.scope === undefined)
7913
+ push("audit.finding.trace.shape", `${stepAt}.scope`);
7914
+ else
7915
+ textViolation(`${stepAt}.scope`, step.scope);
7916
+ if (step.description === undefined)
7917
+ push("audit.finding.trace.shape", `${stepAt}.description`);
7918
+ else
7919
+ textViolation(`${stepAt}.description`, step.description);
7920
+ });
7921
+ }
7922
+ }
7923
+ if (!Array.isArray(finding.evidence))
7924
+ push("audit.finding.evidence.shape", "evidence");
7925
+ else
7926
+ finding.evidence.forEach((item, itemIndex) => {
7927
+ if (typeof item === "string") {
7928
+ textViolation(`evidence[${itemIndex}]`, item);
7929
+ return;
7930
+ }
7931
+ if (!isPlainObject9(item)) {
7932
+ push("audit.finding.evidence.shape", `evidence[${itemIndex}]`);
7933
+ return;
7934
+ }
7935
+ const itemFile = item.file;
7936
+ if (typeof itemFile !== "string" || !safeAuditPath(itemFile))
7937
+ push("audit.finding.path.unsafe", `evidence[${itemIndex}].file`);
7938
+ else if (redactSecrets(itemFile).text !== itemFile)
7939
+ push("audit.finding.path.secret", `evidence[${itemIndex}].file`);
7940
+ if (item.line !== undefined && (typeof item.line !== "number" || !Number.isSafeInteger(item.line) || item.line <= 0)) {
7941
+ push("audit.finding.evidence.line", `evidence[${itemIndex}].line`);
7942
+ }
7943
+ textViolation(`evidence[${itemIndex}].description`, item.description);
7944
+ });
7945
+ textViolation("title", finding.title);
7946
+ textViolation("impact", finding.impact);
7947
+ textViolation("fixSketch", finding.fixSketch);
7948
+ textViolation("verification", finding.verification);
7949
+ });
7950
+ return { ok: violations.length === 0, violations };
7951
+ }
7569
7952
  function renderPlanFile(finding, plannedAt) {
7570
7953
  const sections = [
7571
7954
  `# ${finding.title}`,
@@ -7574,15 +7957,22 @@ function renderPlanFile(finding, plannedAt) {
7574
7957
  `- **Priority**: ${finding.priority}`,
7575
7958
  `- **Effort**: ${finding.effort}`,
7576
7959
  `- **Risk**: ${finding.risk}`,
7960
+ ...finding.confidence !== "MED" || hasEnrichedMetadata(finding) ? [`- **Confidence**: ${finding.confidence}`] : [],
7961
+ ...finding.fingerprint !== undefined ? [`- **Fingerprint**: ${finding.fingerprint}`] : [],
7962
+ ...finding.severity !== undefined ? [`- **Likelihood**: ${finding.severity.likelihood}`, `- **Severity impact**: ${finding.severity.impact}`, `- **Severity**: ${finding.severity.overall}`] : [],
7577
7963
  `- **Depends on**: ${finding.dependsOn ?? "none"}`,
7578
7964
  `- **Category**: ${finding.category}`,
7965
+ ...finding.evidence.length > 0 ? [`- **Evidence**: ${collapseEvidenceWs(evidenceText(finding.evidence[0]))}`] : [],
7579
7966
  `- **Planned at**: commit \`${plannedAt.commit}\`, ${plannedAt.date}`,
7580
7967
  "",
7581
7968
  "## Impact",
7582
7969
  finding.impact
7583
7970
  ];
7584
7971
  if (finding.evidence.length > 0) {
7585
- sections.push("", "## Evidence", ...finding.evidence.map((e) => `- ${e}`));
7972
+ sections.push("", "## Evidence", ...finding.evidence.map((item) => `- ${evidenceText(item)}`));
7973
+ }
7974
+ if (finding.trace !== undefined) {
7975
+ sections.push("", "## Trace", "", "| Kind | Location | Scope / Description |", "|------|----------|---------------------|", ...finding.trace.map((step) => `| ${escapeCell(step.kind)} | ${escapeCell(`${step.file}:${step.line}`)} | ${escapeCell(`${step.scope} — ${step.description}`).replace(/\r\n|\r|\n/g, "\\n")} |`));
7586
7976
  }
7587
7977
  if (finding.fixSketch !== undefined) {
7588
7978
  sections.push("", "## Fix sketch", finding.fixSketch);
@@ -7602,7 +7992,8 @@ function redactFinding(finding) {
7602
7992
  ...finding,
7603
7993
  title: redactText(finding.title),
7604
7994
  impact: redactText(finding.impact),
7605
- evidence: finding.evidence.map(redactText),
7995
+ evidence: finding.evidence.map((item) => typeof item === "string" ? redactText(item) : { ...item, description: redactText(item.description) }),
7996
+ ...finding.trace !== undefined ? { trace: finding.trace.map((step) => ({ ...step, scope: redactText(step.scope), description: redactText(step.description) })) } : {},
7606
7997
  ...finding.fixSketch !== undefined ? { fixSketch: redactText(finding.fixSketch) } : {},
7607
7998
  ...finding.verification !== undefined ? { verification: redactText(finding.verification) } : {}
7608
7999
  };
@@ -7624,7 +8015,10 @@ function extractSecurityDispositionSections(text) {
7624
8015
  }
7625
8016
  function renderIndex(params) {
7626
8017
  const { date, repoName, repoShortSha, rows, rejected, needsVerification, hardeningChecked } = params;
7627
- const findingsRows = rows.map((r) => `| ${r.num} | ${escapeCell(r.title)} | ${r.category} | ${escapeCell(truncate(r.impact, 80))} | ${r.effort} | ${r.risk} | ${r.confidence} | ${escapeCell(truncate(r.evidence, 80))} |`).join(`
8018
+ const showFingerprint = rows.some((r) => r.fingerprint !== undefined);
8019
+ const showSeverity = rows.some((r) => r.likelihood !== undefined || r.severityImpact !== undefined || r.severity !== undefined);
8020
+ const cell = (value) => escapeCell(value ?? "—");
8021
+ const findingsRows = rows.map((r) => `| ${r.num} | ${escapeCell(r.title)} | ${r.category} | ${escapeCell(truncate(r.impact, 80))} | ${r.effort} | ${r.risk} | ${r.confidence} | ${escapeCell(truncate(r.evidence, 80))}` + (showFingerprint ? ` | ${cell(r.fingerprint)}` : "") + (showSeverity ? ` | ${cell(r.likelihood)} | ${cell(r.severityImpact)} | ${cell(r.severity)}` : "") + " |").join(`
7628
8022
  `);
7629
8023
  const directionRows = rows.filter((r) => r.category === "direction").map((r) => `- ${escapeCell(r.title)} — ${escapeCell(truncate(r.impact, 120))}`).join(`
7630
8024
  `);
@@ -7637,8 +8031,8 @@ function renderIndex(params) {
7637
8031
  "",
7638
8032
  "## Findings",
7639
8033
  "",
7640
- "| # | Finding | Category | Impact | Effort | Risk | Confidence | Evidence |",
7641
- "|---|---------|----------|--------|--------|------|------------|----------|",
8034
+ "| # | Finding | Category | Impact | Effort | Risk | Confidence | Evidence" + (showFingerprint ? " | Fingerprint" : "") + (showSeverity ? " | Likelihood | Severity impact | Severity" : "") + " |",
8035
+ "|---|---------|----------|--------|--------|------|------------|----------" + (showFingerprint ? "|------------" : "") + (showSeverity ? "|------------|-----------------|----------" : "") + "|",
7642
8036
  findingsRows
7643
8037
  ];
7644
8038
  if (directionRows !== "") {
@@ -7662,6 +8056,11 @@ function renderIndex(params) {
7662
8056
  function scaffoldAuditPlan(outDir, findings, options = {}) {
7663
8057
  const date = options.date ?? new Date().toISOString().slice(0, 10);
7664
8058
  const plannedAt = options.plannedAt ?? { commit: options.repoShortSha ?? "unknown", date };
8059
+ const gate2 = validateAuditFindingGates(findings);
8060
+ if (!gate2.ok) {
8061
+ const first = gate2.violations[0];
8062
+ throw new TypeError(`invalid audit findings — ${first.code}: ${first.message}`);
8063
+ }
7665
8064
  mkdirSync8(outDir, { recursive: true });
7666
8065
  const existingReadme = join16(outDir, "README.md");
7667
8066
  const carried = existsSync12(existingReadme) ? extractSecurityDispositionSections(readFileSync11(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
@@ -7696,10 +8095,14 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
7696
8095
  impact: "see plan file",
7697
8096
  effort: fields.get("Effort") ?? "—",
7698
8097
  risk: fields.get("Risk") ?? "—",
7699
- confidence: "—",
8098
+ confidence: fields.get("Confidence") ?? "—",
7700
8099
  evidence: fields.get("Evidence") ?? "—",
7701
8100
  priority: fields.get("Priority") ?? "—",
7702
- dependsOn: fields.get("Depends on") ?? "—"
8101
+ dependsOn: fields.get("Depends on") ?? "—",
8102
+ fingerprint: fields.get("Fingerprint"),
8103
+ likelihood: fields.get("Likelihood"),
8104
+ severityImpact: fields.get("Severity impact"),
8105
+ severity: fields.get("Severity")
7703
8106
  };
7704
8107
  });
7705
8108
  const byNum = new Map(rows.map((r) => [r.num, r]));
@@ -7714,9 +8117,13 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
7714
8117
  row.effort = finding.effort;
7715
8118
  row.risk = finding.risk;
7716
8119
  row.confidence = finding.confidence;
7717
- row.evidence = finding.evidence[0] ?? "";
8120
+ row.evidence = finding.evidence.length > 0 ? collapseEvidenceWs(evidenceText(finding.evidence[0])) : "";
7718
8121
  row.priority = finding.priority;
7719
8122
  row.dependsOn = finding.dependsOn ?? "none";
8123
+ row.fingerprint = finding.fingerprint;
8124
+ row.likelihood = finding.severity?.likelihood;
8125
+ row.severityImpact = finding.severity?.impact;
8126
+ row.severity = finding.severity?.overall;
7720
8127
  }
7721
8128
  });
7722
8129
  const needsVerificationLines = options.needsVerification !== undefined ? options.needsVerification.map((nv) => `- ${escapeCell(redactText(nv.lead))}: ${escapeCell(redactText(nv.how))}${nv.evidence ? ` (${escapeCell(redactText(nv.evidence))})` : ""}`) : carried.needsVerification;
@@ -8952,7 +9359,7 @@ function computePrTally(input) {
8952
9359
  var REVIEW_SCHEMA_ID = "mstar.review/v1";
8953
9360
  var INSPECTOR_VERDICTS = ["comment", "request_changes", "approve"];
8954
9361
  var INSPECTOR_SEVERITIES = ["critical", "warning", "suggestion", "info"];
8955
- function isPlainObject9(value) {
9362
+ function isPlainObject10(value) {
8956
9363
  return typeof value === "object" && value !== null && !Array.isArray(value);
8957
9364
  }
8958
9365
  var TALLY_COUNT_KEYS = ["mustFix", "shouldFix", "nit", "unverified"];
@@ -8963,7 +9370,7 @@ function checkProvidedTallyShape(tally, violations) {
8963
9370
  if (typeof tally.scorePct !== "number" || !Number.isInteger(tally.scorePct) || tally.scorePct < 0 || tally.scorePct > 100) {
8964
9371
  violations.push(violation15("high", "review.tally-malformed", `tally.scorePct must be an integer in [0, 100] - got ${String(tally.scorePct)}`));
8965
9372
  }
8966
- if (!isPlainObject9(tally.tally)) {
9373
+ if (!isPlainObject10(tally.tally)) {
8967
9374
  violations.push(violation15("high", "review.tally-malformed", "tally.tally must be an object carrying the four class counts"));
8968
9375
  } else {
8969
9376
  for (const key of TALLY_COUNT_KEYS) {
@@ -8979,7 +9386,7 @@ function checkProvidedTallyShape(tally, violations) {
8979
9386
  }
8980
9387
  function validateMstarReviewV1(doc) {
8981
9388
  const violations = [];
8982
- if (!isPlainObject9(doc)) {
9389
+ if (!isPlainObject10(doc)) {
8983
9390
  return {
8984
9391
  ok: false,
8985
9392
  violations: [violation15("high", "review.not-object", "review document must be a JSON object")]
@@ -9006,7 +9413,7 @@ function validateMstarReviewV1(doc) {
9006
9413
  violations.push(violation15("high", "review.findings-not-array", "findings must be an array"));
9007
9414
  } else {
9008
9415
  doc.findings.forEach((finding, index) => {
9009
- if (!isPlainObject9(finding)) {
9416
+ if (!isPlainObject10(finding)) {
9010
9417
  violations.push(violation15("high", "review.invalid-finding", `findings[${index}] must be an object`));
9011
9418
  return;
9012
9419
  }
@@ -9046,7 +9453,7 @@ function validateMstarReviewV1(doc) {
9046
9453
  });
9047
9454
  }
9048
9455
  if (doc.tally !== undefined) {
9049
- if (!isPlainObject9(doc.tally)) {
9456
+ if (!isPlainObject10(doc.tally)) {
9050
9457
  violations.push(violation15("high", "review.invalid-tally", "tally must be a PrTallyResult object"));
9051
9458
  } else {
9052
9459
  checkProvidedTallyShape(doc.tally, violations);
@@ -9056,7 +9463,7 @@ function validateMstarReviewV1(doc) {
9056
9463
  }
9057
9464
  }
9058
9465
  if (doc.target !== undefined) {
9059
- if (!isPlainObject9(doc.target)) {
9466
+ if (!isPlainObject10(doc.target)) {
9060
9467
  violations.push(violation15("high", "review.invalid-target", "target must be an object"));
9061
9468
  } else {
9062
9469
  if (doc.target.owner !== undefined && typeof doc.target.owner !== "string") {
@@ -9906,6 +10313,7 @@ var IMPLEMENTED_OPERATIONS = {
9906
10313
  "integration-start": true,
9907
10314
  "integration-accept": true,
9908
10315
  complete: true,
10316
+ "repair-delivery-source": true,
9909
10317
  reconcile: true
9910
10318
  };
9911
10319
  function nowIso() {
@@ -10465,7 +10873,11 @@ function allowedOperations(role, sessionId, snapshot, row) {
10465
10873
  out.push("accept", "return");
10466
10874
  break;
10467
10875
  case "accepted":
10468
- out.push("return", "integration-start");
10876
+ if (isStandaloneDevelopmentWorkflow(snapshot)) {
10877
+ out.push("return", "complete", "repair-delivery-source");
10878
+ } else {
10879
+ out.push("return", "integration-start");
10880
+ }
10469
10881
  break;
10470
10882
  case "integrating":
10471
10883
  out.push("integration-accept", "complete", "reconcile");
@@ -11240,6 +11652,13 @@ async function mutatePlanCoordination(request) {
11240
11652
  expectedRevision
11241
11653
  });
11242
11654
  }
11655
+ case "repair-delivery-source": {
11656
+ assertExactKeys(operation, ["kind", "handoffId"], "repair-delivery-source operation");
11657
+ return mutateRepairDeliverySource(await coordinatorScope(session, request.planId, kind), session, sessionAbs, {
11658
+ handoffId: namedHandoffId(operation),
11659
+ expectedRevision
11660
+ });
11661
+ }
11243
11662
  case "reconcile": {
11244
11663
  assertExactKeys(operation, ["kind", "handoffId"], "reconcile operation");
11245
11664
  return mutateReconcile(await coordinatorScope(session, request.planId, kind), session, sessionAbs, {
@@ -11270,6 +11689,7 @@ var COORDINATOR_OPERATIONS = [
11270
11689
  "integration-start",
11271
11690
  "integration-accept",
11272
11691
  "complete",
11692
+ "repair-delivery-source",
11273
11693
  "reconcile"
11274
11694
  ];
11275
11695
  function assertSessionRole(session, kind) {
@@ -11910,6 +12330,313 @@ async function mutateIntegrationAccept(scope, session, sessionPath, request) {
11910
12330
  view: buildView(scope.harnessRoot, scope.workflowId, scope.projectId, scope, result.snapshot, result.row, session, sessionPath)
11911
12331
  };
11912
12332
  }
12333
+ function validateRowCoordinationInContext(context, coordination, what) {
12334
+ assertViolationFree(validateRowCoordination(coordination, what, rowValidationRoute(context.snapshot, context.row)), what);
12335
+ }
12336
+ function standaloneDeliveryAnchors(snapshot, planId) {
12337
+ const source = snapshot.branch?.source;
12338
+ const target = snapshot.branch?.target;
12339
+ if (!isNonEmptyString(source) || !isNonEmptyString(target)) {
12340
+ throw new CoordinationError("coordination.invalid-transition", `plan ${planId} has no delivery anchors — the snapshot must name branch.source and branch.target`, { plan_id: planId });
12341
+ }
12342
+ return { source, target };
12343
+ }
12344
+ function assertStandaloneRoute(snapshot, planId, what) {
12345
+ if (!isStandaloneDevelopmentWorkflow(snapshot)) {
12346
+ throw new CoordinationError("coordination.invalid-transition", `${what} requires a standalone development workflow for plan ${planId}`, { plan_id: planId });
12347
+ }
12348
+ }
12349
+ function assertNoIntegrationContamination(context, planId, handoff, what, code = "coordination.invalid-transition") {
12350
+ if (handoff.integration !== undefined) {
12351
+ throw new CoordinationError(code, `${what} refuses plan ${planId} because the handoff already carries an integration record`, { plan_id: planId });
12352
+ }
12353
+ if (context.snapshot.integration_worktree_path !== undefined) {
12354
+ throw new CoordinationError(code, `${what} refuses plan ${planId} because the snapshot names integration_worktree_path`, { plan_id: planId });
12355
+ }
12356
+ if (isNonEmptyString(context.snapshot.branch?.integration)) {
12357
+ throw new CoordinationError(code, `${what} refuses plan ${planId} because the snapshot names branch.integration`, { plan_id: planId, integration: context.snapshot.branch?.integration });
12358
+ }
12359
+ if (context.snapshot.integration_merge_lease !== undefined) {
12360
+ throw new CoordinationError(code, `${what} refuses plan ${planId} because the snapshot carries an integration merge lease`, { plan_id: planId });
12361
+ }
12362
+ }
12363
+ function assertAcceptedReviewDecision(handoff, planId, what) {
12364
+ if (handoff.qc.decision !== "Approve" && handoff.qc.decision !== "Approve with residuals") {
12365
+ 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 });
12366
+ }
12367
+ if (handoff.qa.decision !== "pass") {
12368
+ 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 });
12369
+ }
12370
+ }
12371
+ function assertStandaloneBranchIdentity(context, scope, handoff, anchors, what, requireLease = true) {
12372
+ const worktree = canonicalTarget(scope.worktreePath);
12373
+ if (handoff.source_branch !== anchors.source) {
12374
+ 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 });
12375
+ }
12376
+ if (requireLease) {
12377
+ const lease = requireExecutionLease(context.row, scope.planId, what);
12378
+ if (scope.workingBranch !== anchors.source) {
12379
+ 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 });
12380
+ }
12381
+ if (lease.working_branch !== anchors.source) {
12382
+ 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 });
12383
+ }
12384
+ if (canonicalTarget(lease.worktree_path) !== worktree) {
12385
+ 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 });
12386
+ }
12387
+ }
12388
+ if (canonicalTarget(handoff.worktree_path) !== worktree) {
12389
+ 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 });
12390
+ }
12391
+ const metadata = context.row.metadata;
12392
+ if (isPlainObject2(metadata) && metadata.working_branch !== undefined && metadata.working_branch !== anchors.source) {
12393
+ 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 });
12394
+ }
12395
+ if (isPlainObject2(metadata) && metadata.worktree_path !== undefined && canonicalTarget(String(metadata.worktree_path)) !== worktree) {
12396
+ 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 });
12397
+ }
12398
+ }
12399
+ function assertStandaloneSourceGitProof(scope, handoff, sourceBranch, what) {
12400
+ assertFeatureCheckout(scope, handoff.source_sha, what);
12401
+ const branch = gitRead(scope.worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
12402
+ if (branch !== sourceBranch) {
12403
+ 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 });
12404
+ }
12405
+ const refTip = gitRead(scope.worktreePath, ["rev-parse", `refs/heads/${sourceBranch}`]);
12406
+ if (refTip !== handoff.source_sha) {
12407
+ 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 });
12408
+ }
12409
+ if (handoff.review_head !== handoff.source_sha) {
12410
+ 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 });
12411
+ }
12412
+ if (!gitObjectExists(scope.worktreePath, handoff.review_base)) {
12413
+ throw gitProof(`${what} review base ${handoff.review_base} is not a commit of ${scope.worktreePath}`, {
12414
+ plan_id: scope.planId,
12415
+ review_base: handoff.review_base
12416
+ });
12417
+ }
12418
+ if (!gitIsAncestor(scope.worktreePath, handoff.review_base, handoff.review_head)) {
12419
+ 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 });
12420
+ }
12421
+ }
12422
+ async function assertStandaloneCompletionPrecheck(context, scope, session, handoff) {
12423
+ assertStandaloneRoute(context.snapshot, scope.planId, "complete");
12424
+ if (context.snapshot.status !== "running") {
12425
+ 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 });
12426
+ }
12427
+ if (handoff.state !== "accepted") {
12428
+ 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 });
12429
+ }
12430
+ if (rowStatusOf(context.row) !== "InReview") {
12431
+ throw new CoordinationError("coordination.invalid-transition", `complete requires ${scope.planId} to still be InReview`, { plan_id: scope.planId, status: context.row.status });
12432
+ }
12433
+ const prepared = context.coordination?.prepared;
12434
+ if (prepared === undefined) {
12435
+ throw new CoordinationError("coordination.not-prepared", `plan ${scope.planId} is not prepared in this workflow`, { plan_id: scope.planId });
12436
+ }
12437
+ assertNoIntegrationContamination(context, scope.planId, handoff, "complete");
12438
+ assertEvidenceDigests(handoff);
12439
+ assertAcceptedReviewDecision(handoff, scope.planId, "complete");
12440
+ if (handoff.qa.gate !== prepared.qa_gate) {
12441
+ 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 });
12442
+ }
12443
+ await assertFindingsClosed(scope, prepared, "complete");
12444
+ assertExecutionHolder(context.row, session.session_id, scope.planId, "complete");
12445
+ const anchors = standaloneDeliveryAnchors(context.snapshot, scope.planId);
12446
+ assertStandaloneBranchIdentity(context, scope, handoff, anchors, "complete");
12447
+ assertStandaloneSourceGitProof(scope, handoff, anchors.source, "complete");
12448
+ }
12449
+ function requireAcceptedHandoffForRepair(context, planId, namedHandoffId2) {
12450
+ const handoff = context.coordination?.handoff;
12451
+ if (handoff === undefined || handoff.id !== namedHandoffId2 || handoff.state !== "accepted" || rowStatusOf(context.row) !== "InReview") {
12452
+ 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 });
12453
+ }
12454
+ return handoff;
12455
+ }
12456
+ function assertRepairNotTerminal(snapshot) {
12457
+ if (WORKFLOW_TERMINAL_STATUSES.includes(snapshot.status)) {
12458
+ 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 });
12459
+ }
12460
+ }
12461
+ function assertLegacyRepairShape(snapshot, planId, handoff) {
12462
+ const source = snapshot.branch?.source;
12463
+ const target = snapshot.branch?.target;
12464
+ if (!isNonEmptyString(source) || !isNonEmptyString(target)) {
12465
+ throw new CoordinationError("coordination.delivery-source-repair.not-legacy-shape", `repair-delivery-source requires nonblank delivery anchors on plan ${planId}`, { plan_id: planId });
12466
+ }
12467
+ const candidateSource = handoff.source_branch;
12468
+ if (!isNonEmptyString(candidateSource)) {
12469
+ 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 });
12470
+ }
12471
+ if (source === candidateSource) {
12472
+ 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 });
12473
+ }
12474
+ if (source !== target) {
12475
+ 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 });
12476
+ }
12477
+ return { target, candidateSource };
12478
+ }
12479
+ function assertRepairBranchIdentity(context, scope, handoff, candidateSource, what) {
12480
+ const worktree = canonicalTarget(scope.worktreePath);
12481
+ if (handoff.source_branch !== candidateSource) {
12482
+ 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 });
12483
+ }
12484
+ const lease = requireExecutionLease(context.row, scope.planId, what);
12485
+ if (scope.workingBranch !== candidateSource) {
12486
+ 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 });
12487
+ }
12488
+ if (lease.working_branch !== candidateSource) {
12489
+ 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 });
12490
+ }
12491
+ if (canonicalTarget(lease.worktree_path) !== worktree) {
12492
+ 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 });
12493
+ }
12494
+ if (canonicalTarget(handoff.worktree_path) !== worktree) {
12495
+ 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 });
12496
+ }
12497
+ const metadata = context.row.metadata;
12498
+ if (isPlainObject2(metadata) && metadata.working_branch !== undefined && metadata.working_branch !== candidateSource) {
12499
+ 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 });
12500
+ }
12501
+ if (isPlainObject2(metadata) && metadata.worktree_path !== undefined && canonicalTarget(String(metadata.worktree_path)) !== worktree) {
12502
+ 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 });
12503
+ }
12504
+ }
12505
+ function assertDeliveryPrCompatible(snapshot, candidateSource, registeredTarget, planId) {
12506
+ const pr = snapshot.delivery?.pr;
12507
+ if (pr === undefined)
12508
+ return;
12509
+ if (pr.head !== candidateSource || pr.target !== registeredTarget) {
12510
+ 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 });
12511
+ }
12512
+ }
12513
+ function assertRepairDeliverySourceAdmission(context, scope) {
12514
+ if (!isStandaloneDevelopmentWorkflow(context.snapshot)) {
12515
+ 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 });
12516
+ }
12517
+ assertRepairNotTerminal(context.snapshot);
12518
+ if (context.snapshot.status === "paused") {
12519
+ throw new CoordinationError("coordination.invalid-transition", `repair-delivery-source refuses paused workflow ${context.snapshot.id}`, { workflow_id: context.snapshot.id, status: context.snapshot.status });
12520
+ }
12521
+ if (context.snapshot.status !== "running") {
12522
+ 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 });
12523
+ }
12524
+ }
12525
+ async function assertRepairDeliverySourcePrecheck(context, scope, session, handoff) {
12526
+ assertNoIntegrationContamination(context, scope.planId, handoff, "repair-delivery-source", "coordination.delivery-source-repair.not-legacy-shape");
12527
+ const prepared = context.coordination?.prepared;
12528
+ if (prepared === undefined) {
12529
+ throw new CoordinationError("coordination.not-prepared", `plan ${scope.planId} is not prepared in this workflow`, { plan_id: scope.planId });
12530
+ }
12531
+ assertEvidenceDigests(handoff);
12532
+ assertAcceptedReviewDecision(handoff, scope.planId, "repair-delivery-source");
12533
+ if (handoff.qa.gate !== prepared.qa_gate) {
12534
+ 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 });
12535
+ }
12536
+ await assertFindingsClosed(scope, prepared, "repair-delivery-source");
12537
+ assertExecutionHolder(context.row, session.session_id, scope.planId, "repair-delivery-source");
12538
+ const { target, candidateSource } = assertLegacyRepairShape(context.snapshot, scope.planId, handoff);
12539
+ assertRepairBranchIdentity(context, scope, handoff, candidateSource, "repair-delivery-source");
12540
+ assertDeliveryPrCompatible(context.snapshot, candidateSource, target, scope.planId);
12541
+ assertStandaloneSourceGitProof(scope, handoff, candidateSource, "repair-delivery-source");
12542
+ }
12543
+ function repairDeliverySourceRow(context, scope, candidateSource) {
12544
+ const nextCoordination = {
12545
+ ...context.coordination ?? { revision: 0 },
12546
+ revision: context.revision + 1
12547
+ };
12548
+ validateRowCoordinationInContext(context, nextCoordination, `plan ${scope.planId} coordination`);
12549
+ const currentBranch = context.snapshot.branch ?? {};
12550
+ return {
12551
+ row: { ...context.row, coordination: nextCoordination },
12552
+ coordination: nextCoordination,
12553
+ topLevel: {
12554
+ branch: { ...currentBranch, source: candidateSource }
12555
+ }
12556
+ };
12557
+ }
12558
+ async function mutateRepairDeliverySource(scope, session, sessionPath, request) {
12559
+ const result = await withRowCommit(scope, {
12560
+ expectedRevision: request.expectedRevision,
12561
+ precheck: async (context) => {
12562
+ assertCoordinatorBinding(session, sessionPath, context.snapshot);
12563
+ assertRepairDeliverySourceAdmission(context, scope);
12564
+ const handoff = requireAcceptedHandoffForRepair(context, scope.planId, request.handoffId);
12565
+ await assertRepairDeliverySourcePrecheck(context, scope, session, handoff);
12566
+ },
12567
+ mutate: (context) => {
12568
+ const handoff = requireAcceptedHandoffForRepair(context, scope.planId, request.handoffId);
12569
+ const { candidateSource } = assertLegacyRepairShape(context.snapshot, scope.planId, handoff);
12570
+ return repairDeliverySourceRow(context, scope, candidateSource);
12571
+ }
12572
+ });
12573
+ return {
12574
+ ok: true,
12575
+ operation: "repair-delivery-source",
12576
+ session,
12577
+ session_file: sessionPath,
12578
+ outcome: "delivery-source-repaired",
12579
+ view: buildView(scope.harnessRoot, scope.workflowId, scope.projectId, scope, result.snapshot, result.row, session, sessionPath)
12580
+ };
12581
+ }
12582
+ function completeStandaloneRow(context, scope, handoff) {
12583
+ const metadata = isPlainObject2(context.row.metadata) ? { ...context.row.metadata } : {};
12584
+ metadata.working_branch = handoff.source_branch;
12585
+ metadata.worktree_path = handoff.worktree_path;
12586
+ const nextCoordination = {
12587
+ ...context.coordination ?? { revision: 0 },
12588
+ revision: context.revision + 1,
12589
+ handoff: { ...handoff, state: "completed", completed_at: nowIso() }
12590
+ };
12591
+ validateRowCoordinationInContext(context, nextCoordination, `plan ${scope.planId} coordination`);
12592
+ const nextRow = { ...context.row, status: "Done", metadata, coordination: nextCoordination };
12593
+ delete nextRow.execution_lease;
12594
+ return { row: nextRow, coordination: nextCoordination };
12595
+ }
12596
+ function assertStandaloneCompletedReplay(context, scope, handoff) {
12597
+ assertStandaloneRoute(context.snapshot, scope.planId, "reconcile");
12598
+ if (rowStatusOf(context.row) !== "Done") {
12599
+ 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 });
12600
+ }
12601
+ if (context.row.execution_lease !== undefined) {
12602
+ throw new CoordinationError("coordination.invalid-transition", `reconcile requires no execution lease on ${scope.planId} for a standalone completed replay`, { plan_id: scope.planId });
12603
+ }
12604
+ if (context.snapshot.integration_merge_lease !== undefined) {
12605
+ throw new CoordinationError("coordination.invalid-transition", `reconcile requires no integration merge lease for a standalone completed replay of ${scope.planId}`, { plan_id: scope.planId });
12606
+ }
12607
+ assertNoIntegrationContamination(context, scope.planId, handoff, "reconcile");
12608
+ const storedHandoffViolations = validatePlanHandoff(handoff, `plan ${scope.planId} coordination.handoff`, "standalone-development");
12609
+ const storedHandoffFailure = storedHandoffViolations.find((entry) => !entry.ok);
12610
+ if (storedHandoffFailure !== undefined) {
12611
+ throw new CoordinationError(storedHandoffFailure.code, storedHandoffFailure.message, { plan_id: scope.planId });
12612
+ }
12613
+ const anchors = standaloneDeliveryAnchors(context.snapshot, scope.planId);
12614
+ assertStandaloneBranchIdentity(context, scope, handoff, anchors, "reconcile", false);
12615
+ const repository = proofRepository([handoff.worktree_path, scope.worktreePath, scope.harnessRoot]);
12616
+ if (repository !== undefined && !gitObjectExists(repository, handoff.source_sha)) {
12617
+ 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 });
12618
+ }
12619
+ }
12620
+ async function assertIterationCompletionPrecheck(context, scope, session, handoff) {
12621
+ if (handoff.state !== "merged") {
12622
+ 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 });
12623
+ }
12624
+ if (rowStatusOf(context.row) !== "InReview") {
12625
+ throw new CoordinationError("coordination.invalid-transition", `complete requires ${scope.planId} to still be InReview`, { plan_id: scope.planId, status: context.row.status });
12626
+ }
12627
+ const prepared = context.coordination?.prepared;
12628
+ if (prepared === undefined) {
12629
+ throw new CoordinationError("coordination.not-prepared", `plan ${scope.planId} is not prepared in this workflow`, { plan_id: scope.planId });
12630
+ }
12631
+ assertEvidenceDigests(handoff);
12632
+ await assertFindingsClosed(scope, prepared, "complete");
12633
+ assertExecutionHolder(context.row, session.session_id, scope.planId, "complete");
12634
+ assertMergeLease(context.snapshot, session, scope.planId, handoff);
12635
+ const integration = requireIntegration(handoff, scope.planId);
12636
+ const anchors = integrationAnchors(context.snapshot, scope.planId);
12637
+ const checkout = assertIntegrationCheckout(anchors, scope.planId);
12638
+ assertRecordedResult(anchors.worktreePath, scope.planId, integration, handoff.source_sha, checkout.head);
12639
+ }
11913
12640
  function completeRow(context, scope, handoff, resultSha) {
11914
12641
  const integration = requireIntegration(handoff, scope.planId);
11915
12642
  const completed = { ...integration, result_sha: resultSha, verified_at: nowIso() };
@@ -11928,33 +12655,27 @@ function completeRow(context, scope, handoff, resultSha) {
11928
12655
  dropTopLevel: release === undefined ? [] : ["integration_merge_lease"]
11929
12656
  };
11930
12657
  }
12658
+ var completeStandaloneMutateGapForTest;
11931
12659
  async function mutateComplete(scope, session, sessionPath, request) {
11932
12660
  const result = await withRowCommit(scope, {
11933
12661
  expectedRevision: request.expectedRevision,
11934
12662
  precheck: async (context) => {
11935
12663
  assertCoordinatorBinding(session, sessionPath, context.snapshot);
11936
12664
  const handoff = requireHandoff(context, scope.planId, request.handoffId);
11937
- if (handoff.state !== "merged") {
11938
- 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 });
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 });
12665
+ if (isStandaloneDevelopmentWorkflow(context.snapshot)) {
12666
+ await assertStandaloneCompletionPrecheck(context, scope, session, handoff);
12667
+ return;
11946
12668
  }
11947
- assertEvidenceDigests(handoff);
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);
12669
+ await assertIterationCompletionPrecheck(context, scope, session, handoff);
11955
12670
  },
11956
12671
  mutate: (context) => {
11957
12672
  const handoff = requireHandoff(context, scope.planId, request.handoffId);
12673
+ if (isStandaloneDevelopmentWorkflow(context.snapshot)) {
12674
+ completeStandaloneMutateGapForTest?.();
12675
+ const anchors = standaloneDeliveryAnchors(context.snapshot, scope.planId);
12676
+ assertStandaloneSourceGitProof(scope, handoff, anchors.source, "complete");
12677
+ return completeStandaloneRow(context, scope, handoff);
12678
+ }
11958
12679
  const integration = requireIntegration(handoff, scope.planId);
11959
12680
  const resultSha = assertRecordedResult(integration.worktree_path, scope.planId, integration, handoff.source_sha, undefined);
11960
12681
  return completeRow(context, scope, handoff, resultSha);
@@ -11978,6 +12699,10 @@ async function classifyReconcile(context, scope, session, handoff, expectedHando
11978
12699
  });
11979
12700
  }
11980
12701
  if (handoff.state === "completed") {
12702
+ if (isStandaloneDevelopmentWorkflow(context.snapshot)) {
12703
+ assertStandaloneCompletedReplay(context, scope, handoff);
12704
+ return { outcome: "already-completed", apply: () => null };
12705
+ }
11981
12706
  const integration = requireIntegration(handoff, planId);
11982
12707
  const repository = proofRepository([integration.worktree_path, handoff.worktree_path, session.harness_root]);
11983
12708
  if (repository === undefined) {
@@ -13051,6 +13776,7 @@ export {
13051
13776
  readWorkflowSnapshot,
13052
13777
  recordWorkflowDelivery,
13053
13778
  referenceExists,
13779
+ registerIterationWorkflow,
13054
13780
  registerPlanWorkflow,
13055
13781
  registerWorkflow,
13056
13782
  releaseLease,
@@ -13095,6 +13821,7 @@ export {
13095
13821
  techDebtRollup,
13096
13822
  unregisterWorkflow,
13097
13823
  validateAssignmentFields,
13824
+ validateAuditFindingGates,
13098
13825
  validateAuditStatusBlocks,
13099
13826
  validateCompassFrontmatter,
13100
13827
  validateDesignTokenFrontmatter,