@indigoai-us/hq-cli 5.291.0 → 5.293.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 +53 -8
- package/assets/scaffold/core/scripts/handoff-finalize.sh +18 -0
- package/assets/scaffold/core/scripts/handoff-post.sh +71 -6
- package/assets/scaffold/core/scripts/hq-delegate-bundle.sh +307 -0
- package/assets/scaffold/core/scripts/hq-delegate-grant.sh +226 -0
- package/assets/scaffold/core/scripts/hq-delegate-publish.sh +91 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +259 -0
- package/assets/scaffold/core/scripts/hq-delegate-verify.sh +213 -0
- package/assets/scaffold/core/scripts/hq-job-notify.sh +389 -0
- package/assets/scaffold/core/scripts/hq-job-probe.sh +835 -0
- package/assets/scaffold/core/scripts/hq-job-remediate.sh +359 -0
- package/assets/scaffold/core/scripts/hq-job-run.sh +1025 -0
- package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +830 -0
- package/dist/command-catalog.generated.d.ts +109 -1
- package/dist/command-catalog.generated.js +136 -1
- package/dist/commands/core-delegate-w1b.d.ts +8 -0
- package/dist/commands/core-delegate-w1b.js +1049 -0
- package/dist/commands/core-delegate.d.ts +28 -0
- package/dist/commands/core-delegate.js +15 -13
- package/dist/commands/core-handoff.js +152 -23
- package/dist/commands/core-jobs-native.d.ts +15 -0
- package/dist/commands/core-jobs-native.js +709 -2
- package/dist/commands/core-ontology.d.ts +22 -4
- package/dist/commands/core-ontology.js +90 -8
- package/dist/commands/core.js +10 -0
- package/dist/commands/lanes-move.js +54 -1
- package/dist/commands/lanes-questions.js +4 -0
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +119 -5
- package/dist/lib/checkpoint-ontology/drop-aliases.d.ts +41 -0
- package/dist/lib/checkpoint-ontology/drop-aliases.js +76 -0
- package/dist/lib/checkpoint-ontology/entity-file.d.ts +6 -3
- package/dist/lib/checkpoint-ontology/entity-file.js +12 -6
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +27 -8
- package/dist/lib/checkpoint-ontology/merge-copies.js +64 -97
- package/dist/lib/core-utils/sentry-report.js +2 -2
- package/dist/lib/daemon/flag.d.ts +2 -0
- package/dist/lib/daemon/flag.js +2 -0
- package/dist/lib/daemon/run.d.ts +11 -0
- package/dist/lib/daemon/run.js +17 -1
- package/dist/lib/lanes/autopilot.d.ts +13 -1
- package/dist/lib/lanes/autopilot.js +271 -28
- package/dist/lib/lanes/grants.d.ts +1 -1
- package/dist/lib/lanes/grants.js +7 -5
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +5 -0
- package/dist/lib/lanes/keep-alive.js +9 -4
- package/dist/lib/lanes/questions.d.ts +2 -1
- package/dist/lib/lanes/questions.js +2 -0
- package/dist/lib/lanes/reconcile.js +186 -9
- package/dist/lib/lanes/spawn.js +4 -0
- package/dist/lib/lanes/store.js +21 -1
- package/dist/lib/lanes/types.d.ts +13 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.js +3 -1
- package/dist/utils/scaffold-sibling-references.js +4 -4
- package/dist/utils/self-update.d.ts +13 -0
- package/dist/utils/self-update.js +33 -7
- package/dist/utils/staged-install.d.ts +34 -0
- package/dist/utils/staged-install.js +296 -0
- package/dist/utils/version-gate.d.ts +15 -0
- package/dist/utils/version-gate.js +46 -2
- package/package.json +1 -1
- package/scripts/mesh-daemon-if-stale.mjs +4 -0
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
import { type FlagReader } from "../lib/checkpoint-ontology/plan.js";
|
|
12
|
-
import { type
|
|
12
|
+
import { type AliasDrop } from "../lib/checkpoint-ontology/drop-aliases.js";
|
|
13
|
+
import { type DeleteVaultKey, type ListedPair, type MergeCopiesReport } from "../lib/checkpoint-ontology/merge-copies.js";
|
|
13
14
|
export declare const ONTOLOGY_PAYLOAD_FILE = "payload.json";
|
|
14
15
|
/** The hq-flags read, scoped to the payload company. */
|
|
15
16
|
export declare const registryFlagReader: FlagReader;
|
|
@@ -31,12 +32,15 @@ export interface MergeCopiesCommandOptions {
|
|
|
31
32
|
apply?: boolean;
|
|
32
33
|
delete?: boolean;
|
|
33
34
|
variants?: boolean;
|
|
35
|
+
/** Merge exactly the `--only` pairs, whatever their names (flag merge-listed-pairs). */
|
|
36
|
+
listed?: boolean;
|
|
34
37
|
limit?: number;
|
|
35
38
|
only?: string;
|
|
36
39
|
/** Also re-point refs and links held today (flag merge-rewrite-refs). */
|
|
37
40
|
rewriteRefs?: boolean;
|
|
38
41
|
json?: boolean;
|
|
39
42
|
}
|
|
43
|
+
export declare function mergeCopiesSummary(report: MergeCopiesReport): string;
|
|
40
44
|
/** Reads a `--only` file: a JSON array of `{ copy, original }` company-relative paths. */
|
|
41
45
|
export declare function readListedPairs(file: string): ListedPair[];
|
|
42
46
|
/**
|
|
@@ -47,10 +51,24 @@ export declare function readListedPairs(file: string): ListedPair[];
|
|
|
47
51
|
* With `--variants`, merges two-file duplicate groups whose names differ only
|
|
48
52
|
* in case, spacing or punctuation instead, gated by
|
|
49
53
|
* `checkpoint.ontology.merge-variants`. `--rewrite-refs` (flag
|
|
50
|
-
* `checkpoint.ontology.merge-rewrite-refs`) also re-points
|
|
51
|
-
*
|
|
52
|
-
*
|
|
54
|
+
* `checkpoint.ontology.merge-rewrite-refs`) also re-points invoice and
|
|
55
|
+
* knowledge links to the survivor and carries external ids and web domains;
|
|
56
|
+
* pairs named by signals or sources stay held. `--listed` (flag
|
|
57
|
+
* `checkpoint.ontology.merge-listed-pairs`) merges exactly the `--only` pairs,
|
|
58
|
+
* which a person reviewed, instead of the pairs a finder produces.
|
|
53
59
|
*/
|
|
54
60
|
export declare function runOntologyMergeCopies(hqRoot: string, slug: string | undefined, options: MergeCopiesCommandOptions, deps?: OntologyCommandDeps): Promise<string>;
|
|
61
|
+
/** Reads a `drop-aliases --only` file: a JSON array of `{ entity, alias }`. */
|
|
62
|
+
export declare function readAliasDrops(file: string): AliasDrop[];
|
|
63
|
+
/**
|
|
64
|
+
* `hq core ontology drop-aliases`: remove the reviewed `{ entity, alias }`
|
|
65
|
+
* pairs in `--only` from entity files (`--apply`); without it only reports.
|
|
66
|
+
* Gated by `checkpoint.ontology.drop-aliases` and `checkpoint.ontology.multi-writer`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function runOntologyDropAliases(hqRoot: string, slug: string | undefined, options: {
|
|
69
|
+
only?: string;
|
|
70
|
+
apply?: boolean;
|
|
71
|
+
json?: boolean;
|
|
72
|
+
}, deps?: OntologyCommandDeps): Promise<string>;
|
|
55
73
|
export declare function registerCoreOntologyCommand(core: Command): void;
|
|
56
74
|
//# sourceMappingURL=core-ontology.d.ts.map
|
|
@@ -17,7 +17,8 @@ import { applyOntologyRun, ApplyError } from "../lib/checkpoint-ontology/apply.j
|
|
|
17
17
|
import { resolveOntologyCompany, WriteBoundsError } from "../lib/checkpoint-ontology/company.js";
|
|
18
18
|
import { findDuplicateGroups, summarizeMergeBack, } from "../lib/checkpoint-ontology/multi-writer.js";
|
|
19
19
|
import { buildEntityIndex } from "../lib/checkpoint-ontology/tree.js";
|
|
20
|
-
import {
|
|
20
|
+
import { DROP_ALIASES_FLAG, dropAliases } from "../lib/checkpoint-ontology/drop-aliases.js";
|
|
21
|
+
import { MERGE_COPIES_FLAG, MERGE_LISTED_PAIRS_FLAG, MERGE_REWRITE_REFS_FLAG, MERGE_VARIANTS_FLAG, mergeCopies, } from "../lib/checkpoint-ontology/merge-copies.js";
|
|
21
22
|
import { callDeleteEndpoint } from "./files.js";
|
|
22
23
|
import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
|
|
23
24
|
import { registerCoreOntologyNativeCommands } from "./core-ontology-native.js";
|
|
@@ -142,7 +143,7 @@ async function companyVaultDeleter(companyUid) {
|
|
|
142
143
|
return (await callDeleteEndpoint({ token, companyUid, prefix: key, dryRun: false })).deleted;
|
|
143
144
|
};
|
|
144
145
|
}
|
|
145
|
-
function mergeCopiesSummary(report) {
|
|
146
|
+
export function mergeCopiesSummary(report) {
|
|
146
147
|
const held = report.plans.filter((plan) => plan.held).length;
|
|
147
148
|
const lines = [
|
|
148
149
|
`ontology merge-copies (${report.mode.replace("_", " ")}) in ${report.company} (${report.pair_kind}): ${report.plans.length} of ${report.copies_found} copies, ${held} held, ${report.skipped.length} skipped`,
|
|
@@ -150,7 +151,7 @@ function mergeCopiesSummary(report) {
|
|
|
150
151
|
` duplicate groups ${report.duplicate_groups_before} -> ${report.duplicate_groups_after}; broken entity links ${report.broken_links_before} -> ${report.broken_links_after}`,
|
|
151
152
|
];
|
|
152
153
|
for (const plan of report.plans.filter((entry) => entry.held)) {
|
|
153
|
-
lines.push(` held ${plan.copy}: ${plan.held}${plan.held === "external_id_conflict" ? ` (${plan.detail})` : ""}`);
|
|
154
|
+
lines.push(` held ${plan.copy}: ${plan.held}${plan.held === "external_id_conflict" || plan.held === "server_owned_refs" ? ` (${plan.detail})` : ""}`);
|
|
154
155
|
}
|
|
155
156
|
for (const error of report.errors)
|
|
156
157
|
lines.push(` error ${error.path}: ${error.error}`);
|
|
@@ -216,9 +217,11 @@ function readResolution(copy, raw) {
|
|
|
216
217
|
* With `--variants`, merges two-file duplicate groups whose names differ only
|
|
217
218
|
* in case, spacing or punctuation instead, gated by
|
|
218
219
|
* `checkpoint.ontology.merge-variants`. `--rewrite-refs` (flag
|
|
219
|
-
* `checkpoint.ontology.merge-rewrite-refs`) also re-points
|
|
220
|
-
*
|
|
221
|
-
*
|
|
220
|
+
* `checkpoint.ontology.merge-rewrite-refs`) also re-points invoice and
|
|
221
|
+
* knowledge links to the survivor and carries external ids and web domains;
|
|
222
|
+
* pairs named by signals or sources stay held. `--listed` (flag
|
|
223
|
+
* `checkpoint.ontology.merge-listed-pairs`) merges exactly the `--only` pairs,
|
|
224
|
+
* which a person reviewed, instead of the pairs a finder produces.
|
|
222
225
|
*/
|
|
223
226
|
export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
|
|
224
227
|
if (options.apply && options.delete) {
|
|
@@ -227,13 +230,20 @@ export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
|
|
|
227
230
|
if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1)) {
|
|
228
231
|
throw new OntologyUsageError("ontology: --limit must be a positive whole number");
|
|
229
232
|
}
|
|
233
|
+
if (options.listed && options.variants) {
|
|
234
|
+
throw new OntologyUsageError("ontology: use either --listed or --variants");
|
|
235
|
+
}
|
|
236
|
+
if (options.listed && options.only === undefined) {
|
|
237
|
+
throw new OntologyUsageError("ontology: --listed needs --only <file>");
|
|
238
|
+
}
|
|
230
239
|
const only = options.only !== undefined ? readListedPairs(options.only) : undefined;
|
|
231
240
|
const resolution = resolveOntologyCompany(hqRoot, slug);
|
|
232
241
|
if (!resolution.ok)
|
|
233
242
|
throw new OntologyUsageError(`ontology: company rejected: ${resolution.reason}`);
|
|
234
243
|
const company = resolution.company;
|
|
235
244
|
const readFlag = deps.readFlag ?? registryFlagReader;
|
|
236
|
-
|
|
245
|
+
const kindFlag = options.listed ? MERGE_LISTED_PAIRS_FLAG : options.variants ? MERGE_VARIANTS_FLAG : MERGE_COPIES_FLAG;
|
|
246
|
+
for (const flag of [kindFlag, MULTI_WRITER_FLAG, ...(options.rewriteRefs ? [MERGE_REWRITE_REFS_FLAG] : [])]) {
|
|
237
247
|
if (!(await readFlag(flag, company))) {
|
|
238
248
|
return options.json ? JSON.stringify({ company: company.slug, skipped: `${flag} off` }) : `ontology merge-copies: skipped (${flag} is off)`;
|
|
239
249
|
}
|
|
@@ -251,6 +261,7 @@ export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
|
|
|
251
261
|
mode,
|
|
252
262
|
now: deps.now?.() ?? new Date(),
|
|
253
263
|
...(options.variants ? { pairKind: "variants" } : {}),
|
|
264
|
+
...(options.listed ? { pairKind: "listed" } : {}),
|
|
254
265
|
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
255
266
|
...(only ? { only } : {}),
|
|
256
267
|
...(options.rewriteRefs ? { rewriteRefs: true } : {}),
|
|
@@ -258,6 +269,59 @@ export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
|
|
|
258
269
|
});
|
|
259
270
|
return options.json ? JSON.stringify(report, null, 2) : mergeCopiesSummary(report);
|
|
260
271
|
}
|
|
272
|
+
/** Reads a `drop-aliases --only` file: a JSON array of `{ entity, alias }`. */
|
|
273
|
+
export function readAliasDrops(file) {
|
|
274
|
+
let parsed;
|
|
275
|
+
try {
|
|
276
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
throw new OntologyUsageError(`ontology: --only file ${file} is not readable JSON: ${error.message}`);
|
|
280
|
+
}
|
|
281
|
+
const usage = "ontology: --only must be a non-empty JSON array of { entity, alias } strings";
|
|
282
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
283
|
+
throw new OntologyUsageError(usage);
|
|
284
|
+
return parsed.map((item) => {
|
|
285
|
+
const record = item;
|
|
286
|
+
if (!record || typeof record.entity !== "string" || typeof record.alias !== "string" || record.alias === "") {
|
|
287
|
+
throw new OntologyUsageError(usage);
|
|
288
|
+
}
|
|
289
|
+
return { entity: record.entity, alias: record.alias };
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* `hq core ontology drop-aliases`: remove the reviewed `{ entity, alias }`
|
|
294
|
+
* pairs in `--only` from entity files (`--apply`); without it only reports.
|
|
295
|
+
* Gated by `checkpoint.ontology.drop-aliases` and `checkpoint.ontology.multi-writer`.
|
|
296
|
+
*/
|
|
297
|
+
export async function runOntologyDropAliases(hqRoot, slug, options, deps = {}) {
|
|
298
|
+
if (options.only === undefined)
|
|
299
|
+
throw new OntologyUsageError("ontology: drop-aliases needs --only <file>");
|
|
300
|
+
const drops = readAliasDrops(options.only);
|
|
301
|
+
const resolution = resolveOntologyCompany(hqRoot, slug);
|
|
302
|
+
if (!resolution.ok)
|
|
303
|
+
throw new OntologyUsageError(`ontology: company rejected: ${resolution.reason}`);
|
|
304
|
+
const company = resolution.company;
|
|
305
|
+
const readFlag = deps.readFlag ?? registryFlagReader;
|
|
306
|
+
for (const flag of [DROP_ALIASES_FLAG, MULTI_WRITER_FLAG]) {
|
|
307
|
+
if (!(await readFlag(flag, company))) {
|
|
308
|
+
return options.json ? JSON.stringify({ company: company.slug, skipped: `${flag} off` }) : `ontology drop-aliases: skipped (${flag} is off)`;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const report = dropAliases({ hqRoot, company, mode: options.apply ? "apply" : "dry_run", drops, now: deps.now?.() ?? new Date() });
|
|
312
|
+
if (options.json)
|
|
313
|
+
return JSON.stringify(report, null, 2);
|
|
314
|
+
const planned = Object.values(report.planned).reduce((sum, list) => sum + list.length, 0);
|
|
315
|
+
const lines = [
|
|
316
|
+
`ontology drop-aliases (${report.mode.replace("_", " ")}) in ${report.company}: ${planned} alias(es) on ${Object.keys(report.planned).length} entities, written ${report.written.length}, skipped ${report.skipped.length}, errors ${report.errors.length}`,
|
|
317
|
+
` duplicate groups ${report.duplicate_groups_before} -> ${report.duplicate_groups_after}`,
|
|
318
|
+
];
|
|
319
|
+
for (const skip of report.skipped)
|
|
320
|
+
lines.push(` skipped ${skip.entity} "${skip.alias}": ${skip.reason}${skip.detail ? ` (${skip.detail})` : ""}`);
|
|
321
|
+
for (const error of report.errors)
|
|
322
|
+
lines.push(` error ${error.path}: ${error.error}`);
|
|
323
|
+
return lines.join("\n");
|
|
324
|
+
}
|
|
261
325
|
function fail(error) {
|
|
262
326
|
if (error instanceof OntologyUsageError || error instanceof ApplyError || error instanceof WriteBoundsError) {
|
|
263
327
|
process.stderr.write(`${error.message}\n`);
|
|
@@ -306,9 +370,10 @@ export function registerCoreOntologyCommand(core) {
|
|
|
306
370
|
.option("--apply", "merge each copy into its original and re-point links (writes entity files)")
|
|
307
371
|
.option("--delete", "delete fully merged copies from the vault by exact key (after sync pushed the merge)")
|
|
308
372
|
.option("--variants", "merge two-file duplicate groups whose names differ only in case, spacing or punctuation (checkpoint.ontology.merge-variants)")
|
|
373
|
+
.option("--listed", "merge exactly the pairs in the --only file, whatever their names (checkpoint.ontology.merge-listed-pairs)")
|
|
309
374
|
.option("--limit <n>", "handle at most this many copies", (value) => Number(value))
|
|
310
375
|
.option("--only <file>", "handle only the copy -> original pairs listed in this JSON file")
|
|
311
|
-
.option("--rewrite-refs", "also re-point
|
|
376
|
+
.option("--rewrite-refs", "also re-point invoice and knowledge links to the survivor and carry external ids and web domains; pairs named by signals or sources stay held (checkpoint.ontology.merge-rewrite-refs)")
|
|
312
377
|
.option("--json", "print JSON")
|
|
313
378
|
.option("--hq-root <path>", "HQ installation to operate on")
|
|
314
379
|
.action(async (options) => {
|
|
@@ -320,6 +385,23 @@ export function registerCoreOntologyCommand(core) {
|
|
|
320
385
|
}
|
|
321
386
|
});
|
|
322
387
|
describeCoreCommand(mergeCopies, { execution: "native", root: "live" });
|
|
388
|
+
const dropAliasesCommand = ontology
|
|
389
|
+
.command("drop-aliases")
|
|
390
|
+
.description("Remove reviewed wrong aliases from entity files (dry run by default)")
|
|
391
|
+
.requiredOption("--company <slug>", "company to change")
|
|
392
|
+
.option("--only <file>", "JSON array of { entity, alias } to remove; entity is a company-relative path")
|
|
393
|
+
.option("--apply", "write the entity files")
|
|
394
|
+
.option("--json", "print JSON")
|
|
395
|
+
.option("--hq-root <path>", "HQ installation to operate on")
|
|
396
|
+
.action(async (options) => {
|
|
397
|
+
try {
|
|
398
|
+
process.stdout.write(`${await runOntologyDropAliases(rootFor(options), options.company, options)}\n`);
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
fail(error);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
describeCoreCommand(dropAliasesCommand, { execution: "native", root: "live" });
|
|
323
405
|
const apply = ontology
|
|
324
406
|
.command("apply")
|
|
325
407
|
.description("Validate the run's entity candidates and write them to the payload company")
|
package/dist/commands/core.js
CHANGED
|
@@ -214,6 +214,11 @@ export const SCAFFOLD_COMMANDS = [
|
|
|
214
214
|
export const SCAFFOLD_ONLY_ASSETS = [
|
|
215
215
|
{ asset: "core/scripts/jobs-schedule-parse.sh", root: "live", summary: "Rollback companion for native jobs schedule-parse" },
|
|
216
216
|
{ asset: "core/scripts/jobs-validate.sh", root: "live", summary: "Rollback companion for native jobs validate" },
|
|
217
|
+
{ asset: "core/scripts/outpost-jobs-reconcile.sh", root: "live", summary: "Rollback companion for native jobs reconcile" },
|
|
218
|
+
{ asset: "core/scripts/hq-job-probe.sh", root: "live", summary: "Scheduled jobs readiness probe used by the reconcile rollback path" },
|
|
219
|
+
{ asset: "core/scripts/hq-job-run.sh", root: "live", summary: "Scheduled jobs runner used by generated systemd units" },
|
|
220
|
+
{ asset: "core/scripts/hq-job-notify.sh", root: "live", summary: "Scheduled jobs failure notification helper" },
|
|
221
|
+
{ asset: "core/scripts/hq-job-remediate.sh", root: "live", summary: "Scheduled jobs bounded remediation helper" },
|
|
217
222
|
// Shared shell libs sourced by the two relocated scripts (and hooks). Never
|
|
218
223
|
// dispatched on their own (so no `hq core` command), but they MUST ship in the
|
|
219
224
|
// tarball, or the bundled hq-session / checkpoint-stop-gate die at `source`
|
|
@@ -235,6 +240,11 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
235
240
|
{ asset: "core/scripts/hq-delegate-repo.sh", root: "live", summary: "Rollback shell for native delegate repo" },
|
|
236
241
|
{ asset: "core/scripts/hq-delegate-secrets.sh", root: "live", summary: "Rollback shell for native delegate secrets" },
|
|
237
242
|
{ asset: "core/scripts/hq-delegate-transfer.sh", root: "live", summary: "Rollback shell for native delegate transfer" },
|
|
243
|
+
{ asset: "core/scripts/hq-delegate-bundle.sh", root: "live", summary: "Rollback shell for native delegate bundle" },
|
|
244
|
+
{ asset: "core/scripts/hq-delegate-grant.sh", root: "live", summary: "Rollback shell for native delegate grant" },
|
|
245
|
+
{ asset: "core/scripts/hq-delegate-publish.sh", root: "live", summary: "Rollback shell for native delegate publish" },
|
|
246
|
+
{ asset: "core/scripts/hq-delegate-send.sh", root: "live", summary: "Rollback shell for native delegate send" },
|
|
247
|
+
{ asset: "core/scripts/hq-delegate-verify.sh", root: "live", summary: "Rollback shell for native delegate verify" },
|
|
238
248
|
{ asset: "core/scripts/lib/secret-patterns.sh", root: "live", summary: "Secret-pattern library sourced by the delegate rollback shells" },
|
|
239
249
|
// Retained rollback companions for the gated native ontology commands. These
|
|
240
250
|
// are loose HQ-tree assets, but `hq core ontology` dispatches natively.
|
|
@@ -19,6 +19,38 @@ export function createLaneMoveHandlers(deps) {
|
|
|
19
19
|
lane.last_host_report_at === undefined &&
|
|
20
20
|
lane.pending_resume === undefined;
|
|
21
21
|
}
|
|
22
|
+
function sameHost(a, b) {
|
|
23
|
+
return a.kind === b.kind && a.id === b.id;
|
|
24
|
+
}
|
|
25
|
+
function reportAlreadyOnTarget(lane) {
|
|
26
|
+
printJson({
|
|
27
|
+
ok: true,
|
|
28
|
+
action: "move",
|
|
29
|
+
lane_id: lane.lane_id,
|
|
30
|
+
state: lane.state,
|
|
31
|
+
host: lane.host,
|
|
32
|
+
account: lane.account ?? null,
|
|
33
|
+
moved: false,
|
|
34
|
+
reason: "already_on_target",
|
|
35
|
+
lane,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function reportAlreadyOnTargetIfCurrent(hqRoot, laneId, provider, target, callerHost, runtimeHost, ctx) {
|
|
39
|
+
const current = await withLaneAdmissionLock(hqRoot, provider, async () => {
|
|
40
|
+
const lane = readLane(hqRoot, laneId);
|
|
41
|
+
if (!laneCanMoveBeforeStart(lane)) {
|
|
42
|
+
throw new LanesError(lane.state === "queued" ? "lane_move_started" : "lane_not_queued", `Lane ${laneId} cannot move after it has started; current state is ${lane.state}`, { lane_id: laneId, state: lane.state });
|
|
43
|
+
}
|
|
44
|
+
return sameHost(lane.host, target) ||
|
|
45
|
+
(laneRunsHere(lane, runtimeHost) && sameHost(callerHost, target))
|
|
46
|
+
? lane
|
|
47
|
+
: undefined;
|
|
48
|
+
}, ctx.sleep);
|
|
49
|
+
if (current === undefined)
|
|
50
|
+
return false;
|
|
51
|
+
reportAlreadyOnTarget(current);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
22
54
|
function readLaneTimeout(hqRoot, laneId) {
|
|
23
55
|
const raw = readJsonFile(timeoutPath(hqRoot, laneId));
|
|
24
56
|
if (raw === null)
|
|
@@ -51,6 +83,22 @@ export function createLaneMoveHandlers(deps) {
|
|
|
51
83
|
throw new LanesError(source.state === "queued" ? "lane_move_started" : "lane_not_queued", `Lane ${laneId} cannot move after it has started; current state is ${source.state}`, { lane_id: laneId, state: source.state });
|
|
52
84
|
}
|
|
53
85
|
const sourceHost = source.host;
|
|
86
|
+
const runtimeHost = reconcileHostFromCtx(ctx);
|
|
87
|
+
const runningHere = laneRunsHere(source, runtimeHost);
|
|
88
|
+
const callerHost = runtimeHost.outpostId
|
|
89
|
+
? { kind: "outpost", id: runtimeHost.outpostId }
|
|
90
|
+
: { kind: "local", id: runtimeHost.hostname };
|
|
91
|
+
const explicitTarget = policy.kind === "local"
|
|
92
|
+
? callerHost
|
|
93
|
+
: policy.kind === "outpost"
|
|
94
|
+
? { kind: "outpost", id: policy.id }
|
|
95
|
+
: undefined;
|
|
96
|
+
if (explicitTarget !== undefined &&
|
|
97
|
+
(sameHost(sourceHost, explicitTarget) ||
|
|
98
|
+
(runningHere && sameHost(callerHost, explicitTarget)))) {
|
|
99
|
+
if (await reportAlreadyOnTargetIfCurrent(hqRoot, laneId, source.provider, explicitTarget, callerHost, runtimeHost, ctx))
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
54
102
|
const config = loadLanesConfig(hqRoot);
|
|
55
103
|
const capacity = await loadHostCapacitySnapshots(ctx, hqRoot, config);
|
|
56
104
|
let targetSnapshots = capacity.snapshots.filter((snapshot) => snapshot.host.kind !== sourceHost.kind || snapshot.host.id !== sourceHost.id);
|
|
@@ -69,8 +117,13 @@ export function createLaneMoveHandlers(deps) {
|
|
|
69
117
|
...(source.account !== undefined ? { account: source.account } : {}),
|
|
70
118
|
...(allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
71
119
|
});
|
|
120
|
+
if (sameHost(sourceHost, choice.host) ||
|
|
121
|
+
(runningHere && sameHost(callerHost, choice.host))) {
|
|
122
|
+
if (await reportAlreadyOnTargetIfCurrent(hqRoot, laneId, source.provider, choice.host, callerHost, runtimeHost, ctx))
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
72
125
|
let moved;
|
|
73
|
-
if (
|
|
126
|
+
if (runningHere) {
|
|
74
127
|
moved = await commitLaneMove(ctx, laneId, choice.host, choice.account, placementEnabled);
|
|
75
128
|
}
|
|
76
129
|
else if (source.host.kind === "outpost") {
|
|
@@ -475,6 +475,8 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
475
475
|
.map((q) => {
|
|
476
476
|
const row = {
|
|
477
477
|
...q,
|
|
478
|
+
priority: q.priority ?? null,
|
|
479
|
+
priority_display: q.priority ?? "-",
|
|
478
480
|
audience: resolveAudience(hqRoot, q, ctx),
|
|
479
481
|
...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
|
|
480
482
|
};
|
|
@@ -503,6 +505,8 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
503
505
|
})
|
|
504
506
|
.map(({ question: q, host, audience }) => ({
|
|
505
507
|
...q,
|
|
508
|
+
priority: q.priority ?? null,
|
|
509
|
+
priority_display: q.priority ?? "-",
|
|
506
510
|
host,
|
|
507
511
|
audience,
|
|
508
512
|
...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -114,6 +114,8 @@ export declare function parseQuestionBody(raw: unknown): QuestionBody | null;
|
|
|
114
114
|
*/
|
|
115
115
|
export declare const ASK_BACKOFF_MS: readonly [5000, 15000];
|
|
116
116
|
export declare const ASK_ATTEMPTS: number;
|
|
117
|
+
/** Flip to true in the next release to make --priority mandatory. */
|
|
118
|
+
export declare const ASK_PRIORITY_REQUIRED = false;
|
|
117
119
|
export declare function casUpdateQuestion(hqRoot: string, questionId: string, updater: (current: QuestionRecord) => QuestionRecord): QuestionRecord;
|
|
118
120
|
export declare function casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
|
|
119
121
|
/**
|
package/dist/commands/lanes.js
CHANGED
|
@@ -490,6 +490,67 @@ function mergeOwnerLaneHierarchy(supplied, ownerLanes) {
|
|
|
490
490
|
byId.set(lane.lane_id, lane);
|
|
491
491
|
return [...byId.values()];
|
|
492
492
|
}
|
|
493
|
+
function hierarchyWithRemoteSenior(hierarchy, remoteSenior) {
|
|
494
|
+
return [
|
|
495
|
+
...hierarchy.filter((lane) => lane.lane_id !== remoteSenior.lane_id),
|
|
496
|
+
remoteSenior,
|
|
497
|
+
];
|
|
498
|
+
}
|
|
499
|
+
async function readRemoteHireGrantSenior(ctx, hqRoot, seniorLaneId) {
|
|
500
|
+
if (!listLaneIds(hqRoot).includes(seniorLaneId))
|
|
501
|
+
return undefined;
|
|
502
|
+
const mirror = readLane(hqRoot, seniorLaneId);
|
|
503
|
+
if (mirror.host.kind !== "outpost" ||
|
|
504
|
+
laneRunsHere(mirror, reconcileHostFromCtx(ctx))) {
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
const outpostId = mirror.host.id;
|
|
508
|
+
const unavailable = (reason) => new LanesError("hire_grant_senior_unavailable", `Cannot verify senior lane ${seniorLaneId} from owning Outpost ${outpostId}; refusing hire-grant admission (${reason})`, { senior_lane_id: seniorLaneId, outpost_id: outpostId, reason });
|
|
509
|
+
let ownerRecords;
|
|
510
|
+
try {
|
|
511
|
+
ownerRecords = await readOutpostLaneRecords(ctx, outpostId, {
|
|
512
|
+
laneIds: [seniorLaneId],
|
|
513
|
+
snapshot: true,
|
|
514
|
+
strictSnapshot: true,
|
|
515
|
+
}, {}, { bypassCache: true });
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
throw unavailable(isLanesError(error) ? error.error : "outpost_lane_list_unavailable");
|
|
519
|
+
}
|
|
520
|
+
const matches = ownerRecords.filter((lane) => lane.lane_id === seniorLaneId);
|
|
521
|
+
const owner = matches[0];
|
|
522
|
+
const ownerIsHostedByOutpost = owner !== undefined && ((owner.host.kind === "outpost" && owner.host.id === outpostId) ||
|
|
523
|
+
(owner.host.kind === "local" && owner.host.id.trim() !== ""));
|
|
524
|
+
if (matches.length !== 1 || owner === undefined || !ownerIsHostedByOutpost) {
|
|
525
|
+
throw unavailable(matches.length === 0
|
|
526
|
+
? "owner_did_not_return_senior"
|
|
527
|
+
: matches.length > 1
|
|
528
|
+
? "owner_returned_duplicate_seniors"
|
|
529
|
+
: "owner_senior_identity_mismatch");
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const mirrorCompany = resolveLaneCompany(hqRoot, mirror).company;
|
|
533
|
+
const ownerCompany = resolveLaneCompany(hqRoot, owner).company;
|
|
534
|
+
if ((ownerCompany?.uid ?? null) !== (mirrorCompany?.uid ?? null) ||
|
|
535
|
+
(ownerCompany?.slug ?? null) !== (mirrorCompany?.slug ?? null)) {
|
|
536
|
+
throw unavailable("owner_company_did_not_match_mirror");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
if (isLanesError(error) && error.error === "hire_grant_senior_unavailable")
|
|
541
|
+
throw error;
|
|
542
|
+
throw unavailable("owner_company_identity_unavailable");
|
|
543
|
+
}
|
|
544
|
+
return owner;
|
|
545
|
+
}
|
|
546
|
+
async function hierarchyWithVerifiedRemoteSenior(ctx, hqRoot, lane, hierarchy) {
|
|
547
|
+
if (lane.senior.kind !== "lane" || !lane.senior.id)
|
|
548
|
+
return [...hierarchy];
|
|
549
|
+
const remoteSenior = await readRemoteHireGrantSenior(ctx, hqRoot, lane.senior.id);
|
|
550
|
+
return remoteSenior === undefined
|
|
551
|
+
? [...hierarchy]
|
|
552
|
+
: mergeOwnerLaneRecords(hierarchy, [remoteSenior]);
|
|
553
|
+
}
|
|
493
554
|
function mergeOwnerLaneRecords(supplied, ownerLanes) {
|
|
494
555
|
const byId = new Map(supplied.map((lane) => [lane.lane_id, lane]));
|
|
495
556
|
for (const lane of ownerLanes)
|
|
@@ -911,8 +972,18 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
|
|
|
911
972
|
throw new LanesError("hire_grant_context_required", `Resume of Outpost lane ${lane.lane_id} requires the primary senior's hire-grant context`, { lane_id: lane.lane_id, senior_lane_id: lane.senior.id });
|
|
912
973
|
}
|
|
913
974
|
await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), new Set([lane.senior.id]));
|
|
975
|
+
const remoteSenior = grantContext === undefined
|
|
976
|
+
? await readRemoteHireGrantSenior(ctx, hqRoot, lane.senior.id)
|
|
977
|
+
: undefined;
|
|
978
|
+
const suppliedHierarchy = grantContext?.hierarchy ?? (remoteSenior === undefined
|
|
979
|
+
? undefined
|
|
980
|
+
: hierarchyWithRemoteSenior(loadAllLanes(hqRoot), remoteSenior));
|
|
914
981
|
return withHireGrantAdmission(hqRoot, lane.senior.id, (snapshot) => {
|
|
915
982
|
const current = readLane(hqRoot, lane.lane_id);
|
|
983
|
+
if (remoteSenior !== undefined &&
|
|
984
|
+
(current.senior.kind !== "lane" || current.senior.id !== remoteSenior.lane_id)) {
|
|
985
|
+
throw new LanesError("hire_grant_senior_changed", `Lane ${current.lane_id}'s senior changed while its owning Outpost was being checked`, { lane_id: current.lane_id, expected_senior_lane_id: remoteSenior.lane_id, current_senior: current.senior });
|
|
986
|
+
}
|
|
916
987
|
if (grantContext !== undefined &&
|
|
917
988
|
!sameHireGrantSenior(current.senior, grantContext.expected_senior)) {
|
|
918
989
|
throw new LanesError("hire_grant_senior_changed", `Lane ${current.lane_id}'s senior changed before the Outpost resumed it`, {
|
|
@@ -929,7 +1000,7 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
|
|
|
929
1000
|
if (refusal !== null)
|
|
930
1001
|
throw refusal;
|
|
931
1002
|
return fn();
|
|
932
|
-
},
|
|
1003
|
+
}, suppliedHierarchy, remoteSenior === undefined ? undefined : new Set([remoteSenior.lane_id]));
|
|
933
1004
|
}
|
|
934
1005
|
const CAPACITY_PROBE_TIMEOUT_MS = 15_000;
|
|
935
1006
|
const MAX_PLACEMENT_OUTPOSTS = 16;
|
|
@@ -1205,8 +1276,11 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
|
|
|
1205
1276
|
}
|
|
1206
1277
|
return lanes;
|
|
1207
1278
|
}
|
|
1208
|
-
async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispatchOptions = {}) {
|
|
1279
|
+
async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispatchOptions = {}, options = {}) {
|
|
1209
1280
|
const query = normalizedLaneListQuery(requestedQuery);
|
|
1281
|
+
if (options.bypassCache === true) {
|
|
1282
|
+
return readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions);
|
|
1283
|
+
}
|
|
1210
1284
|
let invocationMemo = outpostLaneListMemo.get(ctx);
|
|
1211
1285
|
if (!invocationMemo) {
|
|
1212
1286
|
invocationMemo = new OutpostSnapshotInvocationMemo();
|
|
@@ -2144,6 +2218,10 @@ function assertKnownOutpost(requested, known) {
|
|
|
2144
2218
|
*/
|
|
2145
2219
|
export const ASK_BACKOFF_MS = [5_000, 15_000];
|
|
2146
2220
|
export const ASK_ATTEMPTS = ASK_BACKOFF_MS.length + 1;
|
|
2221
|
+
/** Flip to true in the next release to make --priority mandatory. */
|
|
2222
|
+
export const ASK_PRIORITY_REQUIRED = false;
|
|
2223
|
+
const ASK_PRIORITY_WARNING = "--priority will be required in the next release.";
|
|
2224
|
+
const QUESTION_PRIORITIES = ["P0", "P1", "P2"];
|
|
2147
2225
|
function withCasRetry(fn, attempts = 5) {
|
|
2148
2226
|
let last;
|
|
2149
2227
|
for (let i = 0; i < attempts; i += 1) {
|
|
@@ -3980,19 +4058,21 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
3980
4058
|
let grantContext;
|
|
3981
4059
|
let grantHierarchy;
|
|
3982
4060
|
if (action === "grant") {
|
|
3983
|
-
const
|
|
4061
|
+
const ownerHierarchy = mergeOwnerLaneRecords(loadAllLanes(ctx.hqRoot()), await readOutpostLaneRecords(ctx, outpostId, {
|
|
3984
4062
|
hierarchy: [lane.lane_id],
|
|
3985
4063
|
snapshot: true,
|
|
3986
4064
|
}));
|
|
4065
|
+
const hierarchy = await hierarchyWithVerifiedRemoteSenior(ctx, ctx.hqRoot(), lane, ownerHierarchy);
|
|
3987
4066
|
grantHierarchy = hierarchy;
|
|
3988
4067
|
grantContext = createOutpostHireGrantContext(lane, hierarchy);
|
|
3989
4068
|
}
|
|
3990
4069
|
else if ((action === "resume" && lane.senior.kind === "lane") ||
|
|
3991
4070
|
(action === "message" && (lane.senior.kind === "lane" || lane.senior.kind === "session"))) {
|
|
3992
|
-
const
|
|
4071
|
+
const ownerHierarchy = mergeOwnerLaneRecords(loadAllLanes(ctx.hqRoot()), await readOutpostLaneRecords(ctx, outpostId, {
|
|
3993
4072
|
hierarchy: [lane.lane_id],
|
|
3994
4073
|
snapshot: true,
|
|
3995
4074
|
}));
|
|
4075
|
+
const hierarchy = await hierarchyWithVerifiedRemoteSenior(ctx, ctx.hqRoot(), lane, ownerHierarchy);
|
|
3996
4076
|
grantContext = createOutpostHireGrantContext(lane, hierarchy, action === "message" ? options.supersededMessageCallerId : undefined, action === "message" ? options.supersededMessageSessionId : undefined);
|
|
3997
4077
|
}
|
|
3998
4078
|
let dispatched;
|
|
@@ -5254,6 +5334,10 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
|
|
|
5254
5334
|
}
|
|
5255
5335
|
await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), parentLaneIds);
|
|
5256
5336
|
}
|
|
5337
|
+
const remoteSenior = authorization === "senior" &&
|
|
5338
|
+
initial.senior.kind === "lane" && initial.senior.id
|
|
5339
|
+
? await readRemoteHireGrantSenior(ctx, hqRoot, initial.senior.id)
|
|
5340
|
+
: undefined;
|
|
5257
5341
|
const updated = ctx.withGrantHireLocks(hqRoot, lockedLaneIds, () => {
|
|
5258
5342
|
const current = readLane(hqRoot, laneId);
|
|
5259
5343
|
const currentCallerAuthorization = authorization === "senior"
|
|
@@ -5270,11 +5354,18 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
|
|
|
5270
5354
|
(!grantContext || !sameHireGrantSenior(current.senior, grantContext.expected_senior))) {
|
|
5271
5355
|
throw new LanesError("hire_grant_senior_changed", `Lane ${laneId}'s senior changed before the Outpost applied the grant update`, { lane_id: laneId, current_senior: current.senior });
|
|
5272
5356
|
}
|
|
5273
|
-
|
|
5357
|
+
if (remoteSenior !== undefined &&
|
|
5358
|
+
(current.senior.kind !== "lane" || current.senior.id !== remoteSenior.lane_id)) {
|
|
5359
|
+
throw new LanesError("hire_grant_senior_changed", `Lane ${current.lane_id}'s senior changed while its owning Outpost was being checked`, { lane_id: current.lane_id, expected_senior_lane_id: remoteSenior.lane_id, current_senior: current.senior });
|
|
5360
|
+
}
|
|
5361
|
+
const currentHierarchy = grantContext
|
|
5274
5362
|
? authorization === "ssm-owner-dispatch"
|
|
5275
5363
|
? mergeOwnerLaneHierarchy(grantContext.hierarchy, loadAllLanes(hqRoot))
|
|
5276
5364
|
: grantContext.hierarchy
|
|
5277
5365
|
: loadAllLanes(hqRoot);
|
|
5366
|
+
const hierarchy = remoteSenior === undefined
|
|
5367
|
+
? currentHierarchy
|
|
5368
|
+
: hierarchyWithRemoteSenior(currentHierarchy, remoteSenior);
|
|
5278
5369
|
if (current.senior.kind === "lane") {
|
|
5279
5370
|
if (!current.senior.id) {
|
|
5280
5371
|
throw new LanesError("hire_grant_senior_invalid", `Lane ${laneId} has a lane senior without an id`, {
|
|
@@ -7443,7 +7534,23 @@ export function interruptNotResumableWarning(laneId) {
|
|
|
7443
7534
|
return `hq lanes: warning: lane ${laneId} was interrupted before its provider reported a session id, ` +
|
|
7444
7535
|
`so hq lanes resume cannot continue it. Start a new lane for the remaining work.\n`;
|
|
7445
7536
|
}
|
|
7537
|
+
function parseQuestionPriority(value) {
|
|
7538
|
+
if (value === undefined) {
|
|
7539
|
+
if (ASK_PRIORITY_REQUIRED) {
|
|
7540
|
+
throw new LanesError("ask_priority_required", "--priority is required; choose P0, P1, or P2", {
|
|
7541
|
+
allowed: [...QUESTION_PRIORITIES],
|
|
7542
|
+
});
|
|
7543
|
+
}
|
|
7544
|
+
return undefined;
|
|
7545
|
+
}
|
|
7546
|
+
const normalized = value.toUpperCase();
|
|
7547
|
+
if (!QUESTION_PRIORITIES.includes(normalized)) {
|
|
7548
|
+
throw new LanesError("ask_priority_invalid", `Invalid --priority ${JSON.stringify(value)}. Allowed values: P0, P1, P2`, { allowed: [...QUESTION_PRIORITIES], value });
|
|
7549
|
+
}
|
|
7550
|
+
return normalized;
|
|
7551
|
+
}
|
|
7446
7552
|
async function runAsk(ctx, opts) {
|
|
7553
|
+
const priority = parseQuestionPriority(opts.priority);
|
|
7447
7554
|
const hqRoot = ctx.hqRoot();
|
|
7448
7555
|
const laneId = ctx.env.HQ_LANE_ID?.trim();
|
|
7449
7556
|
if (!laneId) {
|
|
@@ -7482,6 +7589,7 @@ async function runAsk(ctx, opts) {
|
|
|
7482
7589
|
at,
|
|
7483
7590
|
activeCompany: questionCompany,
|
|
7484
7591
|
questionId,
|
|
7592
|
+
priority,
|
|
7485
7593
|
});
|
|
7486
7594
|
questionId = withBody.question_id;
|
|
7487
7595
|
await emitLaneMeshEvent(ctx, lane.mesh_company ?? lane.company, lane.thread_id, "lane_question", {
|
|
@@ -7496,12 +7604,16 @@ async function runAsk(ctx, opts) {
|
|
|
7496
7604
|
question_id: withBody.question_id,
|
|
7497
7605
|
});
|
|
7498
7606
|
}
|
|
7607
|
+
if (priority === undefined) {
|
|
7608
|
+
process.stderr.write(`hq lanes ask: warning: ${ASK_PRIORITY_WARNING}\n`);
|
|
7609
|
+
}
|
|
7499
7610
|
printJson({
|
|
7500
7611
|
ok: true,
|
|
7501
7612
|
action: "ask",
|
|
7502
7613
|
question: withBody,
|
|
7503
7614
|
lane_id: lane.lane_id,
|
|
7504
7615
|
lane_state: lane.state,
|
|
7616
|
+
...(priority === undefined ? { warnings: [ASK_PRIORITY_WARNING] } : {}),
|
|
7505
7617
|
});
|
|
7506
7618
|
return;
|
|
7507
7619
|
}
|
|
@@ -8493,6 +8605,8 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8493
8605
|
.option("--context <text>", "Context for the audience")
|
|
8494
8606
|
.option("--option <text>", "Answer choice (repeatable)", collectRepeatable)
|
|
8495
8607
|
.option("--recommended <text>", "Recommended option")
|
|
8608
|
+
.option("--priority <P0|P1|P2>", "Urgency: P0 blocks now, P1 needed today, P2 can wait")
|
|
8609
|
+
.addHelpText("after", "\nThe audience may take up to 24 hours to answer; keep working meanwhile.\n")
|
|
8496
8610
|
.action(wrap(async (opts) => {
|
|
8497
8611
|
await runAsk(ctx, opts);
|
|
8498
8612
|
}));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq core ontology drop-aliases`: remove reviewed wrong aliases from entity
|
|
3
|
+
* files (CO-74). An alias that names a different entity joins two unrelated
|
|
4
|
+
* entities into one duplicate group; a person lists each `{ entity, alias }`
|
|
5
|
+
* to remove, and nothing else changes. The dry run writes nothing; `apply`
|
|
6
|
+
* writes the entity files through the multi-writer outbox so sync pushes them.
|
|
7
|
+
*/
|
|
8
|
+
import { type OntologyCompany } from "./company.js";
|
|
9
|
+
export declare const DROP_ALIASES_FLAG = "checkpoint.ontology.drop-aliases";
|
|
10
|
+
/** One reviewed removal: `entity` is a company-relative entity path. */
|
|
11
|
+
export interface AliasDrop {
|
|
12
|
+
entity: string;
|
|
13
|
+
alias: string;
|
|
14
|
+
}
|
|
15
|
+
export type AliasDropSkipReason = "no_entity" | "type_not_writable" | "alias_absent" | "patch_failed";
|
|
16
|
+
export interface AliasDropReport {
|
|
17
|
+
company: string;
|
|
18
|
+
mode: "dry_run" | "apply";
|
|
19
|
+
duplicate_groups_before: number;
|
|
20
|
+
duplicate_groups_after: number;
|
|
21
|
+
/** Entity -> aliases removed (dry run: would be removed). */
|
|
22
|
+
planned: Record<string, string[]>;
|
|
23
|
+
written: string[];
|
|
24
|
+
skipped: Array<AliasDrop & {
|
|
25
|
+
reason: AliasDropSkipReason;
|
|
26
|
+
detail?: string;
|
|
27
|
+
}>;
|
|
28
|
+
errors: Array<{
|
|
29
|
+
path: string;
|
|
30
|
+
error: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
export interface DropAliasesOptions {
|
|
34
|
+
hqRoot: string;
|
|
35
|
+
company: OntologyCompany;
|
|
36
|
+
mode: "dry_run" | "apply";
|
|
37
|
+
drops: AliasDrop[];
|
|
38
|
+
now: Date;
|
|
39
|
+
}
|
|
40
|
+
export declare function dropAliases(options: DropAliasesOptions): AliasDropReport;
|
|
41
|
+
//# sourceMappingURL=drop-aliases.d.ts.map
|