@unisonlabs/sixminute-admin 0.1.1187-next → 0.1.1189-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.
@@ -21,7 +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.1187-next", commit: "5cffde7fe2a9dc58715a34d8b5f8cc0eb041a328", surface: "60ffe6d6d98eb32b" };
24
+ var BUILT = { version: "0.1.1189-next", commit: "a271427030da8b939def7b46b2abf6f07482db17", surface: "9cd8ad25f1495c42" };
25
25
 
26
26
  // tools/admin/src/run.ts
27
27
  import { rmSync as rmSync3 } from "node:fs";
@@ -4895,7 +4895,8 @@ var TenantPolicySchema = exports_external.object({
4895
4895
  excluded_bundles: exports_external.array(exports_external.string().min(1)).default([]),
4896
4896
  security_tier: exports_external.enum(["standard", "confidential"]).default("standard"),
4897
4897
  raw_archive: exports_external.boolean().default(false),
4898
- research_export: exports_external.boolean().default(false)
4898
+ research_export: exports_external.boolean().default(false),
4899
+ context_retention_days: exports_external.number().int().min(0).max(365).default(30)
4899
4900
  }).default({});
4900
4901
  var NARRATIVE_GUIDANCE_MAX_CHARS = 1500;
4901
4902
  var GUIDANCE_CONTROL = /[\u0000-\u0009\u000B-\u001F\u007F-\u009F]/;
@@ -5682,6 +5683,33 @@ var ResearchExportResultSchema = exports_external.discriminatedUnion("tier", [
5682
5683
  ResearchExportConfidentialSchema,
5683
5684
  ResearchExportSealedSchema
5684
5685
  ]);
5686
+ var ResearchContextExportSchema = exports_external.object({
5687
+ export_id: exports_external.string(),
5688
+ tenant_id: exports_external.string(),
5689
+ tenant_name: exports_external.string(),
5690
+ from: exports_external.string(),
5691
+ to: exports_external.string(),
5692
+ operator: exports_external.string(),
5693
+ contexts: exports_external.array(exports_external.object({
5694
+ member_id: exports_external.string(),
5695
+ recording_id: exports_external.string(),
5696
+ seq: exports_external.number().int(),
5697
+ t_start: exports_external.string(),
5698
+ t_end: exports_external.string(),
5699
+ context: exports_external.unknown()
5700
+ })),
5701
+ steps: exports_external.array(exports_external.object({
5702
+ run_id: exports_external.string(),
5703
+ kind: exports_external.string(),
5704
+ member_id: exports_external.string(),
5705
+ subject_from: exports_external.string(),
5706
+ name: exports_external.string(),
5707
+ prompt_fingerprint: exports_external.string().nullable(),
5708
+ usage: exports_external.unknown(),
5709
+ output: exports_external.unknown(),
5710
+ created_at: exports_external.string()
5711
+ }))
5712
+ });
5685
5713
  // packages/contracts/src/admin-tenants.ts
5686
5714
  var AdminTenantParamsSchema = exports_external.object({ tenant: TenantIdSchema });
5687
5715
  var AdminSecurityTierSchema = exports_external.enum(["standard", "confidential"]);
@@ -5762,6 +5790,26 @@ var AdminEntriesPurgeResultSchema = exports_external.object({
5762
5790
  rows: exports_external.object({ demo: exports_external.array(AdminPurgeRowSchema), unmatched: exports_external.array(AdminPurgeRowSchema) }),
5763
5791
  deleted: exports_external.object({ demo: exports_external.number().int(), unmatched: exports_external.number().int(), total: exports_external.number().int() }).nullable()
5764
5792
  });
5793
+ var AdminContextRetentionSetSchema = exports_external.object({ days: exports_external.number().int().min(1).max(365) });
5794
+ var AdminContextRetentionResultSchema = exports_external.object({
5795
+ tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string() }),
5796
+ days: exports_external.number().int(),
5797
+ previous: exports_external.number().int(),
5798
+ shifted: exports_external.object({
5799
+ interval_contexts: exports_external.number().int(),
5800
+ pipeline_runs: exports_external.number().int(),
5801
+ pipeline_steps: exports_external.number().int(),
5802
+ derivation_cache: exports_external.number().int(),
5803
+ intervals: exports_external.number().int()
5804
+ })
5805
+ });
5806
+ var AdminSealedBackfillResultSchema = exports_external.object({
5807
+ tenant: TenantIdSchema,
5808
+ decisions_sealed: exports_external.number().int(),
5809
+ intervals_aged: exports_external.number().int(),
5810
+ judged_sealed: exports_external.number().int(),
5811
+ done: exports_external.boolean()
5812
+ });
5765
5813
  // packages/contracts/src/admin-firm-acting.ts
5766
5814
  var ACTING_AS_HEADER = "X-Six-Minute-Acting-As";
5767
5815
  var TeamApplyRequestSchema = exports_external.object({
@@ -5998,6 +6046,49 @@ var ServedConfigBundleSchema = ConfigBundleSchema.extend({
5998
6046
  prompts: ServedConfigPromptsSchema,
5999
6047
  vocabularies: ServedConfigVocabulariesSchema
6000
6048
  });
6049
+ // packages/contracts/src/device.ts
6050
+ var DEVICE_CREDENTIAL_PREFIX = "smd_";
6051
+ var ENROLLMENT_CODE_PREFIX = "sme_";
6052
+ var EnrollmentCodeSchema = exports_external.object({
6053
+ code: exports_external.string().startsWith(ENROLLMENT_CODE_PREFIX),
6054
+ expires_at: TimestampSchema
6055
+ });
6056
+ var DeviceEnrollSchema = exports_external.object({
6057
+ code: exports_external.string().startsWith(ENROLLMENT_CODE_PREFIX).max(200),
6058
+ device_id: DeviceIdSchema,
6059
+ name: DeviceRegistrationSchema.shape.name,
6060
+ platform: DeviceSchema.shape.platform
6061
+ });
6062
+ var DeviceEnrolledSchema = exports_external.object({
6063
+ credential: exports_external.string().startsWith(DEVICE_CREDENTIAL_PREFIX),
6064
+ device: DeviceSchema
6065
+ });
6066
+ var DevicePolicySchema = exports_external.object({
6067
+ excluded_bundles: exports_external.array(exports_external.string()),
6068
+ raw_archive: exports_external.boolean(),
6069
+ swallow_short_pauses: exports_external.boolean().nullable(),
6070
+ unit_minutes: UnitMinutesSchema,
6071
+ context_retention_days: exports_external.number().int().nonnegative(),
6072
+ minimum_version: exports_external.string().nullable()
6073
+ });
6074
+ var RecordingStopSchema = exports_external.object({
6075
+ after_seq: exports_external.number().int().nonnegative(),
6076
+ at: TimestampSchema,
6077
+ reason: exports_external.enum(["person", "quit", "update", "sign-out", "roll"])
6078
+ });
6079
+ var RecordingSkipSchema = exports_external.object({ seq: exports_external.number().int().positive() });
6080
+ var RecordingClockSchema = exports_external.object({
6081
+ device_clock: TimestampSchema,
6082
+ last_witness_at: TimestampSchema.nullable(),
6083
+ silence_end_after: TimestampSchema.nullable()
6084
+ });
6085
+ var RecordingParamsSchema = exports_external.object({ id: RecordingIdSchema });
6086
+ var ContextAcceptedSchema = exports_external.object({
6087
+ accepted: exports_external.number().int().positive(),
6088
+ run_id: RunIdSchema.optional(),
6089
+ recording_id: RecordingIdSchema.optional()
6090
+ });
6091
+ var DeviceCredentialParamsSchema = exports_external.object({ id: DeviceIdSchema });
6001
6092
  // packages/contracts/src/recording.ts
6002
6093
  var RecordingSchema = exports_external.object({
6003
6094
  id: RecordingIdSchema,
@@ -6392,6 +6483,32 @@ var LocalHostEventSchema = exports_external.discriminatedUnion("name", [
6392
6483
  }).strict()
6393
6484
  ]);
6394
6485
  var LocalMethodSchema = exports_external.enum(["GET", "POST", "PATCH", "DELETE"]);
6486
+ // packages/contracts/src/pipeline-status.ts
6487
+ var PipelineStatusQuerySchema = exports_external.object({
6488
+ since: exports_external.string().regex(/^\d{1,18}$/, "expected a version number").optional()
6489
+ });
6490
+ var PipelineRecordingStatusSchema = exports_external.object({
6491
+ recording_id: RecordingIdSchema,
6492
+ device_id: DeviceIdSchema,
6493
+ decided_through: TimestampSchema.nullable(),
6494
+ behind_seconds: exports_external.number().int().nonnegative(),
6495
+ stopped_at: TimestampSchema.nullable()
6496
+ });
6497
+ var PipelineStatusSchema = exports_external.object({
6498
+ version: exports_external.number().int().nonnegative(),
6499
+ recordings: exports_external.array(PipelineRecordingStatusSchema),
6500
+ deciding: exports_external.object({ recording_id: RecordingIdSchema, from: TimestampSchema, to: TimestampSchema }).nullable(),
6501
+ generate: exports_external.object({
6502
+ running: exports_external.object({
6503
+ run_id: RunIdSchema,
6504
+ kind: exports_external.enum(["generate", "regenerate", "propose"]),
6505
+ day: LocalDaySchema,
6506
+ step: exports_external.object({ name: exports_external.string(), at: TimestampSchema, entry_from: TimestampSchema.optional() })
6507
+ }).nullable(),
6508
+ pending: exports_external.array(exports_external.object({ day: LocalDaySchema, from: TimestampSchema, to: TimestampSchema })),
6509
+ failed: exports_external.object({ run_id: RunIdSchema, day: LocalDaySchema, error_class: exports_external.string() }).nullable()
6510
+ })
6511
+ });
6395
6512
  // packages/contracts/src/pipeline.ts
6396
6513
  var ContextStopReasonSchema = exports_external.enum(["person", "quit", "update", "sign-out", "roll"]);
6397
6514
  var IntervalContextHeaderSchema = exports_external.object({
@@ -6763,23 +6880,23 @@ async function deviceLogin(io, environment) {
6763
6880
  if (!started.ok) {
6764
6881
  throw new AdminUsageError(`WorkOS answered ${started.status} to the sign-in request for ${environment.name}. CLI Auth must be enabled ` + "for this environment in the WorkOS dashboard.");
6765
6882
  }
6766
- const device = await started.json();
6767
- if (typeof device.device_code !== "string" || typeof device.user_code !== "string") {
6883
+ const device2 = await started.json();
6884
+ if (typeof device2.device_code !== "string" || typeof device2.user_code !== "string") {
6768
6885
  throw new Error("WorkOS answered the sign-in request without a device code");
6769
6886
  }
6770
- const url = typeof device.verification_uri_complete === "string" ? device.verification_uri_complete : typeof device.verification_uri === "string" ? device.verification_uri : null;
6887
+ const url = typeof device2.verification_uri_complete === "string" ? device2.verification_uri_complete : typeof device2.verification_uri === "string" ? device2.verification_uri : null;
6771
6888
  if (url === null)
6772
6889
  throw new Error("WorkOS answered the sign-in request without a url to confirm it at");
6773
6890
  io.say(`Sign in to ${environment.name} in your browser: ${url}`);
6774
- io.say(`Confirm that it shows the code ${device.user_code}.`);
6891
+ io.say(`Confirm that it shows the code ${device2.user_code}.`);
6775
6892
  io.openBrowser(url);
6776
- let interval = (typeof device.interval === "number" && device.interval > 0 ? device.interval : 5) * 1000;
6777
- const deadline = io.now() + (typeof device.expires_in === "number" ? device.expires_in : 300) * 1000;
6893
+ let interval = (typeof device2.interval === "number" && device2.interval > 0 ? device2.interval : 5) * 1000;
6894
+ const deadline = io.now() + (typeof device2.expires_in === "number" ? device2.expires_in : 300) * 1000;
6778
6895
  for (;; ) {
6779
6896
  await io.sleep(interval);
6780
6897
  const answer = await authenticate(io, {
6781
6898
  grant_type: DEVICE_GRANT,
6782
- device_code: device.device_code,
6899
+ device_code: device2.device_code,
6783
6900
  client_id: environment.clientId
6784
6901
  });
6785
6902
  if (answer.status === 200) {
@@ -7219,6 +7336,24 @@ is required; without one every command answers 403.
7219
7336
  The unseal runs as you, with your own Google sign-in (gcloud auth
7220
7337
  login); only the people granted the research key can open a card.
7221
7338
 
7339
+ sixminute-admin retention set --env <env> --tenant tenant_... --days <1-365> [--json]
7340
+ Sets how many days the cloud keeps the firm's interval contexts after
7341
+ each interval's day (30 unless its agreement says otherwise), and moves
7342
+ every stored expiry of the firm by the difference at once: a shorter
7343
+ setting deletes at the next hourly purge. Summaries, labels and entries
7344
+ are not affected.
7345
+
7346
+ sixminute-admin backfill sealed --env <env> --tenant tenant_... [--json]
7347
+ Seals the firm's interval summaries and labels and its entries' judged
7348
+ drafts under its own key, and gives intervals stored before the context
7349
+ store an age, a bounded pass at a time until nothing is left.
7350
+
7351
+ sixminute-admin research contexts --env <env> --tenant tenant_... --from YYYY-MM-DD --to YYYY-MM-DD --out <dir> [--json]
7352
+ A firm's research data (R10): its interval contexts and the step ledger
7353
+ over a day range, written as two jsonl files in --out (a new folder
7354
+ 0700, as research export). Refused unless the firm instructed it in
7355
+ writing (research-export allow); the audit row is written first.
7356
+
7222
7357
  sixminute-admin entries purge --env <env> --tenant tenant_... --author member_... (--demo | --unmatched | both) [--dry-run] [--yes] [--json]
7223
7358
  Removes entry rows from one firm. A person may only soft-delete
7224
7359
  their own draft (2026-09-25) and nothing but a merge removes a row
@@ -7595,6 +7730,12 @@ async function runAdmin(argv, deps) {
7595
7730
  return runResearchExportFlag(run2, subcommand);
7596
7731
  if (group === "research" && subcommand === "export")
7597
7732
  return runResearchExport(run2);
7733
+ if (group === "research" && subcommand === "contexts")
7734
+ return runResearchContexts(run2);
7735
+ if (group === "retention" && subcommand === "set")
7736
+ return runRetentionSet(run2);
7737
+ if (group === "backfill" && subcommand === "sealed")
7738
+ return runBackfillSealed(run2);
7598
7739
  if (group === "entries" && subcommand === "purge")
7599
7740
  return runEntriesPurge(run2);
7600
7741
  if (group === "connector")
@@ -7784,6 +7925,65 @@ async function runResearchExportFlag(run2, subcommand) {
7784
7925
  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."
7785
7926
  ]);
7786
7927
  }
7928
+ async function runRetentionSet(run2) {
7929
+ const tenantId = required(run2.flags, "tenant", "whose retention");
7930
+ const days = Number(required(run2.flags, "days", "how many days after each interval's day its context is kept"));
7931
+ if (!Number.isInteger(days) || days < 1 || days > 365)
7932
+ throw new AdminUsageError("--days is a whole number from 1 to 365.");
7933
+ const done = AdminContextRetentionResultSchema.parse(expectOk(await call(run2.api, "PUT", tenantPath(tenantId, "/context-retention"), { days })));
7934
+ const moved = Object.entries(done.shifted).map(([table2, rows]) => `${table2} ${rows}`).join(", ");
7935
+ return render(run2, done, [
7936
+ `tenant ${done.tenant.id} ${done.tenant.name}`,
7937
+ `retention ${done.previous} -> ${done.days} days`,
7938
+ `moved ${moved}`
7939
+ ]);
7940
+ }
7941
+ async function runBackfillSealed(run2) {
7942
+ const tenantId = required(run2.flags, "tenant", "whose rows to seal");
7943
+ const total = { decisions_sealed: 0, intervals_aged: 0, judged_sealed: 0 };
7944
+ for (;; ) {
7945
+ const pass = AdminSealedBackfillResultSchema.parse(expectOk(await call(run2.api, "POST", tenantPath(tenantId, "/backfill/sealed"))));
7946
+ total.decisions_sealed += pass.decisions_sealed;
7947
+ total.intervals_aged += pass.intervals_aged;
7948
+ total.judged_sealed += pass.judged_sealed;
7949
+ if (pass.done)
7950
+ break;
7951
+ }
7952
+ return render(run2, { tenant: tenantId, ...total }, [
7953
+ `tenant ${tenantId}`,
7954
+ `sealed ${total.decisions_sealed} interval decision(s), ${total.judged_sealed} judged draft(s)`,
7955
+ `aged ${total.intervals_aged} interval(s)`
7956
+ ]);
7957
+ }
7958
+ async function runResearchContexts(run2) {
7959
+ const { flags } = run2;
7960
+ const tenantId = required(flags, "tenant", "an export is one firm's contexts and steps");
7961
+ const from = dayOf2(flags, "from", "the first day of the range");
7962
+ const to = dayOf2(flags, "to", "the day the range stops before; --to is not included");
7963
+ if (from >= to)
7964
+ throw new AdminUsageError("--from must be before --to; the range is half-open and an empty one exports nothing.");
7965
+ const out = resolve(required(flags, "out", "where to write the files; an export lives on this machine and nowhere else"));
7966
+ const claimed = claimExportDirectory(out);
7967
+ let done;
7968
+ try {
7969
+ done = ResearchContextExportSchema.parse(expectOk(await call(run2.api, "POST", tenantPath(tenantId, "/research-exports/contexts"), { from, to })));
7970
+ } catch (err) {
7971
+ releaseExportDirectory(out, claimed);
7972
+ throw err;
7973
+ }
7974
+ const contextsPath = join2(out, `contexts-${tenantId}-${from}-${to}.jsonl`);
7975
+ const stepsPath = join2(out, `steps-${tenantId}-${from}-${to}.jsonl`);
7976
+ writePrivateFile(contextsPath, jsonLines(done.contexts));
7977
+ writePrivateFile(stepsPath, jsonLines(done.steps));
7978
+ 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 }, [
7979
+ `tenant ${tenantId} ${done.tenant_name}`,
7980
+ `range ${from} to ${to} (--to not included)`,
7981
+ `contexts ${done.contexts.length}`,
7982
+ `steps ${done.steps.length}`,
7983
+ `files ${contextsPath}`,
7984
+ ` ${stepsPath}`
7985
+ ]);
7986
+ }
7787
7987
  async function runVersions(run2) {
7788
7988
  const tenantId = required(run2.flags, "tenant", "whose devices");
7789
7989
  const found = AdminDeviceVersionListSchema.parse(expectOk(await call(run2.api, "GET", tenantPath(tenantId, "/devices"))));
@@ -8439,7 +8639,11 @@ ${ADMIN_USAGE}`);
8439
8639
 
8440
8640
  // tools/admin/src/surface.ts
8441
8641
  import { createHash } from "node:crypto";
8442
- var MACHINE_ADMIN_PATHS = ["/v1/admin/research/purge", "/v1/admin/connectors/sync"];
8642
+ var MACHINE_ADMIN_PATHS = [
8643
+ "/v1/admin/research/purge",
8644
+ "/v1/admin/connectors/sync",
8645
+ "/v1/admin/context/purge"
8646
+ ];
8443
8647
  function canonicalJson(value) {
8444
8648
  if (Array.isArray(value))
8445
8649
  return `[${value.map((item) => item === undefined ? "null" : canonicalJson(item)).join(",")}]`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unisonlabs/sixminute-admin",
3
- "version": "0.1.1187-next",
3
+ "version": "0.1.1189-next",
4
4
  "description": "Sixminute operator tool: the admin commands of the Sixminute API, run with your own operator sign-in.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {