@indigoai-us/hq-cli 5.228.1 → 5.228.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.228.3] — 2026-09-27
6
+
7
+ ### Fixed
8
+
9
+ - Work Mesh tracker rows use the local record fields. Missing blocker
10
+ `opened_at` values use `createdAt`, and invalid mesh rows identify Work Mesh
11
+ as their source.
12
+ - KPI and OKR records retain their status when read from Work Mesh, including in
13
+ `kpi list`, `okr list`, and `team --json` output.
14
+
15
+ ## [5.228.2] — 2026-09-27
16
+
5
17
  ## [5.228.1] — 2026-09-27
6
18
 
7
19
  ### Fixed
@@ -435,7 +435,17 @@ function readTeamInputs(root, company, projects, lanes) {
435
435
  export function pathForTrackerRecord(projectDirectory, kind, id) {
436
436
  return path.join(projectDirectory, "tracker", kind, `${id}.json`);
437
437
  }
438
- function recordInvalid(kind, record, projectDirectory, field) {
438
+ function recordInvalid(kind, record, projectDirectory, field, source = "local") {
439
+ if (source === "Work Mesh") {
440
+ return fail("tracker_checks_record_invalid", `Work Mesh tracker ${kind} record ${record.id} has invalid ${field}`, {
441
+ source,
442
+ record_id: record.id,
443
+ kind,
444
+ company: record.company,
445
+ project_id: record.project_id,
446
+ field,
447
+ });
448
+ }
439
449
  return fail("tracker_checks_record_invalid", `Tracker ${kind} record has invalid ${field}`, {
440
450
  kind,
441
451
  id: record.id,
@@ -443,10 +453,18 @@ function recordInvalid(kind, record, projectDirectory, field) {
443
453
  path: pathForTrackerRecord(projectDirectory, kind, record.id),
444
454
  });
445
455
  }
446
- function requiredRecordDate(kind, record, projectDirectory, field) {
456
+ function requiredRecordDate(kind, record, projectDirectory, field, source = "local") {
447
457
  const value = record[field];
448
458
  if (typeof value !== "string" || !Number.isFinite(Date.parse(value)))
449
- return recordInvalid(kind, record, projectDirectory, field);
459
+ return recordInvalid(kind, record, projectDirectory, field, source);
460
+ return value;
461
+ }
462
+ function requiredRecordTimestamp(kind, record, projectDirectory, field, value, source) {
463
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
464
+ if (source === "Work Mesh")
465
+ return recordInvalid(kind, record, projectDirectory, field, source);
466
+ return parseTimestamp(value, field, pathForTrackerRecord(projectDirectory, kind, record.id));
467
+ }
450
468
  return value;
451
469
  }
452
470
  export function listTrackerRecords(projectDirectory, kind) {
@@ -509,6 +527,7 @@ async function readTrackerCheckRecords(root, company, projects, deps) {
509
527
  const kpis = [];
510
528
  const directives = [];
511
529
  const useMesh = await deps.trackerMeshEnabled(company);
530
+ const source = useMesh ? "Work Mesh" : "local";
512
531
  for (const project of projects) {
513
532
  const absoluteDirectory = path.join(root, "companies", company.slug, "projects", project.projectId);
514
533
  const store = useMesh
@@ -522,40 +541,40 @@ async function readTrackerCheckRecords(root, company, projects, deps) {
522
541
  for (const record of records.blocker) {
523
542
  if (record.company !== company.slug || record.project_id !== project.projectId)
524
543
  continue;
525
- const openedAt = requiredRecordDate("blocker", record, absoluteDirectory, "opened_at");
544
+ const openedAt = requiredRecordDate("blocker", record, absoluteDirectory, "opened_at", source);
526
545
  const resolvedAt = record.resolved_at;
527
546
  if (resolvedAt !== null && (typeof resolvedAt !== "string" || !Number.isFinite(Date.parse(resolvedAt))))
528
- recordInvalid("blocker", record, absoluteDirectory, "resolved_at");
547
+ recordInvalid("blocker", record, absoluteDirectory, "resolved_at", source);
529
548
  const nextStep = record.next_step;
530
549
  if (nextStep !== undefined && nextStep !== null && typeof nextStep !== "string")
531
- recordInvalid("blocker", record, absoluteDirectory, "next_step");
550
+ recordInvalid("blocker", record, absoluteDirectory, "next_step", source);
532
551
  blockers.push({ id: record.id, opened_at: openedAt, resolved_at: resolvedAt, next_step: typeof nextStep === "string" ? nextStep : null });
533
552
  }
534
553
  for (const record of records.kpi) {
535
554
  if (record.company !== company.slug || record.project_id !== project.projectId)
536
555
  continue;
537
- const createdAt = requiredRecordDate("kpi", record, absoluteDirectory, "created_at");
556
+ const createdAt = requiredRecordDate("kpi", record, absoluteDirectory, "created_at", source);
538
557
  if (!Array.isArray(record.values))
539
- recordInvalid("kpi", record, absoluteDirectory, "values");
558
+ recordInvalid("kpi", record, absoluteDirectory, "values", source);
540
559
  const values = record.values.map((value) => {
541
560
  if (!isObject(value))
542
- recordInvalid("kpi", record, absoluteDirectory, "values");
543
- return { at: parseTimestamp(value.at, "values.at", pathForTrackerRecord(absoluteDirectory, "kpi", record.id)) };
561
+ recordInvalid("kpi", record, absoluteDirectory, "values", source);
562
+ return { at: requiredRecordTimestamp("kpi", record, absoluteDirectory, "values[].at", value.at, source) };
544
563
  });
545
564
  kpis.push({ id: record.id, created_at: createdAt, values });
546
565
  }
547
566
  for (const record of records.directive) {
548
567
  if (record.company !== company.slug || record.project_id !== project.projectId)
549
568
  continue;
550
- const createdAt = requiredRecordDate("directive", record, absoluteDirectory, "created_at");
569
+ const createdAt = requiredRecordDate("directive", record, absoluteDirectory, "created_at", source);
551
570
  if (!Array.isArray(record.teams) || record.teams.some((team) => typeof team !== "string"))
552
- recordInvalid("directive", record, absoluteDirectory, "teams");
571
+ recordInvalid("directive", record, absoluteDirectory, "teams", source);
553
572
  if (!Array.isArray(record.acknowledgements))
554
- recordInvalid("directive", record, absoluteDirectory, "acknowledgements");
573
+ recordInvalid("directive", record, absoluteDirectory, "acknowledgements", source);
555
574
  const acknowledgements = record.acknowledgements.map((ack) => {
556
575
  if (!isObject(ack) || typeof ack.team !== "string" || typeof ack.lane_id !== "string")
557
- recordInvalid("directive", record, absoluteDirectory, "acknowledgements");
558
- return { team: ack.team, lane_id: ack.lane_id, at: parseTimestamp(ack.at, "acknowledgements.at", pathForTrackerRecord(absoluteDirectory, "directive", record.id)) };
576
+ recordInvalid("directive", record, absoluteDirectory, "acknowledgements", source);
577
+ return { team: ack.team, lane_id: ack.lane_id, at: requiredRecordTimestamp("directive", record, absoluteDirectory, "acknowledgements[].at", ack.at, source) };
559
578
  });
560
579
  directives.push({ id: record.id, created_at: createdAt, teams: record.teams, acknowledgements });
561
580
  }
@@ -3,6 +3,13 @@ interface CachedWorkerMode {
3
3
  version: number;
4
4
  value: boolean;
5
5
  }
6
+ export type FlagRegistryWorkerModeCacheOperation = "read" | "write";
7
+ export type FlagRegistryWorkerModeCacheStep = "mkdir" | "write_temp" | "link" | "unlink" | "prune" | "readdir" | "read";
8
+ /** Build bounded filesystem diagnostics without exposing the state directory. */
9
+ export declare function flagRegistryWorkerModeCacheFailureContext(error: unknown, operation: FlagRegistryWorkerModeCacheOperation, filePath: string, env?: NodeJS.ProcessEnv): {
10
+ exception: unknown;
11
+ tags: Record<string, string>;
12
+ };
6
13
  /** Keep the last server snapshot decision for this one startup-mode flag. */
7
14
  export declare function flagRegistryWorkerModeCachePath(stateDir: string, scopeIdentifiers: readonly string[]): string;
8
15
  /** A missing, invalid, or inaccessible cache never enables the rollout. */
@@ -1,8 +1,116 @@
1
+ import { execFileSync } from "node:child_process";
1
2
  import { createHash, randomUUID } from "node:crypto";
2
3
  import * as fs from "node:fs";
3
4
  import * as os from "node:os";
4
5
  import * as path from "node:path";
5
6
  export const FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG = "cli.flag-worker-self-contained";
7
+ class FlagRegistryWorkerModeCacheStepError extends Error {
8
+ cacheStep;
9
+ originalError;
10
+ constructor(cacheStep, originalError) {
11
+ super(`Flag registry worker-mode cache failed at ${cacheStep}`);
12
+ this.cacheStep = cacheStep;
13
+ this.originalError = originalError;
14
+ this.name = "FlagRegistryWorkerModeCacheStepError";
15
+ }
16
+ }
17
+ const KNOWN_ERRNOS = new Set([
18
+ "EACCES",
19
+ "EBUSY",
20
+ "EEXIST",
21
+ "EIO",
22
+ "EISDIR",
23
+ "EMFILE",
24
+ "ENFILE",
25
+ "ENOENT",
26
+ "ENOSPC",
27
+ "ENOTDIR",
28
+ "EPERM",
29
+ "EROFS",
30
+ ]);
31
+ function withCacheFsStep(cacheStep, operation) {
32
+ try {
33
+ return operation();
34
+ }
35
+ catch (error) {
36
+ if (error instanceof FlagRegistryWorkerModeCacheStepError)
37
+ throw error;
38
+ throw new FlagRegistryWorkerModeCacheStepError(cacheStep, error);
39
+ }
40
+ }
41
+ function originalCacheError(error) {
42
+ return error instanceof FlagRegistryWorkerModeCacheStepError
43
+ ? error.originalError
44
+ : error;
45
+ }
46
+ function cacheErrorCode(error) {
47
+ const original = originalCacheError(error);
48
+ return typeof original === "object" && original !== null && "code" in original
49
+ ? original.code
50
+ : undefined;
51
+ }
52
+ function directoryHasUchg(directory) {
53
+ if (process.platform !== "darwin")
54
+ return "unknown";
55
+ try {
56
+ const rawFlags = execFileSync("/usr/bin/stat", ["-f", "%f", directory], { timeout: 1000 }).toString("utf8").trim();
57
+ if (!/^\d+$/.test(rawFlags))
58
+ return "unknown";
59
+ const flags = Number(rawFlags);
60
+ if (!Number.isSafeInteger(flags))
61
+ return "unknown";
62
+ return String((flags & 0x00000002) !== 0 || (flags & 0x00020000) !== 0);
63
+ }
64
+ catch {
65
+ // This bounded diagnostic is best-effort; preserve unknown and the original cache error.
66
+ return "unknown";
67
+ }
68
+ }
69
+ /** Build bounded filesystem diagnostics without exposing the state directory. */
70
+ export function flagRegistryWorkerModeCacheFailureContext(error, operation, filePath, env = process.env) {
71
+ const cacheError = error instanceof FlagRegistryWorkerModeCacheStepError
72
+ ? error
73
+ : undefined;
74
+ const directory = path.dirname(filePath);
75
+ let dirExists = "unknown";
76
+ let stats;
77
+ try {
78
+ stats = fs.statSync(directory);
79
+ dirExists = "true";
80
+ }
81
+ catch (metadataError) {
82
+ if (cacheErrorCode(metadataError) === "ENOENT")
83
+ dirExists = "false";
84
+ // Keep inaccessible metadata unknown; diagnostics must not hide the cache error.
85
+ }
86
+ const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
87
+ const ownerMatches = typeof uid === "number" && typeof stats?.uid === "number"
88
+ ? String(stats.uid === uid)
89
+ : "unknown";
90
+ const dirMode = typeof stats?.mode === "number"
91
+ ? (stats.mode & 0o777).toString(8).padStart(3, "0")
92
+ : "unknown";
93
+ const dirHasUchg = directoryHasUchg(directory);
94
+ const rawErrno = cacheErrorCode(error);
95
+ const errno = typeof rawErrno === "string" && KNOWN_ERRNOS.has(rawErrno)
96
+ ? rawErrno
97
+ : "UNKNOWN";
98
+ const stateDirSource = env.HQ_STATE_DIR?.trim() ? "env" : "home";
99
+ return {
100
+ exception: originalCacheError(error),
101
+ tags: {
102
+ cache_op: operation,
103
+ cache_step: cacheError?.cacheStep ?? (operation === "read" ? "read" : "write_temp"),
104
+ errno,
105
+ state_dir_source: stateDirSource,
106
+ dir_exists: dirExists,
107
+ dir_owner_matches_uid: ownerMatches,
108
+ dir_mode: dirMode,
109
+ dir_has_uchg: dirHasUchg,
110
+ cache_path_kind: "<stateDir>/flags/<basename>",
111
+ },
112
+ };
113
+ }
6
114
  /** Keep the last server snapshot decision for this one startup-mode flag. */
7
115
  export function flagRegistryWorkerModeCachePath(stateDir, scopeIdentifiers) {
8
116
  const basename = scopeIdentifiers.length === 0
@@ -17,11 +125,11 @@ export function flagRegistryWorkerModeCachePath(stateDir, scopeIdentifiers) {
17
125
  export function readCachedFlagRegistryWorkerMode(filePath) {
18
126
  let latest = readCacheRecordIfPresent(filePath);
19
127
  const directory = path.dirname(filePath);
20
- if (!fs.existsSync(directory))
128
+ if (!withCacheFsStep("read", () => fs.existsSync(directory)))
21
129
  return latest;
22
130
  const basename = path.basename(filePath);
23
131
  const versionedPrefix = `${basename}.v`;
24
- for (const entry of fs.readdirSync(directory)) {
132
+ for (const entry of withCacheFsStep("readdir", () => fs.readdirSync(directory))) {
25
133
  if (!entry.startsWith(versionedPrefix) || !entry.endsWith(".json"))
26
134
  continue;
27
135
  const versionedPath = path.join(directory, entry);
@@ -32,7 +140,7 @@ export function readCachedFlagRegistryWorkerMode(filePath) {
32
140
  return latest;
33
141
  }
34
142
  export function writeCachedFlagRegistryWorkerMode(filePath, snapshot) {
35
- fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
143
+ withCacheFsStep("mkdir", () => fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 }));
36
144
  const existing = readCachedFlagRegistryWorkerMode(filePath);
37
145
  if (existing && snapshot.version < existing.version)
38
146
  return;
@@ -42,26 +150,27 @@ export function writeCachedFlagRegistryWorkerMode(filePath, snapshot) {
42
150
  const versionedPath = `${filePath}.v${snapshot.version}.json`;
43
151
  const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
44
152
  try {
45
- fs.writeFileSync(tempPath, `${JSON.stringify(snapshot)}\n`, {
153
+ withCacheFsStep("write_temp", () => fs.writeFileSync(tempPath, `${JSON.stringify(snapshot)}\n`, {
46
154
  encoding: "utf8",
47
155
  mode: 0o600,
48
156
  flag: "wx",
49
- });
157
+ }));
50
158
  try {
51
- fs.linkSync(tempPath, versionedPath);
159
+ withCacheFsStep("link", () => fs.linkSync(tempPath, versionedPath));
52
160
  }
53
161
  catch (error) {
54
162
  if (!isAlreadyExistsError(error))
55
163
  throw error;
56
164
  const existingVersion = readCacheRecord(versionedPath);
57
165
  if (existingVersion.value !== snapshot.value) {
58
- throw new Error("Conflicting flag-registry worker cache values share a version");
166
+ throw new FlagRegistryWorkerModeCacheStepError("link", new Error("Conflicting flag-registry worker cache values share a version"));
59
167
  }
60
168
  }
61
169
  }
62
170
  finally {
63
- if (fs.existsSync(tempPath))
64
- fs.unlinkSync(tempPath);
171
+ if (withCacheFsStep("unlink", () => fs.existsSync(tempPath))) {
172
+ withCacheFsStep("unlink", () => fs.unlinkSync(tempPath));
173
+ }
65
174
  }
66
175
  pruneOlderCacheRecords(filePath);
67
176
  }
@@ -76,7 +185,8 @@ function isCachedWorkerMode(value) {
76
185
  "value" in value && typeof value.value === "boolean";
77
186
  }
78
187
  function readCacheRecord(filePath) {
79
- const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
188
+ const contents = withCacheFsStep("read", () => fs.readFileSync(filePath, "utf8"));
189
+ const value = JSON.parse(contents);
80
190
  if (!isCachedWorkerMode(value)) {
81
191
  throw new Error("Cached flag-registry worker mode has an invalid shape");
82
192
  }
@@ -97,14 +207,14 @@ function pruneOlderCacheRecords(filePath) {
97
207
  const directory = path.dirname(filePath);
98
208
  const basename = path.basename(filePath);
99
209
  const versionedPrefix = `${basename}.v`;
100
- for (const entry of fs.readdirSync(directory)) {
210
+ for (const entry of withCacheFsStep("readdir", () => fs.readdirSync(directory))) {
101
211
  if (!entry.startsWith(versionedPrefix) || !entry.endsWith(".json"))
102
212
  continue;
103
213
  const recordPath = path.join(directory, entry);
104
214
  const record = readCacheRecordIfPresent(recordPath);
105
215
  if (record && record.version < latest.version) {
106
216
  try {
107
- fs.unlinkSync(recordPath);
217
+ withCacheFsStep("prune", () => fs.unlinkSync(recordPath));
108
218
  }
109
219
  catch (error) {
110
220
  if (!isNotFoundError(error))
@@ -112,11 +222,11 @@ function pruneOlderCacheRecords(filePath) {
112
222
  }
113
223
  }
114
224
  }
115
- if (fs.existsSync(filePath)) {
225
+ if (withCacheFsStep("prune", () => fs.existsSync(filePath))) {
116
226
  const legacyRecord = readCacheRecordIfPresent(filePath);
117
227
  if (legacyRecord && legacyRecord.version < latest.version) {
118
228
  try {
119
- fs.unlinkSync(filePath);
229
+ withCacheFsStep("prune", () => fs.unlinkSync(filePath));
120
230
  }
121
231
  catch (error) {
122
232
  if (!isNotFoundError(error))
@@ -136,11 +246,9 @@ function readCacheRecordIfPresent(filePath) {
136
246
  }
137
247
  }
138
248
  function isAlreadyExistsError(error) {
139
- return typeof error === "object" && error !== null &&
140
- "code" in error && error.code === "EEXIST";
249
+ return cacheErrorCode(error) === "EEXIST";
141
250
  }
142
251
  function isNotFoundError(error) {
143
- return typeof error === "object" && error !== null &&
144
- "code" in error && error.code === "ENOENT";
252
+ return cacheErrorCode(error) === "ENOENT";
145
253
  }
146
254
  //# sourceMappingURL=flag-registry-worker-mode-cache.js.map
@@ -14,7 +14,7 @@ import { workContextRoot } from "./work-context/paths.js";
14
14
  import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
15
15
  import { loadCachedTokens, resolveDefaultHqRoot } from "../utils/cognito-session.js";
16
16
  import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
17
- import { FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG, flagRegistryStateDir, flagRegistryWorkerModeCachePath, readCachedFlagRegistryWorkerMode, writeCachedFlagRegistryWorkerMode, } from "./flag-registry-worker-mode-cache.js";
17
+ import { FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG, flagRegistryWorkerModeCacheFailureContext, flagRegistryStateDir, flagRegistryWorkerModeCachePath, readCachedFlagRegistryWorkerMode, writeCachedFlagRegistryWorkerMode, } from "./flag-registry-worker-mode-cache.js";
18
18
  import { FLAG_REGISTRY_WORKER_SOURCE } from "./flag-registry-worker-source.generated.js";
19
19
  export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
20
20
  const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
@@ -140,8 +140,13 @@ function selfContainedWorkerEnabled(company) {
140
140
  return readCachedFlagRegistryWorkerMode(filePath)?.value ?? false;
141
141
  }
142
142
  catch (error) {
143
- Sentry.captureException(error, {
144
- tags: { component: "flag-registry-worker-mode-cache", worker: "flag-registry" },
143
+ const failure = flagRegistryWorkerModeCacheFailureContext(error, "read", filePath);
144
+ Sentry.captureException(failure.exception, {
145
+ tags: {
146
+ component: "flag-registry-worker-mode-cache",
147
+ worker: "flag-registry",
148
+ ...failure.tags,
149
+ },
145
150
  });
146
151
  return false;
147
152
  }
@@ -165,8 +170,14 @@ function cacheWorkerModeFromSnapshot(snapshot, company) {
165
170
  writeCachedFlagRegistryWorkerMode(flagRegistryWorkerModeCachePath(flagRegistryStateDir(), scopes), { version: snapshot.version, value: decision.value });
166
171
  }
167
172
  catch (error) {
168
- Sentry.captureException(error, {
169
- tags: { component: "flag-registry-worker-mode-cache", worker: "flag-registry" },
173
+ const filePath = flagRegistryWorkerModeCachePath(flagRegistryStateDir(), scopes);
174
+ const failure = flagRegistryWorkerModeCacheFailureContext(error, "write", filePath);
175
+ Sentry.captureException(failure.exception, {
176
+ tags: {
177
+ component: "flag-registry-worker-mode-cache",
178
+ worker: "flag-registry",
179
+ ...failure.tags,
180
+ },
170
181
  });
171
182
  }
172
183
  }
@@ -3,6 +3,12 @@ import { meshJson, requireToken } from "../mesh/client.js";
3
3
  import { LanesError } from "./errors.js";
4
4
  const ROOT = "/v1/work-mesh/tracker/records";
5
5
  const MAX_RECORD_PAGES = 20;
6
+ const RECORD_FIELDS = {
7
+ blocker: ["story_id", "what", "waiting_on", "next_step", "opened_at", "resolved_at", "resolution", "import_key", "source_path", "source_item_id"],
8
+ kpi: ["name", "definition", "unit", "target", "direction", "status", "approved_by", "approved_at", "values", "import_key", "source_path", "source_item_id"],
9
+ okr: ["objective", "period", "key_results", "status", "approved_by", "approved_at", "import_key", "source_path", "source_item_id"],
10
+ directive: ["text", "date", "source_url", "teams", "policy_path", "acknowledgements", "import_key", "source_path", "source_item_id"],
11
+ };
6
12
  function fail(error, message, details = {}) {
7
13
  throw new LanesError(error, message, details);
8
14
  }
@@ -50,23 +56,30 @@ function fromMesh(value) {
50
56
  }
51
57
  function recordFromMeta(raw, company, projectId, expectedKind) {
52
58
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
53
- return fail("tracker_mesh_response_invalid", "Work Mesh returned a malformed tracker record", { project_id: projectId, kind: expectedKind });
59
+ return fail("tracker_mesh_response_invalid", "Work Mesh returned a malformed tracker record", {
60
+ source: "Work Mesh", kind: expectedKind, company: company.slug, project_id: projectId,
61
+ });
54
62
  }
55
63
  const meta = raw;
56
64
  if (meta.companyUid !== company.uid || meta.projectId !== projectId || meta.kind !== expectedKind || typeof meta.threadId !== "string" || !meta.threadId) {
57
- return fail("tracker_mesh_response_invalid", "Work Mesh tracker record identity did not match the requested company, project, and kind", { project_id: projectId, kind: expectedKind });
65
+ return fail("tracker_mesh_response_invalid", "Work Mesh tracker record identity did not match the requested company, project, and kind", {
66
+ source: "Work Mesh",
67
+ ...(typeof meta.threadId === "string" && meta.threadId ? { record_id: meta.threadId } : {}),
68
+ kind: expectedKind,
69
+ company: company.slug,
70
+ project_id: projectId,
71
+ });
58
72
  }
59
73
  const fields = fromMesh(meta);
60
74
  const createdAt = typeof meta.createdAt === "string" ? meta.createdAt : "";
61
75
  const updatedAt = typeof meta.lastActivityAt === "string" ? meta.lastActivityAt : createdAt;
62
76
  const version = meta.version;
63
77
  if (!Number.isSafeInteger(version) || version < 1 || !Number.isFinite(Date.parse(createdAt)) || !Number.isFinite(Date.parse(updatedAt))) {
64
- return fail("tracker_mesh_response_invalid", "Work Mesh tracker record has invalid version or timestamps", { record_id: meta.threadId, kind: expectedKind });
78
+ return fail("tracker_mesh_response_invalid", "Work Mesh tracker record has invalid version or timestamps", {
79
+ source: "Work Mesh", record_id: meta.threadId, kind: expectedKind, company: company.slug, project_id: projectId,
80
+ });
65
81
  }
66
- const { company_uid: _companyUid, project_id: _projectId, thread_id: _threadId, thread_status: threadStatus, ...rest } = fields;
67
82
  const record = {
68
- ...rest,
69
- ...(typeof threadStatus === "string" && rest.status === undefined ? { status: threadStatus } : {}),
70
83
  schema_version: "hq-lanes-tracker-record.v1",
71
84
  kind: expectedKind,
72
85
  id: meta.threadId,
@@ -76,6 +89,16 @@ function recordFromMeta(raw, company, projectId, expectedKind) {
76
89
  created_at: createdAt,
77
90
  updated_at: updatedAt,
78
91
  };
92
+ for (const field of RECORD_FIELDS[expectedKind]) {
93
+ if (Object.hasOwn(fields, field))
94
+ record[field] = fields[field];
95
+ }
96
+ // hq-pro stores KPI and OKR status in the shared threadStatus metadata field.
97
+ if ((expectedKind === "kpi" || expectedKind === "okr") && !Object.hasOwn(record, "status") && Object.hasOwn(fields, "thread_status")) {
98
+ record.status = fields.thread_status;
99
+ }
100
+ if (expectedKind === "blocker" && !Object.hasOwn(record, "opened_at"))
101
+ record.opened_at = createdAt;
79
102
  return record;
80
103
  }
81
104
  function identityMatches(record, kind, id, company, projectId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.228.1",
3
+ "version": "5.228.3",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {