@mnemom/mnemom 0.17.36 → 0.17.37
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/host/commands.js +4 -1
- package/dist/host/owner.d.ts +2 -0
- package/dist/host/verbs.d.ts +16 -1
- package/dist/host/verbs.js +45 -3
- package/dist/vendor/durable-contract/index.js +6 -3
- package/dist/vendor/shared/durable-contract/cli-surface.js +2 -2
- package/dist/vendor/shared/durable-contract/codes.js +4 -1
- package/package.json +1 -1
package/dist/host/commands.js
CHANGED
|
@@ -8,6 +8,7 @@ import * as os from "node:os";
|
|
|
8
8
|
import { CLI_VERSION } from "../version.js";
|
|
9
9
|
import { getApiUrl } from "../lib/config.js";
|
|
10
10
|
import { getAccessToken } from "../lib/auth.js";
|
|
11
|
+
import { getActiveOrg } from "../lib/cli-config.js";
|
|
11
12
|
import { resolveBootId, defaultBootIdDeps } from "./bootid.js";
|
|
12
13
|
import { realLaunchctl } from "./launchd.js";
|
|
13
14
|
import { ownerApi, parseExecutorList } from "./owner.js";
|
|
@@ -37,6 +38,7 @@ function verbIo(json) {
|
|
|
37
38
|
bootId: () => resolveBootId({ ...defaultBootIdDeps(), env }).bootId,
|
|
38
39
|
programArguments: () => [...cliExec(), "agent", "host", "run"],
|
|
39
40
|
claudeBin: () => resolveClaudeBin(env),
|
|
41
|
+
activeOrg: () => getActiveOrg()?.org_id ?? null,
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
async function guarded(json, fn) {
|
|
@@ -66,7 +68,8 @@ export function registerHostCommands(agent, hidden) {
|
|
|
66
68
|
host
|
|
67
69
|
.command("enroll")
|
|
68
70
|
.description("Enroll this machine as an executor (device step); `install` does this when needed")
|
|
69
|
-
.
|
|
71
|
+
.option("--org <org_id>", "Join this organization (you must be a member); defaults to the active org, else your personal org")
|
|
72
|
+
.action(async (o) => guarded(false, (io) => hostEnroll(io, { org: o.org })));
|
|
70
73
|
host
|
|
71
74
|
.command("renew")
|
|
72
75
|
.description("Renew this machine's executor grant (device step)")
|
package/dist/host/owner.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface OwnerApi {
|
|
|
11
11
|
runtime: string;
|
|
12
12
|
contract_version: string;
|
|
13
13
|
machine_label?: string;
|
|
14
|
+
/** Ruling 34.03.4: the org this host joins; the api checks membership. */
|
|
15
|
+
org_id?: string;
|
|
14
16
|
}): Promise<OwnerHttp>;
|
|
15
17
|
enrollPoll(pollSecret: string, approvalId: string | null): Promise<OwnerHttp>;
|
|
16
18
|
list(): Promise<OwnerHttp>;
|
package/dist/host/verbs.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface VerbIo {
|
|
|
20
20
|
readonly pollSleep?: (ms: number) => Promise<void>;
|
|
21
21
|
/** Takes the enrollment lock; acquireHostLock (macOS flock) when absent. */
|
|
22
22
|
readonly acquireLock?: (lockFile: string) => LockResult;
|
|
23
|
+
/** The CLI's active org id (`mnemom org use`), or null with none set; never read when absent. */
|
|
24
|
+
readonly activeOrg?: () => string | null;
|
|
23
25
|
}
|
|
24
26
|
/** Fresh-invocation gate for every host verb (C12-R25): exit 2 with the gated text, nothing else. */
|
|
25
27
|
export declare function freshGate(io: VerbIo): number | null;
|
|
@@ -40,13 +42,26 @@ export declare function executorLiveness(io: VerbIo, executorId: string): Promis
|
|
|
40
42
|
export declare function printRiskLine(io: VerbIo): void;
|
|
41
43
|
/** "<org name> (<org_id>)", or the bare id when the api sent no name. */
|
|
42
44
|
export declare function joinTarget(orgId: string, orgName: string | null): string;
|
|
45
|
+
/**
|
|
46
|
+
* The org a start names (ruling 34.03.4): `--org`, else the active org, else none, so the api
|
|
47
|
+
* keeps its fallback (the token's org, else the personal org).
|
|
48
|
+
*/
|
|
49
|
+
export declare function enrollOrg(io: VerbIo, opts: {
|
|
50
|
+
org?: string;
|
|
51
|
+
}): {
|
|
52
|
+
orgId: string;
|
|
53
|
+
from: "flag" | "active";
|
|
54
|
+
} | null;
|
|
43
55
|
/** The device step: start, show the code, poll, write the state file. Returns an exit code. */
|
|
44
56
|
export declare function enrollDevice(io: VerbIo, opts?: {
|
|
45
57
|
label?: string;
|
|
58
|
+
org?: string;
|
|
46
59
|
}): Promise<number>;
|
|
47
60
|
/** Write the plist and (re)load the job. */
|
|
48
61
|
export declare function writeAndLoad(io: VerbIo): Promise<number>;
|
|
49
62
|
export declare function hostInstall(io: VerbIo): Promise<number>;
|
|
50
|
-
export declare function hostEnroll(io: VerbIo
|
|
63
|
+
export declare function hostEnroll(io: VerbIo, opts?: {
|
|
64
|
+
org?: string;
|
|
65
|
+
}): Promise<number>;
|
|
51
66
|
export declare function hostRenew(io: VerbIo): Promise<number>;
|
|
52
67
|
export declare function hostUninstall(io: VerbIo): Promise<number>;
|
package/dist/host/verbs.js
CHANGED
|
@@ -23,6 +23,7 @@ import { renderPlist } from "./plist.js";
|
|
|
23
23
|
import { loadJob, unloadJob } from "./launchd.js";
|
|
24
24
|
import { classifyPoll, parseEnrollStart, parseExecutorList, EXECUTOR_CONTRACT, } from "./owner.js";
|
|
25
25
|
import { readRuntime } from "./runtime.js";
|
|
26
|
+
import { CODE } from "./s1-names.js";
|
|
26
27
|
import { RENEW_FIX, GRANT_EXPIRED_LINE } from "./grant.js";
|
|
27
28
|
export const INSTALL_FIX = "`mnemom agent host install`";
|
|
28
29
|
export const UNINSTALL_SESSION_LINE = (name) => `${name}: they stop restarting until ${INSTALL_FIX}`;
|
|
@@ -63,6 +64,22 @@ export function printRiskLine(io) {
|
|
|
63
64
|
export function joinTarget(orgId, orgName) {
|
|
64
65
|
return orgName ? `${orgName} (${orgId})` : orgId;
|
|
65
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The org a start names (ruling 34.03.4): `--org`, else the active org, else none, so the api
|
|
69
|
+
* keeps its fallback (the token's org, else the personal org).
|
|
70
|
+
*/
|
|
71
|
+
export function enrollOrg(io, opts) {
|
|
72
|
+
if (opts.org)
|
|
73
|
+
return { orgId: opts.org, from: "flag" };
|
|
74
|
+
const active = io.activeOrg?.() ?? null;
|
|
75
|
+
return active ? { orgId: active, from: "active" } : null;
|
|
76
|
+
}
|
|
77
|
+
/** The api's refusal code from an error body, or null. */
|
|
78
|
+
function errorCode(body) {
|
|
79
|
+
const e = body && typeof body === "object" ? body.error : undefined;
|
|
80
|
+
const code = e && typeof e === "object" ? e.code : undefined;
|
|
81
|
+
return typeof code === "string" ? code : null;
|
|
82
|
+
}
|
|
66
83
|
/** The device step: start, show the code, poll, write the state file. Returns an exit code. */
|
|
67
84
|
export async function enrollDevice(io, opts = {}) {
|
|
68
85
|
const { paths } = ctx(io);
|
|
@@ -71,16 +88,41 @@ export async function enrollDevice(io, opts = {}) {
|
|
|
71
88
|
io.err(`mnemom agent host: an enrollment is already in progress here (${lock.reason}): wait for it, then ${INSTALL_FIX}`);
|
|
72
89
|
return 1;
|
|
73
90
|
}
|
|
91
|
+
let org = enrollOrg(io, opts);
|
|
74
92
|
try {
|
|
75
|
-
const
|
|
93
|
+
const startWith = (o) => io.owner.enrollStart({
|
|
76
94
|
kind: "host",
|
|
77
95
|
runtime: "node",
|
|
78
96
|
contract_version: EXECUTOR_CONTRACT,
|
|
79
97
|
...(opts.label ? { machine_label: opts.label } : {}),
|
|
98
|
+
...(o ? { org_id: o.orgId } : {}),
|
|
80
99
|
});
|
|
100
|
+
let st = await startWith(org);
|
|
101
|
+
// An api from before 34.03.4 refuses org_id on a host start with its plain 400 invalid_scope
|
|
102
|
+
// (34.03.2 (1); mnemom-api enroll.ts "org_id is only accepted with scope org"; recorded beside
|
|
103
|
+
// exec_enroll in S1 codes.ts). An org taken from the active org then falls back to the start this
|
|
104
|
+
// CLI made before; an explicit --org cannot.
|
|
105
|
+
if (org && st.status === 400 && errorCode(st.body) === "invalid_scope") {
|
|
106
|
+
if (org.from === "flag") {
|
|
107
|
+
io.err(`mnemom agent host: this Mnemom server cannot enroll a machine into ${org.orgId} yet.`);
|
|
108
|
+
io.err("fix: `mnemom agent host enroll` without --org (joins your personal org)");
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
io.err(`mnemom agent host: this Mnemom server cannot enroll a machine into your active org ${org.orgId} yet; it joins your personal org.`);
|
|
112
|
+
org = null;
|
|
113
|
+
st = await startWith(null);
|
|
114
|
+
}
|
|
81
115
|
const start = st.status === 202 || st.status === 200 || st.status === 201
|
|
82
116
|
? parseEnrollStart(st.body)
|
|
83
117
|
: null;
|
|
118
|
+
if (!start && org && errorCode(st.body) === CODE.durable_approval_not_eligible) {
|
|
119
|
+
// 34.03.4: only a member of the org may enroll a machine into it.
|
|
120
|
+
io.err(`mnemom agent host: you are not a member of ${org.orgId}, so this machine cannot join it.`);
|
|
121
|
+
io.err(org.from === "flag"
|
|
122
|
+
? "fix: `mnemom org list` shows the organizations you belong to"
|
|
123
|
+
: "fix: `mnemom org use --clear`, then `mnemom agent host enroll` (joins your personal org)");
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
84
126
|
if (!start) {
|
|
85
127
|
const code = st.body && typeof st.body === "object"
|
|
86
128
|
? JSON.stringify(st.body).slice(0, 300)
|
|
@@ -211,8 +253,8 @@ export async function hostInstall(io) {
|
|
|
211
253
|
io.out(reload ? "Host daemon reloaded on this machine's enrollment." : "Host daemon installed.");
|
|
212
254
|
return 0;
|
|
213
255
|
}
|
|
214
|
-
export async function hostEnroll(io) {
|
|
215
|
-
return enrollDevice(io);
|
|
256
|
+
export async function hostEnroll(io, opts = {}) {
|
|
257
|
+
return enrollDevice(io, opts);
|
|
216
258
|
}
|
|
217
259
|
export async function hostRenew(io) {
|
|
218
260
|
const { paths } = ctx(io);
|
|
@@ -1196,7 +1196,10 @@ var DURABLE_CODES = {
|
|
|
1196
1196
|
// ---- api executor enrollment start, POST ROUTES.exec_enroll (C04-R02, R03; wave-0 fix 3b) ----
|
|
1197
1197
|
// Names kept verbatim from C04-R03. A machine principal keeps 403 machine_principal_refused
|
|
1198
1198
|
// (C04-R02); a non-starter approver gets durable_approval_not_eligible (ruling 1.13). Device-poll
|
|
1199
|
-
// errors are not codes here: see ENROLL_POLL_ERRORS.
|
|
1199
|
+
// errors are not codes here: see ENROLL_POLL_ERRORS. Ruling 34.03.4: a host start naming an org_id
|
|
1200
|
+
// the caller is not a member of is also durable_approval_not_eligible (403, no approval row). A
|
|
1201
|
+
// malformed or refused org_id is the api's plain 400 invalid_scope, which is not a durable code;
|
|
1202
|
+
// an api from before 34.03.4 answers it to any org_id on a host start (34.03.2 (1)).
|
|
1200
1203
|
/** The caller lacks the entitlement durable.locked. */
|
|
1201
1204
|
not_entitled: { status: 403, surfaces: ["api"], fields: [] },
|
|
1202
1205
|
/** The declared kind is not in durable.executors (M1 allows 'host' only). */
|
|
@@ -1643,8 +1646,8 @@ var M1_CLI_SURFACE = Object.freeze([
|
|
|
1643
1646
|
entry(["agent", "done"], ["--json"]),
|
|
1644
1647
|
// BACKBONE C2 (`mnemom agent host` replaces `mnemom host`, amendment 2; design-v3 row 7).
|
|
1645
1648
|
entry(["agent", "host"]),
|
|
1646
|
-
// BACKBONE C2 enroll (device step).
|
|
1647
|
-
entry(["agent", "host", "enroll"]),
|
|
1649
|
+
// BACKBONE C2 enroll (device step); --org: ruling 34.03.4 (a host joins an org its user belongs to).
|
|
1650
|
+
entry(["agent", "host", "enroll"], ["--org"]),
|
|
1648
1651
|
// BACKBONE C2 install/status/uninstall; --json: C04 (`mnemom agent host install --json`),
|
|
1649
1652
|
// ruling 32.31.
|
|
1650
1653
|
entry(["agent", "host", "install"], ["--json"]),
|
|
@@ -36,8 +36,8 @@ export const M1_CLI_SURFACE = Object.freeze([
|
|
|
36
36
|
entry(["agent", "done"], ["--json"]),
|
|
37
37
|
// BACKBONE C2 (`mnemom agent host` replaces `mnemom host`, amendment 2; design-v3 row 7).
|
|
38
38
|
entry(["agent", "host"]),
|
|
39
|
-
// BACKBONE C2 enroll (device step).
|
|
40
|
-
entry(["agent", "host", "enroll"]),
|
|
39
|
+
// BACKBONE C2 enroll (device step); --org: ruling 34.03.4 (a host joins an org its user belongs to).
|
|
40
|
+
entry(["agent", "host", "enroll"], ["--org"]),
|
|
41
41
|
// BACKBONE C2 install/status/uninstall; --json: C04 (`mnemom agent host install --json`),
|
|
42
42
|
// ruling 32.31.
|
|
43
43
|
entry(["agent", "host", "install"], ["--json"]),
|
|
@@ -157,7 +157,10 @@ export const DURABLE_CODES = {
|
|
|
157
157
|
// ---- api executor enrollment start, POST ROUTES.exec_enroll (C04-R02, R03; wave-0 fix 3b) ----
|
|
158
158
|
// Names kept verbatim from C04-R03. A machine principal keeps 403 machine_principal_refused
|
|
159
159
|
// (C04-R02); a non-starter approver gets durable_approval_not_eligible (ruling 1.13). Device-poll
|
|
160
|
-
// errors are not codes here: see ENROLL_POLL_ERRORS.
|
|
160
|
+
// errors are not codes here: see ENROLL_POLL_ERRORS. Ruling 34.03.4: a host start naming an org_id
|
|
161
|
+
// the caller is not a member of is also durable_approval_not_eligible (403, no approval row). A
|
|
162
|
+
// malformed or refused org_id is the api's plain 400 invalid_scope, which is not a durable code;
|
|
163
|
+
// an api from before 34.03.4 answers it to any org_id on a host start (34.03.2 (1)).
|
|
161
164
|
/** The caller lacks the entitlement durable.locked. */
|
|
162
165
|
not_entitled: { status: 403, surfaces: ["api"], fields: [] },
|
|
163
166
|
/** The declared kind is not in durable.executors (M1 allows 'host' only). */
|