@xema/omni-protocol 0.1.94 → 0.1.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1217,9 +1217,9 @@ export type DialResult = {
1217
1217
  status: "failed";
1218
1218
  failure: ProtocolFailure;
1219
1219
  };
1220
- export type BreakApproval = "not-requested"
1220
+ export type BreakStatus = "not-requested"
1221
1221
  /** Somebody has to decide. The agent is waiting on a person. */
1222
- | "awaiting-decision"
1222
+ | "awaiting-approval"
1223
1223
  /** Granted, and a promise to honour a later commit. */
1224
1224
  | "granted"
1225
1225
  /** Granted and begins when the current task ends. Nobody needs to act. */
@@ -1249,28 +1249,19 @@ export interface BreakReason {
1249
1249
  alwaysAvailable?: true;
1250
1250
  }
1251
1251
  export interface BreakRequest {
1252
+ /** Optional explanatory text. Omit when no explanation is needed; separate from reasonId. */
1252
1253
  reason?: string;
1253
1254
  /** The chosen `BreakReason.id`, where the provider publishes codes. */
1254
1255
  reasonId?: string;
1255
1256
  }
1256
- /**
1257
- * A break forced on the agent rather than requested by them.
1258
- *
1259
- * `by` is required in both arms. Who put somebody off the floor survives whether or not the
1260
- * break ends on a clock -- a forced break with no origin is a state the agent cannot reason
1261
- * about, and one that ends on a condition is still somebody's decision.
1262
- */
1257
+ /** A break forced on the agent; the agent must explicitly resume when ready. */
1263
1258
  export type ForcedBreak = {
1264
1259
  by: UserId;
1265
- endsAutomatically: true;
1266
- endsAt: IsoTimestamp;
1267
- } | {
1268
- by: UserId;
1269
- endsAutomatically: false;
1270
- endsAt?: never;
1260
+ /** Optional expected time on break, in milliseconds from actual start. Advisory only. */
1261
+ expectedDurationMs?: number;
1271
1262
  };
1272
1263
  export interface BreakState {
1273
- approval: BreakApproval;
1264
+ status: BreakStatus;
1274
1265
  /** Whether the agent may ask at all. Distinct from the fate of a request already made. */
1275
1266
  canRequestBreak: boolean;
1276
1267
  /** Shown when `canRequestBreak` is false, such as "Busy hours". */
@@ -1321,7 +1312,7 @@ export interface TeamMember {
1321
1312
  /** Omitted rather than invented: Omni renders it as a duration. */
1322
1313
  since?: IsoTimestamp;
1323
1314
  /** A request in flight or a grant not yet in effect. `not-requested` is absence, and `on-break` is `availability: "on-break"`. */
1324
- break?: Extract<BreakApproval, "awaiting-decision" | "granted" | "starting-after-task">;
1315
+ break?: Extract<BreakStatus, "awaiting-approval" | "granted" | "starting-after-task">;
1325
1316
  }
1326
1317
  /** A member asking this lead to join their call. */
1327
1318
  export interface LeadRequest {
@@ -1398,13 +1389,16 @@ export type TeamBreakCommand = {
1398
1389
  type: "set-break-policy";
1399
1390
  policy: "approval-required" | "automatically-approved" | "requests-suspended";
1400
1391
  }
1401
- /** `reasonId` names a published `BreakReason.id`, required whenever the provider publishes reasons: the member's forced break carries it as `activeReasonId`. */
1392
+ /** `reason` text is optional. `reasonId` selects a published `BreakReason.id`, required when reasons are published; the forced break carries it as `activeReasonId`. */
1402
1393
  | {
1403
1394
  type: "force-break";
1404
1395
  memberId: UserId;
1405
1396
  reasonId?: string;
1406
1397
  reason?: string;
1407
- } | {
1398
+ expectedDurationMs?: number;
1399
+ }
1400
+ /** Clears forced metadata only; preserves the committed break until the agent explicitly resumes. */
1401
+ | {
1408
1402
  type: "end-forced-break";
1409
1403
  memberId: UserId;
1410
1404
  };
@@ -1693,7 +1687,7 @@ export interface Connection<C extends Channel = Channel> {
1693
1687
  execute(request: TaskCommandRequest<C>): Promise<TaskCommandResult>;
1694
1688
  disconnect(): Promise<void>;
1695
1689
  /** Required of any adapter publishing a `UserId` -- on a forced break, a team member list, or history. */
1696
- describeUsers?(ids: UserId[]): Promise<User[]>;
1690
+ getUserDetails?(ids: UserId[]): Promise<User[]>;
1697
1691
  /** Required when the manifest declares `idleCapabilities.dial`. */
1698
1692
  dial?(request: DialRequest): Promise<DialResult>;
1699
1693
  /**
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Adapter, type AuthenticationState, type ProviderEventEnvelope, type Snapshot, type Task, type TaskCompletion, type BreakApproval, type BrowserSessionKeyInput, type Channel, type ConnectContext, type Host, type HostGuarantees, type HostMute, type LoginStore, type HostReport, type Manifest, type ProviderEvent } from "./index.js";
1
+ import { type Adapter, type AuthenticationState, type ProviderEventEnvelope, type Snapshot, type Task, type TaskCompletion, type BreakStatus, type BrowserSessionKeyInput, type Channel, type ConnectContext, type Host, type HostGuarantees, type HostMute, type LoginStore, type HostReport, type Manifest, type ProviderEvent } from "./index.js";
2
2
  import { type ProtocolViolation } from "./validation.js";
3
3
  export { ProtocolConformanceError, assertNoViolations, type ProtocolViolation } from "./validation.js";
4
4
  /**
@@ -135,11 +135,11 @@ export declare function assertReconnectWithMissedAssignments<C extends Channel>(
135
135
  * pending request nobody is coming to decide is worse than none. So the scenario is a request
136
136
  * that goes back to not-requested, and a later one that is granted.
137
137
  *
138
- * A request shows as `awaiting-decision` where a person decides, and as `granted` at once where
138
+ * A request shows as `awaiting-approval` where a person decides, and as `granted` at once where
139
139
  * the provider decides alone; either is the request being made, and the rule is the same for
140
140
  * both.
141
141
  */
142
- export declare function assertDeniedAndRetriedBreak(approvals: readonly BreakApproval[]): void;
142
+ export declare function assertDeniedAndRetriedBreak(approvals: readonly BreakStatus[]): void;
143
143
  /**
144
144
  * The event kinds a snapshot restates, and so supersedes when delivered while it is read. Every
145
145
  * other kind reports a transaction no snapshot carries, and a host applies it after the snapshot.
@@ -242,7 +242,7 @@ export interface BreakCandidate {
242
242
  export declare function assertBreakAttemptProviders(candidates: readonly BreakCandidate[], asked: readonly string[]): void;
243
243
  /** One published moment of a break asked for on a task: the approval, and how many tasks were outstanding. */
244
244
  export interface BreakOnTaskStep {
245
- approval: BreakApproval;
245
+ approval: BreakStatus;
246
246
  outstanding: number;
247
247
  }
248
248
  /**
package/dist/testing.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { browserSessionKey, effectiveLevels, sameCapabilities, } from "./index.js";
2
- import { assertNoViolations, validateAuthenticationState, validateBreakTransition, validateEventEnvelope, validateHostGuarantees, validateHostReport, validateDescribedUsers, observeRules, ruleEvaluated, validateHostMute, validateHostRecording, validateLoginStore, validateInteractionReport, validateManifest, validateResult, validateSnapshot, validateTask, validateTimeZone, validatePhone, validateTaskCommand, isTimeZone, sameTimeZone, } from "./validation.js";
2
+ import { assertNoViolations, validateAuthenticationState, validateBreakTransition, validateEventEnvelope, validateHostGuarantees, validateHostReport, validateUserDetails, observeRules, ruleEvaluated, validateHostMute, validateHostRecording, validateLoginStore, validateInteractionReport, validateManifest, validateResult, validateSnapshot, validateTask, validateTimeZone, validatePhone, validateTaskCommand, isTimeZone, sameTimeZone, } from "./validation.js";
3
3
  export { ProtocolConformanceError, assertNoViolations } from "./validation.js";
4
4
  /**
5
5
  * A part of the contract a run may never reach: state nothing obliges an adapter to publish, so
@@ -356,7 +356,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
356
356
  violations.push(...undeterminedTasks(eventTasks(envelope), "event"));
357
357
  lockedPartySeen ||= eventTasks(envelope).some(locksParty);
358
358
  if (eventNamesUsers(envelope))
359
- requireMethod(live, "describeUsers", "an event publishes a UserId");
359
+ requireMethod(live, "getUserDetails", "an event publishes a UserId");
360
360
  // Work is pulled, never pushed: an offer before the host stated capacity is an allocation
361
361
  // against nothing, and an offer beyond the count is one too many -- unless the task is the
362
362
  // host's own dial arriving, which counts against nothing.
@@ -458,22 +458,22 @@ export async function exerciseAdapter(adapter, context, options = {}) {
458
458
  violations.push(...undeterminedTasks(tasks, `${path}.tasks`));
459
459
  lockedPartySeen ||= tasks.some(locksParty);
460
460
  if (publishesUserIds(read)) {
461
- requireMethod(source, "describeUsers", "the snapshot publishes a UserId");
461
+ requireMethod(source, "getUserDetails", "the snapshot publishes a UserId");
462
462
  // Required by presence is not enough: the names the snapshot published are looked up, and what
463
463
  // comes back is held to the shape -- an empty answer to a team member list of colleagues is named.
464
464
  const named = userIdsIn(read);
465
- if (typeof source.describeUsers === "function" && named.length > 0) {
465
+ if (typeof source.getUserDetails === "function" && named.length > 0) {
466
466
  let described;
467
467
  try {
468
- described = await source.describeUsers(named);
469
- violations.push(...validateDescribedUsers(described, named, "connection.describeUsers"));
468
+ described = await source.getUserDetails(named);
469
+ violations.push(...validateUserDetails(described, named, "connection.getUserDetails"));
470
470
  if (Array.isArray(described) && described.length === 0) {
471
- violations.push({ rule: "connection.describeUsers.empty", path: "connection.describeUsers",
471
+ violations.push({ rule: "connection.getUserDetails.empty", path: "connection.getUserDetails",
472
472
  message: `asked about ${named.join(", ")}, whom the snapshot itself named, the provider described nobody` });
473
473
  }
474
474
  }
475
475
  catch (error) {
476
- violations.push({ rule: "connection.describeUsers.rejected", path: "connection.describeUsers", message: `describeUsers rejected rather than answered: ${String(error)}` });
476
+ violations.push({ rule: "connection.getUserDetails.rejected", path: "connection.getUserDetails", message: `getUserDetails rejected rather than answered: ${String(error)}` });
477
477
  }
478
478
  }
479
479
  }
@@ -532,7 +532,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
532
532
  violations.push(...malformed);
533
533
  // The provider republishes the agent's day: once connected, the identity carries the zone the
534
534
  // host sent. That proves the round trip, not the store -- an echo passes it -- and the name
535
- // says only what it tests; storage is proved by a colleague's zone arriving from describeUsers().
535
+ // says only what it tests; storage is proved by a colleague's zone arriving from getUserDetails().
536
536
  // A zone is judged by what it denotes: Asia/Kolkata and Asia/Calcutta are one zone, and a
537
537
  // provider that keeps the canonical name has kept the zone.
538
538
  if (isTimeZone(context.timeZone) && !sameTimeZone(current().identity.timeZone, context.timeZone)) {
@@ -938,12 +938,12 @@ export function assertReconnectWithMissedAssignments(before, reconnect, missedTa
938
938
  * pending request nobody is coming to decide is worse than none. So the scenario is a request
939
939
  * that goes back to not-requested, and a later one that is granted.
940
940
  *
941
- * A request shows as `awaiting-decision` where a person decides, and as `granted` at once where
941
+ * A request shows as `awaiting-approval` where a person decides, and as `granted` at once where
942
942
  * the provider decides alone; either is the request being made, and the rule is the same for
943
943
  * both.
944
944
  */
945
945
  export function assertDeniedAndRetriedBreak(approvals) {
946
- const asked = approvals.findIndex(approval => approval === "awaiting-decision" || approval === "granted");
946
+ const asked = approvals.findIndex(approval => approval === "awaiting-approval" || approval === "granted");
947
947
  if (asked < 0)
948
948
  throw new Error("Break retry scenario requires an initial request");
949
949
  const refused = approvals.indexOf("not-requested", asked + 1);
@@ -1865,9 +1865,10 @@ async function driveOneCall(drive) {
1865
1865
  nothingLate();
1866
1866
  return found;
1867
1867
  }
1868
- // A request goes not-requested -> awaiting-decision | granted; a commit goes granted ->
1868
+ // A request goes not-requested -> awaiting-approval | granted; a commit goes granted ->
1869
1869
  // starting-after-task | on-break; work ending goes starting-after-task -> on-break; a denial,
1870
- // a cancel, an agent end or a lead end goes back to not-requested; a forced break arrives on-break
1870
+ // a cancel or an explicit agent end goes back to not-requested; a lead lifting forced status
1871
+ // preserves the committed break. A forced break arrives on-break
1871
1872
  // with `forced`. Nothing else is a move the guide describes.
1872
1873
  /** What a stream has said about the agent's break, and the moves it may not make. */
1873
1874
  export class BreakStream {
@@ -1994,7 +1995,7 @@ export function assertBreakAttemptProviders(candidates, asked) {
1994
1995
  * outstanding -- never beside a task, and never later than the step that has none.
1995
1996
  */
1996
1997
  export function assertBreakBeginsAfterTask(steps) {
1997
- const asked = steps.findIndex(step => step.approval === "awaiting-decision" || step.approval === "granted");
1998
+ const asked = steps.findIndex(step => step.approval === "awaiting-approval" || step.approval === "granted");
1998
1999
  if (asked < 0)
1999
2000
  throw new Error("Break-on-task scenario requires a request");
2000
2001
  if ((steps[asked]?.outstanding ?? 0) < 1)
@@ -150,11 +150,11 @@ export declare function sameTimeZone(a: unknown, b: unknown): boolean;
150
150
  /** The zone a host sends at connect: required, and an IANA name. */
151
151
  export declare function validateTimeZone(value: unknown, path?: string): ProtocolViolation[];
152
152
  /**
153
- * What `describeUsers(ids)` answered: an array of users, each one asked for, each a valid identity.
153
+ * What `getUserDetails(ids)` answered: an array of users, each one asked for, each a valid identity.
154
154
  * The provider may answer fewer than asked, which is what "unknown to it" looks like; it never
155
155
  * answers somebody nobody asked about.
156
156
  */
157
- export declare function validateDescribedUsers(users: unknown, asked: readonly string[], path?: string): ProtocolViolation[];
157
+ export declare function validateUserDetails(users: unknown, asked: readonly string[], path?: string): ProtocolViolation[];
158
158
  /** What a login is validated against beyond its own shape. */
159
159
  export interface LoginValidationContext {
160
160
  /** The level ids in force, from the manifest. The defaults when absent. */
@@ -52,7 +52,7 @@ const AUTHENTICATION_STATUSES = membersOf({
52
52
  "signed-out": true, authenticating: true, authenticated: true, refreshing: true, expired: true,
53
53
  });
54
54
  const BREAK_APPROVALS = membersOf({
55
- "not-requested": true, "awaiting-decision": true, granted: true, "starting-after-task": true, "on-break": true,
55
+ "not-requested": true, "awaiting-approval": true, granted: true, "starting-after-task": true, "on-break": true,
56
56
  });
57
57
  const TEAM_AVAILABILITIES = membersOf({
58
58
  ready: true, "on-task": true, "on-break": true, reserved: true, "signed-out": true,
@@ -80,7 +80,7 @@ const SNAPSHOT_REASONS = membersOf({
80
80
  });
81
81
  const SESSION_CAPABILITIES = membersOf({ breaks: true, team: true, preferences: true });
82
82
  const MEMBER_BREAKS = membersOf({
83
- "awaiting-decision": true, granted: true, "starting-after-task": true,
83
+ "awaiting-approval": true, granted: true, "starting-after-task": true,
84
84
  });
85
85
  const OFFERABLE_PHASES = membersOf({
86
86
  pending: true,
@@ -1298,19 +1298,12 @@ function validateForcedBreak(value, path, into) {
1298
1298
  into.add("break.forced.shape", path, "a forced break must be an object");
1299
1299
  return;
1300
1300
  }
1301
- // `by` is required either way. Who put somebody off the floor survives whether or not the
1302
- // break ends on a clock -- a forced break with no origin is a state the agent cannot
1303
- // reason about.
1304
1301
  into.require(isUserId(value.by), "break.forced.by", `${path}.by`, "a forced break must say who forced it");
1305
- if (value.endsAutomatically === true) {
1306
- into.timestamp(value.endsAt, "break.forced.endsAt", `${path}.endsAt`);
1307
- }
1308
- else if (value.endsAutomatically === false) {
1309
- into.require(value.endsAt === undefined, "break.forced.endsAt.unexpected", `${path}.endsAt`, "a break that does not end automatically must not carry an end time");
1310
- }
1311
- else {
1312
- into.add("break.forced.endsAutomatically", `${path}.endsAutomatically`, "a forced break must say whether it ends automatically");
1302
+ for (const field of ["endsAutomatically", "endsAt"]) {
1303
+ into.require(!Object.hasOwn(value, field), "break.forced.manualResume", `${path}.${field}`, "automatic ending fields are unsupported; the agent must resume manually");
1313
1304
  }
1305
+ if (value.expectedDurationMs !== undefined)
1306
+ into.require(typeof value.expectedDurationMs === "number" && Number.isFinite(value.expectedDurationMs) && value.expectedDurationMs > 0, "break.forced.expectedDurationMs", `${path}.expectedDurationMs`, "expectedDurationMs must be a positive finite number of milliseconds");
1314
1307
  }
1315
1308
  /** Checks break status against the full currently retained task set, after a transaction. */
1316
1309
  export function validateBreakStatus(state, tasks, path = "snapshot") {
@@ -1329,11 +1322,11 @@ export function validateBreakStatus(state, tasks, path = "snapshot") {
1329
1322
  // no task is a break waiting on work that does not exist, and an empty list reading as "still
1330
1323
  // finishing" is the plausible nought.
1331
1324
  ruleEvaluated("break.starting-after-task.tasks");
1332
- if (isPlainObject(state) && state.approval === "starting-after-task"
1325
+ if (isPlainObject(state) && state.status === "starting-after-task"
1333
1326
  && Array.isArray(tasks) && tasks.length === 0) {
1334
- into.add("break.starting-after-task.tasks", `${path}.break.approval`, "a break starting after the task waits on a task, and the snapshot carries none: with nothing outstanding the break is on-break");
1327
+ into.add("break.starting-after-task.tasks", `${path}.break.status`, "a break starting after the task waits on a task, and the snapshot carries none: with nothing outstanding the break is on-break");
1335
1328
  }
1336
- if (isPlainObject(state) && state.approval === "on-break"
1329
+ if (isPlainObject(state) && state.status === "on-break"
1337
1330
  && Array.isArray(tasks) && tasks.length > 0) {
1338
1331
  const listening = tasks.every((task) => isPlainObject(task) && task.listening !== undefined);
1339
1332
  if (!listening) {
@@ -1367,7 +1360,7 @@ export function validateTeamBreakCommand(request, context, path = "teamBreakComm
1367
1360
  const command = request.command;
1368
1361
  const allowed = {
1369
1362
  "decide-break-request": ["type", "memberId", "decision", "reason"], "set-break-policy": ["type", "policy"],
1370
- "force-break": ["type", "memberId", "reasonId", "reason"], "end-forced-break": ["type", "memberId"],
1363
+ "force-break": ["type", "memberId", "reasonId", "reason", "expectedDurationMs"], "end-forced-break": ["type", "memberId"],
1371
1364
  };
1372
1365
  const fields = typeof command.type === "string" && Object.hasOwn(allowed, command.type) ? allowed[command.type] : undefined;
1373
1366
  if (!fields) {
@@ -1392,14 +1385,16 @@ export function validateTeamBreakCommand(request, context, path = "teamBreakComm
1392
1385
  into.require(isPlainObject(member) && command.memberId !== self, "team.break.command.member", path, "the target must be another member of the current authorized team member list");
1393
1386
  if (command.type === "decide-break-request") {
1394
1387
  into.require(command.decision === "granted" || command.decision === "denied", "team.break.command.decision", path, "decide granted or denied");
1395
- into.require(isPlainObject(member) && member.break === "awaiting-decision", "team.break.command.awaiting", path, "decide only a currently awaiting-decision request");
1388
+ into.require(isPlainObject(member) && member.break === "awaiting-approval", "team.break.command.awaiting", path, "decide only a currently awaiting-approval request");
1396
1389
  }
1397
1390
  else {
1398
1391
  validateBreakState(context.memberBreak, `${path}.memberBreak`, into);
1399
1392
  const state = isPlainObject(context.memberBreak) ? context.memberBreak : {};
1400
1393
  if (command.type === "end-forced-break")
1401
- into.require(state.forced !== undefined && (state.approval === "on-break" || state.approval === "starting-after-task"), "team.break.command.endForcedBreak", path, "end a currently forced break");
1394
+ into.require(state.forced !== undefined && (state.status === "on-break" || state.status === "starting-after-task"), "team.break.command.endForcedBreak", path, "lift a current forced-break restriction without resuming the agent");
1402
1395
  if (command.type === "force-break") {
1396
+ if (command.expectedDurationMs !== undefined)
1397
+ into.require(typeof command.expectedDurationMs === "number" && Number.isFinite(command.expectedDurationMs) && command.expectedDurationMs > 0, "team.break.command.expectedDurationMs", `${path}.expectedDurationMs`, "expectedDurationMs must be positive finite milliseconds; it never resumes the agent");
1403
1398
  if (command.reasonId !== undefined)
1404
1399
  into.filled(command.reasonId, "team.break.command.reasonId", path, "reasonId must not be empty");
1405
1400
  const reasons = Array.isArray(state.reasons) ? state.reasons : [];
@@ -1431,9 +1426,9 @@ export function validateBreakCommand(method, request, state, context, path = "br
1431
1426
  }
1432
1427
  if (!isPlainObject(state) || into.violations.length)
1433
1428
  return into.violations;
1434
- const approval = state.approval;
1429
+ const status = state.status;
1435
1430
  if (method === "requestBreak") {
1436
- into.require(approval === "not-requested", "break.command.request.pending", path, "a new request starts only from not-requested");
1431
+ into.require(status === "not-requested", "break.command.request.pending", path, "a new request starts only from not-requested");
1437
1432
  if (!isPlainObject(request)) {
1438
1433
  into.add("break.request.shape", path, "requestBreak takes a request object");
1439
1434
  return into.violations;
@@ -1454,12 +1449,12 @@ export function validateBreakCommand(method, request, state, context, path = "br
1454
1449
  else {
1455
1450
  into.require(request === undefined, "break.command.arguments", path, "this break method takes no arguments");
1456
1451
  if (method === "commitBreak")
1457
- into.require(approval === "granted" || approval === "starting-after-task" || approval === "on-break", "break.command.commit.grant", path, "commit requires a grant; repeated committed state is idempotent");
1452
+ into.require(status === "granted" || status === "starting-after-task" || status === "on-break", "break.command.commit.grant", path, "commit requires a grant; repeated committed state is idempotent");
1458
1453
  if (method === "cancelBreak")
1459
- into.require(approval === "awaiting-decision" || approval === "granted", "break.command.cancel.precommit", path, "cancel only a pre-commit request; a raced commit requires commit recovery");
1454
+ into.require(status === "awaiting-approval" || status === "granted", "break.command.cancel.precommit", path, "cancel only a pre-commit request; a raced commit requires commit recovery");
1460
1455
  if (method === "endBreak") {
1461
- into.require(approval === "starting-after-task" || approval === "on-break", "break.command.end.started", path, "end a committed break, including a returning provider still finishing work");
1462
- into.require(state.forced === undefined, "break.command.end.forced", path, "an agent cannot end a forced break; an authorized lead ends it");
1456
+ into.require(status === "starting-after-task" || status === "on-break", "break.command.end.started", path, "end a committed break, including a returning provider still finishing work");
1457
+ // A forced break also ends only when the agent explicitly resumes.
1463
1458
  }
1464
1459
  }
1465
1460
  return into.violations;
@@ -1477,17 +1472,17 @@ export function validateBreakTransition(before, after, path = "break") {
1477
1472
  validateBreakState(after, `${path}.after`, into);
1478
1473
  if (into.violations.length || !isPlainObject(before) || !isPlainObject(after))
1479
1474
  return into.violations;
1480
- const from = before.approval;
1481
- const to = after.approval;
1475
+ const from = before.status;
1476
+ const to = after.status;
1482
1477
  const committed = to === "starting-after-task" || to === "on-break";
1483
- if (committed && (from === "not-requested" || from === "awaiting-decision") && after.forced === undefined) {
1484
- into.add("stream.breakState.commitBeforeGrant", `${path}.after.approval`, `${to} follows a commit, and a commit follows granted; the break stood at ${from}`);
1478
+ if (committed && (from === "not-requested" || from === "awaiting-approval") && after.forced === undefined) {
1479
+ into.add("stream.breakState.commitBeforeGrant", `${path}.after.status`, `${to} follows a commit, and a commit follows granted; the break stood at ${from}`);
1485
1480
  }
1486
- const backwards = (from === "on-break" && (to === "awaiting-decision" || to === "granted" || to === "starting-after-task")) ||
1487
- (from === "starting-after-task" && (to === "awaiting-decision" || to === "granted")) ||
1488
- (from === "granted" && to === "awaiting-decision");
1481
+ const backwards = (from === "on-break" && (to === "awaiting-approval" || to === "granted" || to === "starting-after-task")) ||
1482
+ (from === "starting-after-task" && (to === "awaiting-approval" || to === "granted")) ||
1483
+ (from === "granted" && to === "awaiting-approval");
1489
1484
  if (backwards)
1490
- into.add("stream.breakState.backwards", `${path}.after.approval`, `a break does not go from ${from} back to ${to}; a new request passes through not-requested`);
1485
+ into.add("stream.breakState.backwards", `${path}.after.status`, `a break does not go from ${from} back to ${to}; a new request passes through not-requested`);
1491
1486
  return into.violations;
1492
1487
  }
1493
1488
  function validateBreakState(value, path, into) {
@@ -1496,7 +1491,8 @@ function validateBreakState(value, path, into) {
1496
1491
  return;
1497
1492
  }
1498
1493
  into.require(!Object.hasOwn(value, "imposed"), "break.forced.renamed", `${path}.imposed`, "imposed was renamed to forced; use only forced");
1499
- into.oneOf(value.approval, BREAK_APPROVALS, "break.approval", `${path}.approval`);
1494
+ into.require(!Object.hasOwn(value, "approval"), "break.status.renamed", `${path}.approval`, "BreakState.approval was renamed to status; use only status");
1495
+ into.oneOf(value.status, BREAK_APPROVALS, "break.status", `${path}.status`);
1500
1496
  into.require(!Object.hasOwn(value, "mayAsk"), "break.canRequestBreak.renamed", `${path}.mayAsk`, "mayAsk was renamed to canRequestBreak; use only canRequestBreak");
1501
1497
  into.require(typeof value.canRequestBreak === "boolean", "break.canRequestBreak", `${path}.canRequestBreak`, "canRequestBreak says whether the agent may ask for a break: a boolean");
1502
1498
  into.require(!Object.hasOwn(value, "refusedReason"), "break.requestUnavailableReason.renamed", `${path}.refusedReason`, "refusedReason was renamed to requestUnavailableReason; use only requestUnavailableReason");
@@ -1517,14 +1513,14 @@ function validateBreakState(value, path, into) {
1517
1513
  into.filled(value.activeReasonId, "break.activeReasonId", `${path}.activeReasonId`, "activeReasonId must not be empty when present");
1518
1514
  // A break nobody is on has no reason. Reporting one beside `not-requested` describes a
1519
1515
  // break that is not happening.
1520
- into.require(value.approval !== "not-requested", "break.activeReasonId.approval", `${path}.activeReasonId`, "activeReasonId must be omitted when no break is requested or in effect");
1516
+ into.require(value.status !== "not-requested", "break.activeReasonId.status", `${path}.activeReasonId`, "activeReasonId must be omitted when no break is requested or in effect");
1521
1517
  }
1522
1518
  if (value.forced !== undefined) {
1523
1519
  validateForcedBreak(value.forced, `${path}.forced`, into);
1524
1520
  // A forced break is a break somebody forced; beside `not-requested` there is no break.
1525
1521
  // A forced break is a break in progress or about to be: it travels with on-break or
1526
1522
  // starting-after-task and nothing else. Beside granted, the host would commit a break nobody asked for.
1527
- into.require(value.approval === "on-break" || value.approval === "starting-after-task", "break.forced.approval", `${path}.forced`, `a forced break is in effect or starting after the task; ${describeValue(value.approval)} says the agent asked, or that there is none`);
1523
+ into.require(value.status === "on-break" || value.status === "starting-after-task", "break.forced.status", `${path}.forced`, `a forced break is in effect or starting after the task; ${describeValue(value.status)} says the agent asked, or that there is none`);
1528
1524
  }
1529
1525
  if (value.reasons === undefined)
1530
1526
  return;
@@ -1563,8 +1559,8 @@ function validateBreakState(value, path, into) {
1563
1559
  // forced one included, since the lead's force-break command named it. A break of no kind is a break whose
1564
1560
  // rules -- who may listen through it, whether it counts -- nobody can apply.
1565
1561
  ruleEvaluated("break.activeReasonId.required");
1566
- if (seen.size > 0 && (value.approval === "on-break" || value.approval === "starting-after-task")) {
1567
- into.require(typeof value.activeReasonId === "string" && value.activeReasonId.length > 0, "break.activeReasonId.required", `${path}.activeReasonId`, `a break ${describeValue(value.approval)} on a provider that publishes reasons names the one it is on`);
1562
+ if (seen.size > 0 && (value.status === "on-break" || value.status === "starting-after-task")) {
1563
+ into.require(typeof value.activeReasonId === "string" && value.activeReasonId.length > 0, "break.activeReasonId.required", `${path}.activeReasonId`, `a break ${describeValue(value.status)} on a provider that publishes reasons names the one it is on`);
1568
1564
  }
1569
1565
  }
1570
1566
  export function validateTeamMembers(teamMembers, path = "team", context = {}) {
@@ -2653,21 +2649,21 @@ export function validateTimeZone(value, path = "context.timeZone") {
2653
2649
  return into.violations;
2654
2650
  }
2655
2651
  /**
2656
- * What `describeUsers(ids)` answered: an array of users, each one asked for, each a valid identity.
2652
+ * What `getUserDetails(ids)` answered: an array of users, each one asked for, each a valid identity.
2657
2653
  * The provider may answer fewer than asked, which is what "unknown to it" looks like; it never
2658
2654
  * answers somebody nobody asked about.
2659
2655
  */
2660
- export function validateDescribedUsers(users, asked, path = "describeUsers") {
2656
+ export function validateUserDetails(users, asked, path = "getUserDetails") {
2661
2657
  const into = new Collector();
2662
2658
  if (!Array.isArray(users)) {
2663
- into.add("describeUsers.shape", path, "describeUsers answers an array of users, empty when it knows none of them");
2659
+ into.add("getUserDetails.shape", path, "getUserDetails answers an array of users, empty when it knows none of them");
2664
2660
  return into.violations;
2665
2661
  }
2666
2662
  users.forEach((user, index) => {
2667
2663
  const at = `${path}[${index}]`;
2668
- validateUser(user, "describeUsers.user", at, into);
2664
+ validateUser(user, "getUserDetails.user", at, into);
2669
2665
  if (isPlainObject(user) && typeof user.id === "string") {
2670
- into.require(asked.includes(user.id), "describeUsers.unasked", `${at}.id`, `${user.id} was not among the ids asked for`);
2666
+ into.require(asked.includes(user.id), "getUserDetails.unasked", `${at}.id`, `${user.id} was not among the ids asked for`);
2671
2667
  }
2672
2668
  });
2673
2669
  return into.violations;