@indigoai-us/hq-cli 5.123.2 → 5.125.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/command-catalog.generated.d.ts +111 -1
  3. package/dist/command-catalog.generated.js +144 -1
  4. package/dist/commands/billing-sponsor.d.ts +83 -0
  5. package/dist/commands/billing-sponsor.js +344 -0
  6. package/dist/commands/billing.d.ts +3 -0
  7. package/dist/commands/billing.js +5 -0
  8. package/dist/commands/login.js +49 -6
  9. package/dist/commands/mesh.js +96 -19
  10. package/dist/lib/doctor/checks/work-context.js +4 -2
  11. package/dist/lib/mesh/live/backfill-held.d.ts +2 -0
  12. package/dist/lib/mesh/live/backfill-held.js +8 -4
  13. package/dist/lib/mesh/live/daemon/board-refresh.d.ts +3 -0
  14. package/dist/lib/mesh/live/daemon/board-refresh.js +6 -0
  15. package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
  16. package/dist/lib/mesh/live/daemon/doctor.js +21 -1
  17. package/dist/lib/mesh/live/daemon/run.d.ts +11 -0
  18. package/dist/lib/mesh/live/daemon/run.js +129 -10
  19. package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
  20. package/dist/lib/mesh/live/flush.d.ts +3 -0
  21. package/dist/lib/mesh/live/flush.js +11 -0
  22. package/dist/lib/work-context/company.d.ts +8 -0
  23. package/dist/lib/work-context/company.js +62 -18
  24. package/dist/lib/work-context/config.d.ts +35 -0
  25. package/dist/lib/work-context/config.js +307 -62
  26. package/dist/lib/work-context/contract.d.ts +2 -0
  27. package/dist/lib/work-context/default-company.d.ts +43 -0
  28. package/dist/lib/work-context/default-company.js +83 -0
  29. package/dist/lib/work-context/outbox.d.ts +5 -1
  30. package/dist/lib/work-context/outbox.js +5 -0
  31. package/dist/lib/work-context/reconcile.d.ts +4 -0
  32. package/dist/lib/work-context/reconcile.js +59 -3
  33. package/dist/lib/work-context/state.d.ts +2 -0
  34. package/dist/lib/work-context/state.js +14 -0
  35. package/package.json +1 -1
@@ -21,6 +21,7 @@ export const OUTBOX_ALLOWLIST = [
21
21
  "companySlug",
22
22
  "projectId",
23
23
  "taskId",
24
+ "companyConfidence",
24
25
  "kind",
25
26
  "digest",
26
27
  "delivery",
@@ -96,6 +97,8 @@ function projectOutbox(op) {
96
97
  out.projectId = op.projectId;
97
98
  if (op.taskId)
98
99
  out.taskId = op.taskId;
100
+ if (op.companyConfidence)
101
+ out.companyConfidence = op.companyConfidence;
99
102
  if (op.lastErrorCode)
100
103
  out.lastErrorCode = op.lastErrorCode;
101
104
  if (op.receiptId)
@@ -143,6 +146,7 @@ export function enqueueOutbox(input, root) {
143
146
  companySlug: input.companySlug,
144
147
  projectId: input.projectId,
145
148
  taskId: input.taskId,
149
+ companyConfidence: input.companyConfidence,
146
150
  kind,
147
151
  destinationCompanyUid: input.destinationCompanyUid,
148
152
  destinationCompanySlug: input.destinationCompanySlug,
@@ -172,6 +176,7 @@ export function enqueueOutbox(input, root) {
172
176
  companySlug: input.companySlug,
173
177
  projectId: input.projectId,
174
178
  taskId: input.taskId,
179
+ companyConfidence: input.companyConfidence,
175
180
  kind,
176
181
  digest,
177
182
  delivery: "queued",
@@ -52,6 +52,10 @@ export interface ReconcileDeps {
52
52
  root: string;
53
53
  env?: NodeJS.ProcessEnv;
54
54
  now?: () => Date;
55
+ /** Used only by explicit historical repair; ordinary reconciliation keeps defaults enabled. */
56
+ allowDeviceDefault?: boolean;
57
+ /** Mark explicit historical default repair as lower-confidence local evidence. */
58
+ deviceDefaultConfidence?: boolean;
55
59
  /** Network deliverer; only called after outbox write. */
56
60
  deliver?: DeliverFn;
57
61
  /** Skip network even when company is resolved (local unit tests). */
@@ -5,6 +5,7 @@
5
5
  import * as fs from "node:fs";
6
6
  import { InvalidSessionIdentityError, normalizeSessionIdentity, } from "../mesh/live/session-identity.js";
7
7
  import { companyCorrectionPath, companySlugFromCwd, resolveCompany, } from "./company.js";
8
+ import { invalidateDefaultCompanyMemberships, } from "./config.js";
8
9
  import { WORK_CONTEXT_CONTRACT_VERSION, normalizeTaskId, } from "./contract.js";
9
10
  import { EXIT_INVALID_IDENTITY, EXIT_NOT_TRACKING, EXIT_OK, InvalidDecisionOriginError, NotTrackingError, } from "./errors.js";
10
11
  import { decisionFromCandidates, } from "./organize.js";
@@ -24,6 +25,8 @@ function resultOf(parts) {
24
25
  };
25
26
  if (parts.companyUid)
26
27
  out.companyUid = parts.companyUid;
28
+ if (parts.companySlug)
29
+ out.companySlug = parts.companySlug;
27
30
  if (parts.projectId)
28
31
  out.projectId = parts.projectId;
29
32
  if (parts.taskId)
@@ -222,6 +225,7 @@ export async function reconcileObservation(obs, deps) {
222
225
  hqRoot,
223
226
  remoteOwnerSlug,
224
227
  repoIdentityKey,
228
+ allowDeviceDefault: deps.allowDeviceDefault,
225
229
  existingState: prior,
226
230
  });
227
231
  const nowIso = nowFn().toISOString();
@@ -341,8 +345,26 @@ export async function reconcileObservation(obs, deps) {
341
345
  };
342
346
  }
343
347
  // Membership-validated slug→uid when local resolution only yielded a slug.
348
+ // A device default is weak, cached evidence: validate it authoritatively on
349
+ // every tenant-I/O path so a membership revocation takes effect immediately.
344
350
  let resolvedCompany = company.company;
345
- if (!resolvedCompany.uid && resolvedCompany.slug && deps.validateMembership) {
351
+ const deviceDefaultRequiresValidation = resolvedCompany.source === "device_default";
352
+ if (deviceDefaultRequiresValidation && !deps.validateMembership) {
353
+ return {
354
+ result: resultOf({
355
+ kind: "needs_company",
356
+ classification: "needs_company",
357
+ delivery: "clean",
358
+ sessionId,
359
+ clientOperationId: obs.clientOperationId,
360
+ }),
361
+ exitCode: EXIT_OK,
362
+ diagnostics: ["needs_company: device-default membership validator unavailable"],
363
+ };
364
+ }
365
+ if (resolvedCompany.slug &&
366
+ deps.validateMembership &&
367
+ (!resolvedCompany.uid || deviceDefaultRequiresValidation)) {
346
368
  try {
347
369
  const membership = await deps.validateMembership(resolvedCompany.slug);
348
370
  if (membership && typeof membership === "object" && membership.uid) {
@@ -353,6 +375,9 @@ export async function reconcileObservation(obs, deps) {
353
375
  diagnostics.push(`membership resolved companyUid for slug=${resolvedCompany.slug}`);
354
376
  }
355
377
  else if (membership === false) {
378
+ if (deviceDefaultRequiresValidation) {
379
+ invalidateDefaultCompanyMemberships({ root: deps.root, now: nowFn });
380
+ }
356
381
  diagnostics.push(`membership denied for slug=${resolvedCompany.slug}; treating as needs_company`);
357
382
  const statePath = writeSessionState(mergeLocalSessionFields({
358
383
  contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
@@ -384,6 +409,19 @@ export async function reconcileObservation(obs, deps) {
384
409
  }
385
410
  catch (err) {
386
411
  diagnostics.push(`membership lookup failed: ${err instanceof Error ? err.message : String(err)}`);
412
+ if (deviceDefaultRequiresValidation) {
413
+ return {
414
+ result: resultOf({
415
+ kind: "needs_company",
416
+ classification: "needs_company",
417
+ delivery: "clean",
418
+ sessionId,
419
+ clientOperationId: obs.clientOperationId,
420
+ }),
421
+ exitCode: EXIT_OK,
422
+ diagnostics: [...diagnostics, "needs_company: device-default membership refresh unavailable"],
423
+ };
424
+ }
387
425
  }
388
426
  }
389
427
  // Company resolved — resolve project/task (US-007B order).
@@ -480,6 +518,11 @@ export async function reconcileObservation(obs, deps) {
480
518
  registerClassification === "unresolved") {
481
519
  registerClassification = "needs_project";
482
520
  }
521
+ // Offline preflight has authoritative membership but deliberately does not
522
+ // invent a project. Expose that usable, validated state to hook consumers.
523
+ if (resolvedCompany.source === "device_default" && deps.offline && registerClassification === "unresolved") {
524
+ registerClassification = "needs_project";
525
+ }
483
526
  // Do not let a weak later observation erase a server-acknowledged company
484
527
  // binding. Keep the result unresolved so the normal candidate threshold can
485
528
  // still advance the session, while preserving the durable emitter projection.
@@ -544,6 +587,7 @@ export async function reconcileObservation(obs, deps) {
544
587
  sessionId,
545
588
  clientOperationId: obs.clientOperationId,
546
589
  companyUid: resolvedCompany.uid,
590
+ companySlug: resolvedCompany.slug,
547
591
  projectId,
548
592
  taskId,
549
593
  bindingEpisodeId: acknowledgedPrior.bindingEpisodeId,
@@ -553,6 +597,9 @@ export async function reconcileObservation(obs, deps) {
553
597
  };
554
598
  }
555
599
  let outboxOp;
600
+ const companyConfidence = company.status === "resolved" && company.company.source === "device_default"
601
+ ? "device_default"
602
+ : undefined;
556
603
  try {
557
604
  outboxOp = enqueueOutbox({
558
605
  clientOperationId: obs.clientOperationId,
@@ -562,6 +609,7 @@ export async function reconcileObservation(obs, deps) {
562
609
  companySlug: resolvedCompany.slug,
563
610
  projectId,
564
611
  taskId,
612
+ companyConfidence,
565
613
  kind: "register",
566
614
  now: nowFn,
567
615
  }, deps.root);
@@ -588,8 +636,9 @@ export async function reconcileObservation(obs, deps) {
588
636
  diagnostics.push(`project source=${projectResolved.source} projectId=${projectId ?? "-"} taskId=${taskId ?? "-"}`);
589
637
  }
590
638
  let statePath;
639
+ let stateWrite;
591
640
  try {
592
- const stateWrite = mergeLocalSessionFields({
641
+ stateWrite = mergeLocalSessionFields({
593
642
  contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
594
643
  sessionId,
595
644
  companyUid: resolvedCompany.uid,
@@ -602,6 +651,7 @@ export async function reconcileObservation(obs, deps) {
602
651
  ...(typeof toolWrites === "number" ? { toolWrites } : {}),
603
652
  ...(hasTurn ? { hasTurn: true } : {}),
604
653
  ...(decision ? { decision } : {}),
654
+ ...(companyConfidence ? { companyConfidence } : {}),
605
655
  ...transcriptFields,
606
656
  }, prior);
607
657
  statePath = writeSessionState(stateWrite, deps.root);
@@ -616,6 +666,7 @@ export async function reconcileObservation(obs, deps) {
616
666
  sessionId,
617
667
  clientOperationId: obs.clientOperationId,
618
668
  companyUid: resolvedCompany.uid,
669
+ companySlug: resolvedCompany.slug,
619
670
  projectId,
620
671
  taskId,
621
672
  errorCode: "NOT_TRACKING",
@@ -636,6 +687,7 @@ export async function reconcileObservation(obs, deps) {
636
687
  sessionId,
637
688
  clientOperationId: obs.clientOperationId,
638
689
  companyUid: resolvedCompany.uid,
690
+ companySlug: resolvedCompany.slug,
639
691
  projectId,
640
692
  taskId,
641
693
  }),
@@ -665,7 +717,10 @@ export async function reconcileObservation(obs, deps) {
665
717
  ...(typeof toolWrites === "number" ? { toolWrites } : {}),
666
718
  ...(hasTurn ? { hasTurn: true } : {}),
667
719
  ...transcriptFields,
668
- }, prior), deps.root);
720
+ },
721
+ // Merge against the just-written projection rather than the stale
722
+ // snapshot from before reconciliation (retains device confidence).
723
+ readSessionState(sessionId, deps.root) ?? stateWrite), deps.root);
669
724
  }
670
725
  const kind = registerClassification === "bound"
671
726
  ? "bound"
@@ -721,6 +776,7 @@ export async function reconcileObservation(obs, deps) {
721
776
  sessionId,
722
777
  clientOperationId: obs.clientOperationId,
723
778
  companyUid: resolvedCompany.uid,
779
+ companySlug: resolvedCompany.slug,
724
780
  projectId,
725
781
  taskId,
726
782
  errorCode: delivered.code,
@@ -89,6 +89,8 @@ export interface SessionStateFile {
89
89
  * before SESSION_STATE sync (kept after AMEND-001).
90
90
  */
91
91
  transcriptMarker?: "from transcript";
92
+ /** Historical repair under device-default evidence is intentionally weak. */
93
+ companyConfidence?: "device_default";
92
94
  updatedAt: string;
93
95
  }
94
96
  /**
@@ -27,6 +27,7 @@ export const SESSION_STATE_ALLOWLIST = new Set([
27
27
  "source",
28
28
  "localHarness",
29
29
  "transcriptMarker",
30
+ "companyConfidence",
30
31
  "updatedAt",
31
32
  ]);
32
33
  export function readSessionState(sessionId, root) {
@@ -100,6 +101,8 @@ export function projectSessionState(state) {
100
101
  out.localHarness = state.localHarness;
101
102
  if (state.transcriptMarker)
102
103
  out.transcriptMarker = state.transcriptMarker;
104
+ if (state.companyConfidence)
105
+ out.companyConfidence = state.companyConfidence;
103
106
  return out;
104
107
  }
105
108
  /**
@@ -148,6 +151,13 @@ export function mergeLocalSessionFields(next, prior, options = {}) {
148
151
  if (!out.transcriptMarker && prior.transcriptMarker) {
149
152
  out.transcriptMarker = prior.transcriptMarker;
150
153
  }
154
+ const sameCompanyBinding = Boolean(prior.companyUid && out.companyUid && prior.companyUid === out.companyUid) ||
155
+ Boolean(prior.companySlug &&
156
+ out.companySlug &&
157
+ prior.companySlug.toLowerCase() === out.companySlug.toLowerCase());
158
+ if (!out.companyConfidence && prior.companyConfidence && sameCompanyBinding) {
159
+ out.companyConfidence = prior.companyConfidence;
160
+ }
151
161
  const sameBinding = prior.companyUid === out.companyUid &&
152
162
  prior.companySlug === out.companySlug &&
153
163
  prior.projectId === out.projectId &&
@@ -237,6 +247,10 @@ export function isHookWrittenSessionState(state) {
237
247
  export function authoritativeCompanyFromState(state) {
238
248
  if (!state)
239
249
  return null;
250
+ // A binding created only from the device default is deliberately weak. A
251
+ // later /startwork meta designation or remembered repo choice may replace it.
252
+ if (state.companyConfidence === "device_default")
253
+ return null;
240
254
  const writable = new Set([
241
255
  "unresolved",
242
256
  "needs_project",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.123.2",
3
+ "version": "5.125.0",
4
4
  "description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {