@skrr-ai/cli 0.1.108 → 0.1.110
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 +10 -0
- package/dist/base-command.d.ts +74 -0
- package/dist/base-command.js +182 -24
- package/dist/commands/agents/triggers/create.js +1 -1
- package/dist/commands/agents/triggers/update.js +1 -1
- package/dist/commands/agents/update.d.ts +25 -0
- package/dist/commands/agents/update.js +98 -3
- package/dist/commands/commitments/accounting.js +7 -2
- package/dist/commands/commitments/cost.d.ts +11 -0
- package/dist/commands/commitments/cost.js +26 -3
- package/dist/commands/commitments/create.js +2 -1
- package/dist/commands/commitments/operating-review.d.ts +15 -0
- package/dist/commands/commitments/operating-review.js +103 -50
- package/dist/commands/commitments/update.js +12 -5
- package/dist/commands/daemon/install.js +17 -1
- package/dist/commands/daemon/login.js +14 -0
- package/dist/commands/doctor.js +9 -0
- package/dist/commands/goals/list.js +6 -1
- package/dist/commands/grants/rotate.js +4 -0
- package/dist/commands/harnesses/prune.d.ts +26 -0
- package/dist/commands/harnesses/prune.js +172 -0
- package/dist/commands/login.js +53 -14
- package/dist/commands/machines/list.js +28 -6
- package/dist/commands/projects/show.js +1 -1
- package/dist/commands/revoke-daemon.js +2 -1
- package/dist/commands/spaces/workflows/archive.d.ts +1 -0
- package/dist/commands/spaces/workflows/archive.js +1 -0
- package/dist/commands/spaces/workflows/artifact.d.ts +1 -0
- package/dist/commands/spaces/workflows/artifact.js +1 -0
- package/dist/commands/spaces/workflows/cancel-node.d.ts +1 -0
- package/dist/commands/spaces/workflows/cancel-node.js +1 -0
- package/dist/commands/spaces/workflows/cancel.d.ts +1 -0
- package/dist/commands/spaces/workflows/cancel.js +6 -2
- package/dist/commands/spaces/workflows/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/create.js +7 -1
- package/dist/commands/spaces/workflows/events.d.ts +1 -0
- package/dist/commands/spaces/workflows/events.js +1 -0
- package/dist/commands/spaces/workflows/fixtures/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/fixtures/create.js +1 -0
- package/dist/commands/spaces/workflows/fixtures/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/fixtures/list.js +1 -0
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +1 -0
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +1 -0
- package/dist/commands/spaces/workflows/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/list.js +1 -0
- package/dist/commands/spaces/workflows/node-types.d.ts +2 -0
- package/dist/commands/spaces/workflows/node-types.js +24 -6
- package/dist/commands/spaces/workflows/pause.d.ts +1 -0
- package/dist/commands/spaces/workflows/pause.js +1 -0
- package/dist/commands/spaces/workflows/plan/show.d.ts +1 -0
- package/dist/commands/spaces/workflows/plan/show.js +1 -0
- package/dist/commands/spaces/workflows/plan/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/plan/update.js +1 -0
- package/dist/commands/spaces/workflows/proposals/apply.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/apply.js +1 -0
- package/dist/commands/spaces/workflows/proposals/confirm.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/confirm.js +1 -0
- package/dist/commands/spaces/workflows/proposals/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/create.js +1 -0
- package/dist/commands/spaces/workflows/proposals/dismiss.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/dismiss.js +1 -0
- package/dist/commands/spaces/workflows/proposals/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/list.js +1 -0
- package/dist/commands/spaces/workflows/publish.d.ts +1 -0
- package/dist/commands/spaces/workflows/publish.js +9 -0
- package/dist/commands/spaces/workflows/reconcile.d.ts +1 -0
- package/dist/commands/spaces/workflows/reconcile.js +1 -0
- package/dist/commands/spaces/workflows/resume.d.ts +1 -0
- package/dist/commands/spaces/workflows/resume.js +1 -0
- package/dist/commands/spaces/workflows/retry.d.ts +1 -0
- package/dist/commands/spaces/workflows/retry.js +1 -0
- package/dist/commands/spaces/workflows/run.d.ts +1 -0
- package/dist/commands/spaces/workflows/run.js +14 -7
- package/dist/commands/spaces/workflows/runs.d.ts +1 -0
- package/dist/commands/spaces/workflows/runs.js +1 -0
- package/dist/commands/spaces/workflows/show.d.ts +25 -0
- package/dist/commands/spaces/workflows/show.js +42 -4
- package/dist/commands/spaces/workflows/signal.d.ts +12 -0
- package/dist/commands/spaces/workflows/signal.js +40 -2
- package/dist/commands/spaces/workflows/start.d.ts +1 -0
- package/dist/commands/spaces/workflows/start.js +4 -1
- package/dist/commands/spaces/workflows/task-execution/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/task-execution/create.js +1 -0
- package/dist/commands/spaces/workflows/test.d.ts +1 -0
- package/dist/commands/spaces/workflows/test.js +1 -0
- package/dist/commands/spaces/workflows/triggers/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/create.js +1 -0
- package/dist/commands/spaces/workflows/triggers/fire.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/fire.js +1 -0
- package/dist/commands/spaces/workflows/triggers/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/list.js +1 -0
- package/dist/commands/spaces/workflows/triggers/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/update.js +1 -0
- package/dist/commands/spaces/workflows/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/update.js +4 -0
- package/dist/commands/spaces/workflows/validate.d.ts +1 -0
- package/dist/commands/spaces/workflows/validate.js +27 -2
- package/dist/commands/tasks/activity.js +17 -3
- package/dist/commands/tasks/bulk-update.d.ts +1 -0
- package/dist/commands/tasks/bulk-update.js +7 -0
- package/dist/commands/tasks/complete.js +2 -1
- package/dist/commands/tasks/create-tree.d.ts +69 -0
- package/dist/commands/tasks/create-tree.js +413 -47
- package/dist/commands/tasks/list.d.ts +2 -0
- package/dist/commands/tasks/list.js +25 -3
- package/dist/commands/tasks/rearm-recurrence.d.ts +22 -0
- package/dist/commands/tasks/rearm-recurrence.js +87 -0
- package/dist/commands/tasks/show.d.ts +9 -0
- package/dist/commands/tasks/show.js +79 -33
- package/dist/commands/tasks/timeline.js +4 -0
- package/dist/commands/tasks/unschedule.js +5 -1
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +17 -2
- package/dist/commands/triggers/audit.js +12 -3
- package/dist/commands/triggers/create.js +9 -3
- package/dist/commands/triggers/fire-detail.d.ts +7 -0
- package/dist/commands/triggers/fire-detail.js +30 -12
- package/dist/commands/triggers/fire.js +13 -3
- package/dist/commands/triggers/parse-intent.d.ts +7 -0
- package/dist/commands/triggers/parse-intent.js +31 -9
- package/dist/commands/triggers/update.js +1 -1
- package/dist/commands/workspaces/grants.js +20 -3
- package/dist/commands/workspaces/members.d.ts +20 -0
- package/dist/commands/workspaces/members.js +64 -0
- package/dist/commands/workspaces/stewardship.js +14 -2
- package/dist/help.js +12 -0
- package/dist/hooks/command-not-found.js +27 -1
- package/dist/lib/command-miss.d.ts +52 -2
- package/dist/lib/command-miss.js +187 -18
- package/dist/lib/commitment-loop-status.d.ts +8 -0
- package/dist/lib/commitment-loop-status.js +45 -0
- package/dist/lib/commitment-product.js +5 -2
- package/dist/lib/commitments.d.ts +18 -3
- package/dist/lib/commitments.js +85 -45
- package/dist/lib/daemon-command-hint.d.ts +15 -0
- package/dist/lib/daemon-command-hint.js +21 -0
- package/dist/lib/daemon-machine-identity-copy.d.ts +14 -0
- package/dist/lib/daemon-machine-identity-copy.js +27 -0
- package/dist/lib/daemon-machine-identity.d.ts +27 -0
- package/dist/lib/daemon-machine-identity.js +61 -0
- package/dist/lib/daemon-setup.js +10 -1
- package/dist/lib/daemonBrokerRefusal.js +3 -2
- package/dist/lib/daemonHandoff.d.ts +19 -1
- package/dist/lib/daemonHandoff.js +61 -1
- package/dist/lib/deployed-build.d.ts +43 -0
- package/dist/lib/deployed-build.js +66 -0
- package/dist/lib/grants.d.ts +27 -1
- package/dist/lib/grants.js +39 -21
- package/dist/lib/harnesses.d.ts +71 -0
- package/dist/lib/harnesses.js +70 -0
- package/dist/lib/json-mode.d.ts +11 -0
- package/dist/lib/json-mode.js +19 -0
- package/dist/lib/json-output.d.ts +25 -0
- package/dist/lib/json-output.js +30 -0
- package/dist/lib/list-output.d.ts +10 -1
- package/dist/lib/list-output.js +22 -1
- package/dist/lib/local-daemon-readiness.d.ts +90 -0
- package/dist/lib/local-daemon-readiness.js +143 -0
- package/dist/lib/machines.d.ts +117 -0
- package/dist/lib/machines.js +139 -0
- package/dist/lib/managed-instruction-local.d.ts +2 -0
- package/dist/lib/managed-instruction-local.js +2 -0
- package/dist/lib/profile-machine-identity-guard.d.ts +18 -0
- package/dist/lib/profile-machine-identity-guard.js +39 -0
- package/dist/lib/request-field-flags.d.ts +20 -0
- package/dist/lib/request-field-flags.js +34 -0
- package/dist/lib/stewardship-output.d.ts +1 -1
- package/dist/lib/stewardship-output.js +26 -30
- package/dist/lib/task-batch.d.ts +61 -4
- package/dist/lib/task-batch.js +316 -80
- package/dist/lib/task-economics.d.ts +15 -0
- package/dist/lib/task-economics.js +39 -2
- package/dist/lib/task-recurrence-outcome.d.ts +6 -6
- package/dist/lib/task-recurrence-outcome.js +27 -3
- package/dist/lib/tasks.d.ts +39 -0
- package/dist/lib/tasks.js +132 -0
- package/dist/lib/triggers.d.ts +42 -1
- package/dist/lib/triggers.js +98 -2
- package/dist/lib/workflow-issues.d.ts +14 -0
- package/dist/lib/workflow-issues.js +32 -0
- package/dist/lib/workflow-run-view.d.ts +59 -0
- package/dist/lib/workflow-run-view.js +110 -0
- package/dist/lib/workspaces.d.ts +48 -4
- package/dist/lib/workspaces.js +55 -0
- package/dist/lib/write-target.d.ts +29 -0
- package/dist/lib/write-target.js +72 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +5 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +60 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +130 -6
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.js +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +60 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +128 -6
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.js +46 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +7523 -7140
- package/oclif.manifest.json +473 -59
- package/package.json +28 -1
package/dist/lib/workspaces.d.ts
CHANGED
|
@@ -12,11 +12,8 @@ export interface StewardshipCoverage {
|
|
|
12
12
|
truncated: boolean;
|
|
13
13
|
sources: StewardshipSource[];
|
|
14
14
|
}
|
|
15
|
-
export type WorkspaceGrantMetadata = Omit<GrantPublic, '
|
|
15
|
+
export type WorkspaceGrantMetadata = Omit<GrantPublic, 'createdAt' | 'updatedAt'> & {
|
|
16
16
|
ownerUserId: string;
|
|
17
|
-
credential: (NonNullable<GrantPublic['credential']> & {
|
|
18
|
-
fingerprint?: string;
|
|
19
|
-
}) | null;
|
|
20
17
|
createdAt?: string;
|
|
21
18
|
updatedAt?: string;
|
|
22
19
|
referencedBy: string[] | null;
|
|
@@ -38,6 +35,8 @@ export interface WorkspaceStewardshipReport extends WorkspaceGrantReport {
|
|
|
38
35
|
membership: {
|
|
39
36
|
status: 'active' | 'absent' | 'unavailable';
|
|
40
37
|
role?: string;
|
|
38
|
+
/** `false` on `absent`: a removed member and an id that never existed read alike. */
|
|
39
|
+
canDistinguishRemoved?: boolean;
|
|
41
40
|
};
|
|
42
41
|
commitments: StewardshipCommitmentMetadata[] | null;
|
|
43
42
|
approverSeats: StewardshipCommitmentMetadata[] | null;
|
|
@@ -47,6 +46,50 @@ export interface WorkspaceStewardshipReport extends WorkspaceGrantReport {
|
|
|
47
46
|
}> | null;
|
|
48
47
|
}
|
|
49
48
|
export declare function getWorkspaceStewardship(workspaceId: string, memberUserId: string): Promise<WorkspaceStewardshipReport>;
|
|
49
|
+
/** One current member of a workspace, as `GET /api/workspaces/:id/members` returns it. */
|
|
50
|
+
/**
|
|
51
|
+
* One current member as `skrr workspaces members` shows it, in its table and in
|
|
52
|
+
* `--json` alike (OSK-16472). Only these fields: the endpoint's raw row also carries
|
|
53
|
+
* the membership `_id`, monthly usage counters the web billing tab reads, and the
|
|
54
|
+
* user's avatar, none of which a members listing promises.
|
|
55
|
+
*/
|
|
56
|
+
export interface WorkspaceMemberRow {
|
|
57
|
+
userId: string;
|
|
58
|
+
name: string | null;
|
|
59
|
+
email: string | null;
|
|
60
|
+
role: string | null;
|
|
61
|
+
joinedAt: string | null;
|
|
62
|
+
}
|
|
63
|
+
interface RawWorkspaceMember {
|
|
64
|
+
userId?: unknown;
|
|
65
|
+
role?: unknown;
|
|
66
|
+
joinedAt?: unknown;
|
|
67
|
+
user?: {
|
|
68
|
+
name?: unknown;
|
|
69
|
+
username?: unknown;
|
|
70
|
+
email?: unknown;
|
|
71
|
+
} | null;
|
|
72
|
+
}
|
|
73
|
+
/** The ONE projection of a raw members row; nothing else of it reaches output. */
|
|
74
|
+
export declare function toWorkspaceMemberRow(raw: RawWorkspaceMember): WorkspaceMemberRow;
|
|
75
|
+
export declare function listWorkspaceMembers(workspaceId: string): Promise<WorkspaceMemberRow[]>;
|
|
76
|
+
/** A user id as the stewardship route takes it: a 24-hex ObjectId. */
|
|
77
|
+
export declare const USER_ID_PATTERN: RegExp;
|
|
78
|
+
export declare class MemberReferenceError extends Error {
|
|
79
|
+
constructor(message: string);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Turn what a person typed into the member's user id (OSK-15356).
|
|
83
|
+
*
|
|
84
|
+
* A 24-hex id is passed through untouched: the server answers for it, including
|
|
85
|
+
* for an id that is not (or is no longer) a member. An email is looked up among
|
|
86
|
+
* the workspace's CURRENT members, because that is the only directory this
|
|
87
|
+
* command may read; a former member's email therefore does not resolve, and the
|
|
88
|
+
* error says so instead of guessing. Anything else is refused here with the
|
|
89
|
+
* shapes that work, rather than sent to the server for a bare "valid user id
|
|
90
|
+
* is required".
|
|
91
|
+
*/
|
|
92
|
+
export declare function resolveMemberUserId(workspaceId: string, reference: string, list?: (workspaceId: string) => Promise<WorkspaceMemberRow[]>): Promise<string>;
|
|
50
93
|
export declare function listWorkspaceGrants(workspaceId: string): Promise<WorkspaceGrantReport>;
|
|
51
94
|
/**
|
|
52
95
|
* The workspaces this account belongs to.
|
|
@@ -199,3 +242,4 @@ export declare function getWorkspace(workspaceId: string): Promise<WorkspaceRow>
|
|
|
199
242
|
* session history to the wrong team.
|
|
200
243
|
*/
|
|
201
244
|
export declare function matchWorkspace(rows: WorkspaceRow[], ref: string): WorkspaceRow[];
|
|
245
|
+
export {};
|
package/dist/lib/workspaces.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemberReferenceError = exports.USER_ID_PATTERN = void 0;
|
|
3
4
|
exports.getWorkspaceStewardship = getWorkspaceStewardship;
|
|
5
|
+
exports.toWorkspaceMemberRow = toWorkspaceMemberRow;
|
|
6
|
+
exports.listWorkspaceMembers = listWorkspaceMembers;
|
|
7
|
+
exports.resolveMemberUserId = resolveMemberUserId;
|
|
4
8
|
exports.listWorkspaceGrants = listWorkspaceGrants;
|
|
5
9
|
exports.describeUnselectedWorkspace = describeUnselectedWorkspace;
|
|
6
10
|
exports.listWorkspaces = listWorkspaces;
|
|
@@ -15,6 +19,57 @@ const triggers_1 = require("./triggers");
|
|
|
15
19
|
async function getWorkspaceStewardship(workspaceId, memberUserId) {
|
|
16
20
|
return (0, api_fetch_1.apiFetch)(`/api/workspaces/${encodeURIComponent(workspaceId)}/stewardship/members/${encodeURIComponent(memberUserId)}`);
|
|
17
21
|
}
|
|
22
|
+
const text = (value) => (typeof value === 'string' && value ? value : null);
|
|
23
|
+
/** The ONE projection of a raw members row; nothing else of it reaches output. */
|
|
24
|
+
function toWorkspaceMemberRow(raw) {
|
|
25
|
+
return {
|
|
26
|
+
userId: String(raw.userId ?? ''),
|
|
27
|
+
name: text(raw.user?.name) ?? text(raw.user?.username),
|
|
28
|
+
email: text(raw.user?.email),
|
|
29
|
+
role: text(raw.role),
|
|
30
|
+
joinedAt: text(raw.joinedAt),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function listWorkspaceMembers(workspaceId) {
|
|
34
|
+
const response = await (0, api_fetch_1.apiFetch)(`/api/workspaces/${encodeURIComponent(workspaceId)}/members`);
|
|
35
|
+
return Array.isArray(response?.data) ? response.data.map(toWorkspaceMemberRow) : [];
|
|
36
|
+
}
|
|
37
|
+
/** A user id as the stewardship route takes it: a 24-hex ObjectId. */
|
|
38
|
+
exports.USER_ID_PATTERN = /^[a-f0-9]{24}$/i;
|
|
39
|
+
class MemberReferenceError extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'MemberReferenceError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.MemberReferenceError = MemberReferenceError;
|
|
46
|
+
/**
|
|
47
|
+
* Turn what a person typed into the member's user id (OSK-15356).
|
|
48
|
+
*
|
|
49
|
+
* A 24-hex id is passed through untouched: the server answers for it, including
|
|
50
|
+
* for an id that is not (or is no longer) a member. An email is looked up among
|
|
51
|
+
* the workspace's CURRENT members, because that is the only directory this
|
|
52
|
+
* command may read; a former member's email therefore does not resolve, and the
|
|
53
|
+
* error says so instead of guessing. Anything else is refused here with the
|
|
54
|
+
* shapes that work, rather than sent to the server for a bare "valid user id
|
|
55
|
+
* is required".
|
|
56
|
+
*/
|
|
57
|
+
async function resolveMemberUserId(workspaceId, reference, list = listWorkspaceMembers) {
|
|
58
|
+
const value = reference.trim();
|
|
59
|
+
if (exports.USER_ID_PATTERN.test(value))
|
|
60
|
+
return value;
|
|
61
|
+
const listHint = `\`skrr workspaces members ${workspaceId}\` lists current members and their user ids.`;
|
|
62
|
+
if (!value.includes('@')) {
|
|
63
|
+
throw new MemberReferenceError(`"${reference}" is not a user id or an email. A user id is 24 hexadecimal characters. ${listHint}`);
|
|
64
|
+
}
|
|
65
|
+
const email = value.toLowerCase();
|
|
66
|
+
const matches = (await list(workspaceId)).filter((member) => member.email?.toLowerCase() === email);
|
|
67
|
+
if (matches.length === 1)
|
|
68
|
+
return String(matches[0].userId);
|
|
69
|
+
throw new MemberReferenceError(matches.length === 0
|
|
70
|
+
? `No current member of this workspace has the email ${value}. A former member's email cannot be looked up here; use their user id. ${listHint}`
|
|
71
|
+
: `More than one member has the email ${value}; use the user id. ${listHint}`);
|
|
72
|
+
}
|
|
18
73
|
async function listWorkspaceGrants(workspaceId) {
|
|
19
74
|
return (0, api_fetch_1.apiFetch)(`/api/workspaces/${encodeURIComponent(workspaceId)}/stewardship/grants`);
|
|
20
75
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type WriteTarget = {
|
|
2
|
+
/** The API origin writes go to, e.g. `https://skrr.ai`. */
|
|
3
|
+
server: string;
|
|
4
|
+
/** The signed-in account's email, or null when it could not be read. */
|
|
5
|
+
account: string | null;
|
|
6
|
+
accountId: string | null;
|
|
7
|
+
/** The workspace the request is scoped to, or null for none. */
|
|
8
|
+
workspace: string | null;
|
|
9
|
+
};
|
|
10
|
+
/** `stevedu518@gmail.com on https://skrr.ai (no workspace)` */
|
|
11
|
+
export declare function formatWriteTarget(target: WriteTarget): string;
|
|
12
|
+
/**
|
|
13
|
+
* Does one `--expect-target` value describe this target?
|
|
14
|
+
*
|
|
15
|
+
* - contains `@` -> the account email (case-insensitive)
|
|
16
|
+
* - a URL or a host name -> the server's host (`skrr.ai` matches `https://skrr.ai`)
|
|
17
|
+
* - anything else -> the account id
|
|
18
|
+
*
|
|
19
|
+
* One value names one fact; pass the flag twice to pin both account and server.
|
|
20
|
+
* An unreadable fact never matches: an expectation about an account the command
|
|
21
|
+
* could not read is not met.
|
|
22
|
+
*/
|
|
23
|
+
export declare function matchesExpectedTarget(target: WriteTarget, expected: string): boolean;
|
|
24
|
+
/** The first expectation that does NOT hold, or undefined when all do. */
|
|
25
|
+
export declare function firstUnmetExpectation(target: WriteTarget, expected: readonly string[] | undefined): string | undefined;
|
|
26
|
+
/** The assertion flag, declared once so every bulk writer spells it the same way. */
|
|
27
|
+
export declare const expectTargetFlag: {
|
|
28
|
+
'expect-target': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
29
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expectTargetFlag = void 0;
|
|
4
|
+
exports.formatWriteTarget = formatWriteTarget;
|
|
5
|
+
exports.matchesExpectedTarget = matchesExpectedTarget;
|
|
6
|
+
exports.firstUnmetExpectation = firstUnmetExpectation;
|
|
7
|
+
/**
|
|
8
|
+
* Which account and which server a bulk write is about to touch.
|
|
9
|
+
*
|
|
10
|
+
* `skrr` is increasingly driven by agents, against a platform whose development
|
|
11
|
+
* and production share one database. A person (or an agent) who has run `skrr
|
|
12
|
+
* login` once can write 94 tasks to an account and a server they have not looked
|
|
13
|
+
* at since, and nothing in the command's own output said so — the target was
|
|
14
|
+
* visible only to someone who thought to run `whoami` first (OSK-S-440 #10).
|
|
15
|
+
* "I checked once" is not a control; the command that is about to write in bulk
|
|
16
|
+
* has to say where, and let its caller assert where.
|
|
17
|
+
*
|
|
18
|
+
* Pure: the command gathers the facts (a request or two), this decides how to say
|
|
19
|
+
* them and whether an expectation holds.
|
|
20
|
+
*/
|
|
21
|
+
const core_1 = require("@oclif/core");
|
|
22
|
+
/** `stevedu518@gmail.com on https://skrr.ai (no workspace)` */
|
|
23
|
+
function formatWriteTarget(target) {
|
|
24
|
+
const who = target.account ?? target.accountId ?? 'an account that could not be read';
|
|
25
|
+
const where = target.workspace ? `workspace ${target.workspace}` : 'no workspace';
|
|
26
|
+
return `${who} on ${target.server} (${where})`;
|
|
27
|
+
}
|
|
28
|
+
function hostOf(value) {
|
|
29
|
+
try {
|
|
30
|
+
return new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`).host.toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Does one `--expect-target` value describe this target?
|
|
38
|
+
*
|
|
39
|
+
* - contains `@` -> the account email (case-insensitive)
|
|
40
|
+
* - a URL or a host name -> the server's host (`skrr.ai` matches `https://skrr.ai`)
|
|
41
|
+
* - anything else -> the account id
|
|
42
|
+
*
|
|
43
|
+
* One value names one fact; pass the flag twice to pin both account and server.
|
|
44
|
+
* An unreadable fact never matches: an expectation about an account the command
|
|
45
|
+
* could not read is not met.
|
|
46
|
+
*/
|
|
47
|
+
function matchesExpectedTarget(target, expected) {
|
|
48
|
+
const value = expected.trim();
|
|
49
|
+
if (!value)
|
|
50
|
+
return false;
|
|
51
|
+
if (value.includes('@') && !/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
|
|
52
|
+
return target.account !== null && target.account.toLowerCase() === value.toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value) ||
|
|
55
|
+
value.includes('.') ||
|
|
56
|
+
value.startsWith('localhost')) {
|
|
57
|
+
const expectedHost = hostOf(value);
|
|
58
|
+
return expectedHost !== null && expectedHost === hostOf(target.server);
|
|
59
|
+
}
|
|
60
|
+
return target.accountId !== null && target.accountId === value;
|
|
61
|
+
}
|
|
62
|
+
/** The first expectation that does NOT hold, or undefined when all do. */
|
|
63
|
+
function firstUnmetExpectation(target, expected) {
|
|
64
|
+
return (expected ?? []).find((value) => !matchesExpectedTarget(target, value));
|
|
65
|
+
}
|
|
66
|
+
/** The assertion flag, declared once so every bulk writer spells it the same way. */
|
|
67
|
+
exports.expectTargetFlag = {
|
|
68
|
+
'expect-target': core_1.Flags.string({
|
|
69
|
+
description: 'Refuse to write unless the target matches: an account email, a server host (skrr.ai) or an account id. Repeat to pin more than one (account AND server). Checked before anything is created.',
|
|
70
|
+
multiple: true,
|
|
71
|
+
}),
|
|
72
|
+
};
|
|
@@ -30,7 +30,7 @@ export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, n
|
|
|
30
30
|
export { DPOP_HEADER, DPOP_MAX_AGE_SECONDS, DPOP_PROOF_INVALID, DPOP_PROOF_REQUIRED, DPOP_PROOF_TYP, DPOP_PROOF_VERIFY_UNAVAILABLE, accessTokenHash, createDpopKey, isAth, isJwkThumbprint, normalizeDpopHtu, verifyDpopProof, type DpopKey, type DpopProofClaims, type DpopProofRequest, type DpopVerifyFailureReason, type DpopVerifyOptions, type DpopVerifyResult, } from './dpop.js';
|
|
31
31
|
export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
|
|
32
32
|
export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, describeDeviceKeySplit, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
|
|
33
|
-
export { getMachineUuid, getHardwareMachineUuid, setMachineUuidProfile, getMachineUuidProfile, qualifyMachineUuid, MACHINE_IDENTITY_FILENAME, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
|
|
33
|
+
export { getMachineUuid, getMachineUuidForProfile, describeMachineIdentity, type MachineIdentityDescription, type MachineIdentityReason, getHardwareMachineUuid, setMachineUuidProfile, getMachineUuidProfile, qualifyMachineUuid, MACHINE_IDENTITY_FILENAME, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
|
|
34
34
|
export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
|
|
35
35
|
export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, type AssembleBundleOptions as PairAssembleBundleOptions, type OpenBundleOptions as PairOpenBundleOptions, type PairBundle, type PairBundleErrorCode, type PairKeyPair, type PairPlaintextV1, type PairUrlFields, } from './daemonPair.js';
|
|
36
36
|
export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConfiguredAutoMode, type HarnessTrustTier, type CredentialDeliveryPolicy, type CredentialMode, } from './harnessTrust.js';
|
|
@@ -24,9 +24,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.runHeadersHelper = exports.SENTINEL_TRIGGERING_FAILURES = exports.__resetAuthHelperFailureReasonForTest = exports.getLastAuthHelperFailureReason = exports.getDefaultHelperTtlMs = exports.invokeAuthHelper = exports.AuthHelperUnavailableError = exports.AuthHelperUntrustedError = exports.isStaticTokenKind = exports.PERSONAL_ACCESS_TOKEN_PREFIX = exports.CI_TOKEN_PREFIX = exports.classifyTokenKind = exports.invalidateAuthHelperCache = exports.__resetCredentialResolverForTest = exports.CREDENTIAL_PRECEDENCE = exports.CredentialResolver = exports.__setFdReaderForTest = exports.readAuthFromFd = exports.createRefreshScheduler = exports.UNKNOWN_401_BUDGET = exports.__resetUnknown401StreaksForTest = exports.__refreshDiagnostics = exports.installLockExitHandlers = exports.clearReauthState = exports.needsReauthState = exports.persistReauthState = exports.withAuthLock = exports.revokeDaemonRefreshSession = exports.refreshDaemonToken = exports.classifyRefreshResponse = exports.EXIT_NEEDS_REAUTH = exports.reauthCommand = exports.formatReauthMessage = exports.handleMidStreamAuthExpiry = exports.isAuthExpiredSseEvent = exports.decodeJwtPayload = exports.decodeJwtExpiry = exports.TransientAuthFailure = exports.PermanentAuthFailure = exports.emitAuthTelemetry = exports.getAuthBinaryName = exports.getAuthKillSwitches = exports.getAuthConfigDir = exports.getAuthLogger = exports.configureAuthCore = exports.OAUTH_CLI_SCOPE = exports.isDaemonScope = exports.VALID_DAEMON_SCOPE_SET = exports.ALL_DAEMON_SCOPES = exports.SCOPES = void 0;
|
|
26
26
|
exports.jwkThumbprint = exports.generateDeviceKeyPair = exports.buildDeviceProof = exports.zeroizeKekCaches = exports.WindowsKek = exports.registerZeroizeHook = exports.MacosKek = exports.LinuxKek = exports.KekUnavailableError = exports.UnavailableKek = exports.InMemoryKek = exports.getKekStrategy = exports.__setKekStrategyForTest = exports.credEnvelopeWrap = exports.credEnvelopeUnwrap = exports.credEnvelopeSerialize = exports.credSealBuffer = exports.credOpenBuffer = exports.CRED_KEY_LEN = exports.isCredEnvelopeString = exports.generateDek = exports.CRED_ENVELOPE_MAGIC = exports.credEnvelopeEncrypt = exports.credEnvelopeDeserialize = exports.credEnvelopeDecrypt = exports.buildCredentialAad = exports.isValidRecoveryCode = exports.normalizeRecoveryCode = exports.parseRecoveryCode = exports.formatRecoveryCode = exports.MAX_TTL_DAYS = exports.MIN_TTL_DAYS = exports.parseTtlDays = exports.LoginExchangeError = exports.LoginInitError = exports.loginWithLocalhost = exports.LocalCallbackMalformedError = exports.LocalCallbackDeniedError = exports.LocalCallbackStateMismatchError = exports.LocalCallbackTimeoutError = exports.LocalCallbackBindError = exports.LocalCallbackError = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.startLocalCallback = exports.verifyChallenge = exports.safeEqual = exports.generateState = exports.challenge = exports.generateVerifier = exports.invalidateHeadersHelperCache = void 0;
|
|
27
|
-
exports.
|
|
28
|
-
exports.
|
|
29
|
-
exports.compareReleaseVersions = exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.isTrustedArtifactOrigin = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.TRUSTED_FEED_ORIGINS = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = exports.resolveChannelManifestUrl = exports.isFirstPartyHarnessChannel = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.firstPartyHarnessChannelPrefix = exports.FIRST_PARTY_HARNESS_CHANNELS = exports.DEFAULT_FIRST_PARTY_HARNESS_CHANNEL = exports.migrateFirstPartyHarnessHome = exports.linkFirstPartyHarnessAliases = exports.firstPartyHarnessInstalledBinaryPath = exports.firstPartyHarnessHomeDir = exports.firstPartyHarnessBinDir = exports.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = exports.describeLegacyState = exports.findLegacyLocalState = exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = void 0;
|
|
27
|
+
exports.setMachineUuidProfile = exports.getHardwareMachineUuid = exports.describeMachineIdentity = exports.getMachineUuidForProfile = exports.getMachineUuid = exports.__getDeviceIdentityStateForTest = exports.__setDeviceIdentityStateForTest = exports.__enrollmentBackoffPathForTest = exports.__enrollmentMarkerPathForTest = exports.__privateKeyPathForTest = exports.__publicKeyPathForTest = exports.describeDeviceKeySplit = exports.describeDeviceIdentityState = exports.getDevicePublicKey = exports.isDeviceIdentityActive = exports.clearEnrollmentMarker = exports.enrollWithServer = exports.signServerRequest = exports.resetDeviceIdentity = exports.initDeviceIdentity = exports.__resetShutdownGuardForTest = exports.__getCredEnvelopeStateForTest = exports.__setCredEnvelopeStateForTest = exports.__wrappedDekFilePathForTest = exports.DEFAULT_UNREADABLE_CREDENTIAL_HINT = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.splitKeyConvergencePolicy = exports.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDpopProof = exports.normalizeDpopHtu = exports.isJwkThumbprint = exports.isAth = exports.createDpopKey = exports.accessTokenHash = exports.DPOP_PROOF_VERIFY_UNAVAILABLE = exports.DPOP_PROOF_TYP = exports.DPOP_PROOF_REQUIRED = exports.DPOP_PROOF_INVALID = exports.DPOP_MAX_AGE_SECONDS = exports.DPOP_HEADER = exports.verifyDeviceProof = exports.tokenHash = exports.publicJwkFromX = exports.normalizeHtu = void 0;
|
|
28
|
+
exports.isLoopbackAddress = exports.isLoopbackHostname = exports.assertLoopbackHttpUrl = exports.loopbackFetch = exports.withLoopbackNoProxy = exports.envHasProxy = exports.LOOPBACK_NO_PROXY_HOSTS = exports.isDaemonChildEnv = exports.DAEMON_CHILD_ENV = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_NAME_TOKENS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = exports.sanitizeSpawnEnv = exports.harnessLoadsClaudeSkillsLayout = exports.claudeSkillsLayoutEvidence = exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT = exports.tierPermitsConfiguredAutoMode = exports.credentialPolicyForTier = exports.getHarnessTier = exports.HARNESS_TIERS = exports.pairUint8ArrayToBase64Url = exports.pairUint8ArrayToBase64 = exports.parsePairUrl = exports.parsePairPlaintext = exports.parsePairBundle = exports.openPairBundleBase64 = exports.generatePairKeyPair = exports.buildPairUrl = exports.buildPairAad = exports.pairBase64UrlToUint8Array = exports.pairBase64ToUint8Array = exports.assemblePairBundleBase64 = exports.PairBundleError = exports.PAIR_SECRET_LEN = exports.PAIR_PUBKEY_LEN = exports.PAIR_NONCE_LEN = exports.PAIR_BUNDLE_MAX_BASE64_LEN = exports.PAIR_AAD_MAX_LEN = exports.PAIR_AAD_PREFIX = exports.__resetCachedDaemonIdForTest = exports.legacyDaemonUuids = exports.ensureMachineDaemonId = exports.__resetMachineUuidCacheForTest = exports.HOSTED_RUNTIME_MACHINE_SOURCES = exports.hostedRuntimeIdOverride = exports.MACHINE_IDENTITY_FILENAME = exports.qualifyMachineUuid = exports.getMachineUuidProfile = void 0;
|
|
29
|
+
exports.compareReleaseVersions = exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.isTrustedArtifactOrigin = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.TRUSTED_FEED_ORIGINS = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = exports.resolveChannelManifestUrl = exports.isFirstPartyHarnessChannel = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.firstPartyHarnessChannelPrefix = exports.FIRST_PARTY_HARNESS_CHANNELS = exports.DEFAULT_FIRST_PARTY_HARNESS_CHANNEL = exports.migrateFirstPartyHarnessHome = exports.linkFirstPartyHarnessAliases = exports.firstPartyHarnessInstalledBinaryPath = exports.firstPartyHarnessHomeDir = exports.firstPartyHarnessBinDir = exports.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = exports.describeLegacyState = exports.findLegacyLocalState = exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = exports.LOOPBACK_DEFAULT_MAX_BODY_BYTES = exports.LoopbackTargetRefusedError = void 0;
|
|
30
30
|
// Daemon scope vocabulary — single source of truth for non-Mongoose tiers
|
|
31
31
|
// (daemon, desktop, cli). Schema-bound canonical lives in
|
|
32
32
|
// `@skrr-ai/data-schemas/common/daemonScopes`; the two are kept byte-equal
|
|
@@ -284,6 +284,8 @@ Object.defineProperty(exports, "__getDeviceIdentityStateForTest", { enumerable:
|
|
|
284
284
|
// OSK-12063. Also exposed dependency-free as `@skrr-ai/auth-core/machine-uuid`.
|
|
285
285
|
var machineUuid_js_1 = require("./machineUuid.js");
|
|
286
286
|
Object.defineProperty(exports, "getMachineUuid", { enumerable: true, get: function () { return machineUuid_js_1.getMachineUuid; } });
|
|
287
|
+
Object.defineProperty(exports, "getMachineUuidForProfile", { enumerable: true, get: function () { return machineUuid_js_1.getMachineUuidForProfile; } });
|
|
288
|
+
Object.defineProperty(exports, "describeMachineIdentity", { enumerable: true, get: function () { return machineUuid_js_1.describeMachineIdentity; } });
|
|
287
289
|
Object.defineProperty(exports, "getHardwareMachineUuid", { enumerable: true, get: function () { return machineUuid_js_1.getHardwareMachineUuid; } });
|
|
288
290
|
Object.defineProperty(exports, "setMachineUuidProfile", { enumerable: true, get: function () { return machineUuid_js_1.setMachineUuidProfile; } });
|
|
289
291
|
Object.defineProperty(exports, "getMachineUuidProfile", { enumerable: true, get: function () { return machineUuid_js_1.getMachineUuidProfile; } });
|
|
@@ -56,5 +56,65 @@ export declare function getMachineUuidProfile(): string | null;
|
|
|
56
56
|
* profile that already held credentials; see the block above.
|
|
57
57
|
*/
|
|
58
58
|
export declare function getMachineUuid(): string;
|
|
59
|
+
/**
|
|
60
|
+
* The `machineUuid` a process running as `profile` sends, WITHOUT changing which
|
|
61
|
+
* profile this process runs as.
|
|
62
|
+
*
|
|
63
|
+
* The CLI needs the answer for the profile it is about to hand a credential to,
|
|
64
|
+
* and asks the daemon binary what IT will send (`accept-handoff --print-id`).
|
|
65
|
+
* Two programs that compute one identity can only be known to agree by
|
|
66
|
+
* comparing them, and a daemon that predates per-profile identities does not
|
|
67
|
+
* agree: it sends the hardware value whatever the profile's
|
|
68
|
+
* `machine-identity.json` says, so a credential minted for what it reports
|
|
69
|
+
* registers the profile on the default machine's row (the OSK-13845 failure, now
|
|
70
|
+
* reached through version skew rather than through the rule). The comparison
|
|
71
|
+
* needs this: the expected value for the TARGET profile, which is not
|
|
72
|
+
* necessarily the one this process is running as.
|
|
73
|
+
*
|
|
74
|
+
* Decides and persists the profile's identity if nothing has yet — the same
|
|
75
|
+
* side effect `getMachineUuid()` has, for the same reason.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getMachineUuidForProfile(profile: string | null | undefined): string;
|
|
78
|
+
export type MachineIdentityReason =
|
|
79
|
+
/** The default profile, which always uses this computer's own identity. */
|
|
80
|
+
'default_profile'
|
|
81
|
+
/** A hosted runtime, whose identity the control plane issued. */
|
|
82
|
+
| 'hosted_runtime'
|
|
83
|
+
/** A profile that held no credentials when its identity was first decided. */
|
|
84
|
+
| 'new_profile'
|
|
85
|
+
/** A profile that already held credentials, so it keeps the shared identity. */
|
|
86
|
+
| 'pre_existing_profile'
|
|
87
|
+
/** Recovered from the copy kept outside the profile after product state was removed. */
|
|
88
|
+
| 'restored_profile'
|
|
89
|
+
/** The profile's record could not be read (the identity is still the one in use). */
|
|
90
|
+
| 'unrecorded';
|
|
91
|
+
export interface MachineIdentityDescription {
|
|
92
|
+
/** The profile this process runs as; null for the default profile. */
|
|
93
|
+
profile: string | null;
|
|
94
|
+
/** The value sent to the server as `machineUuid`. */
|
|
95
|
+
machineUuid: string;
|
|
96
|
+
/** This computer's own identity. */
|
|
97
|
+
hardware: string;
|
|
98
|
+
/**
|
|
99
|
+
* A non-default profile that registers on the SAME machine row as the default
|
|
100
|
+
* profile: they share one Daemon row, one device-key pin and one machine page.
|
|
101
|
+
*/
|
|
102
|
+
sharesDefaultMachine: boolean;
|
|
103
|
+
reason: MachineIdentityReason;
|
|
104
|
+
/** The files that made a profile "already hold credentials", when recorded. */
|
|
105
|
+
evidence: string[];
|
|
106
|
+
decidedAt: string | null;
|
|
107
|
+
/** One sentence for a person. Written once, here, so no surface words it differently. */
|
|
108
|
+
sentence: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Which machine this process registers as, and WHY — the question a person has
|
|
112
|
+
* when two profiles turn up on one machine page and the logs say nothing.
|
|
113
|
+
*
|
|
114
|
+
* A read, not a decision: it reports the identity `getMachineUuid()` settled on
|
|
115
|
+
* (settling it if nothing has, exactly as any caller would) and the record that
|
|
116
|
+
* explains it. It never changes what a profile registers as.
|
|
117
|
+
*/
|
|
118
|
+
export declare function describeMachineIdentity(): MachineIdentityDescription;
|
|
59
119
|
/** @internal test seam — forget the cached value so the next call re-probes. */
|
|
60
120
|
export declare function __resetMachineUuidCacheForTest(): void;
|
|
@@ -10,6 +10,8 @@ exports.qualifyMachineUuid = qualifyMachineUuid;
|
|
|
10
10
|
exports.setMachineUuidProfile = setMachineUuidProfile;
|
|
11
11
|
exports.getMachineUuidProfile = getMachineUuidProfile;
|
|
12
12
|
exports.getMachineUuid = getMachineUuid;
|
|
13
|
+
exports.getMachineUuidForProfile = getMachineUuidForProfile;
|
|
14
|
+
exports.describeMachineIdentity = describeMachineIdentity;
|
|
13
15
|
exports.__resetMachineUuidCacheForTest = __resetMachineUuidCacheForTest;
|
|
14
16
|
/**
|
|
15
17
|
* machineUuid.ts — the ONE hardware machine identity sent to the server as
|
|
@@ -231,7 +233,7 @@ function readPersistedChoice(file, hardware, profile, requireProfile = false) {
|
|
|
231
233
|
return null;
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
|
-
function persistChoice(dir, file, hardware, machineUuid, reason, profile) {
|
|
236
|
+
function persistChoice(dir, file, hardware, machineUuid, reason, profile, evidence) {
|
|
235
237
|
try {
|
|
236
238
|
node_fs_1.default.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
237
239
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
@@ -241,6 +243,12 @@ function persistChoice(dir, file, hardware, machineUuid, reason, profile) {
|
|
|
241
243
|
machineUuid,
|
|
242
244
|
profile,
|
|
243
245
|
reason,
|
|
246
|
+
// WHICH file made a profile "already hold credentials". The reason alone
|
|
247
|
+
// says a profile shares the default's machine row; this says what put it
|
|
248
|
+
// there, which is the difference between "an older Skrr set it up" and
|
|
249
|
+
// "its first credential was written before anything chose its identity".
|
|
250
|
+
// Additive: a reader that predates it ignores the field.
|
|
251
|
+
...(evidence && evidence.length > 0 ? { evidence: [...evidence] } : {}),
|
|
244
252
|
decidedAt: new Date().toISOString(),
|
|
245
253
|
};
|
|
246
254
|
node_fs_1.default.writeFileSync(tmp, `${JSON.stringify(body)}\n`, { mode: 0o600 });
|
|
@@ -280,7 +288,7 @@ function resolveProfileMachineUuid(profile, hardware) {
|
|
|
280
288
|
persistChoice(dir, file, hardware, chosen, 'restored_profile', profile);
|
|
281
289
|
}
|
|
282
290
|
if (!chosen) {
|
|
283
|
-
const
|
|
291
|
+
const evidence = PRE_EXISTING_PROFILE_EVIDENCE.filter((name) => {
|
|
284
292
|
try {
|
|
285
293
|
return node_fs_1.default.existsSync(node_path_1.default.join(dir, name));
|
|
286
294
|
}
|
|
@@ -288,8 +296,9 @@ function resolveProfileMachineUuid(profile, hardware) {
|
|
|
288
296
|
return false;
|
|
289
297
|
}
|
|
290
298
|
});
|
|
299
|
+
const preExisting = evidence.length > 0;
|
|
291
300
|
chosen = preExisting ? hardware : qualifyMachineUuid(hardware, profile);
|
|
292
|
-
persistChoice(dir, file, hardware, chosen, preExisting ? 'pre_existing_profile' : 'new_profile', profile);
|
|
301
|
+
persistChoice(dir, file, hardware, chosen, preExisting ? 'pre_existing_profile' : 'new_profile', profile, evidence);
|
|
293
302
|
}
|
|
294
303
|
preserveRecoveryChoice(profile, hardware, chosen);
|
|
295
304
|
profileChoiceCache.set(cacheKey, chosen);
|
|
@@ -309,14 +318,129 @@ function resolveProfileMachineUuid(profile, hardware) {
|
|
|
309
318
|
* profile that already held credentials; see the block above.
|
|
310
319
|
*/
|
|
311
320
|
function getMachineUuid() {
|
|
321
|
+
return getMachineUuidForProfile(activeMachineProfile);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* The `machineUuid` a process running as `profile` sends, WITHOUT changing which
|
|
325
|
+
* profile this process runs as.
|
|
326
|
+
*
|
|
327
|
+
* The CLI needs the answer for the profile it is about to hand a credential to,
|
|
328
|
+
* and asks the daemon binary what IT will send (`accept-handoff --print-id`).
|
|
329
|
+
* Two programs that compute one identity can only be known to agree by
|
|
330
|
+
* comparing them, and a daemon that predates per-profile identities does not
|
|
331
|
+
* agree: it sends the hardware value whatever the profile's
|
|
332
|
+
* `machine-identity.json` says, so a credential minted for what it reports
|
|
333
|
+
* registers the profile on the default machine's row (the OSK-13845 failure, now
|
|
334
|
+
* reached through version skew rather than through the rule). The comparison
|
|
335
|
+
* needs this: the expected value for the TARGET profile, which is not
|
|
336
|
+
* necessarily the one this process is running as.
|
|
337
|
+
*
|
|
338
|
+
* Decides and persists the profile's identity if nothing has yet — the same
|
|
339
|
+
* side effect `getMachineUuid()` has, for the same reason.
|
|
340
|
+
*/
|
|
341
|
+
function getMachineUuidForProfile(profile) {
|
|
312
342
|
const hardware = getHardwareMachineUuid();
|
|
313
343
|
// A hosted runtime's identity is ISSUED and fenced by the control plane.
|
|
314
344
|
if (hostedRuntimeIdOverride('OVERSKY_MACHINE_UUID'))
|
|
315
345
|
return hardware;
|
|
316
|
-
const
|
|
317
|
-
if (!
|
|
346
|
+
const name = typeof profile === 'string' ? profile.trim() : '';
|
|
347
|
+
if (!name || name === DEFAULT_PROFILE_NAME)
|
|
318
348
|
return hardware;
|
|
319
|
-
return resolveProfileMachineUuid(
|
|
349
|
+
return resolveProfileMachineUuid(name, hardware);
|
|
350
|
+
}
|
|
351
|
+
function readRecordedChoice(profile) {
|
|
352
|
+
try {
|
|
353
|
+
const file = node_path_1.default.join((0, configRoot_js_1.resolveConfigRoot)(), 'profiles', profile, exports.MACHINE_IDENTITY_FILENAME);
|
|
354
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(file, 'utf-8'));
|
|
355
|
+
const reason = parsed.reason === 'new_profile' ||
|
|
356
|
+
parsed.reason === 'pre_existing_profile' ||
|
|
357
|
+
parsed.reason === 'restored_profile'
|
|
358
|
+
? parsed.reason
|
|
359
|
+
: null;
|
|
360
|
+
return {
|
|
361
|
+
reason,
|
|
362
|
+
evidence: Array.isArray(parsed.evidence)
|
|
363
|
+
? parsed.evidence.filter((e) => typeof e === 'string')
|
|
364
|
+
: [],
|
|
365
|
+
decidedAt: typeof parsed.decidedAt === 'string' ? parsed.decidedAt : null,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return { reason: null, evidence: [], decidedAt: null };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Which machine this process registers as, and WHY — the question a person has
|
|
374
|
+
* when two profiles turn up on one machine page and the logs say nothing.
|
|
375
|
+
*
|
|
376
|
+
* A read, not a decision: it reports the identity `getMachineUuid()` settled on
|
|
377
|
+
* (settling it if nothing has, exactly as any caller would) and the record that
|
|
378
|
+
* explains it. It never changes what a profile registers as.
|
|
379
|
+
*/
|
|
380
|
+
function describeMachineIdentity() {
|
|
381
|
+
const hardware = getHardwareMachineUuid();
|
|
382
|
+
const machineUuid = getMachineUuid();
|
|
383
|
+
const active = activeMachineProfile;
|
|
384
|
+
const profile = active && active !== DEFAULT_PROFILE_NAME ? active : null;
|
|
385
|
+
const base = { profile, machineUuid, hardware };
|
|
386
|
+
if (hostedRuntimeIdOverride('OVERSKY_MACHINE_UUID')) {
|
|
387
|
+
return {
|
|
388
|
+
...base,
|
|
389
|
+
sharesDefaultMachine: false,
|
|
390
|
+
reason: 'hosted_runtime',
|
|
391
|
+
evidence: [],
|
|
392
|
+
decidedAt: null,
|
|
393
|
+
sentence: 'This runtime is hosted; its machine identity was issued by the control plane.',
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (!profile) {
|
|
397
|
+
return {
|
|
398
|
+
...base,
|
|
399
|
+
sharesDefaultMachine: false,
|
|
400
|
+
reason: 'default_profile',
|
|
401
|
+
evidence: [],
|
|
402
|
+
decidedAt: null,
|
|
403
|
+
sentence: "The default profile uses this computer's own machine identity.",
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const recorded = readRecordedChoice(profile);
|
|
407
|
+
const sharesDefaultMachine = machineUuid === hardware;
|
|
408
|
+
const reason = recorded.reason ?? 'unrecorded';
|
|
409
|
+
const decided = recorded.decidedAt ? ` (decided ${recorded.decidedAt})` : '';
|
|
410
|
+
const held = recorded.evidence.length > 0 ? ` (${recorded.evidence.join(', ')})` : '';
|
|
411
|
+
let sentence;
|
|
412
|
+
if (reason === 'new_profile') {
|
|
413
|
+
sentence =
|
|
414
|
+
`Profile '${profile}' has its own machine identity: it held no credentials when that ` +
|
|
415
|
+
`identity was first chosen${decided}.`;
|
|
416
|
+
}
|
|
417
|
+
else if (reason === 'pre_existing_profile') {
|
|
418
|
+
sentence =
|
|
419
|
+
`Profile '${profile}' shares this computer's default machine identity: it already held ` +
|
|
420
|
+
`credentials${held} when its identity was first chosen${decided}, and the server ties ` +
|
|
421
|
+
'those credentials to that identity, so changing it would sign the profile out.';
|
|
422
|
+
}
|
|
423
|
+
else if (reason === 'restored_profile') {
|
|
424
|
+
sentence = sharesDefaultMachine
|
|
425
|
+
? `Profile '${profile}' shares this computer's default machine identity, restored from the ` +
|
|
426
|
+
`copy kept outside the profile after its state was removed${decided}.`
|
|
427
|
+
: `Profile '${profile}' has its own machine identity, restored from the copy kept outside ` +
|
|
428
|
+
`the profile after its state was removed${decided}.`;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
sentence = sharesDefaultMachine
|
|
432
|
+
? `Profile '${profile}' uses this computer's default machine identity; the record of why ` +
|
|
433
|
+
'could not be read.'
|
|
434
|
+
: `Profile '${profile}' has its own machine identity; the record of why could not be read.`;
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
...base,
|
|
438
|
+
sharesDefaultMachine,
|
|
439
|
+
reason,
|
|
440
|
+
evidence: recorded.evidence,
|
|
441
|
+
decidedAt: recorded.decidedAt,
|
|
442
|
+
sentence,
|
|
443
|
+
};
|
|
320
444
|
}
|
|
321
445
|
/** @internal test seam — forget the cached value so the next call re-probes. */
|
|
322
446
|
function __resetMachineUuidCacheForTest() {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace-isolation probe wire contract (OSK-15099).
|
|
3
|
+
*
|
|
4
|
+
* A Commitment Run is always isolated: the daemon cuts it a private git
|
|
5
|
+
* worktree from the directory the Run is rooted in, and refuses the Run with
|
|
6
|
+
* `EXECUTION_ISOLATION_UNAVAILABLE` before any tool when it cannot. Preflight
|
|
7
|
+
* had no way to read that machine's filesystem, so it reported `ready`,
|
|
8
|
+
* activation succeeded, and the first Run failed. The daemon now answers the
|
|
9
|
+
* same question BEFORE a Run, through the same predicate its executor refuses
|
|
10
|
+
* on, and this module is the one place both ends spell it.
|
|
11
|
+
*
|
|
12
|
+
* - The daemon announces {@link WORKSPACE_ISOLATION_PROBE_CAPABILITY}. A daemon
|
|
13
|
+
* that does not is never asked, and the server reports the prerequisite as
|
|
14
|
+
* UNVERIFIED — never as satisfied (unknown is never ok).
|
|
15
|
+
* - The server sends {@link WORKSPACE_ISOLATION_PROBE_METHOD} as a control RPC
|
|
16
|
+
* with `{ workingDirectory? }`: exactly the directory it would put on the
|
|
17
|
+
* Run's request (`agent.workingDirectory || daemon cwd`).
|
|
18
|
+
* - The daemon answers {@link WorkspaceIsolationProbeAnswer}.
|
|
19
|
+
*
|
|
20
|
+
* Pure: no I/O, safe for the browser, the CLI and the API.
|
|
21
|
+
*/
|
|
22
|
+
export declare const WORKSPACE_ISOLATION_PROBE_CAPABILITY: "workspace_isolation_probe_v1";
|
|
23
|
+
export declare const WORKSPACE_ISOLATION_PROBE_METHOD: "daemon:workspace:isolation-probe";
|
|
24
|
+
/**
|
|
25
|
+
* What an explicit isolation request can be given from a base directory.
|
|
26
|
+
*
|
|
27
|
+
* - `worktree` — a git repository with a commit: a worktree is cut;
|
|
28
|
+
* - `sandbox` — the daemon's own bare scratch workspace: an empty
|
|
29
|
+
* private sandbox is used instead;
|
|
30
|
+
* - `not_a_repository` — refused: not inside a git repository;
|
|
31
|
+
* - `no_initial_commit` — refused: a repository with an unborn HEAD.
|
|
32
|
+
*/
|
|
33
|
+
export declare const WORKTREE_BASE_VERDICTS: readonly ["worktree", "sandbox", "not_a_repository", "no_initial_commit"];
|
|
34
|
+
export type WorktreeBaseVerdict = (typeof WORKTREE_BASE_VERDICTS)[number];
|
|
35
|
+
/** The verdicts on which the daemon refuses an isolated Run before any tool. */
|
|
36
|
+
export declare const REFUSING_WORKTREE_BASE_VERDICTS: ReadonlySet<WorktreeBaseVerdict>;
|
|
37
|
+
export declare function isWorktreeBaseVerdict(value: unknown): value is WorktreeBaseVerdict;
|
|
38
|
+
export interface WorkspaceIsolationProbeRequest {
|
|
39
|
+
/** Omitted: the daemon resolves its own default, as it would for the Run. */
|
|
40
|
+
workingDirectory?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface WorkspaceIsolationProbeAnswer {
|
|
43
|
+
/** The directory a Run asked with this `workingDirectory` would be rooted in. */
|
|
44
|
+
baseCwd: string;
|
|
45
|
+
/** Whether a person chose it (agent/task directory or configured default). */
|
|
46
|
+
chosenExplicitly: boolean;
|
|
47
|
+
verdict: WorktreeBaseVerdict;
|
|
48
|
+
/** The refusal sentence the Run would fail with; null when it would be admitted. */
|
|
49
|
+
remedy: string | null;
|
|
50
|
+
checkedAt: string;
|
|
51
|
+
}
|