@indigoai-us/hq-cli 5.231.1 → 5.232.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.
- package/CHANGELOG.md +14 -0
- package/dist/commands/core-ontology.js +2 -0
- package/dist/lib/checkpoint-ontology/apply.d.ts +6 -1
- package/dist/lib/checkpoint-ontology/apply.js +10 -0
- package/dist/lib/checkpoint-ontology/plan.d.ts +11 -0
- package/dist/lib/checkpoint-ontology/plan.js +13 -2
- package/dist/lib/checkpoint-ontology/sync-client.d.ts +38 -0
- package/dist/lib/checkpoint-ontology/sync-client.js +84 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.232.0] — 2026-09-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `checkpoint.ontology.sync-gated` (off by default) turns the checkpoint
|
|
10
|
+
ontology step on like `checkpoint.ontology`. Older CLI builds do not read it,
|
|
11
|
+
so it can be turned on widely without them. With either flag on, the step runs
|
|
12
|
+
only when the hq-cloud bundled with this CLI and the one the hq daemon runs
|
|
13
|
+
are both 6.18.7 or later; otherwise it skips and says which copy is older.
|
|
14
|
+
It also skips while the daemon's sync still runs from before its hq-cloud
|
|
15
|
+
copy was installed. `hq core ontology apply` checks again before it writes and
|
|
16
|
+
reports `sync_client_old` with the reason. Earlier hq-cloud releases pushed
|
|
17
|
+
stale copies of deleted entities.
|
|
18
|
+
|
|
5
19
|
## [5.231.1] — 2026-09-27
|
|
6
20
|
|
|
7
21
|
### Fixed
|
|
@@ -86,6 +86,8 @@ export function runOntologyApply(hqRoot, runDirOption, deps = {}) {
|
|
|
86
86
|
throw new OntologyUsageError("ontology: the run payload names no company");
|
|
87
87
|
}
|
|
88
88
|
const report = applyOntologyRun({ hqRoot, runDir, company, now: deps.now?.() });
|
|
89
|
+
if (report.status === "sync_client_old")
|
|
90
|
+
return `ontology: skipped (${report.skip_reason}); nothing written`;
|
|
89
91
|
if (report.status !== "applied")
|
|
90
92
|
return `ontology: ${report.status.replaceAll("_", " ")}; nothing written`;
|
|
91
93
|
return (`ontology: ${report.created.length} created, ${report.updated.length} updated, ` +
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type DuplicateReport, type MergeBackOutcome } from "./multi-writer.js";
|
|
9
9
|
import { type CompanyBriefResult } from "./company-brief.js";
|
|
10
|
+
import { type SyncClientVersions } from "./sync-client.js";
|
|
10
11
|
import { type WritableEntityType } from "./identity.js";
|
|
11
12
|
import { type EntityIndex } from "./tree.js";
|
|
12
13
|
export declare const CAPS: {
|
|
@@ -30,7 +31,9 @@ export interface ApplyReport {
|
|
|
30
31
|
schema: "hq-checkpoint-ontology-report.v1";
|
|
31
32
|
company: string;
|
|
32
33
|
run_id: string;
|
|
33
|
-
status: "applied" | "no_candidates" | "invalid_candidates";
|
|
34
|
+
status: "applied" | "no_candidates" | "invalid_candidates" | "sync_client_old";
|
|
35
|
+
/** Present only with status `sync_client_old`: which sync client copy is older or unknown. */
|
|
36
|
+
skip_reason?: string;
|
|
34
37
|
planned_inputs: number;
|
|
35
38
|
planned_tokens: number;
|
|
36
39
|
candidates: number;
|
|
@@ -94,6 +97,8 @@ export interface ApplyOptions {
|
|
|
94
97
|
/** The payload company. Must equal the plan's company. */
|
|
95
98
|
company: string;
|
|
96
99
|
now?: Date;
|
|
100
|
+
/** Test seam for the sync client check; defaults to reading this machine's hq-cloud copies. */
|
|
101
|
+
syncClientVersions?: () => SyncClientVersions;
|
|
97
102
|
}
|
|
98
103
|
export declare class ApplyError extends Error {
|
|
99
104
|
constructor(message: string);
|
|
@@ -12,6 +12,7 @@ import { entityKey, freshAliases, MAX_NAME_LENGTH, ownNames } from "./aliases.js
|
|
|
12
12
|
import { recordOutbox, scanDuplicates } from "./multi-writer.js";
|
|
13
13
|
import { writeCompanyBrief } from "./company-brief.js";
|
|
14
14
|
import { entitiesRoot, resolveOntologyCompany, safeWriteFile } from "./company.js";
|
|
15
|
+
import { checkSyncClient, readSyncClientVersions } from "./sync-client.js";
|
|
15
16
|
import { appendBackRefs, parseEntityFile, patchEntityFile, previewSignalContent, renderNewEntity, validateEntityFrontmatter, } from "./entity-file.js";
|
|
16
17
|
import { canonicalizeName, entityId, isRejectedName, isWritableEntityType, normalizeEntityName, squash, toSlug, } from "./identity.js";
|
|
17
18
|
import { CANDIDATES_FILE, CANDIDATES_SCHEMA, PLAN_FILE, PLAN_SCHEMA, REPORT_FILE, readLedger, writeLedger, } from "./plan.js";
|
|
@@ -232,6 +233,15 @@ export function applyOntologyRun(options) {
|
|
|
232
233
|
fs.writeFileSync(path.join(options.runDir, REPORT_FILE), `${JSON.stringify(report, null, 2)}\n`);
|
|
233
234
|
return report;
|
|
234
235
|
};
|
|
236
|
+
// CO-45: the sync client can change between plan and apply, so check it
|
|
237
|
+
// again before anything is written.
|
|
238
|
+
const syncClient = checkSyncClient((options.syncClientVersions ?? readSyncClientVersions)());
|
|
239
|
+
if (!syncClient.ok) {
|
|
240
|
+
report.status = "sync_client_old";
|
|
241
|
+
report.skip_reason = syncClient.reason;
|
|
242
|
+
report.inputs_pending = plan.inputs.filter((i) => i.kind !== "session").map((i) => i.id);
|
|
243
|
+
return finish();
|
|
244
|
+
}
|
|
235
245
|
const candidatesPath = path.join(options.runDir, CANDIDATES_FILE);
|
|
236
246
|
if (!fs.existsSync(candidatesPath)) {
|
|
237
247
|
report.status = "no_candidates";
|
|
@@ -11,7 +11,16 @@ import { type CompanyBriefResult } from "./company-brief.js";
|
|
|
11
11
|
import { type OntologyCompany } from "./company.js";
|
|
12
12
|
import { type MultiWriterStart } from "./multi-writer.js";
|
|
13
13
|
import { type EntityHint } from "./hints.js";
|
|
14
|
+
import { type SyncClientVersions } from "./sync-client.js";
|
|
14
15
|
export declare const ONTOLOGY_FLAG = "checkpoint.ontology";
|
|
16
|
+
/**
|
|
17
|
+
* Turns the step on like `checkpoint.ontology`, but only builds that check the
|
|
18
|
+
* sync client read it (CO-45, owner decision CO-D-005). Older CLI builds read
|
|
19
|
+
* only `checkpoint.ontology`, so this key can default on without starting the
|
|
20
|
+
* step next to an hq-cloud older than 6.18.7. Either key needs a sync client of
|
|
21
|
+
* 6.18.7 or later; see ./sync-client.ts.
|
|
22
|
+
*/
|
|
23
|
+
export declare const ONTOLOGY_SYNC_GATED_FLAG = "checkpoint.ontology.sync-gated";
|
|
15
24
|
export declare const COMPANY_BRIEF_FLAG = "checkpoint.ontology.company-brief";
|
|
16
25
|
/**
|
|
17
26
|
* Applies only when the payload's sibling backend is grok. On gold-v2, Claude
|
|
@@ -159,6 +168,8 @@ export interface PlanOptions {
|
|
|
159
168
|
payload: CheckpointPayloadLike;
|
|
160
169
|
readFlag: FlagReader;
|
|
161
170
|
now?: Date;
|
|
171
|
+
/** This machine's hq-cloud versions; defaults to reading them. */
|
|
172
|
+
syncClientVersions?: () => SyncClientVersions;
|
|
162
173
|
}
|
|
163
174
|
export type PlanOutcome = {
|
|
164
175
|
status: "skipped";
|
|
@@ -14,8 +14,17 @@ import { writeCompanyBrief } from "./company-brief.js";
|
|
|
14
14
|
import { resolveOntologyCompany, safeWriteFile, stateRoot } from "./company.js";
|
|
15
15
|
import { startMultiWriterRun } from "./multi-writer.js";
|
|
16
16
|
import { entityHints, wordWeights } from "./hints.js";
|
|
17
|
+
import { checkSyncClient, readSyncClientVersions } from "./sync-client.js";
|
|
17
18
|
import { buildEntityIndex, listKnowledgeFiles, listSignalFiles, readSignal } from "./tree.js";
|
|
18
19
|
export const ONTOLOGY_FLAG = "checkpoint.ontology";
|
|
20
|
+
/**
|
|
21
|
+
* Turns the step on like `checkpoint.ontology`, but only builds that check the
|
|
22
|
+
* sync client read it (CO-45, owner decision CO-D-005). Older CLI builds read
|
|
23
|
+
* only `checkpoint.ontology`, so this key can default on without starting the
|
|
24
|
+
* step next to an hq-cloud older than 6.18.7. Either key needs a sync client of
|
|
25
|
+
* 6.18.7 or later; see ./sync-client.ts.
|
|
26
|
+
*/
|
|
27
|
+
export const ONTOLOGY_SYNC_GATED_FLAG = "checkpoint.ontology.sync-gated";
|
|
19
28
|
export const COMPANY_BRIEF_FLAG = "checkpoint.ontology.company-brief";
|
|
20
29
|
/**
|
|
21
30
|
* Applies only when the payload's sibling backend is grok. On gold-v2, Claude
|
|
@@ -231,10 +240,12 @@ export async function planOntologyRun(options) {
|
|
|
231
240
|
if (!resolution.ok)
|
|
232
241
|
return { status: "skipped", reason: resolution.reason };
|
|
233
242
|
const company = resolution.company;
|
|
234
|
-
|
|
243
|
+
const ontologyOn = (await options.readFlag(ONTOLOGY_FLAG, company)) || (await options.readFlag(ONTOLOGY_SYNC_GATED_FLAG, company));
|
|
244
|
+
const syncClient = ontologyOn ? checkSyncClient((options.syncClientVersions ?? readSyncClientVersions)()) : null;
|
|
245
|
+
if (!ontologyOn || !syncClient?.ok) {
|
|
235
246
|
// A preference recorded while the ontology was on must not outlive it.
|
|
236
247
|
fs.rmSync(backendPreferencePath(hqRoot, company), { force: true });
|
|
237
|
-
return { status: "skipped", reason: "flag off" };
|
|
248
|
+
return { status: "skipped", reason: syncClient && !syncClient.ok ? syncClient.reason : "flag off" };
|
|
238
249
|
}
|
|
239
250
|
const briefOn = await options.readFlag(COMPANY_BRIEF_FLAG, company);
|
|
240
251
|
const ignoreAnnotations = (await options.readFlag(IGNORE_ANNOTATIONS_FLAG, company)) && payload.backend === "grok";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sync client check for the checkpoint ontology step (CO-45, owner
|
|
3
|
+
* decision CO-D-005).
|
|
4
|
+
*
|
|
5
|
+
* The step writes entity files locally and a sync client pushes them. hq-cloud
|
|
6
|
+
* before 6.18.7 pushed stale local copies of deleted entities as new objects
|
|
7
|
+
* and overwrote newer entities with older ones (INC-CO-RESURRECT). hq-flags
|
|
8
|
+
* cannot target by client version, so the step checks this machine's hq-cloud
|
|
9
|
+
* copies itself: the one bundled with this CLI (`hq sync`) and the one the hq
|
|
10
|
+
* daemon runs. If either is older than 6.18.7, or the bundled copy cannot be
|
|
11
|
+
* read, the step does not run. The daemon picks up a new hq-cloud only when
|
|
12
|
+
* its sync unit restarts, so a sync unit that started before its copy was
|
|
13
|
+
* installed may still run older code; the step does not run then either.
|
|
14
|
+
*
|
|
15
|
+
* The desktop app's sync runner is not visible from here. The vault's
|
|
16
|
+
* `vault.refuse-stale-entity-recreate` guard refuses those two writes from any
|
|
17
|
+
* client.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MIN_SYNC_CLIENT_VERSION = "6.18.7";
|
|
20
|
+
export interface SyncClientVersions {
|
|
21
|
+
/** hq-cloud bundled with this CLI, or null when it cannot be read. */
|
|
22
|
+
bundled: string | null;
|
|
23
|
+
/** hq-cloud the hq daemon runs, or null when it cannot be read. */
|
|
24
|
+
daemon: string | null;
|
|
25
|
+
/** True when the daemon's running sync unit started before that copy was installed. */
|
|
26
|
+
daemonRestartPending?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export type SyncClientCheck = {
|
|
29
|
+
ok: true;
|
|
30
|
+
} | {
|
|
31
|
+
ok: false;
|
|
32
|
+
reason: string;
|
|
33
|
+
};
|
|
34
|
+
/** Read this machine's hq-cloud versions. Never throws. */
|
|
35
|
+
export declare function readSyncClientVersions(): SyncClientVersions;
|
|
36
|
+
/** Every readable copy must be at least 6.18.7, and the bundled copy must be readable. */
|
|
37
|
+
export declare function checkSyncClient(versions: SyncClientVersions): SyncClientCheck;
|
|
38
|
+
//# sourceMappingURL=sync-client.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sync client check for the checkpoint ontology step (CO-45, owner
|
|
3
|
+
* decision CO-D-005).
|
|
4
|
+
*
|
|
5
|
+
* The step writes entity files locally and a sync client pushes them. hq-cloud
|
|
6
|
+
* before 6.18.7 pushed stale local copies of deleted entities as new objects
|
|
7
|
+
* and overwrote newer entities with older ones (INC-CO-RESURRECT). hq-flags
|
|
8
|
+
* cannot target by client version, so the step checks this machine's hq-cloud
|
|
9
|
+
* copies itself: the one bundled with this CLI (`hq sync`) and the one the hq
|
|
10
|
+
* daemon runs. If either is older than 6.18.7, or the bundled copy cannot be
|
|
11
|
+
* read, the step does not run. The daemon picks up a new hq-cloud only when
|
|
12
|
+
* its sync unit restarts, so a sync unit that started before its copy was
|
|
13
|
+
* installed may still run older code; the step does not run then either.
|
|
14
|
+
*
|
|
15
|
+
* The desktop app's sync runner is not visible from here. The vault's
|
|
16
|
+
* `vault.refuse-stale-entity-recreate` guard refuses those two writes from any
|
|
17
|
+
* client.
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
import semver from "semver";
|
|
22
|
+
import { bundledHqCloud, currentHqCloud } from "../daemon/hq-cloud.js";
|
|
23
|
+
import { daemonPaths } from "../daemon/paths.js";
|
|
24
|
+
export const MIN_SYNC_CLIENT_VERSION = "6.18.7";
|
|
25
|
+
/** Read this machine's hq-cloud versions. Never throws. */
|
|
26
|
+
export function readSyncClientVersions() {
|
|
27
|
+
const bundled = bundledHqCloud()?.version ?? null;
|
|
28
|
+
let daemon = null;
|
|
29
|
+
let daemonRestartPending = false;
|
|
30
|
+
try {
|
|
31
|
+
const paths = daemonPaths();
|
|
32
|
+
const picked = currentHqCloud(paths.root);
|
|
33
|
+
daemon = picked?.version ?? null;
|
|
34
|
+
if (picked) {
|
|
35
|
+
try {
|
|
36
|
+
daemonRestartPending = syncStartedBefore(paths.statePath, path.join(picked.root, "package.json"));
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
// Unknown start time: assume the running sync may be older.
|
|
40
|
+
daemonRestartPending = true;
|
|
41
|
+
console.warn(`ontology: could not read the hq daemon's sync start time: ${err instanceof Error ? err.message : String(err)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.warn(`ontology: could not read the hq daemon's hq-cloud version: ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
}
|
|
48
|
+
return { bundled, daemon, daemonRestartPending };
|
|
49
|
+
}
|
|
50
|
+
/** Whether the daemon's running sync unit started before `copyPackageJson` was written. No state file: no running unit. */
|
|
51
|
+
function syncStartedBefore(statePath, copyPackageJson) {
|
|
52
|
+
let state;
|
|
53
|
+
try {
|
|
54
|
+
state = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (err.code === "ENOENT")
|
|
58
|
+
return false;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
const sync = state.units?.find((u) => u.id === "sync" && u.status === "running");
|
|
62
|
+
if (!sync || typeof sync.startedAt !== "number")
|
|
63
|
+
return false;
|
|
64
|
+
return fs.statSync(copyPackageJson).mtimeMs > sync.startedAt;
|
|
65
|
+
}
|
|
66
|
+
/** Every readable copy must be at least 6.18.7, and the bundled copy must be readable. */
|
|
67
|
+
export function checkSyncClient(versions) {
|
|
68
|
+
if (versions.bundled === null) {
|
|
69
|
+
return { ok: false, reason: `sync client version unknown; needs hq-cloud ${MIN_SYNC_CLIENT_VERSION} or later` };
|
|
70
|
+
}
|
|
71
|
+
for (const [name, version] of [["bundled", versions.bundled], ["daemon", versions.daemon]]) {
|
|
72
|
+
if (version === null)
|
|
73
|
+
continue;
|
|
74
|
+
const parsed = semver.valid(version);
|
|
75
|
+
if (parsed === null || semver.lt(parsed, MIN_SYNC_CLIENT_VERSION)) {
|
|
76
|
+
return { ok: false, reason: `sync client hq-cloud ${version} (${name}) is older than ${MIN_SYNC_CLIENT_VERSION}` };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (versions.daemonRestartPending) {
|
|
80
|
+
return { ok: false, reason: `the hq daemon's sync has not restarted onto hq-cloud ${versions.daemon ?? "(unknown)"} yet` };
|
|
81
|
+
}
|
|
82
|
+
return { ok: true };
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=sync-client.js.map
|