@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.
Files changed (202) hide show
  1. package/README.md +10 -0
  2. package/dist/base-command.d.ts +74 -0
  3. package/dist/base-command.js +182 -24
  4. package/dist/commands/agents/triggers/create.js +1 -1
  5. package/dist/commands/agents/triggers/update.js +1 -1
  6. package/dist/commands/agents/update.d.ts +25 -0
  7. package/dist/commands/agents/update.js +98 -3
  8. package/dist/commands/commitments/accounting.js +7 -2
  9. package/dist/commands/commitments/cost.d.ts +11 -0
  10. package/dist/commands/commitments/cost.js +26 -3
  11. package/dist/commands/commitments/create.js +2 -1
  12. package/dist/commands/commitments/operating-review.d.ts +15 -0
  13. package/dist/commands/commitments/operating-review.js +103 -50
  14. package/dist/commands/commitments/update.js +12 -5
  15. package/dist/commands/daemon/install.js +17 -1
  16. package/dist/commands/daemon/login.js +14 -0
  17. package/dist/commands/doctor.js +9 -0
  18. package/dist/commands/goals/list.js +6 -1
  19. package/dist/commands/grants/rotate.js +4 -0
  20. package/dist/commands/harnesses/prune.d.ts +26 -0
  21. package/dist/commands/harnesses/prune.js +172 -0
  22. package/dist/commands/login.js +53 -14
  23. package/dist/commands/machines/list.js +28 -6
  24. package/dist/commands/projects/show.js +1 -1
  25. package/dist/commands/revoke-daemon.js +2 -1
  26. package/dist/commands/spaces/workflows/archive.d.ts +1 -0
  27. package/dist/commands/spaces/workflows/archive.js +1 -0
  28. package/dist/commands/spaces/workflows/artifact.d.ts +1 -0
  29. package/dist/commands/spaces/workflows/artifact.js +1 -0
  30. package/dist/commands/spaces/workflows/cancel-node.d.ts +1 -0
  31. package/dist/commands/spaces/workflows/cancel-node.js +1 -0
  32. package/dist/commands/spaces/workflows/cancel.d.ts +1 -0
  33. package/dist/commands/spaces/workflows/cancel.js +6 -2
  34. package/dist/commands/spaces/workflows/create.d.ts +1 -0
  35. package/dist/commands/spaces/workflows/create.js +7 -1
  36. package/dist/commands/spaces/workflows/events.d.ts +1 -0
  37. package/dist/commands/spaces/workflows/events.js +1 -0
  38. package/dist/commands/spaces/workflows/fixtures/create.d.ts +1 -0
  39. package/dist/commands/spaces/workflows/fixtures/create.js +1 -0
  40. package/dist/commands/spaces/workflows/fixtures/list.d.ts +1 -0
  41. package/dist/commands/spaces/workflows/fixtures/list.js +1 -0
  42. package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +1 -0
  43. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +1 -0
  44. package/dist/commands/spaces/workflows/list.d.ts +1 -0
  45. package/dist/commands/spaces/workflows/list.js +1 -0
  46. package/dist/commands/spaces/workflows/node-types.d.ts +2 -0
  47. package/dist/commands/spaces/workflows/node-types.js +24 -6
  48. package/dist/commands/spaces/workflows/pause.d.ts +1 -0
  49. package/dist/commands/spaces/workflows/pause.js +1 -0
  50. package/dist/commands/spaces/workflows/plan/show.d.ts +1 -0
  51. package/dist/commands/spaces/workflows/plan/show.js +1 -0
  52. package/dist/commands/spaces/workflows/plan/update.d.ts +1 -0
  53. package/dist/commands/spaces/workflows/plan/update.js +1 -0
  54. package/dist/commands/spaces/workflows/proposals/apply.d.ts +1 -0
  55. package/dist/commands/spaces/workflows/proposals/apply.js +1 -0
  56. package/dist/commands/spaces/workflows/proposals/confirm.d.ts +1 -0
  57. package/dist/commands/spaces/workflows/proposals/confirm.js +1 -0
  58. package/dist/commands/spaces/workflows/proposals/create.d.ts +1 -0
  59. package/dist/commands/spaces/workflows/proposals/create.js +1 -0
  60. package/dist/commands/spaces/workflows/proposals/dismiss.d.ts +1 -0
  61. package/dist/commands/spaces/workflows/proposals/dismiss.js +1 -0
  62. package/dist/commands/spaces/workflows/proposals/list.d.ts +1 -0
  63. package/dist/commands/spaces/workflows/proposals/list.js +1 -0
  64. package/dist/commands/spaces/workflows/publish.d.ts +1 -0
  65. package/dist/commands/spaces/workflows/publish.js +9 -0
  66. package/dist/commands/spaces/workflows/reconcile.d.ts +1 -0
  67. package/dist/commands/spaces/workflows/reconcile.js +1 -0
  68. package/dist/commands/spaces/workflows/resume.d.ts +1 -0
  69. package/dist/commands/spaces/workflows/resume.js +1 -0
  70. package/dist/commands/spaces/workflows/retry.d.ts +1 -0
  71. package/dist/commands/spaces/workflows/retry.js +1 -0
  72. package/dist/commands/spaces/workflows/run.d.ts +1 -0
  73. package/dist/commands/spaces/workflows/run.js +14 -7
  74. package/dist/commands/spaces/workflows/runs.d.ts +1 -0
  75. package/dist/commands/spaces/workflows/runs.js +1 -0
  76. package/dist/commands/spaces/workflows/show.d.ts +25 -0
  77. package/dist/commands/spaces/workflows/show.js +42 -4
  78. package/dist/commands/spaces/workflows/signal.d.ts +12 -0
  79. package/dist/commands/spaces/workflows/signal.js +40 -2
  80. package/dist/commands/spaces/workflows/start.d.ts +1 -0
  81. package/dist/commands/spaces/workflows/start.js +4 -1
  82. package/dist/commands/spaces/workflows/task-execution/create.d.ts +1 -0
  83. package/dist/commands/spaces/workflows/task-execution/create.js +1 -0
  84. package/dist/commands/spaces/workflows/test.d.ts +1 -0
  85. package/dist/commands/spaces/workflows/test.js +1 -0
  86. package/dist/commands/spaces/workflows/triggers/create.d.ts +1 -0
  87. package/dist/commands/spaces/workflows/triggers/create.js +1 -0
  88. package/dist/commands/spaces/workflows/triggers/fire.d.ts +1 -0
  89. package/dist/commands/spaces/workflows/triggers/fire.js +1 -0
  90. package/dist/commands/spaces/workflows/triggers/list.d.ts +1 -0
  91. package/dist/commands/spaces/workflows/triggers/list.js +1 -0
  92. package/dist/commands/spaces/workflows/triggers/update.d.ts +1 -0
  93. package/dist/commands/spaces/workflows/triggers/update.js +1 -0
  94. package/dist/commands/spaces/workflows/update.d.ts +1 -0
  95. package/dist/commands/spaces/workflows/update.js +4 -0
  96. package/dist/commands/spaces/workflows/validate.d.ts +1 -0
  97. package/dist/commands/spaces/workflows/validate.js +27 -2
  98. package/dist/commands/tasks/activity.js +17 -3
  99. package/dist/commands/tasks/bulk-update.d.ts +1 -0
  100. package/dist/commands/tasks/bulk-update.js +7 -0
  101. package/dist/commands/tasks/complete.js +2 -1
  102. package/dist/commands/tasks/create-tree.d.ts +69 -0
  103. package/dist/commands/tasks/create-tree.js +413 -47
  104. package/dist/commands/tasks/list.d.ts +2 -0
  105. package/dist/commands/tasks/list.js +25 -3
  106. package/dist/commands/tasks/rearm-recurrence.d.ts +22 -0
  107. package/dist/commands/tasks/rearm-recurrence.js +87 -0
  108. package/dist/commands/tasks/show.d.ts +9 -0
  109. package/dist/commands/tasks/show.js +79 -33
  110. package/dist/commands/tasks/timeline.js +4 -0
  111. package/dist/commands/tasks/unschedule.js +5 -1
  112. package/dist/commands/tasks/update.d.ts +1 -0
  113. package/dist/commands/tasks/update.js +17 -2
  114. package/dist/commands/triggers/audit.js +12 -3
  115. package/dist/commands/triggers/create.js +9 -3
  116. package/dist/commands/triggers/fire-detail.d.ts +7 -0
  117. package/dist/commands/triggers/fire-detail.js +30 -12
  118. package/dist/commands/triggers/fire.js +13 -3
  119. package/dist/commands/triggers/parse-intent.d.ts +7 -0
  120. package/dist/commands/triggers/parse-intent.js +31 -9
  121. package/dist/commands/triggers/update.js +1 -1
  122. package/dist/commands/workspaces/grants.js +20 -3
  123. package/dist/commands/workspaces/members.d.ts +20 -0
  124. package/dist/commands/workspaces/members.js +64 -0
  125. package/dist/commands/workspaces/stewardship.js +14 -2
  126. package/dist/help.js +12 -0
  127. package/dist/hooks/command-not-found.js +27 -1
  128. package/dist/lib/command-miss.d.ts +52 -2
  129. package/dist/lib/command-miss.js +187 -18
  130. package/dist/lib/commitment-loop-status.d.ts +8 -0
  131. package/dist/lib/commitment-loop-status.js +45 -0
  132. package/dist/lib/commitment-product.js +5 -2
  133. package/dist/lib/commitments.d.ts +18 -3
  134. package/dist/lib/commitments.js +85 -45
  135. package/dist/lib/daemon-command-hint.d.ts +15 -0
  136. package/dist/lib/daemon-command-hint.js +21 -0
  137. package/dist/lib/daemon-machine-identity-copy.d.ts +14 -0
  138. package/dist/lib/daemon-machine-identity-copy.js +27 -0
  139. package/dist/lib/daemon-machine-identity.d.ts +27 -0
  140. package/dist/lib/daemon-machine-identity.js +61 -0
  141. package/dist/lib/daemon-setup.js +10 -1
  142. package/dist/lib/daemonBrokerRefusal.js +3 -2
  143. package/dist/lib/daemonHandoff.d.ts +19 -1
  144. package/dist/lib/daemonHandoff.js +61 -1
  145. package/dist/lib/deployed-build.d.ts +43 -0
  146. package/dist/lib/deployed-build.js +66 -0
  147. package/dist/lib/grants.d.ts +27 -1
  148. package/dist/lib/grants.js +39 -21
  149. package/dist/lib/harnesses.d.ts +71 -0
  150. package/dist/lib/harnesses.js +70 -0
  151. package/dist/lib/json-mode.d.ts +11 -0
  152. package/dist/lib/json-mode.js +19 -0
  153. package/dist/lib/json-output.d.ts +25 -0
  154. package/dist/lib/json-output.js +30 -0
  155. package/dist/lib/list-output.d.ts +10 -1
  156. package/dist/lib/list-output.js +22 -1
  157. package/dist/lib/local-daemon-readiness.d.ts +90 -0
  158. package/dist/lib/local-daemon-readiness.js +143 -0
  159. package/dist/lib/machines.d.ts +117 -0
  160. package/dist/lib/machines.js +139 -0
  161. package/dist/lib/managed-instruction-local.d.ts +2 -0
  162. package/dist/lib/managed-instruction-local.js +2 -0
  163. package/dist/lib/profile-machine-identity-guard.d.ts +18 -0
  164. package/dist/lib/profile-machine-identity-guard.js +39 -0
  165. package/dist/lib/request-field-flags.d.ts +20 -0
  166. package/dist/lib/request-field-flags.js +34 -0
  167. package/dist/lib/stewardship-output.d.ts +1 -1
  168. package/dist/lib/stewardship-output.js +26 -30
  169. package/dist/lib/task-batch.d.ts +61 -4
  170. package/dist/lib/task-batch.js +316 -80
  171. package/dist/lib/task-economics.d.ts +15 -0
  172. package/dist/lib/task-economics.js +39 -2
  173. package/dist/lib/task-recurrence-outcome.d.ts +6 -6
  174. package/dist/lib/task-recurrence-outcome.js +27 -3
  175. package/dist/lib/tasks.d.ts +39 -0
  176. package/dist/lib/tasks.js +132 -0
  177. package/dist/lib/triggers.d.ts +42 -1
  178. package/dist/lib/triggers.js +98 -2
  179. package/dist/lib/workflow-issues.d.ts +14 -0
  180. package/dist/lib/workflow-issues.js +32 -0
  181. package/dist/lib/workflow-run-view.d.ts +59 -0
  182. package/dist/lib/workflow-run-view.js +110 -0
  183. package/dist/lib/workspaces.d.ts +48 -4
  184. package/dist/lib/workspaces.js +55 -0
  185. package/dist/lib/write-target.d.ts +29 -0
  186. package/dist/lib/write-target.js +72 -0
  187. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  188. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +5 -3
  189. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +60 -0
  190. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +130 -6
  191. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.d.ts +51 -0
  192. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.js +50 -0
  193. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  194. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  195. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +60 -0
  196. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +128 -6
  197. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.d.ts +51 -0
  198. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.js +46 -0
  199. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  200. package/dist/node_modules/@skrr-ai/data-provider/index.js +7523 -7140
  201. package/oclif.manifest.json +473 -59
  202. package/package.json +28 -1
@@ -12,11 +12,8 @@ export interface StewardshipCoverage {
12
12
  truncated: boolean;
13
13
  sources: StewardshipSource[];
14
14
  }
15
- export type WorkspaceGrantMetadata = Omit<GrantPublic, 'credential' | 'createdAt' | 'updatedAt'> & {
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 {};
@@ -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.qualifyMachineUuid = exports.getMachineUuidProfile = exports.setMachineUuidProfile = exports.getHardwareMachineUuid = 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.LOOPBACK_DEFAULT_MAX_BODY_BYTES = exports.LoopbackTargetRefusedError = 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 = 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 = 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 preExisting = PRE_EXISTING_PROFILE_EVIDENCE.some((name) => {
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 profile = activeMachineProfile;
317
- if (!profile || profile === DEFAULT_PROFILE_NAME)
346
+ const name = typeof profile === 'string' ? profile.trim() : '';
347
+ if (!name || name === DEFAULT_PROFILE_NAME)
318
348
  return hardware;
319
- return resolveProfileMachineUuid(profile, hardware);
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
+ }