omp-conductor 0.15.10 → 0.15.12

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.
Files changed (92) hide show
  1. package/README.md +273 -2544
  2. package/REFERENCE.md +2680 -0
  3. package/package.json +3 -2
  4. package/schema/config.schema.json +11 -23
  5. package/src/arm-challenge.ts +112 -0
  6. package/src/ask.ts +434 -0
  7. package/src/board.ts +81 -15
  8. package/src/brief-upgrade.ts +114 -8
  9. package/src/briefs/orchestrator.md +113 -34
  10. package/src/briefs/policy.md +33 -8
  11. package/src/briefs/worker.md +18 -9
  12. package/src/chain-check.ts +1 -1
  13. package/src/check-trailing-newlines.ts +82 -0
  14. package/src/cli.ts +225 -1406
  15. package/src/commands/arm.ts +21 -0
  16. package/src/commands/board.ts +23 -0
  17. package/src/commands/brief-upgrade.ts +186 -0
  18. package/src/commands/context.ts +150 -0
  19. package/src/commands/daemon.ts +71 -0
  20. package/src/commands/dashboard.ts +74 -0
  21. package/src/commands/decision.ts +103 -0
  22. package/src/commands/disarm.ts +21 -0
  23. package/src/commands/doctor.ts +100 -0
  24. package/src/commands/event.ts +62 -0
  25. package/src/commands/extend.ts +64 -0
  26. package/src/commands/friction.ts +56 -0
  27. package/src/commands/help.ts +9 -0
  28. package/src/commands/hold.ts +26 -0
  29. package/src/commands/intake.ts +155 -0
  30. package/src/commands/ledger.ts +69 -0
  31. package/src/commands/message.ts +96 -0
  32. package/src/commands/report.ts +206 -0
  33. package/src/commands/restart.ts +76 -0
  34. package/src/commands/resume.ts +58 -0
  35. package/src/commands/setup.ts +143 -0
  36. package/src/commands/start.ts +23 -0
  37. package/src/commands/stats.ts +131 -0
  38. package/src/commands/status.ts +48 -0
  39. package/src/commands/stop.ts +51 -0
  40. package/src/commands/tail.ts +109 -0
  41. package/src/commands/unblock.ts +39 -0
  42. package/src/commands/upgrade-install.ts +31 -0
  43. package/src/commands/upgrade-rollback.ts +32 -0
  44. package/src/commands/upgrade.ts +25 -0
  45. package/src/commands/verb.ts +83 -0
  46. package/src/commands/version.ts +30 -0
  47. package/src/commands/worker.ts +100 -0
  48. package/src/config-schema.ts +47 -1
  49. package/src/config.ts +45 -4
  50. package/src/daemon.ts +972 -101
  51. package/src/dashboard/app.js +459 -0
  52. package/src/dashboard/index.html +61 -0
  53. package/src/dashboard/server.ts +481 -0
  54. package/src/dashboard/style.css +348 -0
  55. package/src/decisions.ts +39 -14
  56. package/src/diff-flags.ts +131 -241
  57. package/src/doctor.ts +932 -0
  58. package/src/escalate.ts +2 -2
  59. package/src/failure-class.ts +66 -3
  60. package/src/fleet.ts +58 -1
  61. package/src/gitops.ts +157 -0
  62. package/src/graph-health.ts +1 -1
  63. package/src/label-projection.ts +1 -1
  64. package/src/lifecycle.ts +198 -2
  65. package/src/model-fallback.ts +177 -0
  66. package/src/notices.ts +9 -0
  67. package/src/omp.ts +93 -13
  68. package/src/orchestrator-tick.ts +414 -20
  69. package/src/orchestrator.ts +4 -4
  70. package/src/privileged.ts +10 -0
  71. package/src/release-policy.ts +342 -30
  72. package/src/reports.ts +19 -5
  73. package/src/session-host.ts +11 -5
  74. package/src/setup-host.ts +663 -25
  75. package/src/setup-install.ts +292 -28
  76. package/src/setup-wizard.ts +255 -74
  77. package/src/setup.ts +156 -35
  78. package/src/stats.ts +331 -0
  79. package/src/store.ts +219 -22
  80. package/src/tracker/github.ts +74 -4
  81. package/src/types.ts +215 -31
  82. package/src/unblock.ts +55 -11
  83. package/src/upgrade-journal.ts +220 -0
  84. package/src/upgrade-verify.ts +506 -0
  85. package/src/upgrade.ts +385 -58
  86. package/src/verbs/actions.ts +73 -1
  87. package/src/verbs/protocol.ts +29 -4
  88. package/src/verbs/server.ts +183 -20
  89. package/src/worker.ts +3 -3
  90. package/systemd/omp-conductor-recover.sh +433 -0
  91. package/systemd/omp-conductor.service.example +14 -3
  92. package/systemd/recover-unit-test.sh +428 -0
package/src/config.ts CHANGED
@@ -355,8 +355,11 @@ export function findProject(c: ConductorConfig, name?: string): ProjectConfig {
355
355
  if (name === undefined) {
356
356
  const only = c.projects[0];
357
357
  if (c.projects.length !== 1 || only === undefined) {
358
+ // #514: every operator hitting this has to guess the spelling of the
359
+ // flag that names a project — say it outright. Doctor's project finding
360
+ // quotes this text, so keep the "name one explicitly" phrase.
358
361
  throw new Error(
359
- `Ambiguous project: config has ${c.projects.length} projects (${names.join(", ") || "none"}) — name one explicitly.`,
362
+ `Ambiguous project: config has ${c.projects.length} projects (${names.join(", ") || "none"}) — name one explicitly with --project NAME.`,
360
363
  );
361
364
  }
362
365
  return only;
@@ -558,13 +561,17 @@ function checkDigest(d: Raw, label: string, problems: string[]): void {
558
561
  function checkAuthority(a: Raw, label: string, problems: string[]): void {
559
562
  const merge = a["merge"];
560
563
  const release = a["release"];
564
+ const promotion = a["promotion"];
561
565
  if (merge !== undefined && typeof merge === "string" && !(AUTHORITY_HOLDERS as readonly string[]).includes(merge)) {
562
566
  problems.push(`${label}: authority.merge must be ${AUTHORITY_HOLDER_LIST}, found ${JSON.stringify(merge)}`);
563
567
  }
564
568
  if (release !== undefined && typeof release === "string" && !(AUTHORITY_HOLDERS as readonly string[]).includes(release)) {
565
569
  problems.push(`${label}: authority.release must be ${AUTHORITY_HOLDER_LIST}, found ${JSON.stringify(release)}`);
566
570
  }
567
- const unknown = Object.keys(a).filter((k) => k !== "merge" && k !== "release");
571
+ if (promotion !== undefined && typeof promotion === "string" && !(AUTHORITY_HOLDERS as readonly string[]).includes(promotion)) {
572
+ problems.push(`${label}: authority.promotion must be ${AUTHORITY_HOLDER_LIST}, found ${JSON.stringify(promotion)}`);
573
+ }
574
+ const unknown = Object.keys(a).filter((k) => k !== "merge" && k !== "release" && k !== "promotion");
568
575
  if (unknown.length > 0) problems.push(`${label}: authority has unknown key(s): ${unknown.join(", ")}`);
569
576
  }
570
577
 
@@ -817,7 +824,7 @@ function clauseFor(rel: readonly PropertyKey[], issue: {
817
824
  }
818
825
  if (rel.length === 1 && relStr === "escalation") return `escalation must be an object`;
819
826
  if (rel.length === 1 && relStr === "authority") {
820
- return `authority must be an object with "merge" and "release" of ${AUTHORITY_HOLDER_LIST}`;
827
+ return `authority must be an object with "merge", "release" and "promotion" of ${AUTHORITY_HOLDER_LIST}`;
821
828
  }
822
829
  if (rel.length === 1 && relStr === "policy") {
823
830
  return `policy must be an object with "merge" and "release" sections`;
@@ -909,7 +916,7 @@ function clauseFor(rel: readonly PropertyKey[], issue: {
909
916
  }
910
917
  if (pathStr === "reporting.digest.cadence") return `${pathStr} must be ${DIGEST_CADENCE_LIST}, found ${found()}`;
911
918
  if (pathStr === "escalation.orchestrator") return `${pathStr} must be ${ORCHESTRATOR_MODE_LIST}, found ${found()}`;
912
- if (pathStr === "authority.merge" || pathStr === "authority.release") {
919
+ if (pathStr === "authority.merge" || pathStr === "authority.release" || pathStr === "authority.promotion") {
913
920
  return `${pathStr} must be ${AUTHORITY_HOLDER_LIST}, found ${found()}`;
914
921
  }
915
922
  if (pathStr === "policy.merge.baseFreshness") return `${pathStr} must be ${BASE_FRESHNESS_LIST}, found ${found()}`;
@@ -1076,6 +1083,34 @@ function finalizeProject(
1076
1083
  const workerModel =
1077
1084
  typeof rawWorkerModel === "string" && rawWorkerModel.trim() !== "" ? rawWorkerModel : undefined;
1078
1085
 
1086
+ // The failover chain is a hint like workerModel, not a ceiling: an unusable
1087
+ // entry or a malformed threshold is dropped rather than failing the load,
1088
+ // and absent/empty `modelFallbacks` keeps today's dispatch byte for byte.
1089
+ const rawFallbacks = p["modelFallbacks"];
1090
+ const modelFallbacks = Array.isArray(rawFallbacks)
1091
+ ? rawFallbacks
1092
+ .filter((entry): entry is string => typeof entry === "string")
1093
+ .map((entry) => entry.trim())
1094
+ .filter((entry) => entry !== "")
1095
+ : [];
1096
+ const rawThreshold = p["modelFallbackThreshold"];
1097
+ const modelFallbackThreshold =
1098
+ typeof rawThreshold === "number" && Number.isInteger(rawThreshold) && rawThreshold >= 1
1099
+ ? rawThreshold
1100
+ : undefined;
1101
+
1102
+ // Marked critical-base/safety markers (commit SHAs or refs). A continuation
1103
+ // branch must contain every one before the dispatcher may reattach it;
1104
+ // unusable entries are dropped like `modelFallbacks` and an absent or empty
1105
+ // list keeps today's behaviour byte for byte.
1106
+ const rawCriticalBase = p["criticalBase"];
1107
+ const criticalBase = Array.isArray(rawCriticalBase)
1108
+ ? rawCriticalBase
1109
+ .filter((entry): entry is string => typeof entry === "string")
1110
+ .map((entry) => entry.trim())
1111
+ .filter((entry) => entry !== "")
1112
+ : [];
1113
+
1079
1114
  if (problems.length > before) return undefined;
1080
1115
 
1081
1116
  return {
@@ -1091,12 +1126,15 @@ function finalizeProject(
1091
1126
  routing: { labelPrefix, repos },
1092
1127
  caps,
1093
1128
  ...(workerModel === undefined ? {} : { workerModel }),
1129
+ ...(modelFallbacks.length === 0 ? {} : { modelFallbacks }),
1130
+ ...(modelFallbackThreshold === undefined ? {} : { modelFallbackThreshold }),
1094
1131
  escalation,
1095
1132
  authority,
1096
1133
  releasePolicy,
1097
1134
  policy,
1098
1135
  ...(recoveryMerges === undefined ? {} : { recoveryMerges }),
1099
1136
  reporting,
1137
+ ...(criticalBase.length === 0 ? {} : { criticalBase }),
1100
1138
  workspaceRoot: expandHome(pickString(p["workspaceRoot"], defaultWorkspaceRoot())),
1101
1139
  mirrorRoot: expandHome(pickString(p["mirrorRoot"], defaultMirrorRoot())),
1102
1140
  };
@@ -1136,6 +1174,9 @@ function finalizeAuthority(parsed: Raw | undefined): ProjectConfig["authority"]
1136
1174
  return {
1137
1175
  merge: (parsed["merge"] as ProjectConfig["authority"]["merge"]) ?? DEFAULT_AUTHORITY.merge,
1138
1176
  release: (parsed["release"] as ProjectConfig["authority"]["release"]) ?? DEFAULT_AUTHORITY.release,
1177
+ // Unstated defaults to the human: a config written before promotion was a
1178
+ // key must load as operator-owned, never as a silent delegation.
1179
+ promotion: (parsed["promotion"] as ProjectConfig["authority"]["promotion"] | undefined) ?? DEFAULT_AUTHORITY.promotion,
1139
1180
  };
1140
1181
  }
1141
1182