omp-conductor 0.16.2 → 0.17.1
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/README.md +38 -4
- package/REFERENCE.md +18 -12
- package/package.json +2 -1
- package/schema/config.schema.json +16 -0
- package/src/admission.ts +159 -43
- package/src/availability.ts +27 -1
- package/src/briefs/worker.md +2 -0
- package/src/clack-ui.ts +83 -0
- package/src/command-manifest.ts +16 -7
- package/src/commands/arm.ts +11 -3
- package/src/commands/decision.ts +17 -7
- package/src/commands/doctor.ts +18 -1
- package/src/commands/hold.ts +9 -7
- package/src/commands/ledger.ts +25 -4
- package/src/commands/message.ts +32 -4
- package/src/commands/setup.ts +61 -10
- package/src/commands/stats.ts +9 -5
- package/src/commands/status.ts +32 -5
- package/src/commands/tail.ts +13 -1
- package/src/commands/watch.ts +16 -7
- package/src/config-schema.ts +20 -0
- package/src/config.ts +37 -0
- package/src/daemon.ts +1240 -18
- package/src/doctor.ts +310 -22
- package/src/escalate.ts +560 -57
- package/src/failure-class.ts +56 -13
- package/src/fleet.ts +224 -47
- package/src/gitops.ts +103 -24
- package/src/lifecycle.ts +7 -2
- package/src/orchestrator-tick.ts +372 -157
- package/src/privileged.ts +3 -0
- package/src/release-policy.ts +177 -5
- package/src/setup-answers.ts +135 -0
- package/src/setup-host.ts +193 -4
- package/src/setup-install.ts +2 -0
- package/src/setup-probe.ts +1 -0
- package/src/setup-wizard.ts +1296 -101
- package/src/setup.ts +60 -3
- package/src/status-render.ts +11 -1
- package/src/store.ts +333 -12
- package/src/tracker/github.ts +562 -13
- package/src/types.ts +204 -2
- package/src/ui/progress.ts +32 -0
- package/src/ui/style.ts +11 -0
- package/src/upgrade.ts +50 -19
- package/src/verbs/actions.ts +66 -18
- package/src/verbs/protocol.ts +45 -0
- package/src/verbs/server.ts +212 -11
- package/src/wizard-ui.ts +14 -5
- package/src/worker.ts +26 -0
- package/systemd/omp-conductor-recover.sh +73 -0
- package/systemd/recover-unit-test.sh +61 -0
package/src/commands/watch.ts
CHANGED
|
@@ -50,18 +50,27 @@ export async function watchCommand(ctx: CommandContext): Promise<void> {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
if (sub === "list" || sub === undefined) {
|
|
53
|
-
const
|
|
53
|
+
const open = store.openDecisions(project.name).filter((d) => d.kind === "watch");
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const watches = open.map((d) => ({
|
|
56
|
+
id: d.id,
|
|
57
|
+
ageHours: Math.max(0, Math.round((now - d.askedAt) / 3_600_000)),
|
|
58
|
+
blocks: d.blocks ?? null,
|
|
59
|
+
condition: d.condition === undefined ? null : d.conditionMetAt === undefined ? "pending" : "met",
|
|
60
|
+
note: d.question,
|
|
61
|
+
}));
|
|
62
|
+
if (ctx.argv.includes("--json")) {
|
|
63
|
+
process.stdout.write(`${JSON.stringify({ project: project.name, watches }, null, 2)}\n`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
54
66
|
if (watches.length === 0) {
|
|
55
67
|
process.stdout.write("no watches\n");
|
|
56
68
|
return;
|
|
57
69
|
}
|
|
58
|
-
const
|
|
59
|
-
for (const d of watches) {
|
|
60
|
-
const condition =
|
|
61
|
-
d.condition === undefined ? "-" : d.conditionMetAt === undefined ? "pending" : "met";
|
|
62
|
-
const hours = Math.max(0, Math.round((now - d.askedAt) / 3_600_000));
|
|
70
|
+
for (const watch of watches) {
|
|
63
71
|
process.stdout.write(
|
|
64
|
-
`${
|
|
72
|
+
`${watch.id} ${watch.ageHours}h blocks:${watch.blocks ?? "-"} ` +
|
|
73
|
+
`condition:${watch.condition ?? "-"} ${watch.note}\n`,
|
|
65
74
|
);
|
|
66
75
|
}
|
|
67
76
|
return;
|
package/src/config-schema.ts
CHANGED
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
|
|
23
23
|
import { z } from "zod";
|
|
24
24
|
import {
|
|
25
|
+
ARM_PROOFS,
|
|
25
26
|
AUTHORITY_HOLDERS,
|
|
26
27
|
BASE_FRESHNESS,
|
|
27
28
|
BEHIND_BASE_ACTIONS,
|
|
28
29
|
CONFIG_VERSION,
|
|
30
|
+
DEFAULT_ARM_PROOF,
|
|
29
31
|
DEFAULT_AUTHORITY,
|
|
30
32
|
DEFAULT_CAPS,
|
|
31
33
|
DEFAULT_PROJECT_POLICY,
|
|
@@ -239,6 +241,21 @@ const escalationSchema = z
|
|
|
239
241
|
})
|
|
240
242
|
.strict();
|
|
241
243
|
|
|
244
|
+
/**
|
|
245
|
+
* The per-project arming gate (conductor #613): how `arm` proves a human just
|
|
246
|
+
* approved dispatch. Absent or a legacy config without the key loads as
|
|
247
|
+
* `challenge` — the authenticated round-trip, which is what existing installs
|
|
248
|
+
* already run. `claim-only` arms on the shared live-plumbing verdict with no
|
|
249
|
+
* Telegram send or wait, so an unattended recovery can re-arm a project that
|
|
250
|
+
* opted in.
|
|
251
|
+
*/
|
|
252
|
+
const armSchema = z
|
|
253
|
+
.object({
|
|
254
|
+
proof: z.enum([...ARM_PROOFS]).default(DEFAULT_ARM_PROOF),
|
|
255
|
+
})
|
|
256
|
+
.strict()
|
|
257
|
+
.describe("How `arm` proves a human just approved arming");
|
|
258
|
+
|
|
242
259
|
const releasePolicySchema = z.union([
|
|
243
260
|
releasePolicyLegacyEnum,
|
|
244
261
|
z.record(z.string(), authorityHolderEnum),
|
|
@@ -330,6 +347,9 @@ const projectSchema = z
|
|
|
330
347
|
// overlay); anything else is dropped by the loader, like `workerModel`.
|
|
331
348
|
workerAdvisor: z.unknown().optional(),
|
|
332
349
|
escalation: escalationSchema.optional(),
|
|
350
|
+
// How `arm` proves a human approved arming (#613); absent loads as
|
|
351
|
+
// `challenge`, preserving today's authenticated round-trip.
|
|
352
|
+
arm: armSchema.optional(),
|
|
333
353
|
authority: authoritySchema.optional(),
|
|
334
354
|
releasePolicy: releasePolicySchema.optional(),
|
|
335
355
|
policy: projectPolicySchema.optional(),
|
package/src/config.ts
CHANGED
|
@@ -17,10 +17,12 @@ import { homedir } from "node:os";
|
|
|
17
17
|
import { dirname, isAbsolute, join } from "node:path";
|
|
18
18
|
import { backupTimestamp, copyToUniqueBackup } from "./backups.ts";
|
|
19
19
|
import {
|
|
20
|
+
ARM_PROOFS,
|
|
20
21
|
AUTHORITY_HOLDERS,
|
|
21
22
|
BASE_FRESHNESS,
|
|
22
23
|
BEHIND_BASE_ACTIONS,
|
|
23
24
|
CONFIG_VERSION,
|
|
25
|
+
DEFAULT_ARM_PROOF,
|
|
24
26
|
DEFAULT_AUTHORITY,
|
|
25
27
|
DEFAULT_CAPS,
|
|
26
28
|
DEFAULT_PROJECT_POLICY,
|
|
@@ -55,6 +57,7 @@ import {
|
|
|
55
57
|
type WeeklyAvailability,
|
|
56
58
|
type RepoTarget,
|
|
57
59
|
type ResolvedGrants,
|
|
60
|
+
type ArmProof,
|
|
58
61
|
} from "./types.ts";
|
|
59
62
|
import {
|
|
60
63
|
AUTHORITY_HOLDER_LIST,
|
|
@@ -313,6 +316,20 @@ export function resolvePolicy(p: ProjectConfig): ProjectPolicy {
|
|
|
313
316
|
return clonePolicy(p.policy ?? DEFAULT_PROJECT_POLICY);
|
|
314
317
|
}
|
|
315
318
|
|
|
319
|
+
/**
|
|
320
|
+
* How this project's `arm` proves a human just approved arming (#613),
|
|
321
|
+
* complete.
|
|
322
|
+
*
|
|
323
|
+
* The loader always materialises `arm` (finalizeProject), so this is only ever
|
|
324
|
+
* the fallback for a `ProjectConfig` that never went through it — a hand-built
|
|
325
|
+
* one in a test, or a config written before the key existed. Absent resolves
|
|
326
|
+
* to {@link DEFAULT_ARM_PROOF} (`challenge`): the authenticated round-trip,
|
|
327
|
+
* which is the behaviour every existing install already has.
|
|
328
|
+
*/
|
|
329
|
+
export function resolveArmProof(p: ProjectConfig): ArmProof {
|
|
330
|
+
return p.arm?.proof === "claim-only" ? "claim-only" : DEFAULT_ARM_PROOF;
|
|
331
|
+
}
|
|
332
|
+
|
|
316
333
|
/**
|
|
317
334
|
* A policy with no array shared with its source.
|
|
318
335
|
*
|
|
@@ -835,6 +852,9 @@ function clauseFor(rel: readonly PropertyKey[], issue: {
|
|
|
835
852
|
return `reporting.availability must be an object`;
|
|
836
853
|
}
|
|
837
854
|
if (rel.length === 1 && relStr === "escalation") return `escalation must be an object`;
|
|
855
|
+
if (rel.length === 1 && relStr === "arm") {
|
|
856
|
+
return `arm must be an object with a "proof" of ${ARM_PROOF_LIST}`;
|
|
857
|
+
}
|
|
838
858
|
if (rel.length === 1 && relStr === "authority") {
|
|
839
859
|
return `authority must be an object with "merge", "release" and "promotion" of ${AUTHORITY_HOLDER_LIST}`;
|
|
840
860
|
}
|
|
@@ -995,6 +1015,9 @@ function capProblem(key: string, found: string): string {
|
|
|
995
1015
|
const POLICY_MERGE_KEYS = quoteList(Object.keys(clonePolicy(DEFAULT_PROJECT_POLICY).merge));
|
|
996
1016
|
const POLICY_RELEASE_KEYS = quoteList(Object.keys(clonePolicy(DEFAULT_PROJECT_POLICY).release));
|
|
997
1017
|
|
|
1018
|
+
/** The closed `arm.proof` vocabulary, for the not-an-object wording. */
|
|
1019
|
+
const ARM_PROOF_LIST = quoteList(ARM_PROOFS);
|
|
1020
|
+
|
|
998
1021
|
// ---------------------------------------------------------------------------
|
|
999
1022
|
// residue: cross-field coherence, migrations, defaults, path expansion
|
|
1000
1023
|
// ---------------------------------------------------------------------------
|
|
@@ -1062,6 +1085,7 @@ function finalizeProject(
|
|
|
1062
1085
|
|
|
1063
1086
|
const stateLabels = p["stateLabels"] as Raw | undefined;
|
|
1064
1087
|
const escalation = finalizeEscalation(p["escalation"] as Raw | undefined);
|
|
1088
|
+
const arm = finalizeArm(p["arm"] as Raw | undefined);
|
|
1065
1089
|
const authority = finalizeAuthority(p["authority"] as Raw | undefined);
|
|
1066
1090
|
const releasePolicy = finalizeReleasePolicy(p["releasePolicy"], label, problems);
|
|
1067
1091
|
const strandedTagRepos =
|
|
@@ -1183,6 +1207,7 @@ function finalizeProject(
|
|
|
1183
1207
|
...(modelFallbackThreshold === undefined ? {} : { modelFallbackThreshold }),
|
|
1184
1208
|
...(effectiveOmpSettings === undefined ? {} : { ompSettings: effectiveOmpSettings }),
|
|
1185
1209
|
escalation,
|
|
1210
|
+
arm,
|
|
1186
1211
|
authority,
|
|
1187
1212
|
releasePolicy,
|
|
1188
1213
|
policy,
|
|
@@ -1194,6 +1219,18 @@ function finalizeProject(
|
|
|
1194
1219
|
};
|
|
1195
1220
|
}
|
|
1196
1221
|
|
|
1222
|
+
/**
|
|
1223
|
+
* The project's arming gate (#613), complete. A config written before the key
|
|
1224
|
+
* existed — or one that never answered — materialises as `challenge`, the
|
|
1225
|
+
* authenticated round-trip every existing install already runs, so an upgrade
|
|
1226
|
+
* changes no behaviour. zod has already rejected anything that is not a
|
|
1227
|
+
* `"challenge"` / `"claim-only"` string, so this only fills the absent case.
|
|
1228
|
+
*/
|
|
1229
|
+
function finalizeArm(parsed: Raw | undefined): ProjectConfig["arm"] {
|
|
1230
|
+
if (parsed === undefined) return { proof: DEFAULT_ARM_PROOF };
|
|
1231
|
+
return { proof: (parsed["proof"] as ArmProof | undefined) ?? DEFAULT_ARM_PROOF };
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1197
1234
|
function finalizeEscalation(parsed: Raw | undefined): ProjectConfig["escalation"] {
|
|
1198
1235
|
if (parsed === undefined) {
|
|
1199
1236
|
return { fallbackToIssueComment: true, orchestrator: "embedded" };
|