@unisonlabs/sixminute-admin 0.1.1246 → 0.1.1248-next
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 +0 -1
- package/dist/sixminute-admin.mjs +1438 -891
- package/package.json +1 -1
package/dist/sixminute-admin.mjs
CHANGED
|
@@ -21,11 +21,7 @@ import { homedir } from "node:os";
|
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
22
|
|
|
23
23
|
// tools/admin/src/build-info.ts
|
|
24
|
-
var BUILT = { version: "0.1.
|
|
25
|
-
|
|
26
|
-
// tools/admin/src/run.ts
|
|
27
|
-
import { rmSync as rmSync3 } from "node:fs";
|
|
28
|
-
import { join as join2, resolve } from "node:path";
|
|
24
|
+
var BUILT = { version: "0.1.1248-next", commit: "c6178f55ce53796fb58c8dbcdd518a2296011cf3", surface: "e4b83a8db444655b" };
|
|
29
25
|
|
|
30
26
|
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
31
27
|
var exports_external = {};
|
|
@@ -4221,14 +4217,7 @@ var MatterPartySchema = exports_external.object({
|
|
|
4221
4217
|
reference: RawWord,
|
|
4222
4218
|
external_refs: exports_external.array(exports_external.string().min(1)).optional()
|
|
4223
4219
|
});
|
|
4224
|
-
var SyncSourceSchema = exports_external.enum([
|
|
4225
|
-
"manual",
|
|
4226
|
-
"clio",
|
|
4227
|
-
"legalsense",
|
|
4228
|
-
"basenet",
|
|
4229
|
-
"urios",
|
|
4230
|
-
"import"
|
|
4231
|
-
]);
|
|
4220
|
+
var SyncSourceSchema = exports_external.enum(["manual", "legalsense", "urios", "import"]);
|
|
4232
4221
|
var ClientSchema = exports_external.object({
|
|
4233
4222
|
id: ClientIdSchema,
|
|
4234
4223
|
name: exports_external.string().min(1),
|
|
@@ -4343,64 +4332,6 @@ function readMatterRecord(raw) {
|
|
|
4343
4332
|
};
|
|
4344
4333
|
}
|
|
4345
4334
|
|
|
4346
|
-
// packages/contracts/src/calendar.ts
|
|
4347
|
-
var CalendarProviderSchema = exports_external.enum(["microsoft", "google"]);
|
|
4348
|
-
var CalendarAccountKindSchema = exports_external.enum(["work", "personal"]);
|
|
4349
|
-
var CalendarConnectionStatusSchema = exports_external.enum(["connected", "awaiting_admin", "needs_reconnect"]);
|
|
4350
|
-
var CalendarConnectionSchema = exports_external.object({
|
|
4351
|
-
provider: CalendarProviderSchema,
|
|
4352
|
-
account_kind: CalendarAccountKindSchema,
|
|
4353
|
-
account_email: exports_external.string(),
|
|
4354
|
-
status: CalendarConnectionStatusSchema,
|
|
4355
|
-
admin_consent_url: exports_external.string().nullable(),
|
|
4356
|
-
last_synced_at: TimestampSchema.nullable()
|
|
4357
|
-
});
|
|
4358
|
-
var CalendarConnectRequestSchema = exports_external.object({
|
|
4359
|
-
provider: CalendarProviderSchema,
|
|
4360
|
-
state: exports_external.string().min(16),
|
|
4361
|
-
code_challenge: exports_external.string().min(16)
|
|
4362
|
-
});
|
|
4363
|
-
var CalendarConnectAnswerSchema = exports_external.object({ authorize_url: exports_external.string().url() });
|
|
4364
|
-
var CalendarConnectFinishSchema = exports_external.union([
|
|
4365
|
-
exports_external.object({ state: exports_external.string().min(16), code: exports_external.string().min(1), code_verifier: exports_external.string().min(43).max(128) }).strict(),
|
|
4366
|
-
exports_external.object({
|
|
4367
|
-
state: exports_external.string().min(16),
|
|
4368
|
-
error: exports_external.string().min(1).max(200),
|
|
4369
|
-
error_description: exports_external.string().max(2000).optional(),
|
|
4370
|
-
error_subcode: exports_external.string().max(200).optional()
|
|
4371
|
-
}).strict()
|
|
4372
|
-
]);
|
|
4373
|
-
var CalendarResponseSchema = exports_external.enum(["organiser", "accepted", "tentative", "declined", "none"]);
|
|
4374
|
-
var CalendarGuestSchema = exports_external.object({
|
|
4375
|
-
name: exports_external.string(),
|
|
4376
|
-
email: exports_external.string().nullable(),
|
|
4377
|
-
response: CalendarResponseSchema,
|
|
4378
|
-
organiser: exports_external.boolean(),
|
|
4379
|
-
me: exports_external.boolean()
|
|
4380
|
-
});
|
|
4381
|
-
var CalendarWhereSchema = exports_external.object({
|
|
4382
|
-
kind: exports_external.enum(["call", "address", "none"]),
|
|
4383
|
-
text: exports_external.string().nullable()
|
|
4384
|
-
});
|
|
4385
|
-
var CalendarEventSchema = exports_external.object({
|
|
4386
|
-
id: exports_external.string().min(1),
|
|
4387
|
-
title: exports_external.string(),
|
|
4388
|
-
from: TimestampSchema,
|
|
4389
|
-
to: TimestampSchema,
|
|
4390
|
-
all_day: exports_external.boolean(),
|
|
4391
|
-
status: exports_external.enum(["confirmed", "cancelled"]),
|
|
4392
|
-
my_response: CalendarResponseSchema,
|
|
4393
|
-
where: CalendarWhereSchema,
|
|
4394
|
-
guests: exports_external.array(CalendarGuestSchema),
|
|
4395
|
-
provider: CalendarProviderSchema,
|
|
4396
|
-
web_link: exports_external.string().nullable(),
|
|
4397
|
-
not_attended: exports_external.boolean(),
|
|
4398
|
-
can_mark: exports_external.boolean()
|
|
4399
|
-
});
|
|
4400
|
-
var CalendarDaySchema = exports_external.object({
|
|
4401
|
-
events: exports_external.array(CalendarEventSchema)
|
|
4402
|
-
});
|
|
4403
|
-
|
|
4404
4335
|
// packages/contracts/src/compat.ts
|
|
4405
4336
|
var ActivityTypeKeySchema = exports_external.string().regex(/^[A-Za-z0-9_-]{1,40}$/, "an activity type key is 1 to 40 letters, digits, underscores or hyphens, such as A103 or draft-revise");
|
|
4406
4337
|
var ActivityTypeSourceSchema = exports_external.enum(["published", "firm"]);
|
|
@@ -4603,8 +4534,8 @@ function partOnDay(part, day, zone) {
|
|
|
4603
4534
|
}
|
|
4604
4535
|
|
|
4605
4536
|
// packages/contracts/src/connector.ts
|
|
4606
|
-
var ConnectorKindSchema = exports_external.enum(["urios", "legalsense", "
|
|
4607
|
-
var InitiativeSchema = exports_external.enum(["push"
|
|
4537
|
+
var ConnectorKindSchema = exports_external.enum(["urios", "legalsense", "fake"]);
|
|
4538
|
+
var InitiativeSchema = exports_external.enum(["push"]);
|
|
4608
4539
|
var IdentityModeSchema = exports_external.enum(["mapping", "sign_in"]);
|
|
4609
4540
|
var FieldDeclarationSchema = exports_external.object({
|
|
4610
4541
|
key: AttributeKeySchema.refine((key) => !key.includes("__"), {
|
|
@@ -4621,7 +4552,7 @@ var CapabilitiesSchema = exports_external.object({
|
|
|
4621
4552
|
change_feed: exports_external.enum(["modified_since", "full_list"]),
|
|
4622
4553
|
narrative_max_chars: exports_external.number().int().positive().nullable(),
|
|
4623
4554
|
note_max_chars: exports_external.number().int().positive().nullable().default(null),
|
|
4624
|
-
insolvency_groups: exports_external.
|
|
4555
|
+
insolvency_groups: exports_external.literal(false),
|
|
4625
4556
|
matter_visibility: exports_external.enum(["firm", "member"]).default("firm"),
|
|
4626
4557
|
reviewer: exports_external.enum(["self", "configurable"]).default("configurable"),
|
|
4627
4558
|
fields: exports_external.array(FieldDeclarationSchema).superRefine((fields, ctx) => {
|
|
@@ -4701,7 +4632,6 @@ var PmsMatterSchema = exports_external.object({
|
|
|
4701
4632
|
main_category: exports_external.string().nullable().optional(),
|
|
4702
4633
|
phase: exports_external.string().nullable().optional(),
|
|
4703
4634
|
folders: exports_external.array(exports_external.string()).optional(),
|
|
4704
|
-
insolvency: exports_external.boolean().default(false),
|
|
4705
4635
|
time_entry_blocked: exports_external.boolean().default(false)
|
|
4706
4636
|
});
|
|
4707
4637
|
var PmsTimekeeperSchema = exports_external.object({
|
|
@@ -4757,7 +4687,6 @@ var PmsListsSchema = exports_external.object({
|
|
|
4757
4687
|
}).optional()
|
|
4758
4688
|
});
|
|
4759
4689
|
var MatterPmsRulesSchema = exports_external.object({
|
|
4760
|
-
insolvency: exports_external.boolean().default(false),
|
|
4761
4690
|
time_entry_blocked: exports_external.boolean().default(false)
|
|
4762
4691
|
});
|
|
4763
4692
|
var OutboundEntrySchema = exports_external.object({
|
|
@@ -4771,8 +4700,7 @@ var OutboundEntrySchema = exports_external.object({
|
|
|
4771
4700
|
minutes: exports_external.number().int().nonnegative(),
|
|
4772
4701
|
narrative: NarrativeSchema,
|
|
4773
4702
|
internal_note: exports_external.string().nullable().default(null),
|
|
4774
|
-
fields: exports_external.record(exports_external.string(), exports_external.object({ value: exports_external.string().nullable(), external_ref: exports_external.string().nullable() }))
|
|
4775
|
-
recofa_category: exports_external.string().nullable().default(null)
|
|
4703
|
+
fields: exports_external.record(exports_external.string(), exports_external.object({ value: exports_external.string().nullable(), external_ref: exports_external.string().nullable() }))
|
|
4776
4704
|
});
|
|
4777
4705
|
var PmsEntryStateSchema = exports_external.object({
|
|
4778
4706
|
external_id: exports_external.string().min(1),
|
|
@@ -4938,6 +4866,907 @@ var SyncProgressSchema = exports_external.object({
|
|
|
4938
4866
|
failed: exports_external.boolean()
|
|
4939
4867
|
});
|
|
4940
4868
|
|
|
4869
|
+
// packages/contracts/src/analytics/safe.ts
|
|
4870
|
+
var mark = (schema, kind) => schema.describe(`safe:${kind}`);
|
|
4871
|
+
var TELEMETRY_SECONDS_MAX = 86400;
|
|
4872
|
+
var TELEMETRY_DAY_OFFSET_MAX = 3660;
|
|
4873
|
+
var TELEMETRY_ENTRY_ID_MAX = 64;
|
|
4874
|
+
var count = () => mark(exports_external.number().int().nonnegative(), "count");
|
|
4875
|
+
var daySeconds = () => mark(exports_external.number().int().nonnegative().max(TELEMETRY_SECONDS_MAX), "day-seconds");
|
|
4876
|
+
var duration = () => mark(exports_external.number().int().nonnegative(), "duration");
|
|
4877
|
+
var signed = (min, max) => mark(exports_external.number().int().min(min).max(max), "signed");
|
|
4878
|
+
var dayOffset = () => signed(-TELEMETRY_DAY_OFFSET_MAX, TELEMETRY_DAY_OFFSET_MAX);
|
|
4879
|
+
var minutesDelta = () => mark(exports_external.number().int().min(-1440).max(1440).nullable(), "minutes-delta");
|
|
4880
|
+
var usd = () => mark(exports_external.number().nonnegative().finite(), "usd");
|
|
4881
|
+
var bool = () => exports_external.boolean();
|
|
4882
|
+
var oneOf = (values) => exports_external.enum(values);
|
|
4883
|
+
var day = () => mark(LocalDaySchema, "day");
|
|
4884
|
+
var version = () => mark(exports_external.string().regex(/^\d{1,9}\.\d{1,9}\.\d{1,9}$/), "version");
|
|
4885
|
+
var pageBuild = () => mark(exports_external.string().regex(/^\d{14}-[0-9A-Za-z]{1,40}$/, "expected YYYYMMDDHHMMSS-<commit>"), "page-build");
|
|
4886
|
+
var modelId = () => mark(exports_external.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,79}$/), "model-id");
|
|
4887
|
+
var word = () => mark(exports_external.string().regex(/^[A-Za-z0-9_.-]{1,48}$/), "word");
|
|
4888
|
+
var uuid = () => mark(exports_external.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i), "uuid");
|
|
4889
|
+
var sessionId = () => mark(exports_external.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, "expected a UUIDv7"), "session-id");
|
|
4890
|
+
var ids = {
|
|
4891
|
+
entry: () => mark(EntryIdSchema.max(TELEMETRY_ENTRY_ID_MAX), "id"),
|
|
4892
|
+
run: () => mark(RunIdSchema, "id"),
|
|
4893
|
+
firm: () => mark(TenantIdSchema, "id"),
|
|
4894
|
+
member: () => mark(MemberIdSchema, "id"),
|
|
4895
|
+
device: () => mark(DeviceIdSchema, "id")
|
|
4896
|
+
};
|
|
4897
|
+
|
|
4898
|
+
// packages/contracts/src/analytics/catalogue.ts
|
|
4899
|
+
var RawArchiveReasonSchema = oneOf(["person", "expired", "signed_out", "tenant_changed", "not_allowed", "refused", "disk_low"]);
|
|
4900
|
+
var TelemetryScreenSchema = oneOf(["day", "timeline", "entry", "settings", "sign_in", "onboarding", "permissions"]);
|
|
4901
|
+
var TelemetryCommandSchema = oneOf([
|
|
4902
|
+
"new-entry",
|
|
4903
|
+
"release-all",
|
|
4904
|
+
"settings",
|
|
4905
|
+
"exit",
|
|
4906
|
+
"undo",
|
|
4907
|
+
"redo",
|
|
4908
|
+
"cut",
|
|
4909
|
+
"copy",
|
|
4910
|
+
"paste",
|
|
4911
|
+
"select-all",
|
|
4912
|
+
"today",
|
|
4913
|
+
"previous-day",
|
|
4914
|
+
"next-day",
|
|
4915
|
+
"previous-entry",
|
|
4916
|
+
"next-entry",
|
|
4917
|
+
"actions",
|
|
4918
|
+
"capture",
|
|
4919
|
+
"privacy",
|
|
4920
|
+
"about",
|
|
4921
|
+
"folder",
|
|
4922
|
+
"release",
|
|
4923
|
+
"matter",
|
|
4924
|
+
"attribute",
|
|
4925
|
+
"merge",
|
|
4926
|
+
"delete",
|
|
4927
|
+
"open"
|
|
4928
|
+
]);
|
|
4929
|
+
var GenerateFailureSchema = oneOf(["model", "deadline", "overlap", "other"]);
|
|
4930
|
+
var AppErrorKindSchema = oneOf(["boundary", "uncaught", "unhandled_rejection"]);
|
|
4931
|
+
var AppErrorClassSchema = oneOf(["Error", "TypeError", "RangeError", "ReferenceError", "SyntaxError", "ChunkLoadError", "AbortError", "other"]);
|
|
4932
|
+
var PageBuildSchema = pageBuild();
|
|
4933
|
+
var RELEASE_BLOCKERS = [
|
|
4934
|
+
"narrative_empty",
|
|
4935
|
+
"narrative_too_short",
|
|
4936
|
+
"matter",
|
|
4937
|
+
"internal_note_too_long",
|
|
4938
|
+
"connector_not_connected",
|
|
4939
|
+
"connector_sign_in",
|
|
4940
|
+
"connector_matter",
|
|
4941
|
+
"connector_timekeeper",
|
|
4942
|
+
"connector_narrative",
|
|
4943
|
+
"connector_matter_blocked",
|
|
4944
|
+
"connector_matter_not_found",
|
|
4945
|
+
"connector_matter_closed",
|
|
4946
|
+
"connector_person_not_found",
|
|
4947
|
+
"connector_person_inactive",
|
|
4948
|
+
"connector_not_accepted",
|
|
4949
|
+
"connector_reviewer",
|
|
4950
|
+
"attribute",
|
|
4951
|
+
"other"
|
|
4952
|
+
];
|
|
4953
|
+
var SyncFailureSchema = oneOf(["tls", "login", "schema", "rights", "connect", "timeout", "nobody_signed_in", "dispatch", "error"]);
|
|
4954
|
+
var SourceHealthSchema = oneOf(["healthy", "failing", "needs_reconnect"]);
|
|
4955
|
+
var JobKindSchema = oneOf([
|
|
4956
|
+
"decide",
|
|
4957
|
+
"generate",
|
|
4958
|
+
"propose",
|
|
4959
|
+
"calendar_judge",
|
|
4960
|
+
"calendar_sync",
|
|
4961
|
+
"practice_sync",
|
|
4962
|
+
"day_accounts",
|
|
4963
|
+
"scrub_plaintext",
|
|
4964
|
+
"purge_expired",
|
|
4965
|
+
"research_purge",
|
|
4966
|
+
"sweep"
|
|
4967
|
+
]);
|
|
4968
|
+
var SettingSchema = oneOf([
|
|
4969
|
+
"unit_minutes",
|
|
4970
|
+
"narrative_language",
|
|
4971
|
+
"narrative_max_chars",
|
|
4972
|
+
"narrative_guidance",
|
|
4973
|
+
"always_on",
|
|
4974
|
+
"exclusions",
|
|
4975
|
+
"name",
|
|
4976
|
+
"avatar",
|
|
4977
|
+
"entry_size",
|
|
4978
|
+
"time_zone",
|
|
4979
|
+
"launch_at_login",
|
|
4980
|
+
"theme"
|
|
4981
|
+
]);
|
|
4982
|
+
var DeviceSettingSchema = oneOf(["unit_minutes", "narrative_language", "narrative_max_chars", "always_on", "launch_at_login", "theme", "exclusions", "entry_size"]);
|
|
4983
|
+
var Role = oneOf(["timekeeper", "billing", "admin"]);
|
|
4984
|
+
var Origin = oneOf(["engine", "hand"]);
|
|
4985
|
+
var DEVICE_COUNTER_NAMES = [
|
|
4986
|
+
"samples_received",
|
|
4987
|
+
"observations_written",
|
|
4988
|
+
"snapshots_written",
|
|
4989
|
+
"samples_idle",
|
|
4990
|
+
"samples_excluded",
|
|
4991
|
+
"samples_empty",
|
|
4992
|
+
"samples_out_of_order",
|
|
4993
|
+
"samples_late",
|
|
4994
|
+
"marks_late",
|
|
4995
|
+
"samples_dropped",
|
|
4996
|
+
"browser_breaker_trips",
|
|
4997
|
+
"clock_steps",
|
|
4998
|
+
"capture_seconds",
|
|
4999
|
+
"active_seconds",
|
|
5000
|
+
"idle_seconds",
|
|
5001
|
+
"locked_seconds",
|
|
5002
|
+
"excluded_seconds"
|
|
5003
|
+
];
|
|
5004
|
+
function counterShape() {
|
|
5005
|
+
const shape = {};
|
|
5006
|
+
for (const name of DEVICE_COUNTER_NAMES)
|
|
5007
|
+
shape[name] = name.endsWith("_seconds") ? daySeconds() : count();
|
|
5008
|
+
return shape;
|
|
5009
|
+
}
|
|
5010
|
+
var DELIVERY = {
|
|
5011
|
+
via: oneOf(["release", "read_back"]),
|
|
5012
|
+
entry_id: ids.entry(),
|
|
5013
|
+
kind: ConnectorKindSchema,
|
|
5014
|
+
reason: DeliveryReasonSchema.nullable(),
|
|
5015
|
+
pms_status: word().nullable(),
|
|
5016
|
+
minutes: count(),
|
|
5017
|
+
seconds_since_released: duration(),
|
|
5018
|
+
changed_minutes: bool(),
|
|
5019
|
+
changed_day: bool(),
|
|
5020
|
+
changed_matter: bool(),
|
|
5021
|
+
changed_narrative: bool(),
|
|
5022
|
+
minutes_delta: minutesDelta(),
|
|
5023
|
+
direct: exports_external.literal(true)
|
|
5024
|
+
};
|
|
5025
|
+
var GENERATE = {
|
|
5026
|
+
trigger: oneOf(["press", "backstop"]),
|
|
5027
|
+
run_id: ids.run(),
|
|
5028
|
+
day_offset: dayOffset()
|
|
5029
|
+
};
|
|
5030
|
+
function spec(s) {
|
|
5031
|
+
return { ...s, properties: exports_external.object(s.properties).strict(), wire: s.wire };
|
|
5032
|
+
}
|
|
5033
|
+
var CATALOGUE = {
|
|
5034
|
+
userSignedIn: spec({
|
|
5035
|
+
name: "user signed in",
|
|
5036
|
+
owner: "cloud",
|
|
5037
|
+
expect: "daily",
|
|
5038
|
+
trigger: "POST /v1/me/provision answered a person in a firm",
|
|
5039
|
+
question: "Who uses Sixminute, and how did they start?",
|
|
5040
|
+
properties: { action: oneOf(["ready", "refresh"]), role: Role, first_sign_in: bool(), via: oneOf(["token", "invitation"]) }
|
|
5041
|
+
}),
|
|
5042
|
+
signInFailed: spec({
|
|
5043
|
+
name: "sign in failed",
|
|
5044
|
+
owner: "cloud",
|
|
5045
|
+
expect: "rare",
|
|
5046
|
+
trigger: "a sign-in ended in a refusal a person can act on",
|
|
5047
|
+
question: "Where does signing in break?",
|
|
5048
|
+
properties: { reason: oneOf(["no_firm", "choose_firm", "code_invalid"]) }
|
|
5049
|
+
}),
|
|
5050
|
+
tenantProvisioned: spec({
|
|
5051
|
+
name: "tenant provisioned",
|
|
5052
|
+
owner: "cloud",
|
|
5053
|
+
expect: "rare",
|
|
5054
|
+
trigger: "the first sign-in of a firm created its tenant row",
|
|
5055
|
+
question: "How many firms start?",
|
|
5056
|
+
properties: { via: oneOf(["token", "invitation"]) }
|
|
5057
|
+
}),
|
|
5058
|
+
memberInvited: spec({
|
|
5059
|
+
name: "member invited",
|
|
5060
|
+
owner: "cloud",
|
|
5061
|
+
expect: "rare",
|
|
5062
|
+
trigger: "an admin invited a colleague",
|
|
5063
|
+
question: "Do firms bring their colleagues?",
|
|
5064
|
+
properties: { role: Role }
|
|
5065
|
+
}),
|
|
5066
|
+
memberJoined: spec({
|
|
5067
|
+
name: "member joined",
|
|
5068
|
+
owner: "cloud",
|
|
5069
|
+
expect: "rare",
|
|
5070
|
+
trigger: "a membership row was created",
|
|
5071
|
+
question: "How fast does a firm fill?",
|
|
5072
|
+
properties: { role: Role, via: oneOf(["token", "invitation"]) }
|
|
5073
|
+
}),
|
|
5074
|
+
memberChanged: spec({
|
|
5075
|
+
name: "member changed",
|
|
5076
|
+
owner: "cloud",
|
|
5077
|
+
expect: "rare",
|
|
5078
|
+
trigger: "an admin moved a role or the active flag, or withdrew an invitation",
|
|
5079
|
+
question: "How do firms manage their people?",
|
|
5080
|
+
properties: { change: oneOf(["role", "reactivated", "deactivated", "invitation_revoked"]) }
|
|
5081
|
+
}),
|
|
5082
|
+
deviceRevoked: spec({
|
|
5083
|
+
name: "device revoked",
|
|
5084
|
+
owner: "cloud",
|
|
5085
|
+
expect: "rare",
|
|
5086
|
+
trigger: "a person revoked a device that was not revoked",
|
|
5087
|
+
question: "How often are desktops replaced?",
|
|
5088
|
+
properties: {}
|
|
5089
|
+
}),
|
|
5090
|
+
applicationUpdated: spec({
|
|
5091
|
+
name: "Application updated",
|
|
5092
|
+
owner: "cloud",
|
|
5093
|
+
expect: "weekly",
|
|
5094
|
+
trigger: "a device called with another version than the one it last called with",
|
|
5095
|
+
question: "How fast does a release reach every desktop?",
|
|
5096
|
+
properties: { from_version: version(), to_version: version() }
|
|
5097
|
+
}),
|
|
5098
|
+
clientOutdated: spec({
|
|
5099
|
+
name: "client outdated",
|
|
5100
|
+
owner: "cloud",
|
|
5101
|
+
expect: "rare",
|
|
5102
|
+
trigger: "the floor refused a desktop's registration",
|
|
5103
|
+
question: "Which old desktops are still in the field?",
|
|
5104
|
+
properties: { version: version() }
|
|
5105
|
+
}),
|
|
5106
|
+
installerDownloaded: spec({
|
|
5107
|
+
name: "installer downloaded",
|
|
5108
|
+
owner: "cloud",
|
|
5109
|
+
expect: "weekly",
|
|
5110
|
+
trigger: "the download route served an installer a person pressed for",
|
|
5111
|
+
question: "How many installs start from the site?",
|
|
5112
|
+
properties: { os: oneOf(["macos", "windows"]), arch: oneOf(["arm64", "x64", "aarch64", "x86_64", "universal"]), version: version(), via: oneOf(["latest", "direct"]) }
|
|
5113
|
+
}),
|
|
5114
|
+
settingChanged: spec({
|
|
5115
|
+
name: "setting changed",
|
|
5116
|
+
owner: "cloud",
|
|
5117
|
+
expect: "weekly",
|
|
5118
|
+
trigger: "a firm's or a person's setting row changed, or a desktop relayed its own",
|
|
5119
|
+
question: "Which settings matter to people?",
|
|
5120
|
+
properties: { setting: SettingSchema, scope: oneOf(["firm", "profile", "device"]) },
|
|
5121
|
+
wire: { name: "setting.changed", properties: exports_external.object({ setting: DeviceSettingSchema, scope: oneOf(["device"]) }) }
|
|
5122
|
+
}),
|
|
5123
|
+
entryDrafted: spec({
|
|
5124
|
+
name: "entry drafted",
|
|
5125
|
+
owner: "cloud",
|
|
5126
|
+
expect: "daily",
|
|
5127
|
+
trigger: "an entry was inserted: a person's by hand, or the engine's by a Generate",
|
|
5128
|
+
question: "How much is drafted, and of what shape?",
|
|
5129
|
+
properties: {
|
|
5130
|
+
entry_id: ids.entry(),
|
|
5131
|
+
origin: Origin,
|
|
5132
|
+
minutes: count(),
|
|
5133
|
+
has_matter: bool(),
|
|
5134
|
+
attribute_count: count(),
|
|
5135
|
+
narrative_chars: count(),
|
|
5136
|
+
day_offset: dayOffset(),
|
|
5137
|
+
run_id: ids.run()
|
|
5138
|
+
}
|
|
5139
|
+
}),
|
|
5140
|
+
entryEdited: spec({
|
|
5141
|
+
name: "entry edited",
|
|
5142
|
+
owner: "cloud",
|
|
5143
|
+
expect: "daily",
|
|
5144
|
+
trigger: "a person's edit changed a draft",
|
|
5145
|
+
question: "How much work is reviewing?",
|
|
5146
|
+
properties: {
|
|
5147
|
+
entry_id: ids.entry(),
|
|
5148
|
+
origin: Origin,
|
|
5149
|
+
status: oneOf(["draft", "released"]),
|
|
5150
|
+
is_first_edit: bool(),
|
|
5151
|
+
changed_narrative: bool(),
|
|
5152
|
+
changed_matter: bool(),
|
|
5153
|
+
changed_time: bool(),
|
|
5154
|
+
changed_attributes: bool(),
|
|
5155
|
+
changed_note: bool(),
|
|
5156
|
+
minutes_delta: signed(-1440, 1440),
|
|
5157
|
+
matter_change: oneOf(["set", "changed", "cleared"]).nullable(),
|
|
5158
|
+
attributes_changed_count: count(),
|
|
5159
|
+
seconds_since_drafted: duration()
|
|
5160
|
+
}
|
|
5161
|
+
}),
|
|
5162
|
+
entryReleased: spec({
|
|
5163
|
+
name: "entry released",
|
|
5164
|
+
owner: "cloud",
|
|
5165
|
+
expect: "daily",
|
|
5166
|
+
trigger: "a person released a draft",
|
|
5167
|
+
question: "How good are the drafts people release?",
|
|
5168
|
+
properties: {
|
|
5169
|
+
entry_id: ids.entry(),
|
|
5170
|
+
origin: Origin,
|
|
5171
|
+
is_unchanged: bool(),
|
|
5172
|
+
changed_matter: bool(),
|
|
5173
|
+
changed_narrative: bool(),
|
|
5174
|
+
changed_time: bool(),
|
|
5175
|
+
changed_attributes: bool(),
|
|
5176
|
+
changed_note: bool(),
|
|
5177
|
+
matter_outcome: oneOf(["kept", "filled", "replaced", "cleared"]),
|
|
5178
|
+
narrative_change: oneOf(["none", "light", "heavy", "rewritten"]),
|
|
5179
|
+
minutes: count(),
|
|
5180
|
+
recorded_minutes: count(),
|
|
5181
|
+
override: bool(),
|
|
5182
|
+
judged_minutes: count().nullable(),
|
|
5183
|
+
minutes_delta: minutesDelta(),
|
|
5184
|
+
attributes_changed_count: count(),
|
|
5185
|
+
lineage: oneOf(["original", "split", "merged"]),
|
|
5186
|
+
seconds_since_drafted: duration(),
|
|
5187
|
+
days_after_work_day: dayOffset(),
|
|
5188
|
+
is_delivered: bool(),
|
|
5189
|
+
connector_kind: ConnectorKindSchema.nullable()
|
|
5190
|
+
}
|
|
5191
|
+
}),
|
|
5192
|
+
releaseBlocked: spec({
|
|
5193
|
+
name: "release blocked",
|
|
5194
|
+
owner: "cloud",
|
|
5195
|
+
expect: "weekly",
|
|
5196
|
+
trigger: "a release answered a refusal, once per kind of blocker",
|
|
5197
|
+
question: "What stops people releasing?",
|
|
5198
|
+
properties: { entry_id: ids.entry(), blocker: oneOf(RELEASE_BLOCKERS) }
|
|
5199
|
+
}),
|
|
5200
|
+
entrySplit: spec({
|
|
5201
|
+
name: "entry split",
|
|
5202
|
+
owner: "cloud",
|
|
5203
|
+
expect: "weekly",
|
|
5204
|
+
trigger: "a person split a draft",
|
|
5205
|
+
question: "How much of an entry do people take off?",
|
|
5206
|
+
properties: {
|
|
5207
|
+
entry_id: ids.entry(),
|
|
5208
|
+
new_entry_id: ids.entry(),
|
|
5209
|
+
origin: Origin,
|
|
5210
|
+
was_edited: bool(),
|
|
5211
|
+
first_minutes: count(),
|
|
5212
|
+
second_minutes: count()
|
|
5213
|
+
}
|
|
5214
|
+
}),
|
|
5215
|
+
entryMerged: spec({
|
|
5216
|
+
name: "entry merged",
|
|
5217
|
+
owner: "cloud",
|
|
5218
|
+
expect: "weekly",
|
|
5219
|
+
trigger: "a person joined drafts",
|
|
5220
|
+
question: "How big are the pieces people join?",
|
|
5221
|
+
properties: {
|
|
5222
|
+
entry_id: ids.entry(),
|
|
5223
|
+
entries: count(),
|
|
5224
|
+
minutes: count(),
|
|
5225
|
+
first_minutes: count(),
|
|
5226
|
+
second_minutes: count(),
|
|
5227
|
+
had_same_matter: bool(),
|
|
5228
|
+
was_edited: bool()
|
|
5229
|
+
}
|
|
5230
|
+
}),
|
|
5231
|
+
entryDeleted: spec({
|
|
5232
|
+
name: "entry deleted",
|
|
5233
|
+
owner: "cloud",
|
|
5234
|
+
expect: "weekly",
|
|
5235
|
+
trigger: "a person deleted one of their drafts",
|
|
5236
|
+
question: "Which drafts do people throw away?",
|
|
5237
|
+
properties: {
|
|
5238
|
+
entry_id: ids.entry(),
|
|
5239
|
+
origin: Origin,
|
|
5240
|
+
has_matter: bool(),
|
|
5241
|
+
was_edited: bool(),
|
|
5242
|
+
minutes: count(),
|
|
5243
|
+
seconds_since_drafted: duration()
|
|
5244
|
+
}
|
|
5245
|
+
}),
|
|
5246
|
+
deliveryReleased: spec({
|
|
5247
|
+
name: "delivery released",
|
|
5248
|
+
owner: "cloud",
|
|
5249
|
+
expect: "daily",
|
|
5250
|
+
trigger: "a delivery reached `released`: written by the release, or moved back by a read-back",
|
|
5251
|
+
question: "How many entries reach the practice system?",
|
|
5252
|
+
properties: DELIVERY
|
|
5253
|
+
}),
|
|
5254
|
+
deliveryAccepted: spec({
|
|
5255
|
+
name: "delivery accepted",
|
|
5256
|
+
owner: "cloud",
|
|
5257
|
+
expect: "weekly",
|
|
5258
|
+
trigger: "a read-back moved a delivery to `accepted`",
|
|
5259
|
+
question: "What share of releases does the firm accept?",
|
|
5260
|
+
properties: DELIVERY
|
|
5261
|
+
}),
|
|
5262
|
+
deliveryRejected: spec({
|
|
5263
|
+
name: "delivery rejected",
|
|
5264
|
+
owner: "cloud",
|
|
5265
|
+
expect: "rare",
|
|
5266
|
+
trigger: "a read-back moved a delivery to `rejected`",
|
|
5267
|
+
question: "What share of releases does the firm reject?",
|
|
5268
|
+
properties: DELIVERY
|
|
5269
|
+
}),
|
|
5270
|
+
deliveryEdited: spec({
|
|
5271
|
+
name: "delivery edited",
|
|
5272
|
+
owner: "cloud",
|
|
5273
|
+
expect: "weekly",
|
|
5274
|
+
trigger: "a read-back found a line edited, or edited further",
|
|
5275
|
+
question: "What does the firm change on a line?",
|
|
5276
|
+
properties: DELIVERY
|
|
5277
|
+
}),
|
|
5278
|
+
deliveryBilled: spec({
|
|
5279
|
+
name: "delivery billed",
|
|
5280
|
+
owner: "cloud",
|
|
5281
|
+
expect: "weekly",
|
|
5282
|
+
trigger: "the first read-back that says the line is billed",
|
|
5283
|
+
question: "How long until released time is billed?",
|
|
5284
|
+
properties: DELIVERY
|
|
5285
|
+
}),
|
|
5286
|
+
practiceConnectionChanged: spec({
|
|
5287
|
+
name: "practice connection changed",
|
|
5288
|
+
owner: "cloud",
|
|
5289
|
+
expect: "rare",
|
|
5290
|
+
trigger: "a firm's link to its practice system, or a member's own account at it, changed",
|
|
5291
|
+
question: "How do firms set up and keep their practice system?",
|
|
5292
|
+
properties: {
|
|
5293
|
+
kind: ConnectorKindSchema,
|
|
5294
|
+
change: oneOf(["connected", "team_confirmed", "settings_saved", "disconnected", "needs_reconnect", "synced_now", "signed_in", "signed_out", "session_failed"])
|
|
5295
|
+
}
|
|
5296
|
+
}),
|
|
5297
|
+
practiceSynced: spec({
|
|
5298
|
+
name: "practice synced",
|
|
5299
|
+
owner: "cloud",
|
|
5300
|
+
expect: "daily",
|
|
5301
|
+
trigger: "a practice sync run ended having written a row or failed (a quiet run counts in `jobs day`)",
|
|
5302
|
+
question: "Is the practice system's link healthy, and what does a sync cost?",
|
|
5303
|
+
properties: {
|
|
5304
|
+
kind: ConnectorKindSchema,
|
|
5305
|
+
duration_ms: duration(),
|
|
5306
|
+
rows_read: count(),
|
|
5307
|
+
rows_written: count(),
|
|
5308
|
+
pages: count(),
|
|
5309
|
+
whole: bool(),
|
|
5310
|
+
failure: SyncFailureSchema.nullable(),
|
|
5311
|
+
members: count(),
|
|
5312
|
+
members_failed: count(),
|
|
5313
|
+
pressed: bool()
|
|
5314
|
+
}
|
|
5315
|
+
}),
|
|
5316
|
+
calendarSynced: spec({
|
|
5317
|
+
name: "calendar synced",
|
|
5318
|
+
owner: "cloud",
|
|
5319
|
+
expect: "daily",
|
|
5320
|
+
trigger: "a person's calendar sync changed an event or failed (a quiet run counts in `jobs day`)",
|
|
5321
|
+
question: "Are calendars read, and do they change?",
|
|
5322
|
+
properties: { provider: oneOf(["microsoft", "google"]), outcome: oneOf(["synced", "failed"]), events_read: count(), events_written: count() }
|
|
5323
|
+
}),
|
|
5324
|
+
sourceHealthChanged: spec({
|
|
5325
|
+
name: "source health changed",
|
|
5326
|
+
owner: "cloud",
|
|
5327
|
+
expect: "rare",
|
|
5328
|
+
trigger: "a practice system or a calendar moved between healthy, failing and needs reconnect",
|
|
5329
|
+
question: "Which links are broken, and since when?",
|
|
5330
|
+
properties: { kind: oneOf(["practice", "calendar"]), from: SourceHealthSchema, to: SourceHealthSchema }
|
|
5331
|
+
}),
|
|
5332
|
+
listChanged: spec({
|
|
5333
|
+
name: "list changed",
|
|
5334
|
+
owner: "cloud",
|
|
5335
|
+
expect: "weekly",
|
|
5336
|
+
trigger: "a matter or a client was created, edited or imported",
|
|
5337
|
+
question: "How do firms keep their lists?",
|
|
5338
|
+
properties: { list: oneOf(["matters", "clients"]), change: oneOf(["created", "edited", "imported"]), count: count() }
|
|
5339
|
+
}),
|
|
5340
|
+
generateStarted: spec({
|
|
5341
|
+
name: "generate started",
|
|
5342
|
+
owner: "cloud",
|
|
5343
|
+
expect: "daily",
|
|
5344
|
+
trigger: "a Generate run started its first attempt",
|
|
5345
|
+
question: "Do people press Generate, and over how much?",
|
|
5346
|
+
properties: { trigger: GENERATE.trigger, intervals: count(), recordings: count(), run_id: GENERATE.run_id, day_offset: GENERATE.day_offset },
|
|
5347
|
+
wire: { name: "generate.started" }
|
|
5348
|
+
}),
|
|
5349
|
+
generateFinished: spec({
|
|
5350
|
+
name: "generate finished",
|
|
5351
|
+
owner: "cloud",
|
|
5352
|
+
expect: "daily",
|
|
5353
|
+
trigger: "a Generate run ended done",
|
|
5354
|
+
question: "Does Generate work, and what does it cost?",
|
|
5355
|
+
properties: {
|
|
5356
|
+
trigger: GENERATE.trigger,
|
|
5357
|
+
entries: count(),
|
|
5358
|
+
blocks_joined: count(),
|
|
5359
|
+
seconds: count(),
|
|
5360
|
+
calls: count(),
|
|
5361
|
+
run_id: GENERATE.run_id,
|
|
5362
|
+
day_offset: GENERATE.day_offset,
|
|
5363
|
+
entries_clipped: count(),
|
|
5364
|
+
attempts: count()
|
|
5365
|
+
},
|
|
5366
|
+
wire: { name: "generate.finished" }
|
|
5367
|
+
}),
|
|
5368
|
+
generateFailed: spec({
|
|
5369
|
+
name: "generate failed",
|
|
5370
|
+
owner: "cloud",
|
|
5371
|
+
expect: "rare",
|
|
5372
|
+
trigger: "a Generate run failed for good",
|
|
5373
|
+
question: "Why does Generate fail?",
|
|
5374
|
+
properties: {
|
|
5375
|
+
trigger: GENERATE.trigger,
|
|
5376
|
+
step: oneOf(["decide", "join", "narrative", "write"]),
|
|
5377
|
+
run_id: GENERATE.run_id,
|
|
5378
|
+
day_offset: GENERATE.day_offset,
|
|
5379
|
+
failure: GenerateFailureSchema,
|
|
5380
|
+
attempts: count()
|
|
5381
|
+
},
|
|
5382
|
+
wire: { name: "generate.failed" }
|
|
5383
|
+
}),
|
|
5384
|
+
entryProposed: spec({
|
|
5385
|
+
name: "entry proposed",
|
|
5386
|
+
owner: "cloud",
|
|
5387
|
+
expect: "weekly",
|
|
5388
|
+
trigger: "a propose job answered",
|
|
5389
|
+
question: "Do the merge, split and drag proposals help?",
|
|
5390
|
+
properties: {
|
|
5391
|
+
ranges: count(),
|
|
5392
|
+
intervals: count(),
|
|
5393
|
+
how: oneOf(["chosen", "most", "tie", "none"]),
|
|
5394
|
+
tie_called: bool(),
|
|
5395
|
+
attribute_calls: count(),
|
|
5396
|
+
kept: oneOf(["no_text", "not_signed_in", "refused", "offline"]),
|
|
5397
|
+
profile_ms: duration(),
|
|
5398
|
+
read_ms: duration(),
|
|
5399
|
+
matter_ms: duration(),
|
|
5400
|
+
attributes_ms: duration(),
|
|
5401
|
+
narrative_ms: duration(),
|
|
5402
|
+
total_ms: duration(),
|
|
5403
|
+
run_id: ids.run()
|
|
5404
|
+
},
|
|
5405
|
+
wire: { name: "entry.proposed" }
|
|
5406
|
+
}),
|
|
5407
|
+
aiGeneration: spec({
|
|
5408
|
+
name: "$ai_generation",
|
|
5409
|
+
owner: "cloud",
|
|
5410
|
+
expect: "daily",
|
|
5411
|
+
trigger: "a `job_calls` row of a model call was written (a cache hit is no call)",
|
|
5412
|
+
question: "What do model calls cost, and do they fail?",
|
|
5413
|
+
properties: {
|
|
5414
|
+
$ai_model: modelId(),
|
|
5415
|
+
$ai_provider: word(),
|
|
5416
|
+
$ai_span_name: word(),
|
|
5417
|
+
$ai_input_tokens: count(),
|
|
5418
|
+
$ai_output_tokens: count(),
|
|
5419
|
+
$ai_cache_read_input_tokens: count(),
|
|
5420
|
+
$ai_cache_reporting_exclusive: bool(),
|
|
5421
|
+
$ai_total_cost_usd: usd(),
|
|
5422
|
+
$ai_cost_passthrough: bool(),
|
|
5423
|
+
$ai_latency: usd(),
|
|
5424
|
+
$ai_is_error: bool(),
|
|
5425
|
+
$ai_http_status: count(),
|
|
5426
|
+
$ai_trace_id: ids.run().or(uuid()).describe("safe:trace"),
|
|
5427
|
+
is_priced: bool(),
|
|
5428
|
+
run_kind: oneOf(["interval", "generate", "regenerate", "propose", "calendar_judge"])
|
|
5429
|
+
}
|
|
5430
|
+
}),
|
|
5431
|
+
modelCapHit: spec({
|
|
5432
|
+
name: "model cap hit",
|
|
5433
|
+
owner: "cloud",
|
|
5434
|
+
expect: "rare",
|
|
5435
|
+
trigger: "a member's daily cap refused a call or held a run",
|
|
5436
|
+
question: "Do the caps bite?",
|
|
5437
|
+
properties: {
|
|
5438
|
+
purpose: word(),
|
|
5439
|
+
day: day(),
|
|
5440
|
+
calls: count(),
|
|
5441
|
+
cost_usd: usd(),
|
|
5442
|
+
prompt_tokens: count(),
|
|
5443
|
+
completion_tokens: count(),
|
|
5444
|
+
run_kind: oneOf(["interval", "generate", "regenerate", "propose", "calendar_judge"])
|
|
5445
|
+
}
|
|
5446
|
+
}),
|
|
5447
|
+
jobsDay: spec({
|
|
5448
|
+
name: "jobs day",
|
|
5449
|
+
owner: "cloud",
|
|
5450
|
+
expect: "daily",
|
|
5451
|
+
trigger: "the first job of a subject to end on a UTC day counts that subject's runs of the day before, per kind",
|
|
5452
|
+
question: "How much background work runs, and how much of it fails?",
|
|
5453
|
+
properties: { kind: JobKindSchema, day: day(), runs: count(), failed: count() }
|
|
5454
|
+
}),
|
|
5455
|
+
dayAccounted: spec({
|
|
5456
|
+
name: "day accounted",
|
|
5457
|
+
owner: "cloud",
|
|
5458
|
+
expect: "daily",
|
|
5459
|
+
trigger: "the `day_accounts` job accounted a person's day two days after it ended",
|
|
5460
|
+
question: "Where did each person's day go?",
|
|
5461
|
+
properties: {
|
|
5462
|
+
day: day(),
|
|
5463
|
+
unobserved_seconds: daySeconds(),
|
|
5464
|
+
away_seconds: daySeconds(),
|
|
5465
|
+
work_undrafted_seconds: daySeconds(),
|
|
5466
|
+
drafted_seconds: daySeconds(),
|
|
5467
|
+
released_seconds: daySeconds(),
|
|
5468
|
+
day_seconds: duration(),
|
|
5469
|
+
devices: count(),
|
|
5470
|
+
capturing: bool(),
|
|
5471
|
+
late_contexts: count()
|
|
5472
|
+
}
|
|
5473
|
+
}),
|
|
5474
|
+
deviceCounters: spec({
|
|
5475
|
+
name: "device counters",
|
|
5476
|
+
owner: "host",
|
|
5477
|
+
expect: "daily",
|
|
5478
|
+
trigger: "a desktop posted its day's counters to POST /v1/telemetry",
|
|
5479
|
+
question: "Is capture healthy on each machine?",
|
|
5480
|
+
properties: { day: day(), ...counterShape() }
|
|
5481
|
+
}),
|
|
5482
|
+
captureStarted: spec({
|
|
5483
|
+
name: "capture started",
|
|
5484
|
+
owner: "host",
|
|
5485
|
+
expect: "daily",
|
|
5486
|
+
trigger: "a desktop turned capture on",
|
|
5487
|
+
question: "How do people start capturing?",
|
|
5488
|
+
properties: { trigger: oneOf(["button", "tray", "always_on", "launch", "sign_in"]), is_always_on: bool(), swallow_short_pauses: bool() },
|
|
5489
|
+
wire: { name: "capture.started" }
|
|
5490
|
+
}),
|
|
5491
|
+
captureStopped: spec({
|
|
5492
|
+
name: "capture stopped",
|
|
5493
|
+
owner: "host",
|
|
5494
|
+
expect: "daily",
|
|
5495
|
+
trigger: "a desktop turned capture off",
|
|
5496
|
+
question: "How long do people capture?",
|
|
5497
|
+
properties: { trigger: oneOf(["button", "tray", "quit", "update", "sign_out"]), seconds: count(), entries: count(), windows_failed: count() },
|
|
5498
|
+
wire: { name: "capture.stopped" }
|
|
5499
|
+
}),
|
|
5500
|
+
userSignedOut: spec({
|
|
5501
|
+
name: "user signed out",
|
|
5502
|
+
owner: "host",
|
|
5503
|
+
expect: "rare",
|
|
5504
|
+
trigger: "a desktop signed out",
|
|
5505
|
+
question: "Why do people sign out?",
|
|
5506
|
+
properties: { trigger: oneOf(["menu", "tray", "session_expired"]) },
|
|
5507
|
+
wire: { name: "user.signed_out" }
|
|
5508
|
+
}),
|
|
5509
|
+
permissionChanged: spec({
|
|
5510
|
+
name: "permission changed",
|
|
5511
|
+
owner: "host",
|
|
5512
|
+
expect: "rare",
|
|
5513
|
+
trigger: "a desktop's system permission changed",
|
|
5514
|
+
question: "Do people grant what capture needs?",
|
|
5515
|
+
properties: { permission: oneOf(["accessibility", "automation"]), is_granted: bool() },
|
|
5516
|
+
wire: { name: "permission.changed" }
|
|
5517
|
+
}),
|
|
5518
|
+
updateFailed: spec({
|
|
5519
|
+
name: "update failed",
|
|
5520
|
+
owner: "host",
|
|
5521
|
+
expect: "rare",
|
|
5522
|
+
trigger: "a desktop's update failed",
|
|
5523
|
+
question: "Where do updates break?",
|
|
5524
|
+
properties: { stage: oneOf(["check", "download", "apply"]) },
|
|
5525
|
+
wire: { name: "update.failed" }
|
|
5526
|
+
}),
|
|
5527
|
+
rawArchiveTurnedOn: spec({
|
|
5528
|
+
name: "raw archive turned on",
|
|
5529
|
+
owner: "host",
|
|
5530
|
+
expect: "rare",
|
|
5531
|
+
trigger: "a person switched the raw archive on",
|
|
5532
|
+
question: "Who keeps a raw archive?",
|
|
5533
|
+
properties: { reason: oneOf(["person"]) },
|
|
5534
|
+
wire: { name: "raw_archive.turned_on", beside: ["reason"] }
|
|
5535
|
+
}),
|
|
5536
|
+
rawArchiveTurnedOff: spec({
|
|
5537
|
+
name: "raw archive turned off",
|
|
5538
|
+
owner: "host",
|
|
5539
|
+
expect: "rare",
|
|
5540
|
+
trigger: "the raw archive was switched off",
|
|
5541
|
+
question: "Why does a raw archive stop?",
|
|
5542
|
+
properties: { reason: RawArchiveReasonSchema },
|
|
5543
|
+
wire: { name: "raw_archive.turned_off", beside: ["reason"] }
|
|
5544
|
+
}),
|
|
5545
|
+
applicationOpened: spec({
|
|
5546
|
+
name: "Application opened",
|
|
5547
|
+
owner: "page",
|
|
5548
|
+
expect: "daily",
|
|
5549
|
+
trigger: "the window opened",
|
|
5550
|
+
question: "How often do people open Sixminute?",
|
|
5551
|
+
properties: { trigger: oneOf(["launch", "login_item", "after_update", "tray"]), is_first_open: bool() },
|
|
5552
|
+
wire: { name: "application.opened" }
|
|
5553
|
+
}),
|
|
5554
|
+
applicationBecameActive: spec({
|
|
5555
|
+
name: "Application became active",
|
|
5556
|
+
owner: "page",
|
|
5557
|
+
expect: "daily",
|
|
5558
|
+
trigger: "the window came to the front",
|
|
5559
|
+
question: "How do people come back to the window?",
|
|
5560
|
+
properties: { trigger: oneOf(["launch", "tray", "dock", "notification", "shortcut"]) },
|
|
5561
|
+
wire: { name: "application.became_active" }
|
|
5562
|
+
}),
|
|
5563
|
+
applicationBackgrounded: spec({
|
|
5564
|
+
name: "Application backgrounded",
|
|
5565
|
+
owner: "page",
|
|
5566
|
+
expect: "daily",
|
|
5567
|
+
trigger: "the window left the front",
|
|
5568
|
+
question: "How long is the window in front?",
|
|
5569
|
+
properties: { foreground_seconds: daySeconds() },
|
|
5570
|
+
wire: { name: "application.backgrounded" }
|
|
5571
|
+
}),
|
|
5572
|
+
appUsage: spec({
|
|
5573
|
+
name: "app usage",
|
|
5574
|
+
owner: "page",
|
|
5575
|
+
expect: "daily",
|
|
5576
|
+
trigger: "a day's time in front of the window",
|
|
5577
|
+
question: "How much time do people spend in Sixminute?",
|
|
5578
|
+
properties: { day: day(), foreground_seconds: daySeconds(), sessions: count() },
|
|
5579
|
+
wire: { name: "app.usage" }
|
|
5580
|
+
}),
|
|
5581
|
+
screenViewed: spec({
|
|
5582
|
+
name: "$screen",
|
|
5583
|
+
owner: "page",
|
|
5584
|
+
expect: "daily",
|
|
5585
|
+
trigger: "a screen was shown",
|
|
5586
|
+
question: "Which screens do people use?",
|
|
5587
|
+
properties: {
|
|
5588
|
+
$screen_name: TelemetryScreenSchema,
|
|
5589
|
+
settings_page: oneOf([
|
|
5590
|
+
"profile",
|
|
5591
|
+
"calendar",
|
|
5592
|
+
"preferences",
|
|
5593
|
+
"appearance",
|
|
5594
|
+
"capture",
|
|
5595
|
+
"privacy",
|
|
5596
|
+
"general",
|
|
5597
|
+
"members",
|
|
5598
|
+
"timekeeping",
|
|
5599
|
+
"narratives",
|
|
5600
|
+
"practice-system",
|
|
5601
|
+
"matters",
|
|
5602
|
+
"member"
|
|
5603
|
+
]),
|
|
5604
|
+
from_screen: TelemetryScreenSchema,
|
|
5605
|
+
day_offset: dayOffset()
|
|
5606
|
+
},
|
|
5607
|
+
wire: { name: "screen.viewed", rename: { screen: "$screen_name" } }
|
|
5608
|
+
}),
|
|
5609
|
+
commandUsed: spec({
|
|
5610
|
+
name: "command used",
|
|
5611
|
+
owner: "page",
|
|
5612
|
+
expect: "daily",
|
|
5613
|
+
trigger: "a command ran from a menu, the palette, a shortcut or a button",
|
|
5614
|
+
question: "Which commands do people use, and how?",
|
|
5615
|
+
properties: { command: TelemetryCommandSchema, via: oneOf(["menu", "palette", "shortcut", "row_menu", "tray", "button"]) },
|
|
5616
|
+
wire: { name: "command.used" }
|
|
5617
|
+
}),
|
|
5618
|
+
entryOpened: spec({
|
|
5619
|
+
name: "entry opened",
|
|
5620
|
+
owner: "page",
|
|
5621
|
+
expect: "daily",
|
|
5622
|
+
trigger: "an entry's page opened",
|
|
5623
|
+
question: "Which entries do people look into?",
|
|
5624
|
+
properties: { entry_id: ids.entry(), from_screen: TelemetryScreenSchema, status: oneOf(["draft", "released"]) },
|
|
5625
|
+
wire: { name: "entry.opened" }
|
|
5626
|
+
}),
|
|
5627
|
+
releaseClicked: spec({
|
|
5628
|
+
name: "release clicked",
|
|
5629
|
+
owner: "page",
|
|
5630
|
+
expect: "daily",
|
|
5631
|
+
trigger: "a person pressed release",
|
|
5632
|
+
question: "How do people release?",
|
|
5633
|
+
properties: { entries: count(), via: oneOf(["row", "release_all", "palette", "entry_page", "selection", "shortcut", "menu"]) },
|
|
5634
|
+
wire: { name: "release.clicked" }
|
|
5635
|
+
}),
|
|
5636
|
+
generateClicked: spec({
|
|
5637
|
+
name: "generate clicked",
|
|
5638
|
+
owner: "page",
|
|
5639
|
+
expect: "daily",
|
|
5640
|
+
trigger: "a person pressed Generate",
|
|
5641
|
+
question: "Where do people press Generate?",
|
|
5642
|
+
properties: { layout: oneOf(["timeline", "list"]) },
|
|
5643
|
+
wire: { name: "generate.clicked" }
|
|
5644
|
+
}),
|
|
5645
|
+
onboardingStepViewed: spec({
|
|
5646
|
+
name: "onboarding step viewed",
|
|
5647
|
+
owner: "page",
|
|
5648
|
+
expect: "weekly",
|
|
5649
|
+
trigger: "an onboarding step was shown",
|
|
5650
|
+
question: "Where does onboarding lose people?",
|
|
5651
|
+
properties: { step: oneOf(["capture", "entries", "permissions", "release"]) },
|
|
5652
|
+
wire: { name: "onboarding.step_viewed" }
|
|
5653
|
+
}),
|
|
5654
|
+
onboardingCompleted: spec({
|
|
5655
|
+
name: "onboarding completed",
|
|
5656
|
+
owner: "page",
|
|
5657
|
+
expect: "weekly",
|
|
5658
|
+
trigger: "onboarding ended",
|
|
5659
|
+
question: "How long does onboarding take?",
|
|
5660
|
+
properties: { seconds: duration() },
|
|
5661
|
+
wire: { name: "onboarding.completed" }
|
|
5662
|
+
}),
|
|
5663
|
+
appError: spec({
|
|
5664
|
+
name: "app error",
|
|
5665
|
+
owner: "page",
|
|
5666
|
+
expect: "rare",
|
|
5667
|
+
trigger: "the page caught an error",
|
|
5668
|
+
question: "Where does the page break?",
|
|
5669
|
+
properties: { kind: AppErrorKindSchema, error_class: AppErrorClassSchema, $screen_name: TelemetryScreenSchema, page_build: PageBuildSchema },
|
|
5670
|
+
wire: { name: "app.error", rename: { screen: "$screen_name" } }
|
|
5671
|
+
})
|
|
5672
|
+
};
|
|
5673
|
+
var EVENT_KEYS = Object.keys(CATALOGUE);
|
|
5674
|
+
var WIRE_KEYS = new Map(EVENT_KEYS.flatMap((key) => {
|
|
5675
|
+
const wire = CATALOGUE[key].wire;
|
|
5676
|
+
return wire === undefined ? [] : [[wire.name, key]];
|
|
5677
|
+
}));
|
|
5678
|
+
var TELEMETRY_WIRE_NAMES = [...WIRE_KEYS.keys()];
|
|
5679
|
+
function keyOfWire(name) {
|
|
5680
|
+
return WIRE_KEYS.get(name);
|
|
5681
|
+
}
|
|
5682
|
+
function wireSchemaOf(name) {
|
|
5683
|
+
const wire = CATALOGUE[keyOfWire(name)].wire;
|
|
5684
|
+
if (wire.properties !== undefined)
|
|
5685
|
+
return wire.properties;
|
|
5686
|
+
const back = new Map(Object.entries(wire.rename ?? {}).map(([wireKey, eventKey]) => [eventKey, wireKey]));
|
|
5687
|
+
const shape = {};
|
|
5688
|
+
for (const [key, schema] of Object.entries(CATALOGUE[keyOfWire(name)].properties.shape)) {
|
|
5689
|
+
if (!(wire.beside ?? []).includes(key))
|
|
5690
|
+
shape[back.get(key) ?? key] = schema;
|
|
5691
|
+
}
|
|
5692
|
+
return exports_external.object(shape);
|
|
5693
|
+
}
|
|
5694
|
+
// packages/contracts/src/analytics/common.ts
|
|
5695
|
+
var EventSourceSchema = oneOf(["api", "pipeline", "job", "daemon", "app"]);
|
|
5696
|
+
var OsSchema = oneOf(["macos", "windows", "ios", "android", "web"]);
|
|
5697
|
+
var COMMON_PROPERTIES = {
|
|
5698
|
+
source: EventSourceSchema,
|
|
5699
|
+
is_internal: bool(),
|
|
5700
|
+
app_version: version(),
|
|
5701
|
+
os: OsSchema,
|
|
5702
|
+
device_id: ids.device(),
|
|
5703
|
+
firm_id: ids.firm(),
|
|
5704
|
+
$session_id: sessionId()
|
|
5705
|
+
};
|
|
5706
|
+
// packages/contracts/src/calendar.ts
|
|
5707
|
+
var CalendarProviderSchema = exports_external.enum(["microsoft", "google"]);
|
|
5708
|
+
var CalendarAccountKindSchema = exports_external.enum(["work", "personal"]);
|
|
5709
|
+
var CalendarConnectionStatusSchema = exports_external.enum(["connected", "awaiting_admin", "needs_reconnect"]);
|
|
5710
|
+
var CalendarConnectionSchema = exports_external.object({
|
|
5711
|
+
provider: CalendarProviderSchema,
|
|
5712
|
+
account_kind: CalendarAccountKindSchema,
|
|
5713
|
+
account_email: exports_external.string(),
|
|
5714
|
+
status: CalendarConnectionStatusSchema,
|
|
5715
|
+
admin_consent_url: exports_external.string().nullable(),
|
|
5716
|
+
last_synced_at: TimestampSchema.nullable()
|
|
5717
|
+
});
|
|
5718
|
+
var CalendarConnectRequestSchema = exports_external.object({
|
|
5719
|
+
provider: CalendarProviderSchema,
|
|
5720
|
+
state: exports_external.string().min(16),
|
|
5721
|
+
code_challenge: exports_external.string().min(16)
|
|
5722
|
+
});
|
|
5723
|
+
var CalendarConnectAnswerSchema = exports_external.object({ authorize_url: exports_external.string().url(), state_token: exports_external.string().min(1).optional() });
|
|
5724
|
+
var CalendarConnectFinishSchema = exports_external.union([
|
|
5725
|
+
exports_external.object({
|
|
5726
|
+
state: exports_external.string().min(16),
|
|
5727
|
+
state_token: exports_external.string().min(1).max(4096).optional(),
|
|
5728
|
+
code: exports_external.string().min(1),
|
|
5729
|
+
code_verifier: exports_external.string().min(43).max(128)
|
|
5730
|
+
}).strict(),
|
|
5731
|
+
exports_external.object({
|
|
5732
|
+
state: exports_external.string().min(16),
|
|
5733
|
+
state_token: exports_external.string().min(1).max(4096).optional(),
|
|
5734
|
+
error: exports_external.string().min(1).max(200),
|
|
5735
|
+
error_description: exports_external.string().max(2000).optional(),
|
|
5736
|
+
error_subcode: exports_external.string().max(200).optional()
|
|
5737
|
+
}).strict()
|
|
5738
|
+
]);
|
|
5739
|
+
var CalendarResponseSchema = exports_external.enum(["organiser", "accepted", "tentative", "declined", "none"]);
|
|
5740
|
+
var CalendarGuestSchema = exports_external.object({
|
|
5741
|
+
name: exports_external.string(),
|
|
5742
|
+
email: exports_external.string().nullable(),
|
|
5743
|
+
response: CalendarResponseSchema,
|
|
5744
|
+
organiser: exports_external.boolean(),
|
|
5745
|
+
me: exports_external.boolean()
|
|
5746
|
+
});
|
|
5747
|
+
var CalendarWhereSchema = exports_external.object({
|
|
5748
|
+
kind: exports_external.enum(["call", "address", "none"]),
|
|
5749
|
+
text: exports_external.string().nullable()
|
|
5750
|
+
});
|
|
5751
|
+
var CalendarEventSchema = exports_external.object({
|
|
5752
|
+
id: exports_external.string().min(1),
|
|
5753
|
+
title: exports_external.string(),
|
|
5754
|
+
from: TimestampSchema,
|
|
5755
|
+
to: TimestampSchema,
|
|
5756
|
+
all_day: exports_external.boolean(),
|
|
5757
|
+
status: exports_external.enum(["confirmed", "cancelled"]),
|
|
5758
|
+
my_response: CalendarResponseSchema,
|
|
5759
|
+
where: CalendarWhereSchema,
|
|
5760
|
+
guests: exports_external.array(CalendarGuestSchema),
|
|
5761
|
+
provider: CalendarProviderSchema,
|
|
5762
|
+
web_link: exports_external.string().nullable(),
|
|
5763
|
+
not_attended: exports_external.boolean(),
|
|
5764
|
+
can_mark: exports_external.boolean()
|
|
5765
|
+
});
|
|
5766
|
+
var CalendarDaySchema = exports_external.object({
|
|
5767
|
+
events: exports_external.array(CalendarEventSchema)
|
|
5768
|
+
});
|
|
5769
|
+
|
|
4941
5770
|
// packages/contracts/src/evidence.ts
|
|
4942
5771
|
var HostSchema = exports_external.string().transform((h) => h.trim().toLowerCase()).pipe(exports_external.string().max(253).regex(/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/, "expected a url host: lower-case labels, no scheme, no port and no path"));
|
|
4943
5772
|
var WitnessSchema = exports_external.object({
|
|
@@ -5036,11 +5865,18 @@ var IntervalTextSchema = exports_external.object({
|
|
|
5036
5865
|
// packages/contracts/src/api.ts
|
|
5037
5866
|
var ProblemSchema = exports_external.object({
|
|
5038
5867
|
type: exports_external.string().min(1),
|
|
5868
|
+
code: exports_external.string().min(1).optional(),
|
|
5039
5869
|
title: exports_external.string().min(1),
|
|
5040
5870
|
status: exports_external.number().int().min(400).max(599),
|
|
5041
5871
|
detail: exports_external.string().optional(),
|
|
5042
5872
|
instance: exports_external.string().optional(),
|
|
5043
|
-
errors: exports_external.array(exports_external.object({
|
|
5873
|
+
errors: exports_external.array(exports_external.object({
|
|
5874
|
+
path: exports_external.string(),
|
|
5875
|
+
message: exports_external.string(),
|
|
5876
|
+
reason: exports_external.string().optional(),
|
|
5877
|
+
rule: exports_external.string().min(1).optional(),
|
|
5878
|
+
params: exports_external.record(exports_external.union([exports_external.string(), exports_external.number()])).optional()
|
|
5879
|
+
})).optional()
|
|
5044
5880
|
});
|
|
5045
5881
|
var CursorSchema = exports_external.string().min(1);
|
|
5046
5882
|
var PullQuerySchema = exports_external.object({
|
|
@@ -5108,10 +5944,9 @@ var EntrySizeSchema = exports_external.enum(["fewer", "balanced", "more"]);
|
|
|
5108
5944
|
var MePatchSchema = exports_external.object({
|
|
5109
5945
|
name: exports_external.string().trim().min(1).max(200).optional(),
|
|
5110
5946
|
narrative_language: NarrativeLanguageSchema.nullable().optional(),
|
|
5111
|
-
entry_size: EntrySizeSchema.nullable().optional()
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
});
|
|
5947
|
+
entry_size: EntrySizeSchema.nullable().optional(),
|
|
5948
|
+
time_zone: exports_external.string().min(1).max(64).optional()
|
|
5949
|
+
}).refine((patch) => patch.name !== undefined || patch.narrative_language !== undefined || patch.entry_size !== undefined || patch.time_zone !== undefined, { message: "name, narrative_language, entry_size or time_zone is required" });
|
|
5115
5950
|
var MemberSchema = exports_external.object({
|
|
5116
5951
|
id: MemberIdSchema,
|
|
5117
5952
|
user_id: UserIdSchema,
|
|
@@ -5168,14 +6003,19 @@ var MeSchema = exports_external.object({
|
|
|
5168
6003
|
connector: MeConnectorSchema.nullable()
|
|
5169
6004
|
}),
|
|
5170
6005
|
member: MemberSchema.extend({
|
|
5171
|
-
system_link: MeSystemLinkSchema.nullable().default(null)
|
|
6006
|
+
system_link: MeSystemLinkSchema.nullable().default(null),
|
|
6007
|
+
time_zone: exports_external.string().nullable().optional(),
|
|
6008
|
+
time_zone_next: exports_external.string().nullable().optional(),
|
|
6009
|
+
time_zone_next_from: LocalDaySchema.nullable().optional()
|
|
5172
6010
|
}),
|
|
5173
6011
|
permissions: exports_external.array(exports_external.string()).default([]),
|
|
5174
6012
|
judgment_pipeline: JudgmentPipelineSchema.default("standard"),
|
|
5175
6013
|
legalsense_connector: exports_external.boolean().default(false),
|
|
5176
6014
|
calendar_offered: exports_external.boolean().optional(),
|
|
5177
6015
|
swallow_short_pauses: exports_external.boolean().optional(),
|
|
5178
|
-
pipeline_shadow: exports_external.boolean().optional()
|
|
6016
|
+
pipeline_shadow: exports_external.boolean().optional(),
|
|
6017
|
+
narrative_limit: exports_external.object({ limit: exports_external.number().int().positive(), by: exports_external.enum(["firm", "house", "system", "max"]) }).optional(),
|
|
6018
|
+
practice: exports_external.record(exports_external.unknown()).nullable().optional()
|
|
5179
6019
|
});
|
|
5180
6020
|
var ProvisionResultSchema = MeSchema.extend({
|
|
5181
6021
|
action: exports_external.enum(["ready", "refresh"]).default("ready")
|
|
@@ -5297,10 +6137,24 @@ var EntryRowSchema = exports_external.object({
|
|
|
5297
6137
|
entry: EntrySchema,
|
|
5298
6138
|
delivery: DeliverySchema.nullable().optional()
|
|
5299
6139
|
});
|
|
6140
|
+
var DayBlockerSchema = exports_external.object({
|
|
6141
|
+
path: exports_external.string().min(1),
|
|
6142
|
+
rule: exports_external.string().min(1),
|
|
6143
|
+
params: exports_external.record(exports_external.union([exports_external.string(), exports_external.number()])).optional()
|
|
6144
|
+
});
|
|
6145
|
+
var DayEntryRowSchema = EntryRowSchema.extend({
|
|
6146
|
+
editable: exports_external.boolean().optional(),
|
|
6147
|
+
blockers: exports_external.array(DayBlockerSchema).optional(),
|
|
6148
|
+
options: exports_external.record(exports_external.array(OptionSchema)).optional()
|
|
6149
|
+
});
|
|
5300
6150
|
var DayReadSchema = exports_external.object({
|
|
5301
6151
|
day: LocalDaySchema,
|
|
5302
6152
|
unit_seconds: exports_external.number().int().positive(),
|
|
5303
|
-
|
|
6153
|
+
zone: exports_external.string().optional(),
|
|
6154
|
+
t_start: TimestampSchema.optional(),
|
|
6155
|
+
t_end: TimestampSchema.optional(),
|
|
6156
|
+
provisional: exports_external.boolean().optional(),
|
|
6157
|
+
entries: exports_external.array(DayEntryRowSchema),
|
|
5304
6158
|
intervals: exports_external.array(DayIntervalSchema),
|
|
5305
6159
|
entry_intervals: exports_external.array(DayIntervalSchema).default([]),
|
|
5306
6160
|
calendar: CalendarDaySchema.optional()
|
|
@@ -5600,185 +6454,8 @@ var CompletionWireResponseSchema = exports_external.object({
|
|
|
5600
6454
|
priced: exports_external.boolean()
|
|
5601
6455
|
})
|
|
5602
6456
|
});
|
|
5603
|
-
var RawArchiveReasonSchema = exports_external.enum([
|
|
5604
|
-
"person",
|
|
5605
|
-
"expired",
|
|
5606
|
-
"signed_out",
|
|
5607
|
-
"tenant_changed",
|
|
5608
|
-
"not_allowed",
|
|
5609
|
-
"refused",
|
|
5610
|
-
"disk_low"
|
|
5611
|
-
]);
|
|
5612
6457
|
var RAW_ARCHIVE_EVENT_NAMES = ["raw_archive.turned_on", "raw_archive.turned_off"];
|
|
5613
|
-
var
|
|
5614
|
-
var TelemetryCommandSchema = exports_external.enum([
|
|
5615
|
-
"new-entry",
|
|
5616
|
-
"release-all",
|
|
5617
|
-
"settings",
|
|
5618
|
-
"exit",
|
|
5619
|
-
"undo",
|
|
5620
|
-
"redo",
|
|
5621
|
-
"cut",
|
|
5622
|
-
"copy",
|
|
5623
|
-
"paste",
|
|
5624
|
-
"select-all",
|
|
5625
|
-
"today",
|
|
5626
|
-
"previous-day",
|
|
5627
|
-
"next-day",
|
|
5628
|
-
"previous-entry",
|
|
5629
|
-
"next-entry",
|
|
5630
|
-
"actions",
|
|
5631
|
-
"capture",
|
|
5632
|
-
"privacy",
|
|
5633
|
-
"about",
|
|
5634
|
-
"folder",
|
|
5635
|
-
"release",
|
|
5636
|
-
"matter",
|
|
5637
|
-
"attribute",
|
|
5638
|
-
"merge",
|
|
5639
|
-
"delete",
|
|
5640
|
-
"open"
|
|
5641
|
-
]);
|
|
5642
|
-
var Whole = exports_external.number().int().nonnegative();
|
|
5643
|
-
var TELEMETRY_SECONDS_MAX = 86400;
|
|
5644
|
-
var ForegroundSeconds = Whole.max(TELEMETRY_SECONDS_MAX);
|
|
5645
|
-
var TELEMETRY_DAY_OFFSET_MAX = 3660;
|
|
5646
|
-
var TELEMETRY_ENTRY_ID_MAX = 64;
|
|
5647
|
-
var GenerateFailureSchema = exports_external.enum(["model", "deadline", "overlap", "other"]);
|
|
5648
|
-
var AppErrorKindSchema = exports_external.enum(["boundary", "uncaught", "unhandled_rejection"]);
|
|
5649
|
-
var AppErrorClassSchema = exports_external.enum([
|
|
5650
|
-
"Error",
|
|
5651
|
-
"TypeError",
|
|
5652
|
-
"RangeError",
|
|
5653
|
-
"ReferenceError",
|
|
5654
|
-
"SyntaxError",
|
|
5655
|
-
"ChunkLoadError",
|
|
5656
|
-
"AbortError",
|
|
5657
|
-
"other"
|
|
5658
|
-
]);
|
|
5659
|
-
var PageBuildSchema = exports_external.string().regex(/^\d{14}-[0-9A-Za-z]{1,40}$/, "expected YYYYMMDDHHMMSS-<commit>");
|
|
5660
|
-
var TELEMETRY_EVENT_PROPERTIES = {
|
|
5661
|
-
"capture.started": exports_external.object({
|
|
5662
|
-
trigger: exports_external.enum(["button", "tray", "always_on", "launch", "sign_in"]),
|
|
5663
|
-
is_always_on: exports_external.boolean(),
|
|
5664
|
-
swallow_short_pauses: exports_external.boolean().optional()
|
|
5665
|
-
}),
|
|
5666
|
-
"capture.stopped": exports_external.object({
|
|
5667
|
-
trigger: exports_external.enum(["button", "tray", "quit", "update", "sign_out"]),
|
|
5668
|
-
seconds: Whole,
|
|
5669
|
-
entries: Whole,
|
|
5670
|
-
windows_failed: Whole
|
|
5671
|
-
}),
|
|
5672
|
-
"user.signed_out": exports_external.object({ trigger: exports_external.enum(["menu", "tray", "session_expired"]) }),
|
|
5673
|
-
"permission.changed": exports_external.object({ permission: exports_external.enum(["accessibility", "automation"]), is_granted: exports_external.boolean() }),
|
|
5674
|
-
"update.failed": exports_external.object({ stage: exports_external.enum(["check", "download", "apply"]) }),
|
|
5675
|
-
"setting.changed": exports_external.object({
|
|
5676
|
-
setting: exports_external.enum([
|
|
5677
|
-
"unit_minutes",
|
|
5678
|
-
"narrative_language",
|
|
5679
|
-
"narrative_max_chars",
|
|
5680
|
-
"always_on",
|
|
5681
|
-
"launch_at_login",
|
|
5682
|
-
"theme",
|
|
5683
|
-
"exclusions",
|
|
5684
|
-
"entry_size"
|
|
5685
|
-
]),
|
|
5686
|
-
scope: exports_external.enum(["device"])
|
|
5687
|
-
}),
|
|
5688
|
-
"raw_archive.turned_on": exports_external.object({}),
|
|
5689
|
-
"raw_archive.turned_off": exports_external.object({}),
|
|
5690
|
-
"generate.started": exports_external.object({
|
|
5691
|
-
trigger: exports_external.enum(["press", "backstop"]),
|
|
5692
|
-
intervals: Whole,
|
|
5693
|
-
recordings: Whole,
|
|
5694
|
-
run_id: RunIdSchema,
|
|
5695
|
-
day_offset: exports_external.number().int().min(-TELEMETRY_DAY_OFFSET_MAX).max(TELEMETRY_DAY_OFFSET_MAX)
|
|
5696
|
-
}),
|
|
5697
|
-
"generate.finished": exports_external.object({
|
|
5698
|
-
trigger: exports_external.enum(["press", "backstop"]),
|
|
5699
|
-
entries: Whole,
|
|
5700
|
-
blocks_joined: Whole,
|
|
5701
|
-
seconds: Whole,
|
|
5702
|
-
calls: Whole,
|
|
5703
|
-
run_id: RunIdSchema,
|
|
5704
|
-
day_offset: exports_external.number().int().min(-TELEMETRY_DAY_OFFSET_MAX).max(TELEMETRY_DAY_OFFSET_MAX),
|
|
5705
|
-
entries_clipped: Whole,
|
|
5706
|
-
attempts: Whole
|
|
5707
|
-
}),
|
|
5708
|
-
"generate.failed": exports_external.object({
|
|
5709
|
-
trigger: exports_external.enum(["press", "backstop"]),
|
|
5710
|
-
step: exports_external.enum(["decide", "join", "narrative", "write"]),
|
|
5711
|
-
run_id: RunIdSchema,
|
|
5712
|
-
day_offset: exports_external.number().int().min(-TELEMETRY_DAY_OFFSET_MAX).max(TELEMETRY_DAY_OFFSET_MAX),
|
|
5713
|
-
failure: GenerateFailureSchema,
|
|
5714
|
-
attempts: Whole
|
|
5715
|
-
}),
|
|
5716
|
-
"entry.proposed": exports_external.object({
|
|
5717
|
-
ranges: Whole,
|
|
5718
|
-
intervals: Whole,
|
|
5719
|
-
how: exports_external.enum(["chosen", "most", "tie", "none"]),
|
|
5720
|
-
tie_called: exports_external.boolean(),
|
|
5721
|
-
attribute_calls: Whole,
|
|
5722
|
-
kept: exports_external.enum(["no_text", "not_signed_in", "refused", "offline"]),
|
|
5723
|
-
profile_ms: Whole,
|
|
5724
|
-
read_ms: Whole,
|
|
5725
|
-
matter_ms: Whole,
|
|
5726
|
-
attributes_ms: Whole,
|
|
5727
|
-
narrative_ms: Whole,
|
|
5728
|
-
total_ms: Whole,
|
|
5729
|
-
run_id: RunIdSchema
|
|
5730
|
-
}),
|
|
5731
|
-
"application.opened": exports_external.object({
|
|
5732
|
-
trigger: exports_external.enum(["launch", "login_item", "after_update", "tray"]),
|
|
5733
|
-
is_first_open: exports_external.boolean()
|
|
5734
|
-
}),
|
|
5735
|
-
"application.became_active": exports_external.object({ trigger: exports_external.enum(["launch", "tray", "dock", "notification", "shortcut"]) }),
|
|
5736
|
-
"application.backgrounded": exports_external.object({ foreground_seconds: ForegroundSeconds }),
|
|
5737
|
-
"app.usage": exports_external.object({ day: LocalDaySchema, foreground_seconds: ForegroundSeconds, sessions: Whole }),
|
|
5738
|
-
"screen.viewed": exports_external.object({
|
|
5739
|
-
screen: TelemetryScreenSchema,
|
|
5740
|
-
settings_page: exports_external.enum([
|
|
5741
|
-
"profile",
|
|
5742
|
-
"calendar",
|
|
5743
|
-
"preferences",
|
|
5744
|
-
"appearance",
|
|
5745
|
-
"capture",
|
|
5746
|
-
"privacy",
|
|
5747
|
-
"general",
|
|
5748
|
-
"members",
|
|
5749
|
-
"timekeeping",
|
|
5750
|
-
"narratives",
|
|
5751
|
-
"practice-system",
|
|
5752
|
-
"matters",
|
|
5753
|
-
"member"
|
|
5754
|
-
]),
|
|
5755
|
-
from_screen: TelemetryScreenSchema,
|
|
5756
|
-
day_offset: exports_external.number().int().min(-TELEMETRY_DAY_OFFSET_MAX).max(TELEMETRY_DAY_OFFSET_MAX)
|
|
5757
|
-
}),
|
|
5758
|
-
"command.used": exports_external.object({
|
|
5759
|
-
command: TelemetryCommandSchema,
|
|
5760
|
-
via: exports_external.enum(["menu", "palette", "shortcut", "row_menu", "tray", "button"])
|
|
5761
|
-
}),
|
|
5762
|
-
"entry.opened": exports_external.object({
|
|
5763
|
-
entry_id: EntryIdSchema.max(TELEMETRY_ENTRY_ID_MAX),
|
|
5764
|
-
from_screen: TelemetryScreenSchema,
|
|
5765
|
-
status: exports_external.enum(["draft", "released"])
|
|
5766
|
-
}),
|
|
5767
|
-
"release.clicked": exports_external.object({
|
|
5768
|
-
entries: Whole,
|
|
5769
|
-
via: exports_external.enum(["row", "release_all", "palette", "entry_page", "selection", "shortcut", "menu"])
|
|
5770
|
-
}),
|
|
5771
|
-
"generate.clicked": exports_external.object({ layout: exports_external.enum(["timeline", "list"]) }),
|
|
5772
|
-
"onboarding.step_viewed": exports_external.object({ step: exports_external.enum(["capture", "entries", "permissions", "release"]) }),
|
|
5773
|
-
"onboarding.completed": exports_external.object({ seconds: Whole }),
|
|
5774
|
-
"app.error": exports_external.object({
|
|
5775
|
-
kind: AppErrorKindSchema,
|
|
5776
|
-
error_class: AppErrorClassSchema,
|
|
5777
|
-
screen: TelemetryScreenSchema,
|
|
5778
|
-
page_build: PageBuildSchema
|
|
5779
|
-
})
|
|
5780
|
-
};
|
|
5781
|
-
var TelemetryEventNameSchema = exports_external.enum(Object.keys(TELEMETRY_EVENT_PROPERTIES));
|
|
6458
|
+
var TelemetryEventNameSchema = exports_external.enum(TELEMETRY_WIRE_NAMES);
|
|
5782
6459
|
var TelemetryPropertyValueSchema = exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()]);
|
|
5783
6460
|
var TelemetrySessionIdSchema = exports_external.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, "expected a UUIDv7");
|
|
5784
6461
|
var TelemetryEventSchema = exports_external.object({
|
|
@@ -5799,7 +6476,7 @@ var TelemetryEventSchema = exports_external.object({
|
|
|
5799
6476
|
refuse("raw_archive.turned_on carries the reason person, and only that");
|
|
5800
6477
|
}
|
|
5801
6478
|
if (event.properties !== undefined) {
|
|
5802
|
-
const allowed =
|
|
6479
|
+
const allowed = wireSchemaOf(event.name).partial().strict().safeParse(event.properties);
|
|
5803
6480
|
if (!allowed.success) {
|
|
5804
6481
|
for (const issue of allowed.error.issues) {
|
|
5805
6482
|
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["properties", ...issue.path], message: issue.message });
|
|
@@ -5852,111 +6529,6 @@ var FirmInvitationsSchema = exports_external.object({ invitations: exports_exter
|
|
|
5852
6529
|
var InvitationRevokedSchema = exports_external.object({
|
|
5853
6530
|
invitation: exports_external.object({ id: exports_external.string(), email: exports_external.string() })
|
|
5854
6531
|
});
|
|
5855
|
-
// packages/contracts/src/admin-research.ts
|
|
5856
|
-
var ResearchExportParamsSchema = exports_external.object({ tenant: TenantIdSchema });
|
|
5857
|
-
var ResearchExportRequestSchema = exports_external.object({
|
|
5858
|
-
from: LocalDaySchema,
|
|
5859
|
-
to: LocalDaySchema
|
|
5860
|
-
}).refine((range) => !Number.isNaN(Date.parse(`${range.from}T00:00:00.000Z`)), {
|
|
5861
|
-
message: "from is not a real day",
|
|
5862
|
-
path: ["from"]
|
|
5863
|
-
}).refine((range) => !Number.isNaN(Date.parse(`${range.to}T00:00:00.000Z`)), {
|
|
5864
|
-
message: "to is not a real day",
|
|
5865
|
-
path: ["to"]
|
|
5866
|
-
}).refine((range) => range.from < range.to, {
|
|
5867
|
-
message: "from must be before to; the range is half-open and an empty one exports nothing",
|
|
5868
|
-
path: ["to"]
|
|
5869
|
-
});
|
|
5870
|
-
var Base64Schema = exports_external.string().regex(/^[A-Za-z0-9+/]*={0,2}$/, "expected base64");
|
|
5871
|
-
var SealedResearchCardSchema = exports_external.object({
|
|
5872
|
-
id: exports_external.string(),
|
|
5873
|
-
tenant_id: exports_external.string(),
|
|
5874
|
-
member_id: exports_external.string(),
|
|
5875
|
-
device_id: exports_external.string().nullable(),
|
|
5876
|
-
purpose: exports_external.string(),
|
|
5877
|
-
model: exports_external.string(),
|
|
5878
|
-
created_at: exports_external.string(),
|
|
5879
|
-
key_version: exports_external.number().int().positive(),
|
|
5880
|
-
sealed_key: Base64Schema,
|
|
5881
|
-
card_ct: Base64Schema,
|
|
5882
|
-
answer_ct: Base64Schema
|
|
5883
|
-
});
|
|
5884
|
-
var JudgedPairEntrySchema = exports_external.object({
|
|
5885
|
-
day: exports_external.string(),
|
|
5886
|
-
time_zone: exports_external.string(),
|
|
5887
|
-
timekeeper_id: exports_external.string(),
|
|
5888
|
-
t_start: exports_external.string(),
|
|
5889
|
-
t_end: exports_external.string(),
|
|
5890
|
-
parts: exports_external.array(exports_external.object({ t_start: exports_external.string(), t_end: exports_external.string() })).nullable().default(null),
|
|
5891
|
-
override_seconds: exports_external.number().int().nullable().default(null),
|
|
5892
|
-
narrative: exports_external.string(),
|
|
5893
|
-
matter_id: exports_external.string().nullable(),
|
|
5894
|
-
attributes: exports_external.unknown(),
|
|
5895
|
-
internal_note: exports_external.string().nullable()
|
|
5896
|
-
});
|
|
5897
|
-
var JudgedPairSchema = exports_external.object({
|
|
5898
|
-
entry_id: exports_external.string(),
|
|
5899
|
-
tenant_id: exports_external.string(),
|
|
5900
|
-
member_id: exports_external.string(),
|
|
5901
|
-
day: exports_external.string(),
|
|
5902
|
-
status: exports_external.string(),
|
|
5903
|
-
edited_at: exports_external.string().nullable(),
|
|
5904
|
-
as_judged: exports_external.unknown(),
|
|
5905
|
-
as_released_or_current: JudgedPairEntrySchema
|
|
5906
|
-
});
|
|
5907
|
-
var ExportHeadSchema = exports_external.object({
|
|
5908
|
-
export_id: exports_external.string(),
|
|
5909
|
-
tenant_id: exports_external.string(),
|
|
5910
|
-
tenant_name: exports_external.string(),
|
|
5911
|
-
from: exports_external.string(),
|
|
5912
|
-
to: exports_external.string(),
|
|
5913
|
-
operator: exports_external.string()
|
|
5914
|
-
});
|
|
5915
|
-
var ResearchExportConfidentialSchema = ExportHeadSchema.extend({
|
|
5916
|
-
tier: exports_external.literal("confidential"),
|
|
5917
|
-
rows: exports_external.literal(0)
|
|
5918
|
-
});
|
|
5919
|
-
var ResearchExportSealedSchema = ExportHeadSchema.extend({
|
|
5920
|
-
tier: exports_external.literal("standard"),
|
|
5921
|
-
rows: exports_external.number().int().nonnegative(),
|
|
5922
|
-
cards: exports_external.number().int().nonnegative(),
|
|
5923
|
-
pairs: exports_external.number().int().nonnegative(),
|
|
5924
|
-
research_key_name: exports_external.string(),
|
|
5925
|
-
sealed_cards: exports_external.array(SealedResearchCardSchema),
|
|
5926
|
-
judged_pairs: exports_external.array(JudgedPairSchema)
|
|
5927
|
-
});
|
|
5928
|
-
var ResearchExportResultSchema = exports_external.discriminatedUnion("tier", [
|
|
5929
|
-
ResearchExportConfidentialSchema,
|
|
5930
|
-
ResearchExportSealedSchema
|
|
5931
|
-
]);
|
|
5932
|
-
var ResearchContextExportSchema = exports_external.object({
|
|
5933
|
-
export_id: exports_external.string(),
|
|
5934
|
-
tenant_id: exports_external.string(),
|
|
5935
|
-
tenant_name: exports_external.string(),
|
|
5936
|
-
from: exports_external.string(),
|
|
5937
|
-
to: exports_external.string(),
|
|
5938
|
-
operator: exports_external.string(),
|
|
5939
|
-
contexts: exports_external.array(exports_external.object({
|
|
5940
|
-
member_id: exports_external.string(),
|
|
5941
|
-
device_id: exports_external.string().optional(),
|
|
5942
|
-
recording_id: exports_external.string(),
|
|
5943
|
-
seq: exports_external.number().int(),
|
|
5944
|
-
t_start: exports_external.string(),
|
|
5945
|
-
t_end: exports_external.string(),
|
|
5946
|
-
context: exports_external.unknown()
|
|
5947
|
-
})),
|
|
5948
|
-
steps: exports_external.array(exports_external.object({
|
|
5949
|
-
run_id: exports_external.string(),
|
|
5950
|
-
kind: exports_external.string(),
|
|
5951
|
-
member_id: exports_external.string(),
|
|
5952
|
-
subject_from: exports_external.string(),
|
|
5953
|
-
name: exports_external.string(),
|
|
5954
|
-
prompt_fingerprint: exports_external.string().nullable(),
|
|
5955
|
-
usage: exports_external.unknown(),
|
|
5956
|
-
output: exports_external.unknown(),
|
|
5957
|
-
created_at: exports_external.string()
|
|
5958
|
-
}))
|
|
5959
|
-
});
|
|
5960
6532
|
// packages/contracts/src/admin-tenants.ts
|
|
5961
6533
|
var AdminTenantParamsSchema = exports_external.object({ tenant: TenantIdSchema });
|
|
5962
6534
|
var AdminSecurityTierSchema = exports_external.enum(["standard", "confidential"]);
|
|
@@ -5997,10 +6569,6 @@ var AdminRawArchiveSetSchema = exports_external.object({ allowed: exports_extern
|
|
|
5997
6569
|
var AdminTenantFlagsSchema = exports_external.object({
|
|
5998
6570
|
tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string(), internal: exports_external.boolean(), raw_archive: exports_external.boolean() })
|
|
5999
6571
|
});
|
|
6000
|
-
var AdminResearchExportSetSchema = exports_external.object({ research_export: exports_external.boolean() });
|
|
6001
|
-
var AdminResearchExportFlagSchema = exports_external.object({
|
|
6002
|
-
tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string(), research_export: exports_external.boolean() })
|
|
6003
|
-
});
|
|
6004
6572
|
var AdminDemoSetSchema = exports_external.object({ enabled: exports_external.boolean() });
|
|
6005
6573
|
var AdminDemoFlagSchema = exports_external.object({
|
|
6006
6574
|
tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string(), demo: exports_external.boolean() })
|
|
@@ -6039,6 +6607,7 @@ var AdminEntriesPurgeResultSchema = exports_external.object({
|
|
|
6039
6607
|
user: UserIdSchema,
|
|
6040
6608
|
dry_run: exports_external.boolean(),
|
|
6041
6609
|
rows: exports_external.object({ demo: exports_external.array(AdminPurgeRowSchema), unmatched: exports_external.array(AdminPurgeRowSchema) }),
|
|
6610
|
+
kept: exports_external.object({ demo: exports_external.array(AdminPurgeRowSchema) }).default({ demo: [] }),
|
|
6042
6611
|
deleted: exports_external.object({ demo: exports_external.number().int(), unmatched: exports_external.number().int(), total: exports_external.number().int() }).nullable()
|
|
6043
6612
|
});
|
|
6044
6613
|
var AdminContextRetentionSetSchema = exports_external.object({ days: exports_external.number().int().min(1).max(365) });
|
|
@@ -6202,6 +6771,115 @@ var ClientOutdatedProblemSchema = ProblemSchema.extend({
|
|
|
6202
6771
|
status: exports_external.literal(400),
|
|
6203
6772
|
minimum: exports_external.string().regex(/^\d+\.\d+\.\d+$/)
|
|
6204
6773
|
});
|
|
6774
|
+
// packages/contracts/src/compat/retirements.ts
|
|
6775
|
+
var PR4 = { pr: "pr4" };
|
|
6776
|
+
var MIRRORS = "services/api/migrations/0054_mirrors.sql";
|
|
6777
|
+
var COMPAT_PATHS = [
|
|
6778
|
+
{ name: "the app.scope arm for transactions without a scope", file: "services/api/migrations/0053_scopes.sql", needle: "app_scope() is null", retiredBy: PR4 },
|
|
6779
|
+
{ name: "the content-free as_judged marker", file: "services/api/migrations/0056_entry_guards.sql", needle: "as_judged = '{}'", retiredBy: PR4 },
|
|
6780
|
+
{ name: "the calendar connect-state fallback to the old table", file: "services/api/src/features/calendar/store.ts", needle: "calendar_connect_states", retiredBy: PR4 },
|
|
6781
|
+
{ name: "the worker's alias of PR 1's task path", file: "services/api/src/platform/jobs/service.ts", needle: "/internal/pipeline/run", retiredBy: PR4 },
|
|
6782
|
+
{ name: "the 0.5 telemetry relay", file: "services/api/src/features/evidence/telemetry.ts", needle: 'path: "/v1/telemetry"', retiredBy: PR4 },
|
|
6783
|
+
{ name: "the mirror triggers of the merged tables", file: MIRRORS, needle: "create trigger", retiredBy: PR4 },
|
|
6784
|
+
...[
|
|
6785
|
+
"connector_timekeepers_mirror",
|
|
6786
|
+
"connector_sessions_mirror",
|
|
6787
|
+
"connector_links_mirror",
|
|
6788
|
+
"connector_pushes_mirror",
|
|
6789
|
+
"connector_deliveries_mirror",
|
|
6790
|
+
"deliveries_mirror",
|
|
6791
|
+
"calendar_event_marks_mirror",
|
|
6792
|
+
"calendar_events_mirror_insert",
|
|
6793
|
+
"calendar_events_mirror_update"
|
|
6794
|
+
].map((trigger) => ({ name: `the mirror trigger ${trigger}`, file: MIRRORS, needle: `create trigger ${trigger}`, retiredBy: PR4 }))
|
|
6795
|
+
];
|
|
6796
|
+
var markers = (marker, files) => files.map((file) => ({ file, marker }));
|
|
6797
|
+
var COMPAT_MARKERS = [
|
|
6798
|
+
...markers("#180", [
|
|
6799
|
+
"boundaries.ts",
|
|
6800
|
+
"packages/client/test/t31-problems.test.ts",
|
|
6801
|
+
"packages/contracts/src/api.ts",
|
|
6802
|
+
"packages/contracts/src/compat.ts",
|
|
6803
|
+
"packages/contracts/src/config.ts",
|
|
6804
|
+
"packages/contracts/src/entry.ts",
|
|
6805
|
+
"packages/contracts/src/evidence.ts",
|
|
6806
|
+
"packages/contracts/src/index.ts",
|
|
6807
|
+
"packages/contracts/src/run.ts",
|
|
6808
|
+
"packages/contracts/src/vocabulary.ts",
|
|
6809
|
+
"packages/contracts/test/config.test.ts",
|
|
6810
|
+
"packages/contracts/test/desktop-lines.ts",
|
|
6811
|
+
"packages/contracts/test/entry.test.ts",
|
|
6812
|
+
"packages/contracts/test/t36-contracts.test.ts",
|
|
6813
|
+
"packages/contracts/test/vocabulary.test.ts",
|
|
6814
|
+
"services/api/Dockerfile",
|
|
6815
|
+
"services/api/openapi.json",
|
|
6816
|
+
"services/api/src/app.ts",
|
|
6817
|
+
"services/api/src/features/cells/routes.ts",
|
|
6818
|
+
"services/api/src/features/generate/proxy.ts",
|
|
6819
|
+
"services/api/src/features/lists/vocabulary-store.ts",
|
|
6820
|
+
"services/api/src/features/lists/vocabulary.ts",
|
|
6821
|
+
"services/api/src/features/people/me.ts",
|
|
6822
|
+
"services/api/src/features/update/config.ts",
|
|
6823
|
+
"services/api/src/features/update/slice.ts",
|
|
6824
|
+
"services/api/src/platform/http/config-version.ts",
|
|
6825
|
+
"services/api/src/platform/http/problems.ts",
|
|
6826
|
+
"services/api/src/platform/http/router.ts",
|
|
6827
|
+
"services/api/src/platform/models/bundle.ts",
|
|
6828
|
+
"services/api/src/platform/models/compat/activity-types.json",
|
|
6829
|
+
"services/api/src/platform/models/compat/activity-types.ts",
|
|
6830
|
+
"services/api/src/platform/models/compat/activity.md",
|
|
6831
|
+
"services/api/src/platform/models/compat/narrative.md",
|
|
6832
|
+
"services/api/src/platform/models/jev.ts",
|
|
6833
|
+
"services/api/src/platform/models/usage.ts",
|
|
6834
|
+
"services/api/test/admin-acting.test.ts",
|
|
6835
|
+
"services/api/test/compat-180.test.ts",
|
|
6836
|
+
"services/api/test/jev-route.test.ts",
|
|
6837
|
+
"services/api/test/t15-bundle.test.ts",
|
|
6838
|
+
"services/api/test/t15-lists.test.ts",
|
|
6839
|
+
"services/api/test/t34-vocabulary.test.ts",
|
|
6840
|
+
"services/api/test/t40-firm.test.ts",
|
|
6841
|
+
"services/api/test/t42-intervals.test.ts",
|
|
6842
|
+
"services/api/test/t53-bundle.test.ts"
|
|
6843
|
+
]),
|
|
6844
|
+
...markers("narrative-languages", ["services/api/src/app.ts", "services/api/src/features/update/legacy-languages.ts"]),
|
|
6845
|
+
...markers("pr4", [
|
|
6846
|
+
"boundaries.ts",
|
|
6847
|
+
".github/scripts/page-minimum.test.ts",
|
|
6848
|
+
".github/scripts/page-minimum.ts",
|
|
6849
|
+
"apps/web/version.ts",
|
|
6850
|
+
"packages/client/src/copy/index.ts",
|
|
6851
|
+
"packages/client/src/data/failures-compat.ts",
|
|
6852
|
+
"packages/client/src/data/failures.ts",
|
|
6853
|
+
"packages/client/src/data/index.ts",
|
|
6854
|
+
"packages/client/src/data/mutations.ts",
|
|
6855
|
+
"packages/client/src/screens/time/grid.ts",
|
|
6856
|
+
"packages/client/src/screens/time/intervals.ts",
|
|
6857
|
+
"packages/client/src/screens/time/span.tsx",
|
|
6858
|
+
"packages/client/src/screens/time/timeline-blocks.tsx",
|
|
6859
|
+
"packages/client/test/t132-codes.test.ts",
|
|
6860
|
+
"packages/contracts/src/admin-tenants.ts",
|
|
6861
|
+
"packages/contracts/src/analytics/catalogue.ts",
|
|
6862
|
+
"packages/contracts/src/api.ts",
|
|
6863
|
+
"packages/contracts/src/connector.ts",
|
|
6864
|
+
"services/api/migrations/0054_mirrors.sql",
|
|
6865
|
+
"services/api/src/features/calendar/routes.ts",
|
|
6866
|
+
"services/api/src/features/calendar/store.ts",
|
|
6867
|
+
"services/api/src/features/entries/lifecycle.ts",
|
|
6868
|
+
"services/api/src/features/entries/store.ts",
|
|
6869
|
+
"services/api/src/features/lists/store.ts",
|
|
6870
|
+
"services/api/src/features/evidence/retention.ts",
|
|
6871
|
+
"services/api/src/features/evidence/telemetry.ts",
|
|
6872
|
+
"services/api/src/features/operator/tenants.ts",
|
|
6873
|
+
"services/api/src/platform/jobs/jobs.ts",
|
|
6874
|
+
"services/api/src/platform/jobs/ledger.ts",
|
|
6875
|
+
"services/api/src/platform/jobs/service.ts",
|
|
6876
|
+
"services/api/test/t134-scopes.test.ts",
|
|
6877
|
+
"services/api/test/t142-jobs.test.ts",
|
|
6878
|
+
"services/api/test/t90-calendar-connect.test.ts",
|
|
6879
|
+
"tools/release/src/assemble-page.ts"
|
|
6880
|
+
]),
|
|
6881
|
+
...markers("0.6.0", ["packages/client/src/data/page-update.ts"])
|
|
6882
|
+
];
|
|
6205
6883
|
// packages/contracts/src/config.ts
|
|
6206
6884
|
var ConfigVersionSchema = exports_external.string().regex(/^[0-9a-f]{12}$/, "expected a 12-character lower-case hex fingerprint");
|
|
6207
6885
|
var ConfigModelsSchema = exports_external.object({
|
|
@@ -6319,7 +6997,14 @@ var DemoObservationSchema = exports_external.object({
|
|
|
6319
6997
|
}).strict();
|
|
6320
6998
|
var DemoDecisionSchema = exports_external.object({ summary: exports_external.string().min(1), activity: exports_external.string().min(1).nullable(), matter: KeySchema.nullable() }).strict();
|
|
6321
6999
|
var DemoIntervalSchema = exports_external.object({ from: ClockSchema, to: ClockSchema, observations: exports_external.array(DemoObservationSchema).default([]), decision: DemoDecisionSchema.optional() }).strict();
|
|
6322
|
-
var DemoEntrySchema = exports_external.object({
|
|
7000
|
+
var DemoEntrySchema = exports_external.object({
|
|
7001
|
+
from: ClockSchema,
|
|
7002
|
+
to: ClockSchema,
|
|
7003
|
+
matter: KeySchema.nullable(),
|
|
7004
|
+
narrative: exports_external.string(),
|
|
7005
|
+
codes: exports_external.record(exports_external.string().min(1)).default({}),
|
|
7006
|
+
released: exports_external.boolean().optional()
|
|
7007
|
+
}).strict();
|
|
6323
7008
|
var DemoRecordingSchema = exports_external.object({ start: ClockSchema, stop: ClockSchema, intervals: exports_external.array(DemoIntervalSchema).min(1) }).strict();
|
|
6324
7009
|
var minutes = (clock) => Number(clock.slice(0, 2)) * 60 + Number(clock.slice(3));
|
|
6325
7010
|
function problemsOf(s) {
|
|
@@ -6382,6 +7067,14 @@ function problemsOf(s) {
|
|
|
6382
7067
|
for (let k = first;k <= last; k++)
|
|
6383
7068
|
covered.has(k) ? out.push([at, "entries do not overlap"]) : covered.add(k);
|
|
6384
7069
|
});
|
|
7070
|
+
s.entries.forEach((e, i) => {
|
|
7071
|
+
if (e.released !== true)
|
|
7072
|
+
return;
|
|
7073
|
+
if (e.matter === null)
|
|
7074
|
+
out.push([["entries", i, "released"], "a released entry has a matter"]);
|
|
7075
|
+
if (e.narrative.trim() === "")
|
|
7076
|
+
out.push([["entries", i, "released"], "a released entry has a narrative"]);
|
|
7077
|
+
});
|
|
6385
7078
|
const clock = (m) => `${String(Math.floor(m / 60)).padStart(2, "0")}:${String(m % 60).padStart(2, "0")}`;
|
|
6386
7079
|
if (s.entries.length > 0)
|
|
6387
7080
|
all.forEach((one, k) => one.work !== null && !covered.has(k) && out.push([["entries"], `the work at ${clock(one.from)} is in no entry`]));
|
|
@@ -6391,6 +7084,7 @@ var DemoScenarioSchema = exports_external.object({
|
|
|
6391
7084
|
format: exports_external.literal(1),
|
|
6392
7085
|
name: KeySchema,
|
|
6393
7086
|
description: exports_external.string().default(""),
|
|
7087
|
+
weekday: exports_external.number().int().min(1).max(5).optional(),
|
|
6394
7088
|
time_zone: exports_external.string().min(1),
|
|
6395
7089
|
unit_minutes: exports_external.number().int().min(1).max(60),
|
|
6396
7090
|
clients: exports_external.array(exports_external.object({ key: KeySchema, name: exports_external.string().min(1) }).strict()),
|
|
@@ -6407,22 +7101,55 @@ function replayGap(s) {
|
|
|
6407
7101
|
return "every interval needs its decision";
|
|
6408
7102
|
return s.entries.length === 0 ? "a replay needs the scenario's entries" : null;
|
|
6409
7103
|
}
|
|
7104
|
+
var DEMO_MAX_DAYS = 31;
|
|
6410
7105
|
var AdminDemoSeedSchema = exports_external.object({
|
|
6411
|
-
scenario: DemoScenarioSchema,
|
|
7106
|
+
scenario: DemoScenarioSchema.optional(),
|
|
7107
|
+
scenarios: exports_external.array(DemoScenarioSchema).min(1).max(7).optional(),
|
|
6412
7108
|
day: LocalDaySchema.optional(),
|
|
7109
|
+
anchor: LocalDaySchema.optional(),
|
|
7110
|
+
days: exports_external.number().int().min(1).max(DEMO_MAX_DAYS).optional(),
|
|
7111
|
+
members: exports_external.array(exports_external.string().email()).max(20).default([]),
|
|
6413
7112
|
replay: exports_external.boolean().default(false),
|
|
6414
7113
|
generate: exports_external.boolean().default(false)
|
|
7114
|
+
}).superRefine((body, ctx) => {
|
|
7115
|
+
if (body.scenario === undefined === (body.scenarios === undefined)) {
|
|
7116
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["scenario"], message: "give scenario or scenarios, one of the two" });
|
|
7117
|
+
}
|
|
7118
|
+
const range = body.anchor !== undefined || body.days !== undefined || body.scenarios !== undefined;
|
|
7119
|
+
if (range && body.day !== undefined)
|
|
7120
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["day"], message: "day seeds one day; anchor and days seed a range: give one form" });
|
|
7121
|
+
if (range && body.generate)
|
|
7122
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["generate"], message: "a range writes its days' entries itself: generate is false" });
|
|
7123
|
+
if (range && !body.replay)
|
|
7124
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["replay"], message: "a range of days is written from the scenarios' decisions and entries: replay is true" });
|
|
7125
|
+
const weekdays = (body.scenarios ?? []).map((one) => one.weekday);
|
|
7126
|
+
if (weekdays.length > 1 && weekdays.some((one) => one === undefined)) {
|
|
7127
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["scenarios"], message: "with more than one scenario, each names its weekday" });
|
|
7128
|
+
}
|
|
7129
|
+
if (new Set(weekdays).size !== weekdays.length)
|
|
7130
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["scenarios"], message: "a weekday is named twice" });
|
|
6415
7131
|
});
|
|
6416
7132
|
var AdminDemoDaySchema = exports_external.object({ day: LocalDaySchema, generate: exports_external.boolean().default(false) });
|
|
6417
7133
|
var counts = { member_id: MemberIdSchema, day: LocalDaySchema };
|
|
6418
|
-
var
|
|
6419
|
-
|
|
7134
|
+
var SeededDaySchema = exports_external.object({
|
|
7135
|
+
day: LocalDaySchema,
|
|
6420
7136
|
mode: exports_external.enum(["live", "replay"]),
|
|
6421
7137
|
recordings: exports_external.number().int(),
|
|
6422
7138
|
contexts: exports_external.object({ stored: exports_external.number().int(), repeated: exports_external.number().int() }),
|
|
6423
7139
|
created: exports_external.object({ clients: exports_external.number().int(), matters: exports_external.number().int() }),
|
|
6424
7140
|
generate_run: exports_external.string().nullable()
|
|
6425
7141
|
});
|
|
7142
|
+
var AdminDemoRangeDaySchema = SeededDaySchema.extend({
|
|
7143
|
+
scenario: KeySchema,
|
|
7144
|
+
replaced: exports_external.boolean(),
|
|
7145
|
+
entries: exports_external.object({ written: exports_external.number().int(), released: exports_external.number().int().optional(), released_owed: exports_external.number().int() })
|
|
7146
|
+
});
|
|
7147
|
+
var AdminDemoInvitedSchema = exports_external.object({ email: exports_external.string(), outcome: exports_external.enum(["invited", "member", "invited_before"]) });
|
|
7148
|
+
var AdminDemoSeedResultSchema = SeededDaySchema.extend({
|
|
7149
|
+
member_id: MemberIdSchema,
|
|
7150
|
+
range: exports_external.array(AdminDemoRangeDaySchema).optional(),
|
|
7151
|
+
members: exports_external.array(AdminDemoInvitedSchema).optional()
|
|
7152
|
+
});
|
|
6426
7153
|
var AdminDemoResetResultSchema = exports_external.object({
|
|
6427
7154
|
...counts,
|
|
6428
7155
|
runs_deleted: exports_external.number().int(),
|
|
@@ -6717,16 +7444,16 @@ var LocalEventSchema = exports_external.discriminatedUnion("type", [
|
|
|
6717
7444
|
var LocalHostEventSchema = exports_external.discriminatedUnion("name", [
|
|
6718
7445
|
exports_external.object({
|
|
6719
7446
|
name: exports_external.literal("permission.changed"),
|
|
6720
|
-
properties:
|
|
7447
|
+
properties: wireSchemaOf("permission.changed").strict()
|
|
6721
7448
|
}).strict(),
|
|
6722
7449
|
exports_external.object({
|
|
6723
7450
|
name: exports_external.literal("update.failed"),
|
|
6724
|
-
properties:
|
|
7451
|
+
properties: wireSchemaOf("update.failed").strict()
|
|
6725
7452
|
}).strict(),
|
|
6726
7453
|
exports_external.object({
|
|
6727
7454
|
name: exports_external.literal("setting.changed"),
|
|
6728
7455
|
properties: exports_external.object({
|
|
6729
|
-
setting:
|
|
7456
|
+
setting: wireSchemaOf("setting.changed").shape.setting.extract(["launch_at_login", "theme"]),
|
|
6730
7457
|
scope: exports_external.literal("device")
|
|
6731
7458
|
}).strict()
|
|
6732
7459
|
}).strict()
|
|
@@ -6914,6 +7641,40 @@ var ContextSignalsV1Schema = exports_external.object({
|
|
|
6914
7641
|
sent_at: TimestampSchema,
|
|
6915
7642
|
observations: exports_external.array(exports_external.object({ id: ObservationIdSchema, signals: SignalsSchema })).min(1)
|
|
6916
7643
|
});
|
|
7644
|
+
// packages/contracts/src/release.ts
|
|
7645
|
+
var VersionSchema = exports_external.string().regex(/^\d+\.\d+\.\d+$/, "a release is MAJOR.MINOR.N");
|
|
7646
|
+
var HostKeySchema = exports_external.union([exports_external.literal("unkeyed"), exports_external.string().regex(/^h:[0-9a-f]{8}$/)]);
|
|
7647
|
+
var DAY_MS = 24 * 60 * 60000;
|
|
7648
|
+
var ReleaseSchema = exports_external.object({
|
|
7649
|
+
version: VersionSchema,
|
|
7650
|
+
commit: exports_external.string().regex(/^[0-9a-f]{40}$/),
|
|
7651
|
+
key: HostKeySchema,
|
|
7652
|
+
promoted_at: exports_external.string().datetime(),
|
|
7653
|
+
superseded_at: exports_external.string().datetime().nullable(),
|
|
7654
|
+
state: exports_external.enum(["current", "superseded", "withdrawn"]),
|
|
7655
|
+
urgent: exports_external.boolean(),
|
|
7656
|
+
desktop: VersionSchema.nullable()
|
|
7657
|
+
});
|
|
7658
|
+
var ReleaseIndexSchema = exports_external.object({
|
|
7659
|
+
releases: exports_external.array(ReleaseSchema)
|
|
7660
|
+
});
|
|
7661
|
+
var UpdateReleaseQuerySchema = exports_external.object({
|
|
7662
|
+
key: HostKeySchema,
|
|
7663
|
+
version: VersionSchema.optional(),
|
|
7664
|
+
device: exports_external.string().min(1).max(200).optional()
|
|
7665
|
+
});
|
|
7666
|
+
var UpdateStateSchema = exports_external.enum(["current", "available", "required"]);
|
|
7667
|
+
var ReleaseSummarySchema = exports_external.object({
|
|
7668
|
+
version: VersionSchema,
|
|
7669
|
+
key: HostKeySchema,
|
|
7670
|
+
promoted_at: exports_external.string().datetime(),
|
|
7671
|
+
desktop: VersionSchema.nullable()
|
|
7672
|
+
});
|
|
7673
|
+
var UpdateReleaseResponseSchema = exports_external.object({
|
|
7674
|
+
current: ReleaseSummarySchema.nullable(),
|
|
7675
|
+
state: UpdateStateSchema,
|
|
7676
|
+
expired: exports_external.boolean()
|
|
7677
|
+
});
|
|
6917
7678
|
// packages/contracts/src/run.ts
|
|
6918
7679
|
var VerdictSchema = exports_external.enum(["matter", "unmatched", "absent"]);
|
|
6919
7680
|
var WORK_VERDICTS = ["matter", "unmatched"];
|
|
@@ -6951,132 +7712,6 @@ var RunSchema = exports_external.object({
|
|
|
6951
7712
|
message: "an absent run is never an entry, so it carries no proposed attributes and no judged text",
|
|
6952
7713
|
path: ["attributes"]
|
|
6953
7714
|
});
|
|
6954
|
-
// packages/contracts/src/tools.ts
|
|
6955
|
-
var TimeRangeSchema = exports_external.object({
|
|
6956
|
-
from: TimestampSchema.optional(),
|
|
6957
|
-
to: TimestampSchema.optional()
|
|
6958
|
-
});
|
|
6959
|
-
var ZoomSchema = exports_external.enum(["day", "entry", "observation"]);
|
|
6960
|
-
var TimelineInputSchema = exports_external.object({
|
|
6961
|
-
range: TimeRangeSchema,
|
|
6962
|
-
zoom: ZoomSchema
|
|
6963
|
-
});
|
|
6964
|
-
var TimelineTotalsSchema = exports_external.object({
|
|
6965
|
-
entries: exports_external.number().int().min(0),
|
|
6966
|
-
observations: exports_external.number().int().min(0),
|
|
6967
|
-
elapsed_minutes: exports_external.number().int().min(0),
|
|
6968
|
-
away_minutes: exports_external.number().int().min(0),
|
|
6969
|
-
span_seconds: exports_external.number().int().min(0)
|
|
6970
|
-
});
|
|
6971
|
-
var AwayGapSchema = exports_external.object({
|
|
6972
|
-
from: TimestampSchema,
|
|
6973
|
-
to: TimestampSchema,
|
|
6974
|
-
minutes: exports_external.number().int().min(0),
|
|
6975
|
-
reason: exports_external.enum(["no-signal", "lock", "sleep"]),
|
|
6976
|
-
note: exports_external.string().nullable().default(null)
|
|
6977
|
-
});
|
|
6978
|
-
var EntryRefSchema = exports_external.object({
|
|
6979
|
-
id: EntryIdSchema,
|
|
6980
|
-
narrative: exports_external.string(),
|
|
6981
|
-
t_start: TimestampSchema,
|
|
6982
|
-
t_end: TimestampSchema,
|
|
6983
|
-
elapsed_minutes: exports_external.number().int().min(0),
|
|
6984
|
-
idle_ratio: exports_external.number().min(0).max(1),
|
|
6985
|
-
matter_id: MatterIdSchema.nullable(),
|
|
6986
|
-
assignment: AssignmentSchema.nullable(),
|
|
6987
|
-
status: EntryStatusSchema,
|
|
6988
|
-
observation_count: exports_external.number().int().min(0),
|
|
6989
|
-
recording_id: RecordingIdSchema.nullable(),
|
|
6990
|
-
owner: EntryOwnerSchema
|
|
6991
|
-
});
|
|
6992
|
-
var ObservationRefSchema = exports_external.object({
|
|
6993
|
-
id: ObservationIdSchema,
|
|
6994
|
-
t_start: TimestampSchema,
|
|
6995
|
-
t_end: TimestampSchema,
|
|
6996
|
-
app: exports_external.string(),
|
|
6997
|
-
window_title: exports_external.string(),
|
|
6998
|
-
url: exports_external.string().nullable(),
|
|
6999
|
-
entry_id: EntryIdSchema.nullable()
|
|
7000
|
-
});
|
|
7001
|
-
var EvidenceRunSchema = exports_external.object({
|
|
7002
|
-
kind: exports_external.enum(["run", "idle"]),
|
|
7003
|
-
t_start: TimestampSchema,
|
|
7004
|
-
t_end: TimestampSchema,
|
|
7005
|
-
minutes: exports_external.number().int().min(0),
|
|
7006
|
-
app: exports_external.string(),
|
|
7007
|
-
title: exports_external.string(),
|
|
7008
|
-
observation_ids: exports_external.array(ObservationIdSchema)
|
|
7009
|
-
});
|
|
7010
|
-
var TimelineDaySchema = exports_external.object({
|
|
7011
|
-
id: DayIdSchema,
|
|
7012
|
-
date: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
7013
|
-
entries: exports_external.array(EntryRefSchema),
|
|
7014
|
-
away: exports_external.array(AwayGapSchema),
|
|
7015
|
-
totals: TimelineTotalsSchema
|
|
7016
|
-
});
|
|
7017
|
-
var TimelineResultSchema = exports_external.discriminatedUnion("zoom", [
|
|
7018
|
-
exports_external.object({
|
|
7019
|
-
zoom: exports_external.literal("day"),
|
|
7020
|
-
range: TimeRangeSchema,
|
|
7021
|
-
days: exports_external.array(TimelineDaySchema),
|
|
7022
|
-
totals: TimelineTotalsSchema
|
|
7023
|
-
}),
|
|
7024
|
-
exports_external.object({
|
|
7025
|
-
zoom: exports_external.literal("entry"),
|
|
7026
|
-
range: TimeRangeSchema,
|
|
7027
|
-
entries: exports_external.array(EntryRefSchema),
|
|
7028
|
-
totals: TimelineTotalsSchema
|
|
7029
|
-
}),
|
|
7030
|
-
exports_external.object({
|
|
7031
|
-
zoom: exports_external.literal("observation"),
|
|
7032
|
-
range: TimeRangeSchema,
|
|
7033
|
-
observations: exports_external.array(ObservationRefSchema),
|
|
7034
|
-
totals: TimelineTotalsSchema
|
|
7035
|
-
})
|
|
7036
|
-
]);
|
|
7037
|
-
var ReadInputSchema = exports_external.object({
|
|
7038
|
-
id: exports_external.string().min(1)
|
|
7039
|
-
});
|
|
7040
|
-
var ReadObservationSchema = exports_external.object({
|
|
7041
|
-
type: exports_external.literal("observation"),
|
|
7042
|
-
id: ObservationIdSchema,
|
|
7043
|
-
t_start: TimestampSchema,
|
|
7044
|
-
t_end: TimestampSchema,
|
|
7045
|
-
app: exports_external.string(),
|
|
7046
|
-
window_title: exports_external.string(),
|
|
7047
|
-
url: exports_external.string().nullable(),
|
|
7048
|
-
signals: SignalsSchema,
|
|
7049
|
-
snapshot_ids: exports_external.array(SnapshotIdSchema),
|
|
7050
|
-
entry_id: EntryIdSchema.nullable(),
|
|
7051
|
-
text: exports_external.string()
|
|
7052
|
-
});
|
|
7053
|
-
var ReadEntrySchema = exports_external.object({
|
|
7054
|
-
type: exports_external.literal("entry"),
|
|
7055
|
-
id: EntryIdSchema,
|
|
7056
|
-
narrative: exports_external.string(),
|
|
7057
|
-
t_start: TimestampSchema,
|
|
7058
|
-
t_end: TimestampSchema,
|
|
7059
|
-
elapsed_minutes: exports_external.number().int().min(0),
|
|
7060
|
-
idle_ratio: exports_external.number().min(0).max(1),
|
|
7061
|
-
matter_id: MatterIdSchema.nullable(),
|
|
7062
|
-
assignment: AssignmentSchema.nullable(),
|
|
7063
|
-
status: EntryStatusSchema,
|
|
7064
|
-
observations: exports_external.array(ObservationRefSchema),
|
|
7065
|
-
evidence: exports_external.array(EvidenceRunSchema)
|
|
7066
|
-
});
|
|
7067
|
-
var ReadDaySchema = exports_external.object({
|
|
7068
|
-
type: exports_external.literal("day"),
|
|
7069
|
-
id: DayIdSchema,
|
|
7070
|
-
date: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
7071
|
-
entries: exports_external.array(EntryRefSchema),
|
|
7072
|
-
away: exports_external.array(AwayGapSchema),
|
|
7073
|
-
totals: TimelineTotalsSchema
|
|
7074
|
-
});
|
|
7075
|
-
var ReadResultSchema = exports_external.discriminatedUnion("type", [
|
|
7076
|
-
ReadObservationSchema,
|
|
7077
|
-
ReadEntrySchema,
|
|
7078
|
-
ReadDaySchema
|
|
7079
|
-
]);
|
|
7080
7715
|
// packages/contracts/src/trace.ts
|
|
7081
7716
|
var TraceMessageSchema = exports_external.object({
|
|
7082
7717
|
role: exports_external.string().min(1),
|
|
@@ -7145,50 +7780,13 @@ import { join } from "node:path";
|
|
|
7145
7780
|
|
|
7146
7781
|
// tools/admin/src/files.ts
|
|
7147
7782
|
import { randomUUID } from "node:crypto";
|
|
7148
|
-
import { chmodSync, closeSync, mkdirSync, openSync, renameSync, rmSync,
|
|
7149
|
-
import { dirname } from "node:path";
|
|
7783
|
+
import { chmodSync, closeSync, mkdirSync, openSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7150
7784
|
function makePrivateDirectory(path) {
|
|
7151
7785
|
mkdirSync(path, { recursive: true, mode: 448 });
|
|
7152
7786
|
const mode = statSync(path).mode & 511;
|
|
7153
7787
|
if ((mode & 63) !== 0)
|
|
7154
7788
|
chmodSync(path, 448);
|
|
7155
7789
|
}
|
|
7156
|
-
function claimExportDirectory(path) {
|
|
7157
|
-
const cannot = (err) => new AdminUsageError(`--out ${path} cannot be created: ${err.message}`);
|
|
7158
|
-
try {
|
|
7159
|
-
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
7160
|
-
} catch (err) {
|
|
7161
|
-
throw cannot(err);
|
|
7162
|
-
}
|
|
7163
|
-
try {
|
|
7164
|
-
mkdirSync(path, { mode: 448 });
|
|
7165
|
-
return { created: true };
|
|
7166
|
-
} catch (err) {
|
|
7167
|
-
if (err.code !== "EEXIST")
|
|
7168
|
-
throw cannot(err);
|
|
7169
|
-
}
|
|
7170
|
-
const stat = statSync(path);
|
|
7171
|
-
if (!stat.isDirectory())
|
|
7172
|
-
throw new AdminUsageError(`--out ${path} is not a directory; name a new folder, and the export creates it 0700.`);
|
|
7173
|
-
if (process.platform === "win32")
|
|
7174
|
-
return { created: false };
|
|
7175
|
-
const mode = stat.mode & 511;
|
|
7176
|
-
if ((mode & 63) !== 0) {
|
|
7177
|
-
throw new AdminUsageError(`--out ${path} can be read by other accounts on this machine (mode ${mode.toString(8)}); ` + "name a new folder, which the export creates 0700, or run chmod 700 on this one yourself.");
|
|
7178
|
-
}
|
|
7179
|
-
const uid = process.getuid?.();
|
|
7180
|
-
if (uid !== undefined && stat.uid !== uid) {
|
|
7181
|
-
throw new AdminUsageError(`--out ${path} belongs to another account on this machine; name a folder of your own.`);
|
|
7182
|
-
}
|
|
7183
|
-
return { created: false };
|
|
7184
|
-
}
|
|
7185
|
-
function releaseExportDirectory(path, claimed) {
|
|
7186
|
-
if (!claimed.created)
|
|
7187
|
-
return;
|
|
7188
|
-
try {
|
|
7189
|
-
rmdirSync(path);
|
|
7190
|
-
} catch {}
|
|
7191
|
-
}
|
|
7192
7790
|
function writePrivateFile(path, body) {
|
|
7193
7791
|
const temporary = `${path}.${randomUUID()}.part`;
|
|
7194
7792
|
const handle = openSync(temporary, "wx", 384);
|
|
@@ -7204,11 +7802,6 @@ function writePrivateFile(path, body) {
|
|
|
7204
7802
|
rmSync(temporary, { force: true });
|
|
7205
7803
|
}
|
|
7206
7804
|
}
|
|
7207
|
-
function jsonLines(rows) {
|
|
7208
|
-
return rows.map((row) => JSON.stringify(row)).join(`
|
|
7209
|
-
`) + (rows.length === 0 ? "" : `
|
|
7210
|
-
`);
|
|
7211
|
-
}
|
|
7212
7805
|
|
|
7213
7806
|
// tools/admin/src/session.ts
|
|
7214
7807
|
var WORKOS_API = "https://api.workos.com";
|
|
@@ -7465,24 +8058,6 @@ function purgeSection(label, rows) {
|
|
|
7465
8058
|
return [`${label}: none`];
|
|
7466
8059
|
return [`${label}: ${rows.length}`, ...rows.map(purgeLine)];
|
|
7467
8060
|
}
|
|
7468
|
-
function exportLines(exports, error) {
|
|
7469
|
-
if (exports === null) {
|
|
7470
|
-
return [
|
|
7471
|
-
`past exports could not be read: ${error}`,
|
|
7472
|
-
"The tier is set and the cards are deleted; only this list is missing. Run the command again to see it."
|
|
7473
|
-
];
|
|
7474
|
-
}
|
|
7475
|
-
if (exports.length === 0)
|
|
7476
|
-
return ["past exports none: no export of this firm was ever made."];
|
|
7477
|
-
const day = (iso) => iso.slice(0, 10);
|
|
7478
|
-
return [
|
|
7479
|
-
`past exports ${exports.length}`,
|
|
7480
|
-
...exports.map((row) => ` ${row.created_at} ${row.operator} ${day(row.from_at)} to ${day(row.to_at)} ` + `${row.rows} row${row.rows === 1 ? "" : "s"}`),
|
|
7481
|
-
"",
|
|
7482
|
-
"Each of those exports wrote this firm's cards and narratives, decrypted, onto the operator's own machine:",
|
|
7483
|
-
"this command deletes the rows in the cloud and can delete no file on a laptop, so find those files and delete them by hand."
|
|
7484
|
-
];
|
|
7485
|
-
}
|
|
7486
8061
|
function table(headers, rows) {
|
|
7487
8062
|
const widths = headers.map((head, i) => Math.max(head.length, ...rows.map((row) => row[i].length)));
|
|
7488
8063
|
const line = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
|
|
@@ -7600,100 +8175,9 @@ function parseMatterSets(pairs) {
|
|
|
7600
8175
|
return out;
|
|
7601
8176
|
}
|
|
7602
8177
|
|
|
7603
|
-
// tools/admin/src/unseal.ts
|
|
7604
|
-
import { spawnSync } from "node:child_process";
|
|
7605
|
-
import { createDecipheriv } from "node:crypto";
|
|
7606
|
-
var KMS_ENDPOINT = "https://cloudkms.googleapis.com/v1";
|
|
7607
|
-
var FORMAT_VERSION = 1;
|
|
7608
|
-
var NONCE_BYTES = 12;
|
|
7609
|
-
var TAG_BYTES = 16;
|
|
7610
|
-
var HEADER_BYTES = 1 + NONCE_BYTES;
|
|
7611
|
-
|
|
7612
|
-
class UnsealError extends Error {
|
|
7613
|
-
constructor(message) {
|
|
7614
|
-
super(message);
|
|
7615
|
-
this.name = "UnsealError";
|
|
7616
|
-
}
|
|
7617
|
-
}
|
|
7618
|
-
function keyVersionName(researchKeyName, version) {
|
|
7619
|
-
return `${researchKeyName.replace(/\/cryptoKeyVersions\/\d+$/, "")}/cryptoKeyVersions/${version}`;
|
|
7620
|
-
}
|
|
7621
|
-
async function asymmetricDecrypt(versionName, sealedKey, accessToken, fetchImpl) {
|
|
7622
|
-
const response = await fetchImpl(`${KMS_ENDPOINT}/${versionName}:asymmetricDecrypt`, {
|
|
7623
|
-
method: "POST",
|
|
7624
|
-
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
7625
|
-
body: JSON.stringify({ ciphertext: sealedKey.toString("base64") })
|
|
7626
|
-
});
|
|
7627
|
-
if (!response.ok) {
|
|
7628
|
-
throw new UnsealError(`Cloud KMS answered ${response.status} to asymmetricDecrypt on ${versionName}. ` + "A 403 means this identity may not unseal research cards; two named people hold that role and " + "nobody else, the runtime service account included.");
|
|
7629
|
-
}
|
|
7630
|
-
const body = await response.json();
|
|
7631
|
-
if (typeof body.plaintext !== "string")
|
|
7632
|
-
throw new UnsealError("Cloud KMS answered asymmetricDecrypt without a plaintext.");
|
|
7633
|
-
return Buffer.from(body.plaintext, "base64");
|
|
7634
|
-
}
|
|
7635
|
-
function openColumn(rowKey, column) {
|
|
7636
|
-
if (rowKey.length !== 32)
|
|
7637
|
-
throw new UnsealError(`a row key is 32 bytes, got ${rowKey.length}`);
|
|
7638
|
-
if (column.length < HEADER_BYTES + TAG_BYTES)
|
|
7639
|
-
throw new UnsealError("ciphertext is too short to be a sealed column");
|
|
7640
|
-
if (column[0] !== FORMAT_VERSION)
|
|
7641
|
-
throw new UnsealError(`ciphertext format ${column[0]} is not one this tool reads`);
|
|
7642
|
-
const decipher = createDecipheriv("aes-256-gcm", rowKey, column.subarray(1, HEADER_BYTES), { authTagLength: TAG_BYTES });
|
|
7643
|
-
decipher.setAAD(column.subarray(0, 1));
|
|
7644
|
-
decipher.setAuthTag(column.subarray(column.length - TAG_BYTES));
|
|
7645
|
-
try {
|
|
7646
|
-
return Buffer.concat([decipher.update(column.subarray(HEADER_BYTES, column.length - TAG_BYTES)), decipher.final()]);
|
|
7647
|
-
} catch {
|
|
7648
|
-
throw new UnsealError("ciphertext did not authenticate under this key");
|
|
7649
|
-
}
|
|
7650
|
-
}
|
|
7651
|
-
async function openCards(rows, researchKeyName, accessToken, fetchImpl = fetch) {
|
|
7652
|
-
const opened = [];
|
|
7653
|
-
for (const row of rows) {
|
|
7654
|
-
const versionName = keyVersionName(researchKeyName, row.key_version);
|
|
7655
|
-
let rowKey = null;
|
|
7656
|
-
try {
|
|
7657
|
-
rowKey = await asymmetricDecrypt(versionName, Buffer.from(row.sealed_key, "base64"), accessToken, fetchImpl);
|
|
7658
|
-
const card = JSON.parse(openColumn(rowKey, Buffer.from(row.card_ct, "base64")).toString("utf8"));
|
|
7659
|
-
const answer = openColumn(rowKey, Buffer.from(row.answer_ct, "base64")).toString("utf8");
|
|
7660
|
-
opened.push({
|
|
7661
|
-
id: row.id,
|
|
7662
|
-
tenant_id: row.tenant_id,
|
|
7663
|
-
member_id: row.member_id,
|
|
7664
|
-
device_id: row.device_id,
|
|
7665
|
-
purpose: row.purpose,
|
|
7666
|
-
model: row.model,
|
|
7667
|
-
created_at: row.created_at,
|
|
7668
|
-
key_version: row.key_version,
|
|
7669
|
-
card,
|
|
7670
|
-
answer
|
|
7671
|
-
});
|
|
7672
|
-
} catch (err) {
|
|
7673
|
-
throw new UnsealError(`card ${row.id} did not unseal under ${versionName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7674
|
-
} finally {
|
|
7675
|
-
rowKey?.fill(0);
|
|
7676
|
-
}
|
|
7677
|
-
}
|
|
7678
|
-
return opened;
|
|
7679
|
-
}
|
|
7680
|
-
function googleAccessToken(env, platform) {
|
|
7681
|
-
const given = env["GOOGLE_OAUTH_ACCESS_TOKEN"]?.trim();
|
|
7682
|
-
if (given !== undefined && given !== "")
|
|
7683
|
-
return given;
|
|
7684
|
-
const run2 = spawnSync("gcloud", ["auth", "print-access-token"], {
|
|
7685
|
-
encoding: "utf8",
|
|
7686
|
-
shell: platform === "win32",
|
|
7687
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
7688
|
-
});
|
|
7689
|
-
const token = typeof run2.stdout === "string" ? run2.stdout.trim() : "";
|
|
7690
|
-
if (run2.error === undefined && run2.status === 0 && token !== "")
|
|
7691
|
-
return token;
|
|
7692
|
-
throw new UnsealError("no Google access token: set GOOGLE_OAUTH_ACCESS_TOKEN, or install the gcloud CLI and run " + "`gcloud auth login` as yourself. The unseal runs as YOU, never as a service account.");
|
|
7693
|
-
}
|
|
7694
|
-
|
|
7695
8178
|
// tools/admin/src/demo.ts
|
|
7696
|
-
import { readFileSync as readFileSync3 } from "node:fs";
|
|
8179
|
+
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "node:fs";
|
|
8180
|
+
import { join as join2 } from "node:path";
|
|
7697
8181
|
function readScenario(path) {
|
|
7698
8182
|
let raw;
|
|
7699
8183
|
try {
|
|
@@ -7709,21 +8193,88 @@ ${parsed.error.issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message
|
|
|
7709
8193
|
}
|
|
7710
8194
|
return parsed.data;
|
|
7711
8195
|
}
|
|
8196
|
+
var isDirectory = (path) => {
|
|
8197
|
+
try {
|
|
8198
|
+
return statSync2(path).isDirectory();
|
|
8199
|
+
} catch {
|
|
8200
|
+
return false;
|
|
8201
|
+
}
|
|
8202
|
+
};
|
|
8203
|
+
function readScenarios(path) {
|
|
8204
|
+
const files = isDirectory(path) ? readdirSync(path).filter((name) => name.endsWith(".json")).sort().map((name) => join2(path, name)) : [path];
|
|
8205
|
+
if (files.length === 0)
|
|
8206
|
+
throw new AdminUsageError(`${path} holds no scenario file`);
|
|
8207
|
+
return files.map((file) => ({ path: file, scenario: readScenario(file) }));
|
|
8208
|
+
}
|
|
7712
8209
|
function validateScenario(argv) {
|
|
7713
8210
|
const [path, ...rest] = argv;
|
|
7714
8211
|
if (path === undefined || path.startsWith("--") || rest.some((arg) => arg !== "--json"))
|
|
7715
|
-
throw new AdminUsageError("demo validate takes one scenario file, and --json");
|
|
7716
|
-
const
|
|
7717
|
-
const facts = {
|
|
8212
|
+
throw new AdminUsageError("demo validate takes one scenario file or directory, and --json");
|
|
8213
|
+
const all = readScenarios(path).map(({ scenario: s }) => ({
|
|
7718
8214
|
name: s.name,
|
|
8215
|
+
weekday: s.weekday ?? null,
|
|
7719
8216
|
recordings: s.recordings.length,
|
|
7720
8217
|
intervals: s.recordings.reduce((n, r) => n + r.intervals.length, 0),
|
|
7721
8218
|
entries: s.entries.length,
|
|
8219
|
+
released: s.entries.filter((e) => e.released === true).length,
|
|
7722
8220
|
replay: replayGap(s) ?? "yes"
|
|
7723
|
-
};
|
|
8221
|
+
}));
|
|
7724
8222
|
if (rest.includes("--json"))
|
|
7725
|
-
return { code: 0, lines: [JSON.stringify({ valid: true, ...
|
|
7726
|
-
return {
|
|
8223
|
+
return { code: 0, lines: [JSON.stringify(all.length === 1 ? { valid: true, ...all[0] } : { valid: true, scenarios: all }, null, 2)] };
|
|
8224
|
+
return {
|
|
8225
|
+
code: 0,
|
|
8226
|
+
lines: all.flatMap((facts) => [
|
|
8227
|
+
`valid ${facts.name}${facts.weekday === null ? "" : ` weekday ${facts.weekday}`}`,
|
|
8228
|
+
`recordings ${facts.recordings}, ${facts.intervals} intervals, ${facts.entries} entries${facts.released === 0 ? "" : `, ${facts.released} released`}`,
|
|
8229
|
+
`replay ${facts.replay}`
|
|
8230
|
+
])
|
|
8231
|
+
};
|
|
8232
|
+
}
|
|
8233
|
+
function membersOf(run2) {
|
|
8234
|
+
const value = run2.flags.values.get("members");
|
|
8235
|
+
if (value === undefined)
|
|
8236
|
+
return [];
|
|
8237
|
+
const emails = value.split(",").map((one) => one.trim()).filter((one) => one !== "");
|
|
8238
|
+
const bad = emails.find((one) => !/^[^@\s]+@[^@\s]+$/.test(one));
|
|
8239
|
+
if (bad !== undefined)
|
|
8240
|
+
throw new AdminUsageError(`--members is a comma-separated list of addresses, got ${JSON.stringify(bad)}`);
|
|
8241
|
+
return emails;
|
|
8242
|
+
}
|
|
8243
|
+
function daysOf(run2) {
|
|
8244
|
+
const value = run2.flags.values.get("days");
|
|
8245
|
+
if (value === undefined)
|
|
8246
|
+
return;
|
|
8247
|
+
const days = Number(value);
|
|
8248
|
+
if (!Number.isInteger(days) || days < 1 || days > DEMO_MAX_DAYS)
|
|
8249
|
+
throw new AdminUsageError(`--days is a whole number from 1 to ${DEMO_MAX_DAYS}, got ${JSON.stringify(value)}`);
|
|
8250
|
+
return days;
|
|
8251
|
+
}
|
|
8252
|
+
function seedBody(run2) {
|
|
8253
|
+
const path = required(run2.flags, "scenario", "the scenario file to seed, or a directory of week files with --anchor or --days");
|
|
8254
|
+
const members = membersOf(run2);
|
|
8255
|
+
const days = daysOf(run2);
|
|
8256
|
+
const anchor = run2.flags.values.has("anchor") ? dayOf2(run2.flags, "anchor", "the last day of the range") : undefined;
|
|
8257
|
+
const range = days !== undefined || anchor !== undefined;
|
|
8258
|
+
if (!range) {
|
|
8259
|
+
if (isDirectory(path))
|
|
8260
|
+
throw new AdminUsageError("a directory of scenarios seeds a range: give --days or --anchor, or name one file");
|
|
8261
|
+
const scenario = readScenario(path);
|
|
8262
|
+
const day2 = run2.flags.values.has("day") ? dayOf2(run2.flags, "day", "the day to seed on") : undefined;
|
|
8263
|
+
return { body: { scenario, ...day2 === undefined ? {} : { day: day2 }, replay: switched(run2.flags, "replay"), generate: switched(run2.flags, "generate"), members }, names: scenario.name };
|
|
8264
|
+
}
|
|
8265
|
+
if (run2.flags.values.has("day"))
|
|
8266
|
+
throw new AdminUsageError("--day seeds one day; --anchor and --days seed a range: give one form");
|
|
8267
|
+
if (switched(run2.flags, "generate"))
|
|
8268
|
+
throw new AdminUsageError("a range writes its days' entries itself: drop --generate");
|
|
8269
|
+
const read = readScenarios(path).map((one) => one.scenario);
|
|
8270
|
+
const week = read.filter((one) => one.weekday !== undefined);
|
|
8271
|
+
const scenarios = isDirectory(path) ? week : read;
|
|
8272
|
+
if (scenarios.length === 0)
|
|
8273
|
+
throw new AdminUsageError(`${path} holds no scenario that names its weekday`);
|
|
8274
|
+
return {
|
|
8275
|
+
body: { scenarios, ...anchor === undefined ? {} : { anchor }, ...days === undefined ? {} : { days }, replay: true, members },
|
|
8276
|
+
names: scenarios.map((one) => one.name).join(", ")
|
|
8277
|
+
};
|
|
7727
8278
|
}
|
|
7728
8279
|
async function runDemo(run2, subcommand) {
|
|
7729
8280
|
if (run2.environment.name === "production")
|
|
@@ -7744,24 +8295,31 @@ async function runDemo(run2, subcommand) {
|
|
|
7744
8295
|
]);
|
|
7745
8296
|
}
|
|
7746
8297
|
if (subcommand === "seed") {
|
|
7747
|
-
const
|
|
7748
|
-
const day = run2.flags.values.has("day") ? dayOf2(run2.flags, "day", "the day to seed on") : undefined;
|
|
7749
|
-
const body = { scenario, ...day === undefined ? {} : { day }, replay: switched(run2.flags, "replay"), generate: switched(run2.flags, "generate") };
|
|
8298
|
+
const { body, names } = seedBody(run2);
|
|
7750
8299
|
const done = AdminDemoSeedResultSchema.parse(expectOk(await call(run2.api, "POST", path("/demo/seed"), body)));
|
|
8300
|
+
const invited = (done.members ?? []).map((one) => `member ${one.email} ${one.outcome === "invited" ? "invited" : one.outcome === "member" ? "already a member, skipped" : "already invited, skipped"}`);
|
|
8301
|
+
if (done.range !== undefined) {
|
|
8302
|
+
return render(run2, done, [
|
|
8303
|
+
`seeded ${done.range.length} working days into ${done.member_id}`,
|
|
8304
|
+
...done.range.map((one) => `${one.day} ${one.scenario}: ${one.entries.written} drafts written, ${one.contexts.stored} contexts stored` + `${one.replaced ? ", replaced an older seed" : ""}${one.entries.released_owed === 0 ? "" : `, ${one.entries.released_owed} to release`}`),
|
|
8305
|
+
...invited
|
|
8306
|
+
]);
|
|
8307
|
+
}
|
|
7751
8308
|
return render(run2, done, [
|
|
7752
|
-
`seeded ${
|
|
8309
|
+
`seeded ${names} on ${done.day}, ${done.mode}, into ${done.member_id}`,
|
|
7753
8310
|
`contexts ${done.contexts.stored} stored, ${done.contexts.repeated} already there, over ${done.recordings} recordings`,
|
|
7754
8311
|
`created ${done.created.clients} clients, ${done.created.matters} matters`,
|
|
7755
|
-
done.generate_run === null ? "Generate the day from the app, or run demo reset --generate." : `generate ${done.generate_run}
|
|
8312
|
+
done.generate_run === null ? "Generate the day from the app, or run demo reset --generate." : `generate ${done.generate_run}`,
|
|
8313
|
+
...invited
|
|
7756
8314
|
]);
|
|
7757
8315
|
}
|
|
7758
8316
|
if (subcommand === "reset" || subcommand === "remove") {
|
|
7759
|
-
const
|
|
8317
|
+
const day2 = dayOf2(run2.flags, "day", `the seeded day to ${subcommand}`);
|
|
7760
8318
|
if (subcommand === "remove") {
|
|
7761
|
-
const done2 = AdminDemoRemoveResultSchema.parse(expectOk(await call(run2.api, "POST", path("/demo/remove"), { day })));
|
|
8319
|
+
const done2 = AdminDemoRemoveResultSchema.parse(expectOk(await call(run2.api, "POST", path("/demo/remove"), { day: day2 })));
|
|
7762
8320
|
return render(run2, done2, [`removed ${done2.day}: ${done2.recordings_deleted} recordings, ${done2.entries_deleted} entries`]);
|
|
7763
8321
|
}
|
|
7764
|
-
const done = AdminDemoResetResultSchema.parse(expectOk(await call(run2.api, "POST", path("/demo/reset"), { day, generate: switched(run2.flags, "generate") })));
|
|
8322
|
+
const done = AdminDemoResetResultSchema.parse(expectOk(await call(run2.api, "POST", path("/demo/reset"), { day: day2, generate: switched(run2.flags, "generate") })));
|
|
7765
8323
|
return render(run2, done, [
|
|
7766
8324
|
`reset ${done.day}: ${done.runs_deleted} Generate runs, ${done.drafts_deleted} drafts deleted, ${done.released_kept} released kept`,
|
|
7767
8325
|
...done.generate_run === null ? [] : [`generate ${done.generate_run}`]
|
|
@@ -7770,9 +8328,141 @@ async function runDemo(run2, subcommand) {
|
|
|
7770
8328
|
throw new AdminUsageError(`unknown command: demo ${subcommand ?? ""}`.trim());
|
|
7771
8329
|
}
|
|
7772
8330
|
|
|
8331
|
+
// tools/admin/src/telemetry.ts
|
|
8332
|
+
var POSTHOG_API = "https://eu.posthog.com";
|
|
8333
|
+
var ALERT_PREFIX = "sixminute alert:";
|
|
8334
|
+
function wantedAlerts() {
|
|
8335
|
+
return EVENT_KEYS.filter((key) => CATALOGUE[key].owner === "cloud" && CATALOGUE[key].expect === "daily").map((key) => ({
|
|
8336
|
+
event: CATALOGUE[key].name,
|
|
8337
|
+
name: `${ALERT_PREFIX} ${CATALOGUE[key].name}`,
|
|
8338
|
+
lower: 1
|
|
8339
|
+
}));
|
|
8340
|
+
}
|
|
8341
|
+
|
|
8342
|
+
class PosthogError extends Error {
|
|
8343
|
+
}
|
|
8344
|
+
function client(io, project) {
|
|
8345
|
+
const call2 = async (method, path, body) => {
|
|
8346
|
+
const response = await io.fetchImpl(`${POSTHOG_API}/api/projects/${encodeURIComponent(project)}${path}`, {
|
|
8347
|
+
method,
|
|
8348
|
+
headers: { Authorization: `Bearer ${io.posthogKey}`, "Content-Type": "application/json", Accept: "application/json" },
|
|
8349
|
+
...body === undefined ? {} : { body: JSON.stringify(body) }
|
|
8350
|
+
});
|
|
8351
|
+
const text = await response.text();
|
|
8352
|
+
if (!response.ok)
|
|
8353
|
+
throw new PosthogError(`PostHog answered ${response.status} to ${method} ${path}`);
|
|
8354
|
+
return text === "" ? null : JSON.parse(text);
|
|
8355
|
+
};
|
|
8356
|
+
return {
|
|
8357
|
+
call: call2,
|
|
8358
|
+
async list(path) {
|
|
8359
|
+
const page = await call2("GET", path);
|
|
8360
|
+
return page?.results ?? [];
|
|
8361
|
+
}
|
|
8362
|
+
};
|
|
8363
|
+
}
|
|
8364
|
+
var insightOf = (wanted) => ({
|
|
8365
|
+
name: wanted.name,
|
|
8366
|
+
description: `Counts ${wanted.event} a day for its alert (sixminute-admin telemetry alerts).`,
|
|
8367
|
+
query: {
|
|
8368
|
+
kind: "InsightVizNode",
|
|
8369
|
+
source: { kind: "TrendsQuery", series: [{ kind: "EventsNode", event: wanted.event, math: "total" }], interval: "day", dateRange: { date_from: "-14d" }, filterTestAccounts: true }
|
|
8370
|
+
}
|
|
8371
|
+
});
|
|
8372
|
+
var alertOf = (wanted, insight) => ({
|
|
8373
|
+
name: wanted.name,
|
|
8374
|
+
insight,
|
|
8375
|
+
condition: { type: "absolute_value" },
|
|
8376
|
+
threshold: { configuration: { type: "absolute", bounds: { lower: wanted.lower } } },
|
|
8377
|
+
config: { type: "TrendsAlertConfig", series_index: 0 },
|
|
8378
|
+
calculation_interval: "daily",
|
|
8379
|
+
skip_weekend: true,
|
|
8380
|
+
enabled: true
|
|
8381
|
+
});
|
|
8382
|
+
var insightIdOf = (alert) => typeof alert.insight === "number" ? alert.insight : alert.insight.id;
|
|
8383
|
+
async function telemetryAlerts(io, project, yes) {
|
|
8384
|
+
const posthog = client(io, project);
|
|
8385
|
+
const insights = (await posthog.list(`/insights/?search=${encodeURIComponent(ALERT_PREFIX)}&limit=500`)).filter((one) => one.name?.startsWith(ALERT_PREFIX));
|
|
8386
|
+
const alerts = (await posthog.list("/alerts/?limit=500")).filter((one) => one.name.startsWith(ALERT_PREFIX));
|
|
8387
|
+
const plan = [];
|
|
8388
|
+
for (const wanted of wantedAlerts()) {
|
|
8389
|
+
const insight = insights.find((one) => one.name === wanted.name);
|
|
8390
|
+
const alert = alerts.find((one) => one.name === wanted.name);
|
|
8391
|
+
if (insight === undefined) {
|
|
8392
|
+
plan.push({
|
|
8393
|
+
change: `create ${wanted.event}: an insight and its alert below ${wanted.lower} a weekday`,
|
|
8394
|
+
write: async () => {
|
|
8395
|
+
const made = await posthog.call("POST", "/insights/", insightOf(wanted));
|
|
8396
|
+
await posthog.call("POST", "/alerts/", alertOf(wanted, made.id));
|
|
8397
|
+
}
|
|
8398
|
+
});
|
|
8399
|
+
} else if (alert === undefined) {
|
|
8400
|
+
plan.push({ change: `create ${wanted.event}: its alert below ${wanted.lower} a weekday`, write: async () => void await posthog.call("POST", "/alerts/", alertOf(wanted, insight.id)) });
|
|
8401
|
+
} else if (alert.threshold?.configuration?.bounds?.lower !== wanted.lower || insightIdOf(alert) !== insight.id) {
|
|
8402
|
+
plan.push({
|
|
8403
|
+
change: `update ${wanted.event}: the alert's floor to ${wanted.lower} a weekday`,
|
|
8404
|
+
write: async () => void await posthog.call("PATCH", `/alerts/${encodeURIComponent(alert.id)}/`, alertOf(wanted, insight.id))
|
|
8405
|
+
});
|
|
8406
|
+
}
|
|
8407
|
+
}
|
|
8408
|
+
if (yes)
|
|
8409
|
+
for (const step of plan)
|
|
8410
|
+
await step.write();
|
|
8411
|
+
return { changes: plan.map((step) => step.change), written: yes && plan.length > 0 };
|
|
8412
|
+
}
|
|
8413
|
+
async function telemetryCheck(io, project, days) {
|
|
8414
|
+
const posthog = client(io, project);
|
|
8415
|
+
const answer = await posthog.call("POST", "/query/", {
|
|
8416
|
+
query: { kind: "HogQLQuery", query: `select event, count() from events where timestamp > now() - interval ${days} day group by event` }
|
|
8417
|
+
});
|
|
8418
|
+
const received = new Set((answer?.results ?? []).map(([event]) => event));
|
|
8419
|
+
const names = new Set(EVENT_KEYS.map((key) => CATALOGUE[key].name));
|
|
8420
|
+
const own = (event) => event.startsWith("$") && !names.has(event);
|
|
8421
|
+
return {
|
|
8422
|
+
missing: [...names].filter((name) => !received.has(name)).sort(),
|
|
8423
|
+
unknown: [...received].filter((event) => !names.has(event) && !own(event)).sort()
|
|
8424
|
+
};
|
|
8425
|
+
}
|
|
8426
|
+
async function runTelemetry(subcommand, values, switches, json, io) {
|
|
8427
|
+
const project = values.get("project")?.trim();
|
|
8428
|
+
if (subcommand !== "alerts" && subcommand !== "check")
|
|
8429
|
+
return { code: 2, lines: ["usage: sixminute-admin telemetry alerts|check --project <id> [--yes] [--days 7] [--json]"] };
|
|
8430
|
+
if (project === undefined || project === "")
|
|
8431
|
+
return { code: 2, lines: ["--project is required: the PostHog project the alerts live in"] };
|
|
8432
|
+
if (io.posthogKey === null || io.posthogKey === "")
|
|
8433
|
+
return { code: 2, lines: ["SIXMINUTE_POSTHOG_KEY is not set: a PostHog user API key with insight and alert write access"] };
|
|
8434
|
+
try {
|
|
8435
|
+
if (subcommand === "alerts") {
|
|
8436
|
+
const { changes, written } = await telemetryAlerts(io, project, switches.has("yes"));
|
|
8437
|
+
if (json)
|
|
8438
|
+
return { code: 0, lines: [JSON.stringify({ project, changes, written }, null, 2)] };
|
|
8439
|
+
if (changes.length === 0)
|
|
8440
|
+
return { code: 0, lines: [`project ${project}: every daily cloud event has its alert; nothing to change`] };
|
|
8441
|
+
return { code: 0, lines: [`project ${project}:`, ...changes.map((line) => ` ${line}`), written ? "written." : "nothing written: run again with --yes to write these."] };
|
|
8442
|
+
}
|
|
8443
|
+
const days = Number(values.get("days") ?? "7");
|
|
8444
|
+
if (!Number.isInteger(days) || days < 1 || days > 90)
|
|
8445
|
+
return { code: 2, lines: ["--days is a whole number of days from 1 to 90"] };
|
|
8446
|
+
const { missing, unknown } = await telemetryCheck(io, project, days);
|
|
8447
|
+
if (json)
|
|
8448
|
+
return { code: 0, lines: [JSON.stringify({ project, days, missing, unknown }, null, 2)] };
|
|
8449
|
+
return {
|
|
8450
|
+
code: missing.length === 0 && unknown.length === 0 ? 0 : 1,
|
|
8451
|
+
lines: [
|
|
8452
|
+
`project ${project}, the last ${days} days:`,
|
|
8453
|
+
` not received (${missing.length}): ${missing.join(", ") || "none"}`,
|
|
8454
|
+
` not in the catalogue (${unknown.length}): ${unknown.join(", ") || "none"}`
|
|
8455
|
+
]
|
|
8456
|
+
};
|
|
8457
|
+
} catch (err) {
|
|
8458
|
+
if (err instanceof PosthogError)
|
|
8459
|
+
return { code: 2, lines: [err.message] };
|
|
8460
|
+
throw err;
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8463
|
+
|
|
7773
8464
|
// tools/admin/src/run.ts
|
|
7774
8465
|
var ROLE_SLUGS = FIRM_ROLE_SLUGS;
|
|
7775
|
-
var CONFIDENTIAL_EXIT_CODE = 3;
|
|
7776
8466
|
var REFUSED_EXIT_CODE = 2;
|
|
7777
8467
|
var ADMIN_USAGE = `sixminute-admin, the operator's firm tool (D-73)
|
|
7778
8468
|
|
|
@@ -7820,20 +8510,13 @@ is required; without one every command answers 403.
|
|
|
7820
8510
|
|
|
7821
8511
|
sixminute-admin tier set --env <env> --tenant tenant_... --tier <standard|confidential> [--json]
|
|
7822
8512
|
Sets the firm's security tier, which no route lets a customer change.
|
|
7823
|
-
"confidential"
|
|
7824
|
-
|
|
7825
|
-
DESTRUCTIVE: setting "confidential" also DELETES every research card
|
|
7826
|
-
already stored for that firm AND the judged text of every interval
|
|
7827
|
-
it still holds, and prints how many of each. Those rows are
|
|
8513
|
+
DESTRUCTIVE: setting "confidential" also DELETES the cards the firm
|
|
8514
|
+
still holds from the retired export, and prints how many. They are
|
|
7828
8515
|
gone for good, there is no backup and nothing can bring them back.
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
Setting "standard" deletes nothing. If either purge fails after the
|
|
8516
|
+
The firm's entries, their narratives and their judged copies stay.
|
|
8517
|
+
Setting "standard" deletes nothing. If the deletion fails after the
|
|
7832
8518
|
tier has changed the command says so and exits non-zero; run it
|
|
7833
|
-
again, which
|
|
7834
|
-
It then LISTS this firm's past research exports, which are decrypted
|
|
7835
|
-
copies on operator machines that no command can delete: find those
|
|
7836
|
-
files and delete them by hand.
|
|
8519
|
+
again, which deletes again.
|
|
7837
8520
|
|
|
7838
8521
|
sixminute-admin tenant internal set --env <env> --tenant tenant_... --internal <true|false> [--json]
|
|
7839
8522
|
Marks a tenant as Unison's own, or not. Only an internal tenant can be
|
|
@@ -7846,36 +8529,14 @@ is required; without one every command answers 403.
|
|
|
7846
8529
|
Read it before a contract migration or before raising the floor
|
|
7847
8530
|
(docs/plans/six-minute/release-cycle/runbook.md).
|
|
7848
8531
|
|
|
7849
|
-
sixminute-admin raw-archive allow --env
|
|
7850
|
-
sixminute-admin raw-archive revoke --env
|
|
8532
|
+
sixminute-admin raw-archive allow --env staging --tenant tenant_... [--json]
|
|
8533
|
+
sixminute-admin raw-archive revoke --env staging --tenant tenant_... [--json]
|
|
7851
8534
|
Lets a person on the tenant switch on the raw capture archive on their
|
|
7852
8535
|
own device (a development copy of a whole day, kept on that device
|
|
7853
8536
|
and never uploaded), or withdraws it; a device that had it on switches
|
|
7854
8537
|
itself off at its next policy read. allow refuses a tenant that is not
|
|
7855
|
-
internal and exits 2, and the database refuses it again.
|
|
7856
|
-
|
|
7857
|
-
sixminute-admin research-export allow --env <env> --tenant tenant_... [--json]
|
|
7858
|
-
sixminute-admin research-export revoke --env <env> --tenant tenant_... [--json]
|
|
7859
|
-
Records the firm's WRITTEN instruction to keep research data for it, or
|
|
7860
|
-
withdraws it (policy research_export, false until set). Run allow only
|
|
7861
|
-
on that written instruction. Without it no research card is stored for
|
|
7862
|
-
the firm, from the next completion; revoking deletes no card already
|
|
7863
|
-
stored, which the 30-day purge removes.
|
|
7864
|
-
|
|
7865
|
-
sixminute-admin research export --env <env> --tenant tenant_... --from YYYY-MM-DD --to YYYY-MM-DD --out <dir> [--json]
|
|
7866
|
-
Unseals one firm's research cards over a day range onto this machine
|
|
7867
|
-
and writes two jsonl files in --out: a new folder is created 0700, and
|
|
7868
|
-
an existing one is refused unless it is yours and 0700. The cards,
|
|
7869
|
-
and one line per entry the engine judged in that range with the entry
|
|
7870
|
-
as it now stands beside the frozen copy. The audit row naming the
|
|
7871
|
-
operator, the firm and the range is written BEFORE anything is
|
|
7872
|
-
unsealed, in the same transaction as the tier check and holding the
|
|
7873
|
-
firm's row, so this command and "tier set confidential" cannot
|
|
7874
|
-
interleave. Half-open at the top: --to is not included. A confidential
|
|
7875
|
-
firm exports nothing, no file is written and the command exits
|
|
7876
|
-
non-zero.
|
|
7877
|
-
The unseal runs as you, with your own Google sign-in (gcloud auth
|
|
7878
|
-
login); only the people granted the research key can open a card.
|
|
8538
|
+
internal and exits 2, and the database refuses it again. Staging (or
|
|
8539
|
+
local) only: both refuse production, which has no raw archive.
|
|
7879
8540
|
|
|
7880
8541
|
sixminute-admin retention set --env <env> --tenant tenant_... --days <1-365> [--json]
|
|
7881
8542
|
Sets how many days the cloud keeps the firm's interval contexts after
|
|
@@ -7896,11 +8557,15 @@ is required; without one every command answers 403.
|
|
|
7896
8557
|
drafts under its own key, and gives intervals stored before the context
|
|
7897
8558
|
store an age, a bounded pass at a time until nothing is left.
|
|
7898
8559
|
|
|
7899
|
-
sixminute-admin
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
8560
|
+
sixminute-admin scrub plaintext --env <env> --tenant tenant_... [--json]
|
|
8561
|
+
Starts the plaintext scrub of the firm's old rows, one job per active
|
|
8562
|
+
member, and prints how many it started: each job clears the plaintext
|
|
8563
|
+
twin of a sealed value (an entry's judged copy, an old interval's
|
|
8564
|
+
summary and label) only where the sealed value opens to the same
|
|
8565
|
+
content, so nothing is lost. Run backfill sealed first. Refused, exit
|
|
8566
|
+
2, while a release older than the API's may still be served, because
|
|
8567
|
+
an older revision still reads the plaintext. Safe to run again: a
|
|
8568
|
+
member's job already started today is not started twice.
|
|
7904
8569
|
|
|
7905
8570
|
sixminute-admin entries purge --env <env> --tenant tenant_... --author member_... (--demo | --unmatched | both) [--dry-run] [--yes] [--json]
|
|
7906
8571
|
Removes entry rows from one firm. A person may only soft-delete
|
|
@@ -7908,7 +8573,8 @@ is required; without one every command answers 403.
|
|
|
7908
8573
|
from the table: this is the operator's hand, and it
|
|
7909
8574
|
runs under D-162's licence, which lasts only while the owner is the
|
|
7910
8575
|
product's one user.
|
|
7911
|
-
--demo the rows
|
|
8576
|
+
--demo the rows the retired demo seeder minted (id starts entry_demo-).
|
|
8577
|
+
A released one is never deleted: it is listed as kept.
|
|
7912
8578
|
--unmatched engine-written rows charged to no matter: not the demo
|
|
7913
8579
|
prefix, a judged copy (as_judged set, so never a
|
|
7914
8580
|
hand-made entry), and matter_id null. That is the residue the
|
|
@@ -7929,12 +8595,22 @@ is required; without one every command answers 403.
|
|
|
7929
8595
|
The flag, the caps in force, and your own seeded days, live or replay.
|
|
7930
8596
|
sixminute-admin demo validate tools/demo-scenarios/example-small.json [--json]
|
|
7931
8597
|
Checks a scenario file offline: its shape, its times and every reference.
|
|
7932
|
-
sixminute-admin demo seed --env staging --tenant tenant_... --scenario <file> [--day YYYY-MM-DD] [--replay] [--generate] [--json]
|
|
8598
|
+
sixminute-admin demo seed --env staging --tenant tenant_... --scenario <file> [--day YYYY-MM-DD] [--replay] [--generate] [--members a@x,b@y] [--json]
|
|
7933
8599
|
Seeds the scenario into YOUR OWN account of the firm (accept an
|
|
7934
8600
|
invitation with the same address first), on its own Scenario device.
|
|
7935
8601
|
Without --day, the most recent day it lies wholly in the past on, with
|
|
7936
8602
|
nothing captured. --replay makes the day's Generate answer from the
|
|
7937
8603
|
scenario with no model; --generate starts that Generate.
|
|
8604
|
+
sixminute-admin demo seed --env staging --tenant tenant_... --scenario <dir|file> [--anchor YYYY-MM-DD] [--days N] [--members a@x,b@y] [--json]
|
|
8605
|
+
Seeds a range of working days instead: the last --days working days
|
|
8606
|
+
(5 unless named, at most 31) up to and including --anchor (today
|
|
8607
|
+
unless named), Saturdays and Sundays skipped. Each day takes the
|
|
8608
|
+
scenario of the directory whose weekday is its own, or the one file
|
|
8609
|
+
named. A range always replays and writes its days' entries itself, so
|
|
8610
|
+
--day and --generate are refused with it; seeding a day again
|
|
8611
|
+
replaces it.
|
|
8612
|
+
--members invites those addresses to the firm as administrators;
|
|
8613
|
+
one already a member or already invited is skipped.
|
|
7938
8614
|
sixminute-admin demo reset --env staging --tenant tenant_... --day YYYY-MM-DD [--generate] [--json]
|
|
7939
8615
|
Drops the seeded day's Generate runs and drafts, so Generate runs
|
|
7940
8616
|
again over the same activity. Released entries stay.
|
|
@@ -8224,6 +8900,10 @@ async function runAdmin(argv, deps) {
|
|
|
8224
8900
|
}
|
|
8225
8901
|
if (group === "demo" && subcommand === "validate")
|
|
8226
8902
|
return validateScenario(rest);
|
|
8903
|
+
if (group === "telemetry") {
|
|
8904
|
+
const parsed = parseFlags(rest, 2);
|
|
8905
|
+
return runTelemetry(subcommand, parsed.values, parsed.flags, parsed.json, { fetchImpl: deps.fetchImpl, posthogKey: deps.posthogKey ?? null });
|
|
8906
|
+
}
|
|
8227
8907
|
const oneWord = ["login", "logout", "whoami", "doctor", "versions", "audit"].includes(group);
|
|
8228
8908
|
const vocabulary2 = group === "attribute" || group === "matter" && subcommand === "sets";
|
|
8229
8909
|
const threeDeep = group === "tenant" && subcommand === "internal";
|
|
@@ -8299,16 +8979,12 @@ async function runAdmin(argv, deps) {
|
|
|
8299
8979
|
return runTierSet(run2);
|
|
8300
8980
|
if (group === "raw-archive")
|
|
8301
8981
|
return runRawArchive(run2, subcommand);
|
|
8302
|
-
if (group === "research-export")
|
|
8303
|
-
return runResearchExportFlag(run2, subcommand);
|
|
8304
|
-
if (group === "research" && subcommand === "export")
|
|
8305
|
-
return runResearchExport(run2);
|
|
8306
|
-
if (group === "research" && subcommand === "contexts")
|
|
8307
|
-
return runResearchContexts(run2);
|
|
8308
8982
|
if (group === "retention" && subcommand === "set")
|
|
8309
8983
|
return runRetentionSet(run2);
|
|
8310
8984
|
if (group === "backfill" && subcommand === "sealed")
|
|
8311
8985
|
return runBackfillSealed(run2);
|
|
8986
|
+
if (group === "scrub" && subcommand === "plaintext")
|
|
8987
|
+
return runScrubPlaintext(run2);
|
|
8312
8988
|
if (group === "pipeline" && subcommand === "shadow-report")
|
|
8313
8989
|
return runShadowReport(run2);
|
|
8314
8990
|
if (group === "entries" && subcommand === "purge")
|
|
@@ -8431,35 +9107,16 @@ async function runTierSet(run2) {
|
|
|
8431
9107
|
const done = AdminTierResultSchema.parse(expectOk(await call(run2.api, "PUT", tenantPath(tenantId, "/tier"), { tier })));
|
|
8432
9108
|
const tenant = { id: done.tenant.id, name: done.tenant.name, security_tier: done.security_tier };
|
|
8433
9109
|
if (done.security_tier !== "confidential") {
|
|
8434
|
-
return render(run2, { tenant }, [
|
|
8435
|
-
`tenant ${tenant.id} ${tenant.name}`,
|
|
8436
|
-
`security_tier ${tenant.security_tier}`,
|
|
8437
|
-
"",
|
|
8438
|
-
"Research cards are stored for this firm for 30 days, sealed under the research key, only while it has",
|
|
8439
|
-
"instructed it in writing (research-export allow)."
|
|
8440
|
-
]);
|
|
9110
|
+
return render(run2, { tenant }, [`tenant ${tenant.id} ${tenant.name}`, `security_tier ${tenant.security_tier}`]);
|
|
8441
9111
|
}
|
|
8442
9112
|
const deleted = done.cards_deleted ?? 0;
|
|
8443
|
-
|
|
8444
|
-
return render(run2, {
|
|
8445
|
-
tenant,
|
|
8446
|
-
research_cards_deleted: deleted,
|
|
8447
|
-
judged_text_purged: judgedText,
|
|
8448
|
-
...done.past_exports === null ? { research_exports_error: done.past_exports_error } : { research_exports: done.past_exports }
|
|
8449
|
-
}, [
|
|
9113
|
+
return render(run2, { tenant, research_cards_deleted: deleted }, [
|
|
8450
9114
|
`tenant ${tenant.id} ${tenant.name}`,
|
|
8451
9115
|
`security_tier ${tenant.security_tier}`,
|
|
8452
9116
|
`cards deleted ${deleted}`,
|
|
8453
|
-
`judged text ${judgedText}`,
|
|
8454
|
-
"",
|
|
8455
|
-
`The ${deleted} research cards this firm had already stored are deleted, and so is the judged text of`,
|
|
8456
|
-
`${judgedText} of its intervals. That cannot be undone: the cards were sealed to a key nothing keeps a copy of,`,
|
|
8457
|
-
"the text is the only copy of what a narrative was written from, and there is no backup of either.",
|
|
8458
|
-
"The entries, their narratives and hours, the judged copies and the intervals' evidence are untouched.",
|
|
8459
|
-
"No research card and no judged text is stored for this firm from the next completion, and its",
|
|
8460
|
-
"judged copies stay out of every export.",
|
|
8461
9117
|
"",
|
|
8462
|
-
|
|
9118
|
+
`The ${deleted} cards this firm still held are deleted. That cannot be undone: there is no backup.`,
|
|
9119
|
+
"The entries, their narratives and hours, the judged copies and the intervals' evidence are untouched."
|
|
8463
9120
|
]);
|
|
8464
9121
|
}
|
|
8465
9122
|
async function runTenantInternalSet(run2) {
|
|
@@ -8478,6 +9135,9 @@ async function runRawArchive(run2, subcommand) {
|
|
|
8478
9135
|
if (subcommand !== "allow" && subcommand !== "revoke") {
|
|
8479
9136
|
throw new AdminUsageError(`unknown command: raw-archive ${subcommand ?? ""}`.trim());
|
|
8480
9137
|
}
|
|
9138
|
+
if (run2.environment.name === "production") {
|
|
9139
|
+
throw new AdminUsageError("raw-archive runs on staging (or local) only: production has no raw archive");
|
|
9140
|
+
}
|
|
8481
9141
|
const allow = subcommand === "allow";
|
|
8482
9142
|
const tenantId = required(run2.flags, "tenant", "which tenant the raw archive is being set for");
|
|
8483
9143
|
const { tenant } = AdminTenantFlagsSchema.parse(expectOk(await call(run2.api, "PUT", tenantPath(tenantId, "/raw-archive"), { allowed: allow })));
|
|
@@ -8488,23 +9148,9 @@ async function runRawArchive(run2, subcommand) {
|
|
|
8488
9148
|
allow ? "A person on this tenant can now switch on the raw capture archive on their own device, in Capture settings." : "Every device on this tenant switches its raw archive off at its next policy read."
|
|
8489
9149
|
]);
|
|
8490
9150
|
}
|
|
8491
|
-
async function runResearchExportFlag(run2, subcommand) {
|
|
8492
|
-
if (subcommand !== "allow" && subcommand !== "revoke") {
|
|
8493
|
-
throw new AdminUsageError(`unknown command: research-export ${subcommand ?? ""}`.trim());
|
|
8494
|
-
}
|
|
8495
|
-
const allow = subcommand === "allow";
|
|
8496
|
-
const tenantId = required(run2.flags, "tenant", "which firm's written instruction is being recorded");
|
|
8497
|
-
const { tenant } = AdminResearchExportFlagSchema.parse(expectOk(await call(run2.api, "PUT", tenantPath(tenantId, "/research-export"), { research_export: allow })));
|
|
8498
|
-
return render(run2, { tenant }, [
|
|
8499
|
-
`tenant ${tenant.id} ${tenant.name}`,
|
|
8500
|
-
`research_export ${tenant.research_export}`,
|
|
8501
|
-
"",
|
|
8502
|
-
allow ? "Research cards are stored for this firm from its next completion, for 30 days, sealed under the research key." : "No research card is stored for this firm from its next completion. Cards already stored stay until the 30-day purge."
|
|
8503
|
-
]);
|
|
8504
|
-
}
|
|
8505
9151
|
async function runShadowReport(run2) {
|
|
8506
|
-
const
|
|
8507
|
-
const found = ShadowReportSchema.parse(expectOk(await call(run2.api, "GET", `/v1/admin/pipeline/shadow-report?day=${encodeURIComponent(
|
|
9152
|
+
const day2 = required(run2.flags, "day", "which local day, YYYY-MM-DD");
|
|
9153
|
+
const found = ShadowReportSchema.parse(expectOk(await call(run2.api, "GET", `/v1/admin/pipeline/shadow-report?day=${encodeURIComponent(day2)}`)));
|
|
8508
9154
|
const pct = (part, whole) => whole === 0 ? "-" : `${Math.round(100 * part / whole)}%`;
|
|
8509
9155
|
return render(run2, found, [
|
|
8510
9156
|
`day ${found.day}`,
|
|
@@ -8541,33 +9187,13 @@ async function runBackfillSealed(run2) {
|
|
|
8541
9187
|
`aged ${total.intervals_aged} interval(s)`
|
|
8542
9188
|
]);
|
|
8543
9189
|
}
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
const tenantId = required(flags, "tenant", "
|
|
8547
|
-
const
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
const out = resolve(required(flags, "out", "where to write the files; an export lives on this machine and nowhere else"));
|
|
8552
|
-
const claimed = claimExportDirectory(out);
|
|
8553
|
-
let done;
|
|
8554
|
-
try {
|
|
8555
|
-
done = ResearchContextExportSchema.parse(expectOk(await call(run2.api, "POST", tenantPath(tenantId, "/research-exports/contexts"), { from, to })));
|
|
8556
|
-
} catch (err) {
|
|
8557
|
-
releaseExportDirectory(out, claimed);
|
|
8558
|
-
throw err;
|
|
8559
|
-
}
|
|
8560
|
-
const contextsPath = join2(out, `contexts-${tenantId}-${from}-${to}.jsonl`);
|
|
8561
|
-
const stepsPath = join2(out, `steps-${tenantId}-${from}-${to}.jsonl`);
|
|
8562
|
-
writePrivateFile(contextsPath, jsonLines(done.contexts));
|
|
8563
|
-
writePrivateFile(stepsPath, jsonLines(done.steps));
|
|
8564
|
-
return render(run2, { tenant_id: tenantId, from: done.from, to: done.to, contexts: done.contexts.length, steps: done.steps.length, files: [contextsPath, stepsPath], operator: done.operator }, [
|
|
8565
|
-
`tenant ${tenantId} ${done.tenant_name}`,
|
|
8566
|
-
`range ${from} to ${to} (--to not included)`,
|
|
8567
|
-
`contexts ${done.contexts.length}`,
|
|
8568
|
-
`steps ${done.steps.length}`,
|
|
8569
|
-
`files ${contextsPath}`,
|
|
8570
|
-
` ${stepsPath}`
|
|
9190
|
+
var ScrubStartedSchema = exports_external.object({ tenant: exports_external.string(), started: exports_external.number().int().nonnegative() });
|
|
9191
|
+
async function runScrubPlaintext(run2) {
|
|
9192
|
+
const tenantId = required(run2.flags, "tenant", "whose old rows to scrub");
|
|
9193
|
+
const done = ScrubStartedSchema.parse(expectOk(await call(run2.api, "POST", tenantPath(tenantId, "/scrub-plaintext"))));
|
|
9194
|
+
return render(run2, done, [
|
|
9195
|
+
`tenant ${done.tenant}`,
|
|
9196
|
+
`started ${done.started} scrub job(s), one per active member`
|
|
8571
9197
|
]);
|
|
8572
9198
|
}
|
|
8573
9199
|
async function runVersions(run2) {
|
|
@@ -8584,7 +9210,7 @@ async function runEntriesPurge(run2) {
|
|
|
8584
9210
|
const demo2 = switched(flags, "demo");
|
|
8585
9211
|
const unmatched = switched(flags, "unmatched");
|
|
8586
9212
|
if (!demo2 && !unmatched) {
|
|
8587
|
-
throw new AdminUsageError("name what to remove: --demo (the rows
|
|
9213
|
+
throw new AdminUsageError("name what to remove: --demo (the rows the retired demo seeder minted) or --unmatched " + "(engine-written rows charged to no matter), or both. This command never guesses a selector.");
|
|
8588
9214
|
}
|
|
8589
9215
|
const author = optional(flags, "author");
|
|
8590
9216
|
if (author === undefined) {
|
|
@@ -8602,10 +9228,11 @@ async function runEntriesPurge(run2) {
|
|
|
8602
9228
|
...demo2 ? purgeSection("demo entries (entry_demo-)", done.rows.demo) : [],
|
|
8603
9229
|
...demo2 && unmatched ? [""] : [],
|
|
8604
9230
|
...unmatched ? purgeSection("unmatched captured entries (no matter, engine-written)", done.rows.unmatched) : [],
|
|
9231
|
+
...done.kept.demo.length > 0 ? ["", ...purgeSection("kept, released demo entries (a released row is never deleted)", done.kept.demo)] : [],
|
|
8605
9232
|
""
|
|
8606
9233
|
];
|
|
8607
9234
|
if (dryRun) {
|
|
8608
|
-
return render(run2, { tenant: tenantId, author, user: done.user, dry_run: true, would_delete: total, rows: done.rows }, [...listing, `--dry-run: ${total} row(s) would be deleted. Nothing was written.`]);
|
|
9235
|
+
return render(run2, { tenant: tenantId, author, user: done.user, dry_run: true, would_delete: total, rows: done.rows, kept: done.kept }, [...listing, `--dry-run: ${total} row(s) would be deleted. Nothing was written.`]);
|
|
8609
9236
|
}
|
|
8610
9237
|
if (!yes) {
|
|
8611
9238
|
return {
|
|
@@ -8618,84 +9245,10 @@ async function runEntriesPurge(run2) {
|
|
|
8618
9245
|
};
|
|
8619
9246
|
}
|
|
8620
9247
|
const deleted = done.deleted ?? { demo: 0, unmatched: 0, total: 0 };
|
|
8621
|
-
return render(run2, { tenant: tenantId, author, user: done.user, deleted }, [
|
|
9248
|
+
return render(run2, { tenant: tenantId, author, user: done.user, deleted, kept: done.kept }, [
|
|
8622
9249
|
...listing,
|
|
8623
9250
|
`deleted ${deleted.total} row(s): ${deleted.demo} demo, ${deleted.unmatched} unmatched captured.`,
|
|
8624
|
-
"That cannot be undone.
|
|
8625
|
-
]);
|
|
8626
|
-
}
|
|
8627
|
-
async function runResearchExport(run2) {
|
|
8628
|
-
const { flags, deps } = run2;
|
|
8629
|
-
const tenantId = required(flags, "tenant", "an export is one firm's cards and judged entries");
|
|
8630
|
-
const from = dayOf2(flags, "from", "the first day of the range");
|
|
8631
|
-
const to = dayOf2(flags, "to", "the day the range stops before; --to is not included");
|
|
8632
|
-
if (from >= to)
|
|
8633
|
-
throw new AdminUsageError("--from must be before --to; the range is half-open and an empty one exports nothing.");
|
|
8634
|
-
const out = resolve(required(flags, "out", "where to write the files; an export lives on this machine and nowhere else"));
|
|
8635
|
-
let googleToken;
|
|
8636
|
-
try {
|
|
8637
|
-
googleToken = deps.googleAccessToken();
|
|
8638
|
-
} catch (err) {
|
|
8639
|
-
throw new AdminUsageError(err instanceof Error ? err.message : String(err));
|
|
8640
|
-
}
|
|
8641
|
-
const claimed = claimExportDirectory(out);
|
|
8642
|
-
let done;
|
|
8643
|
-
try {
|
|
8644
|
-
done = ResearchExportResultSchema.parse(expectOk(await call(run2.api, "POST", tenantPath(tenantId, "/research-exports"), { from, to })));
|
|
8645
|
-
} catch (err) {
|
|
8646
|
-
releaseExportDirectory(out, claimed);
|
|
8647
|
-
throw err;
|
|
8648
|
-
}
|
|
8649
|
-
const range = `range ${from} to ${to} (--to not included)`;
|
|
8650
|
-
if (done.tier === "confidential") {
|
|
8651
|
-
releaseExportDirectory(out, claimed);
|
|
8652
|
-
return render(run2, { tenant_id: tenantId, from: done.from, to: done.to, rows: 0, files: [], operator: done.operator, security_tier: "confidential" }, [
|
|
8653
|
-
`tenant ${tenantId} ${done.tenant_name}`,
|
|
8654
|
-
"security_tier confidential",
|
|
8655
|
-
range,
|
|
8656
|
-
"",
|
|
8657
|
-
"Nothing was exported and no file was written. This firm stores no research card, and its judged",
|
|
8658
|
-
"copies are kept out of every export: that is what its agreement says, and it is not a range to widen.",
|
|
8659
|
-
`The attempt is in the audit trail, naming ${done.operator}.`
|
|
8660
|
-
], CONFIDENTIAL_EXIT_CODE);
|
|
8661
|
-
}
|
|
8662
|
-
const cardsPath = join2(out, `research-${tenantId}-${from}-${to}.jsonl`);
|
|
8663
|
-
const pairsPath = join2(out, `judged-${tenantId}-${from}-${to}.jsonl`);
|
|
8664
|
-
const written = [];
|
|
8665
|
-
try {
|
|
8666
|
-
const cards = await openCards(done.sealed_cards, done.research_key_name, googleToken, deps.fetchImpl);
|
|
8667
|
-
writePrivateFile(cardsPath, jsonLines(cards));
|
|
8668
|
-
written.push(cardsPath);
|
|
8669
|
-
writePrivateFile(pairsPath, jsonLines(done.judged_pairs));
|
|
8670
|
-
written.push(pairsPath);
|
|
8671
|
-
} catch (err) {
|
|
8672
|
-
for (const path of written)
|
|
8673
|
-
rmSync3(path, { force: true });
|
|
8674
|
-
releaseExportDirectory(out, claimed);
|
|
8675
|
-
if (err instanceof Error && err.name === "UnsealError")
|
|
8676
|
-
throw new AdminUsageError(err.message);
|
|
8677
|
-
throw err;
|
|
8678
|
-
}
|
|
8679
|
-
return render(run2, {
|
|
8680
|
-
tenant_id: tenantId,
|
|
8681
|
-
from: done.from,
|
|
8682
|
-
to: done.to,
|
|
8683
|
-
rows: done.rows,
|
|
8684
|
-
cards: done.cards,
|
|
8685
|
-
pairs: done.pairs,
|
|
8686
|
-
files: [cardsPath, pairsPath],
|
|
8687
|
-
operator: done.operator
|
|
8688
|
-
}, [
|
|
8689
|
-
`tenant ${tenantId} ${done.tenant_name}`,
|
|
8690
|
-
range,
|
|
8691
|
-
`cards ${done.cards}`,
|
|
8692
|
-
`judged pairs ${done.pairs}`,
|
|
8693
|
-
`files ${cardsPath}`,
|
|
8694
|
-
` ${pairsPath}`,
|
|
8695
|
-
`audit row written before the unseal, naming ${done.operator}`,
|
|
8696
|
-
"",
|
|
8697
|
-
"The files hold screen material of a firm's day and the narratives it billed. They live on this",
|
|
8698
|
-
"machine, they are not pseudonymous to anyone who knows the firm, and the audit row says you read them."
|
|
9251
|
+
"That cannot be undone. Seed the demo again with demo seed if the demo rows were wanted."
|
|
8699
9252
|
]);
|
|
8700
9253
|
}
|
|
8701
9254
|
async function runAudit(run2) {
|
|
@@ -8818,7 +9371,7 @@ async function runVocabulary(run2, group, subcommand, action, positionals) {
|
|
|
8818
9371
|
const merged = { ...await readMatterSets(firm, matterId), ...wanted };
|
|
8819
9372
|
await firm("PATCH", `/matters/${enc(matterId)}`, { attribute_sets: merged });
|
|
8820
9373
|
return done({ matter: matterId, attribute_sets: merged }, [
|
|
8821
|
-
`matter ${matterId} ` + Object.entries(merged).map(([key,
|
|
9374
|
+
`matter ${matterId} ` + Object.entries(merged).map(([key, ids3]) => `${key}=${ids3 === null ? "inherit" : ids3.length === 0 ? "none" : ids3.join(",")}`).join(" ")
|
|
8822
9375
|
]);
|
|
8823
9376
|
}
|
|
8824
9377
|
throw new AdminUsageError(`unknown command: ${group} ${subcommand ?? ""}`.trim());
|
|
@@ -8880,10 +9433,10 @@ async function runClient(run2, subcommand) {
|
|
|
8880
9433
|
const answer = await call(run2.api, "POST", tenantPath(tenant, "/clients"), body);
|
|
8881
9434
|
if (answer.status !== 201)
|
|
8882
9435
|
return refused(run2, answer, [actingHead(tenant, answer)]);
|
|
8883
|
-
const
|
|
8884
|
-
return render(run2, { client }, [
|
|
9436
|
+
const client2 = ClientSchema.parse(answer.body);
|
|
9437
|
+
return render(run2, { client: client2 }, [
|
|
8885
9438
|
actingHead(tenant, answer),
|
|
8886
|
-
`client ${
|
|
9439
|
+
`client ${client2.id} ${client2.name}${client2.external_ref === null ? "" : ` ref ${client2.external_ref}`}`
|
|
8887
9440
|
]);
|
|
8888
9441
|
}
|
|
8889
9442
|
if (subcommand === "list") {
|
|
@@ -8910,8 +9463,8 @@ async function runClient(run2, subcommand) {
|
|
|
8910
9463
|
const answer = await call(run2.api, "PATCH", tenantPath(tenant, `/clients/${enc(id)}`), body);
|
|
8911
9464
|
if (answer.status !== 200)
|
|
8912
9465
|
return refused(run2, answer, [actingHead(tenant, answer)]);
|
|
8913
|
-
const
|
|
8914
|
-
return render(run2, { client }, [actingHead(tenant, answer), clientLine(
|
|
9466
|
+
const client2 = ClientSchema.parse(answer.body);
|
|
9467
|
+
return render(run2, { client: client2 }, [actingHead(tenant, answer), clientLine(client2)]);
|
|
8915
9468
|
}
|
|
8916
9469
|
throw new AdminUsageError(`unknown command: client ${subcommand ?? ""}`.trim());
|
|
8917
9470
|
}
|
|
@@ -8945,11 +9498,11 @@ async function runMatter(run2, subcommand) {
|
|
|
8945
9498
|
]);
|
|
8946
9499
|
}
|
|
8947
9500
|
if (subcommand === "list") {
|
|
8948
|
-
const
|
|
8949
|
-
if (
|
|
8950
|
-
checkBody(MatterSchema.pick({ client_id: true }), { client_id:
|
|
9501
|
+
const client2 = optional(flags, "client");
|
|
9502
|
+
if (client2 !== undefined)
|
|
9503
|
+
checkBody(MatterSchema.pick({ client_id: true }), { client_id: client2 });
|
|
8951
9504
|
const { rows, head } = await pullAll(run2, tenant, "/matters", MatterSchema);
|
|
8952
|
-
const ofClient =
|
|
9505
|
+
const ofClient = client2 === undefined ? rows : rows.filter((m) => m.client_id === client2);
|
|
8953
9506
|
const matters = switched(flags, "all") ? ofClient : ofClient.filter((m) => m.active);
|
|
8954
9507
|
return render(run2, { matters }, [
|
|
8955
9508
|
head,
|
|
@@ -8998,7 +9551,7 @@ async function runMattersImport(run2) {
|
|
|
8998
9551
|
return refused(run2, answer, [actingHead(tenant, answer)]);
|
|
8999
9552
|
const report = ImportReportSchema.parse(answer.body);
|
|
9000
9553
|
const line = (n) => `${n.created} created, ${n.updated} updated, ${n.unchanged} unchanged`;
|
|
9001
|
-
const matters = parsed.clients.reduce((n,
|
|
9554
|
+
const matters = parsed.clients.reduce((n, client2) => n + client2.matters.length, 0);
|
|
9002
9555
|
return render(run2, { file, report }, [
|
|
9003
9556
|
actingHead(tenant, answer),
|
|
9004
9557
|
`file ${file} ${parsed.clients.length} client(s), ${matters} matter(s)`,
|
|
@@ -9225,12 +9778,6 @@ ${ADMIN_USAGE}`);
|
|
|
9225
9778
|
|
|
9226
9779
|
// tools/admin/src/surface.ts
|
|
9227
9780
|
import { createHash } from "node:crypto";
|
|
9228
|
-
var MACHINE_ADMIN_PATHS = [
|
|
9229
|
-
"/v1/admin/research/purge",
|
|
9230
|
-
"/v1/admin/connectors/sync",
|
|
9231
|
-
"/v1/admin/context/purge",
|
|
9232
|
-
"/v1/admin/calendar/sync"
|
|
9233
|
-
];
|
|
9234
9781
|
function canonicalJson(value) {
|
|
9235
9782
|
if (Array.isArray(value))
|
|
9236
9783
|
return `[${value.map((item) => item === undefined ? "null" : canonicalJson(item)).join(",")}]`;
|
|
@@ -9245,7 +9792,7 @@ function adminSurfaceOf(document) {
|
|
|
9245
9792
|
const paths = document.paths ?? {};
|
|
9246
9793
|
const kept = {};
|
|
9247
9794
|
for (const path of Object.keys(paths)) {
|
|
9248
|
-
if (path.startsWith("/v1/admin/")
|
|
9795
|
+
if (path.startsWith("/v1/admin/"))
|
|
9249
9796
|
kept[path] = paths[path];
|
|
9250
9797
|
}
|
|
9251
9798
|
return createHash("sha256").update(canonicalJson(kept)).digest("hex").slice(0, 16);
|
|
@@ -9281,9 +9828,9 @@ var result = await runAdmin(process.argv.slice(2), {
|
|
|
9281
9828
|
`),
|
|
9282
9829
|
openBrowser,
|
|
9283
9830
|
localApiUrl: process.env["SIX_MINUTE_API_URL"] ?? null,
|
|
9831
|
+
posthogKey: process.env["SIXMINUTE_POSTHOG_KEY"] ?? null,
|
|
9284
9832
|
build: BUILT ?? sourceBuild(),
|
|
9285
|
-
readStdin: passwordReader(process.stdin, readAllStdin)
|
|
9286
|
-
googleAccessToken: () => googleAccessToken(process.env, process.platform)
|
|
9833
|
+
readStdin: passwordReader(process.stdin, readAllStdin)
|
|
9287
9834
|
}).catch((err) => {
|
|
9288
9835
|
if (err instanceof AdminUsageError) {
|
|
9289
9836
|
process.stderr.write(`sixminute-admin: ${err.message}
|