create-egregore 0.21.0-runtime-mvp.3 → 0.21.0-runtime-mvp.5
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/assets/egregore-launcher.js +97 -16
- package/package.json +1 -1
- package/runtime/claude/egregore_runtime/adapters/qmd.py +19 -5
- package/runtime/claude/egregore_runtime/readiness.py +30 -0
- package/runtime/codex/egregore_runtime/adapters/qmd.py +19 -5
- package/runtime/codex/egregore_runtime/readiness.py +30 -0
- package/runtime/pi/egregore_runtime/adapters/qmd.py +19 -5
- package/runtime/pi/egregore_runtime/readiness.py +30 -0
- package/runtime/prime/egregore_runtime/adapters/qmd.py +19 -5
- package/runtime/prime/egregore_runtime/readiness.py +30 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// egregore-launcher-version: 0.21.0-runtime-mvp.
|
|
2
|
+
// egregore-launcher-version: 0.21.0-runtime-mvp.5
|
|
3
3
|
//
|
|
4
4
|
// egregore — launch and browse Egregore instances from your terminal.
|
|
5
5
|
// Installed by create-egregore. Reads ~/.egregore/instances.json.
|
|
@@ -35,7 +35,7 @@ const crypto = require("node:crypto");
|
|
|
35
35
|
const { spawnSync, spawn } = require("node:child_process");
|
|
36
36
|
const readline = require("node:readline");
|
|
37
37
|
|
|
38
|
-
const VERSION = "0.21.0-runtime-mvp.
|
|
38
|
+
const VERSION = "0.21.0-runtime-mvp.5";
|
|
39
39
|
const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
|
|
40
40
|
const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
|
|
41
41
|
const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
|
|
@@ -2505,6 +2505,40 @@ function upgradeStatusAsync(inst, cb) {
|
|
|
2505
2505
|
} catch { cb(null); }
|
|
2506
2506
|
}
|
|
2507
2507
|
|
|
2508
|
+
// Local retrieval health for the settings surface. Package-update state,
|
|
2509
|
+
// Runtime activation state, and retrieval health are three different truths;
|
|
2510
|
+
// the settings row must not let one stand in for another. Repair-needed means
|
|
2511
|
+
// readiness names a concrete repair command (e.g. the embedding model is
|
|
2512
|
+
// missing while stored vectors are intact).
|
|
2513
|
+
function retrievalHealthAsync(inst, cb) {
|
|
2514
|
+
const script = path.join(inst.path, "bin", "search.sh");
|
|
2515
|
+
if (!fs.existsSync(script)) return cb(null);
|
|
2516
|
+
let buf = "";
|
|
2517
|
+
try {
|
|
2518
|
+
const child = spawn("bash", [script, "readiness", "--json"], {
|
|
2519
|
+
cwd: inst.path,
|
|
2520
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2521
|
+
env: { ...process.env },
|
|
2522
|
+
});
|
|
2523
|
+
child.stdout.on("data", (d) => { buf += d; });
|
|
2524
|
+
child.on("close", () => {
|
|
2525
|
+
try {
|
|
2526
|
+
const report = JSON.parse(buf);
|
|
2527
|
+
const items = Array.isArray(report.items) ? report.items : [];
|
|
2528
|
+
const byKey = Object.fromEntries(items.map((item) => [item.key, item]));
|
|
2529
|
+
const repair = items.some((item) => /repair: bin\/search\.sh/.test(item.detail || ""));
|
|
2530
|
+
cb({
|
|
2531
|
+
repair,
|
|
2532
|
+
searchStage: byKey.search?.stage || "unknown",
|
|
2533
|
+
embeddingsDetail: byKey.embeddings?.detail || "",
|
|
2534
|
+
semanticDetail: byKey.semantic?.detail || "",
|
|
2535
|
+
});
|
|
2536
|
+
} catch { cb(null); }
|
|
2537
|
+
});
|
|
2538
|
+
child.on("error", () => cb(null));
|
|
2539
|
+
} catch { cb(null); }
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2508
2542
|
// Exact candidate resolution: the runtime-mvp dist-tag only, validated
|
|
2509
2543
|
// against the prerelease scheme — never latest, never a retired scheme.
|
|
2510
2544
|
// EGREGORE_RUNTIME_MVP_CANDIDATE is the offline/test seam.
|
|
@@ -2542,6 +2576,26 @@ const UPGRADE_STATUS_LABEL = {
|
|
|
2542
2576
|
"rolled-back": "rolled back",
|
|
2543
2577
|
};
|
|
2544
2578
|
|
|
2579
|
+
// Settings-row truth for the Runtime update line. The row must say outright
|
|
2580
|
+
// whether this egregore runs the new Runtime — "no update staged" is
|
|
2581
|
+
// transaction state, not the condition the user needs to see.
|
|
2582
|
+
function runtimeUpdateRowValue(st, candidate) {
|
|
2583
|
+
if (st === undefined) return `${DIM}checking…${RESET}`;
|
|
2584
|
+
if (st === null) return `${DIM}unavailable${RESET}`;
|
|
2585
|
+
if (st.status === "active") {
|
|
2586
|
+
const v = st.active_version || "?";
|
|
2587
|
+
if (candidate && candidate !== v) return `${v} active ${YELLOW}· update available ${candidate}${RESET}`;
|
|
2588
|
+
return `${GREEN}${v} active${RESET}${candidate ? `${DIM} · up to date${RESET}` : ""}`;
|
|
2589
|
+
}
|
|
2590
|
+
if (st.status === "ready-to-activate") return `${GREEN}${upgradeSummary(st)}${RESET}`;
|
|
2591
|
+
if (st.status === "failed" || st.status === "interrupted") return `${YELLOW}${upgradeSummary(st)}${RESET}`;
|
|
2592
|
+
if (st.status === "preparing" || st.status === "pending") return `${DIM}${upgradeSummary(st)}${RESET}`;
|
|
2593
|
+
// No activation record (or cancelled/rolled back): this egregore is not on
|
|
2594
|
+
// the new Runtime. Say so, and name the candidate when we know it.
|
|
2595
|
+
const base = `${YELLOW}not updated to the new Runtime${RESET}`;
|
|
2596
|
+
return candidate ? `${base}${DIM} · ${candidate} available${RESET}` : base;
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2545
2599
|
function upgradeSummary(st) {
|
|
2546
2600
|
// "none" means no activation record — a first-time state, not freshness.
|
|
2547
2601
|
if (!st || st.status === "none") return "no update staged";
|
|
@@ -2560,7 +2614,7 @@ function upgradeElapsed(st) {
|
|
|
2560
2614
|
// Plain-language body shared by the non-interactive `settings <slug> runtime`
|
|
2561
2615
|
// output and the interactive panel. Returns undecorated strings; callers add
|
|
2562
2616
|
// color. Never JSON, never internal paths beyond the instance's own root.
|
|
2563
|
-
function formatUpgradeLines(inst, st, { candidate = "" } = {}) {
|
|
2617
|
+
function formatUpgradeLines(inst, st, { candidate = "", retrieval = null } = {}) {
|
|
2564
2618
|
const lines = [];
|
|
2565
2619
|
const d = st || { status: "none" };
|
|
2566
2620
|
lines.push(`Selected Egregore: ${inst.name || inst.slug} (${inst.slug || "?"})`);
|
|
@@ -2581,6 +2635,17 @@ function formatUpgradeLines(inst, st, { candidate = "" } = {}) {
|
|
|
2581
2635
|
} else if (d.status !== "none") {
|
|
2582
2636
|
lines.push("Previous Runtime remains active until activation.");
|
|
2583
2637
|
}
|
|
2638
|
+
if (retrieval) {
|
|
2639
|
+
if (retrieval.repair) {
|
|
2640
|
+
lines.push("Local retrieval: repair needed");
|
|
2641
|
+
if (retrieval.embeddingsDetail) lines.push(` ✗ ${retrieval.embeddingsDetail}`);
|
|
2642
|
+
lines.push(" → run: bin/search.sh reindex --embed");
|
|
2643
|
+
} else if (retrieval.searchStage && retrieval.searchStage !== "ready") {
|
|
2644
|
+
lines.push(`Local retrieval: ${retrieval.searchStage}`);
|
|
2645
|
+
} else if (retrieval.searchStage === "ready") {
|
|
2646
|
+
lines.push("Local retrieval: ready");
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2584
2649
|
if (Array.isArray(d.stages) && d.status !== "none" && d.status !== "active") {
|
|
2585
2650
|
lines.push("");
|
|
2586
2651
|
for (const stage of d.stages) {
|
|
@@ -2984,7 +3049,7 @@ const SETTINGS_DOMAIN_NOUN = { repo: "repo name", admin: "github handle", people
|
|
|
2984
3049
|
// Runtime-update panel: renders the durable upgrade state from the Python
|
|
2985
3050
|
// engine. Same geometry as the settings panel; plain-language body from
|
|
2986
3051
|
// formatUpgradeLines with launcher glyph decoration only.
|
|
2987
|
-
function renderUpgradePanel({ inst, data, candidate, mode, notice, width }) {
|
|
3052
|
+
function renderUpgradePanel({ inst, data, candidate, retrieval, mode, notice, width }) {
|
|
2988
3053
|
const outerWidth = Math.min(Math.max(50, width - 2), 126);
|
|
2989
3054
|
const innerWidth = Math.max(48, outerWidth - 2);
|
|
2990
3055
|
const margin = " ".repeat(Math.max(0, Math.floor((width - outerWidth) / 2)));
|
|
@@ -2997,7 +3062,7 @@ function renderUpgradePanel({ inst, data, candidate, mode, notice, width }) {
|
|
|
2997
3062
|
? ["Checking this egregore's Runtime state…"]
|
|
2998
3063
|
: data === null
|
|
2999
3064
|
? ["Runtime update state is unavailable for this egregore."]
|
|
3000
|
-
: formatUpgradeLines(inst, data, { candidate });
|
|
3065
|
+
: formatUpgradeLines(inst, data, { candidate, retrieval });
|
|
3001
3066
|
for (const raw of body) {
|
|
3002
3067
|
let line = raw;
|
|
3003
3068
|
if (/^ ✓ /.test(raw)) line = ` ${GREEN}✓${RESET} ${raw.slice(4)}`;
|
|
@@ -3041,7 +3106,7 @@ function renderUpgradePanel({ inst, data, candidate, mode, notice, width }) {
|
|
|
3041
3106
|
return lines;
|
|
3042
3107
|
}
|
|
3043
3108
|
|
|
3044
|
-
function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCursor, mode, input, notice, width, upgrade }) {
|
|
3109
|
+
function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCursor, mode, input, notice, width, upgrade, retrieval, candidate }) {
|
|
3045
3110
|
const title = inst.slug || inst.name || path.basename(inst.path);
|
|
3046
3111
|
const outerWidth = Math.min(Math.max(50, width - 2), 126);
|
|
3047
3112
|
const innerWidth = Math.max(48, outerWidth - 2);
|
|
@@ -3186,17 +3251,14 @@ function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCurs
|
|
|
3186
3251
|
lines.push(boxLine(`${DIM}↑↓ nav · ${RESET}${BOLD}a${RESET}${DIM} add · ${RESET}${BOLD}d${RESET}${DIM} delete · ← back · q quit${RESET}`, innerWidth, margin));
|
|
3187
3252
|
}
|
|
3188
3253
|
} else {
|
|
3189
|
-
const upgradeValue = upgrade
|
|
3190
|
-
? `${DIM}checking…${RESET}`
|
|
3191
|
-
: upgrade === null
|
|
3192
|
-
? `${DIM}unavailable${RESET}`
|
|
3193
|
-
: upgrade.status === "ready-to-activate"
|
|
3194
|
-
? `${GREEN}${upgradeSummary(upgrade)}${RESET}`
|
|
3195
|
-
: upgrade.status === "failed" || upgrade.status === "interrupted"
|
|
3196
|
-
? `${YELLOW}${upgradeSummary(upgrade)}${RESET}`
|
|
3197
|
-
: `${DIM}${upgradeSummary(upgrade)}${RESET}`;
|
|
3254
|
+
const upgradeValue = runtimeUpdateRowValue(upgrade, candidate);
|
|
3198
3255
|
const rows = [
|
|
3199
|
-
{
|
|
3256
|
+
{
|
|
3257
|
+
name: "Runtime update",
|
|
3258
|
+
value: retrieval && retrieval.repair
|
|
3259
|
+
? `${upgradeValue} ${YELLOW}· retrieval repair needed${RESET}`
|
|
3260
|
+
: upgradeValue,
|
|
3261
|
+
},
|
|
3200
3262
|
{ name: "Privacy & access", value: `${DIM}identity · boundary · consent${RESET}` },
|
|
3201
3263
|
{ name: "Telemetry", value: `${DIM}personal · opt-out${RESET}` },
|
|
3202
3264
|
{ name: "Hosted Artifacts", value: (d.hosting !== false) ? `${GREEN}ON${RESET}` : `${YELLOW}OFF${RESET}` },
|
|
@@ -3350,6 +3412,7 @@ async function browser(args = {}) {
|
|
|
3350
3412
|
inst: state.upgradeInst,
|
|
3351
3413
|
data: state.upgradeData,
|
|
3352
3414
|
candidate: state.upgradeCandidate,
|
|
3415
|
+
retrieval: state.upgradeRetrieval,
|
|
3353
3416
|
mode: state.upgradeMode,
|
|
3354
3417
|
notice: state.upgradeNotice,
|
|
3355
3418
|
width: w,
|
|
@@ -3375,6 +3438,8 @@ async function browser(args = {}) {
|
|
|
3375
3438
|
notice: state.settingsNotice,
|
|
3376
3439
|
width: w,
|
|
3377
3440
|
upgrade: state.settingsUpgrade,
|
|
3441
|
+
retrieval: state.settingsRetrieval,
|
|
3442
|
+
candidate: state.upgradeCandidate,
|
|
3378
3443
|
});
|
|
3379
3444
|
for (const line of settingsLines) out.push(line + ANSI.clearLine + "\n");
|
|
3380
3445
|
const remainingRows = Math.max(0, h - settingsLines.length - 1);
|
|
@@ -3903,8 +3968,13 @@ async function browser(args = {}) {
|
|
|
3903
3968
|
if (S.settingsCursor === 0) {
|
|
3904
3969
|
state.upgradeInst = S.settingsInst;
|
|
3905
3970
|
state.upgradeData = S.settingsUpgrade;
|
|
3971
|
+
state.upgradeRetrieval = S.settingsRetrieval;
|
|
3906
3972
|
state.upgradeMode = "view";
|
|
3907
3973
|
state.upgradeNotice = "";
|
|
3974
|
+
retrievalHealthAsync(S.settingsInst, (health) => {
|
|
3975
|
+
state.upgradeRetrieval = health;
|
|
3976
|
+
if (state.upgradeInst) render();
|
|
3977
|
+
});
|
|
3908
3978
|
refreshUpgrade();
|
|
3909
3979
|
if (!state.upgradeCandidate) {
|
|
3910
3980
|
fetchRuntimeMvpCandidate().then((v) => {
|
|
@@ -4190,10 +4260,21 @@ async function browser(args = {}) {
|
|
|
4190
4260
|
state.settingsCursor = 0;
|
|
4191
4261
|
state.settingsView = "menu";
|
|
4192
4262
|
state.settingsUpgrade = undefined;
|
|
4263
|
+
state.settingsRetrieval = undefined;
|
|
4193
4264
|
upgradeStatusAsync(pick, (st) => {
|
|
4194
4265
|
state.settingsUpgrade = st;
|
|
4195
4266
|
if (state.settingsInst === pick && !state.upgradeInst) render();
|
|
4196
4267
|
});
|
|
4268
|
+
retrievalHealthAsync(pick, (health) => {
|
|
4269
|
+
state.settingsRetrieval = health;
|
|
4270
|
+
if (state.settingsInst === pick && !state.upgradeInst) render();
|
|
4271
|
+
});
|
|
4272
|
+
if (!state.upgradeCandidate) {
|
|
4273
|
+
fetchRuntimeMvpCandidate().then((v) => {
|
|
4274
|
+
state.upgradeCandidate = v || "";
|
|
4275
|
+
if (state.settingsInst === pick && !state.upgradeInst) render();
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4197
4278
|
state.settingsNotice = "";
|
|
4198
4279
|
render();
|
|
4199
4280
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-egregore",
|
|
3
|
-
"version": "0.21.0-runtime-mvp.
|
|
3
|
+
"version": "0.21.0-runtime-mvp.5",
|
|
4
4
|
"description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -353,8 +353,13 @@ class QmdLocalRetriever:
|
|
|
353
353
|
and counts["documents"] > 0
|
|
354
354
|
and self._index_state_usable()
|
|
355
355
|
)
|
|
356
|
+
# Semantic queries embed the query text at request time, so the model
|
|
357
|
+
# must be present even when every stored vector is current. Stored
|
|
358
|
+
# vectors without the model is a repair state, never "ready".
|
|
359
|
+
model_available = self._embedding_model_available()
|
|
356
360
|
semantic_ready = bool(
|
|
357
361
|
bm25_ready
|
|
362
|
+
and model_available
|
|
358
363
|
and counts["vectors"] > 0
|
|
359
364
|
and counts["pending"] == 0
|
|
360
365
|
and self._semantic_state_current()
|
|
@@ -362,9 +367,15 @@ class QmdLocalRetriever:
|
|
|
362
367
|
)
|
|
363
368
|
embedding_state = self._embedding_state(status, semantic_ready=semantic_ready)
|
|
364
369
|
if embedding_state == "missing_model":
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
370
|
+
if counts["vectors"] > 0:
|
|
371
|
+
warnings.append(
|
|
372
|
+
f"{EMBEDDING_MODEL} is missing while stored vectors are intact; "
|
|
373
|
+
"semantic queries are unavailable until repair — run: bin/search.sh reindex --embed"
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
warnings.append(
|
|
377
|
+
f"{EMBEDDING_MODEL} is not installed; hybrid requests degrade to lexical retrieval while embedding warms"
|
|
378
|
+
)
|
|
368
379
|
|
|
369
380
|
runtime = self._owned_runtime()
|
|
370
381
|
if not canonical_ready or not qmd_available or not status_ok:
|
|
@@ -1568,10 +1579,13 @@ class QmdLocalRetriever:
|
|
|
1568
1579
|
def _embedding_state(self, status: str, *, semantic_ready: bool = False) -> str:
|
|
1569
1580
|
if self._live_lock(self._embed_lock_path()):
|
|
1570
1581
|
return "running"
|
|
1571
|
-
|
|
1572
|
-
|
|
1582
|
+
# Model presence outranks stored-vector completeness: query-time
|
|
1583
|
+
# embedding needs the model, so a complete build with a missing model
|
|
1584
|
+
# is a repair state, not "ready".
|
|
1573
1585
|
if not self._embedding_model_available():
|
|
1574
1586
|
return "missing_model"
|
|
1587
|
+
if semantic_ready:
|
|
1588
|
+
return "ready"
|
|
1575
1589
|
counts = self._status_counts(status)
|
|
1576
1590
|
if counts["pending"] > 0 or counts["documents"] > 0:
|
|
1577
1591
|
return "pending"
|
|
@@ -236,6 +236,16 @@ def _embeddings_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
236
236
|
key="embeddings", label=label, stage=STAGE_BUILDING, detail=detail
|
|
237
237
|
)
|
|
238
238
|
if state == "missing_model":
|
|
239
|
+
if health.semantic_source_revision:
|
|
240
|
+
return ReadinessItem(
|
|
241
|
+
key="embeddings",
|
|
242
|
+
label=label,
|
|
243
|
+
stage=STAGE_UNAVAILABLE,
|
|
244
|
+
detail=(
|
|
245
|
+
"embedding model missing — stored vectors intact · "
|
|
246
|
+
"repair: bin/search.sh reindex --embed"
|
|
247
|
+
),
|
|
248
|
+
)
|
|
239
249
|
return ReadinessItem(
|
|
240
250
|
key="embeddings",
|
|
241
251
|
label=label,
|
|
@@ -254,6 +264,16 @@ def _semantic_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
254
264
|
label = "Semantic build"
|
|
255
265
|
source = _short_revision(health.source_revision)
|
|
256
266
|
build = _short_revision(health.semantic_source_revision)
|
|
267
|
+
if health.embedding_state == "missing_model":
|
|
268
|
+
return ReadinessItem(
|
|
269
|
+
key="semantic",
|
|
270
|
+
label=label,
|
|
271
|
+
stage=STAGE_UNAVAILABLE,
|
|
272
|
+
detail=(
|
|
273
|
+
"embedding model missing — semantic queries unavailable · "
|
|
274
|
+
"repair: bin/search.sh reindex --embed"
|
|
275
|
+
),
|
|
276
|
+
)
|
|
257
277
|
if health.semantic_ready:
|
|
258
278
|
return ReadinessItem(
|
|
259
279
|
key="semantic",
|
|
@@ -282,6 +302,16 @@ def _search_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
282
302
|
return ReadinessItem(
|
|
283
303
|
key="search", label=label, stage=STAGE_READY, detail="hybrid current"
|
|
284
304
|
)
|
|
305
|
+
if health.bm25_ready and health.embedding_state == "missing_model":
|
|
306
|
+
return ReadinessItem(
|
|
307
|
+
key="search",
|
|
308
|
+
label=label,
|
|
309
|
+
stage=STAGE_READY,
|
|
310
|
+
detail=(
|
|
311
|
+
"lexical only — embedding model missing · "
|
|
312
|
+
"repair: bin/search.sh reindex --embed"
|
|
313
|
+
),
|
|
314
|
+
)
|
|
285
315
|
if health.bm25_ready and health.semantic_source_revision:
|
|
286
316
|
return ReadinessItem(
|
|
287
317
|
key="search",
|
|
@@ -353,8 +353,13 @@ class QmdLocalRetriever:
|
|
|
353
353
|
and counts["documents"] > 0
|
|
354
354
|
and self._index_state_usable()
|
|
355
355
|
)
|
|
356
|
+
# Semantic queries embed the query text at request time, so the model
|
|
357
|
+
# must be present even when every stored vector is current. Stored
|
|
358
|
+
# vectors without the model is a repair state, never "ready".
|
|
359
|
+
model_available = self._embedding_model_available()
|
|
356
360
|
semantic_ready = bool(
|
|
357
361
|
bm25_ready
|
|
362
|
+
and model_available
|
|
358
363
|
and counts["vectors"] > 0
|
|
359
364
|
and counts["pending"] == 0
|
|
360
365
|
and self._semantic_state_current()
|
|
@@ -362,9 +367,15 @@ class QmdLocalRetriever:
|
|
|
362
367
|
)
|
|
363
368
|
embedding_state = self._embedding_state(status, semantic_ready=semantic_ready)
|
|
364
369
|
if embedding_state == "missing_model":
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
370
|
+
if counts["vectors"] > 0:
|
|
371
|
+
warnings.append(
|
|
372
|
+
f"{EMBEDDING_MODEL} is missing while stored vectors are intact; "
|
|
373
|
+
"semantic queries are unavailable until repair — run: bin/search.sh reindex --embed"
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
warnings.append(
|
|
377
|
+
f"{EMBEDDING_MODEL} is not installed; hybrid requests degrade to lexical retrieval while embedding warms"
|
|
378
|
+
)
|
|
368
379
|
|
|
369
380
|
runtime = self._owned_runtime()
|
|
370
381
|
if not canonical_ready or not qmd_available or not status_ok:
|
|
@@ -1568,10 +1579,13 @@ class QmdLocalRetriever:
|
|
|
1568
1579
|
def _embedding_state(self, status: str, *, semantic_ready: bool = False) -> str:
|
|
1569
1580
|
if self._live_lock(self._embed_lock_path()):
|
|
1570
1581
|
return "running"
|
|
1571
|
-
|
|
1572
|
-
|
|
1582
|
+
# Model presence outranks stored-vector completeness: query-time
|
|
1583
|
+
# embedding needs the model, so a complete build with a missing model
|
|
1584
|
+
# is a repair state, not "ready".
|
|
1573
1585
|
if not self._embedding_model_available():
|
|
1574
1586
|
return "missing_model"
|
|
1587
|
+
if semantic_ready:
|
|
1588
|
+
return "ready"
|
|
1575
1589
|
counts = self._status_counts(status)
|
|
1576
1590
|
if counts["pending"] > 0 or counts["documents"] > 0:
|
|
1577
1591
|
return "pending"
|
|
@@ -236,6 +236,16 @@ def _embeddings_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
236
236
|
key="embeddings", label=label, stage=STAGE_BUILDING, detail=detail
|
|
237
237
|
)
|
|
238
238
|
if state == "missing_model":
|
|
239
|
+
if health.semantic_source_revision:
|
|
240
|
+
return ReadinessItem(
|
|
241
|
+
key="embeddings",
|
|
242
|
+
label=label,
|
|
243
|
+
stage=STAGE_UNAVAILABLE,
|
|
244
|
+
detail=(
|
|
245
|
+
"embedding model missing — stored vectors intact · "
|
|
246
|
+
"repair: bin/search.sh reindex --embed"
|
|
247
|
+
),
|
|
248
|
+
)
|
|
239
249
|
return ReadinessItem(
|
|
240
250
|
key="embeddings",
|
|
241
251
|
label=label,
|
|
@@ -254,6 +264,16 @@ def _semantic_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
254
264
|
label = "Semantic build"
|
|
255
265
|
source = _short_revision(health.source_revision)
|
|
256
266
|
build = _short_revision(health.semantic_source_revision)
|
|
267
|
+
if health.embedding_state == "missing_model":
|
|
268
|
+
return ReadinessItem(
|
|
269
|
+
key="semantic",
|
|
270
|
+
label=label,
|
|
271
|
+
stage=STAGE_UNAVAILABLE,
|
|
272
|
+
detail=(
|
|
273
|
+
"embedding model missing — semantic queries unavailable · "
|
|
274
|
+
"repair: bin/search.sh reindex --embed"
|
|
275
|
+
),
|
|
276
|
+
)
|
|
257
277
|
if health.semantic_ready:
|
|
258
278
|
return ReadinessItem(
|
|
259
279
|
key="semantic",
|
|
@@ -282,6 +302,16 @@ def _search_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
282
302
|
return ReadinessItem(
|
|
283
303
|
key="search", label=label, stage=STAGE_READY, detail="hybrid current"
|
|
284
304
|
)
|
|
305
|
+
if health.bm25_ready and health.embedding_state == "missing_model":
|
|
306
|
+
return ReadinessItem(
|
|
307
|
+
key="search",
|
|
308
|
+
label=label,
|
|
309
|
+
stage=STAGE_READY,
|
|
310
|
+
detail=(
|
|
311
|
+
"lexical only — embedding model missing · "
|
|
312
|
+
"repair: bin/search.sh reindex --embed"
|
|
313
|
+
),
|
|
314
|
+
)
|
|
285
315
|
if health.bm25_ready and health.semantic_source_revision:
|
|
286
316
|
return ReadinessItem(
|
|
287
317
|
key="search",
|
|
@@ -353,8 +353,13 @@ class QmdLocalRetriever:
|
|
|
353
353
|
and counts["documents"] > 0
|
|
354
354
|
and self._index_state_usable()
|
|
355
355
|
)
|
|
356
|
+
# Semantic queries embed the query text at request time, so the model
|
|
357
|
+
# must be present even when every stored vector is current. Stored
|
|
358
|
+
# vectors without the model is a repair state, never "ready".
|
|
359
|
+
model_available = self._embedding_model_available()
|
|
356
360
|
semantic_ready = bool(
|
|
357
361
|
bm25_ready
|
|
362
|
+
and model_available
|
|
358
363
|
and counts["vectors"] > 0
|
|
359
364
|
and counts["pending"] == 0
|
|
360
365
|
and self._semantic_state_current()
|
|
@@ -362,9 +367,15 @@ class QmdLocalRetriever:
|
|
|
362
367
|
)
|
|
363
368
|
embedding_state = self._embedding_state(status, semantic_ready=semantic_ready)
|
|
364
369
|
if embedding_state == "missing_model":
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
370
|
+
if counts["vectors"] > 0:
|
|
371
|
+
warnings.append(
|
|
372
|
+
f"{EMBEDDING_MODEL} is missing while stored vectors are intact; "
|
|
373
|
+
"semantic queries are unavailable until repair — run: bin/search.sh reindex --embed"
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
warnings.append(
|
|
377
|
+
f"{EMBEDDING_MODEL} is not installed; hybrid requests degrade to lexical retrieval while embedding warms"
|
|
378
|
+
)
|
|
368
379
|
|
|
369
380
|
runtime = self._owned_runtime()
|
|
370
381
|
if not canonical_ready or not qmd_available or not status_ok:
|
|
@@ -1568,10 +1579,13 @@ class QmdLocalRetriever:
|
|
|
1568
1579
|
def _embedding_state(self, status: str, *, semantic_ready: bool = False) -> str:
|
|
1569
1580
|
if self._live_lock(self._embed_lock_path()):
|
|
1570
1581
|
return "running"
|
|
1571
|
-
|
|
1572
|
-
|
|
1582
|
+
# Model presence outranks stored-vector completeness: query-time
|
|
1583
|
+
# embedding needs the model, so a complete build with a missing model
|
|
1584
|
+
# is a repair state, not "ready".
|
|
1573
1585
|
if not self._embedding_model_available():
|
|
1574
1586
|
return "missing_model"
|
|
1587
|
+
if semantic_ready:
|
|
1588
|
+
return "ready"
|
|
1575
1589
|
counts = self._status_counts(status)
|
|
1576
1590
|
if counts["pending"] > 0 or counts["documents"] > 0:
|
|
1577
1591
|
return "pending"
|
|
@@ -236,6 +236,16 @@ def _embeddings_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
236
236
|
key="embeddings", label=label, stage=STAGE_BUILDING, detail=detail
|
|
237
237
|
)
|
|
238
238
|
if state == "missing_model":
|
|
239
|
+
if health.semantic_source_revision:
|
|
240
|
+
return ReadinessItem(
|
|
241
|
+
key="embeddings",
|
|
242
|
+
label=label,
|
|
243
|
+
stage=STAGE_UNAVAILABLE,
|
|
244
|
+
detail=(
|
|
245
|
+
"embedding model missing — stored vectors intact · "
|
|
246
|
+
"repair: bin/search.sh reindex --embed"
|
|
247
|
+
),
|
|
248
|
+
)
|
|
239
249
|
return ReadinessItem(
|
|
240
250
|
key="embeddings",
|
|
241
251
|
label=label,
|
|
@@ -254,6 +264,16 @@ def _semantic_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
254
264
|
label = "Semantic build"
|
|
255
265
|
source = _short_revision(health.source_revision)
|
|
256
266
|
build = _short_revision(health.semantic_source_revision)
|
|
267
|
+
if health.embedding_state == "missing_model":
|
|
268
|
+
return ReadinessItem(
|
|
269
|
+
key="semantic",
|
|
270
|
+
label=label,
|
|
271
|
+
stage=STAGE_UNAVAILABLE,
|
|
272
|
+
detail=(
|
|
273
|
+
"embedding model missing — semantic queries unavailable · "
|
|
274
|
+
"repair: bin/search.sh reindex --embed"
|
|
275
|
+
),
|
|
276
|
+
)
|
|
257
277
|
if health.semantic_ready:
|
|
258
278
|
return ReadinessItem(
|
|
259
279
|
key="semantic",
|
|
@@ -282,6 +302,16 @@ def _search_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
282
302
|
return ReadinessItem(
|
|
283
303
|
key="search", label=label, stage=STAGE_READY, detail="hybrid current"
|
|
284
304
|
)
|
|
305
|
+
if health.bm25_ready and health.embedding_state == "missing_model":
|
|
306
|
+
return ReadinessItem(
|
|
307
|
+
key="search",
|
|
308
|
+
label=label,
|
|
309
|
+
stage=STAGE_READY,
|
|
310
|
+
detail=(
|
|
311
|
+
"lexical only — embedding model missing · "
|
|
312
|
+
"repair: bin/search.sh reindex --embed"
|
|
313
|
+
),
|
|
314
|
+
)
|
|
285
315
|
if health.bm25_ready and health.semantic_source_revision:
|
|
286
316
|
return ReadinessItem(
|
|
287
317
|
key="search",
|
|
@@ -353,8 +353,13 @@ class QmdLocalRetriever:
|
|
|
353
353
|
and counts["documents"] > 0
|
|
354
354
|
and self._index_state_usable()
|
|
355
355
|
)
|
|
356
|
+
# Semantic queries embed the query text at request time, so the model
|
|
357
|
+
# must be present even when every stored vector is current. Stored
|
|
358
|
+
# vectors without the model is a repair state, never "ready".
|
|
359
|
+
model_available = self._embedding_model_available()
|
|
356
360
|
semantic_ready = bool(
|
|
357
361
|
bm25_ready
|
|
362
|
+
and model_available
|
|
358
363
|
and counts["vectors"] > 0
|
|
359
364
|
and counts["pending"] == 0
|
|
360
365
|
and self._semantic_state_current()
|
|
@@ -362,9 +367,15 @@ class QmdLocalRetriever:
|
|
|
362
367
|
)
|
|
363
368
|
embedding_state = self._embedding_state(status, semantic_ready=semantic_ready)
|
|
364
369
|
if embedding_state == "missing_model":
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
370
|
+
if counts["vectors"] > 0:
|
|
371
|
+
warnings.append(
|
|
372
|
+
f"{EMBEDDING_MODEL} is missing while stored vectors are intact; "
|
|
373
|
+
"semantic queries are unavailable until repair — run: bin/search.sh reindex --embed"
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
warnings.append(
|
|
377
|
+
f"{EMBEDDING_MODEL} is not installed; hybrid requests degrade to lexical retrieval while embedding warms"
|
|
378
|
+
)
|
|
368
379
|
|
|
369
380
|
runtime = self._owned_runtime()
|
|
370
381
|
if not canonical_ready or not qmd_available or not status_ok:
|
|
@@ -1568,10 +1579,13 @@ class QmdLocalRetriever:
|
|
|
1568
1579
|
def _embedding_state(self, status: str, *, semantic_ready: bool = False) -> str:
|
|
1569
1580
|
if self._live_lock(self._embed_lock_path()):
|
|
1570
1581
|
return "running"
|
|
1571
|
-
|
|
1572
|
-
|
|
1582
|
+
# Model presence outranks stored-vector completeness: query-time
|
|
1583
|
+
# embedding needs the model, so a complete build with a missing model
|
|
1584
|
+
# is a repair state, not "ready".
|
|
1573
1585
|
if not self._embedding_model_available():
|
|
1574
1586
|
return "missing_model"
|
|
1587
|
+
if semantic_ready:
|
|
1588
|
+
return "ready"
|
|
1575
1589
|
counts = self._status_counts(status)
|
|
1576
1590
|
if counts["pending"] > 0 or counts["documents"] > 0:
|
|
1577
1591
|
return "pending"
|
|
@@ -236,6 +236,16 @@ def _embeddings_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
236
236
|
key="embeddings", label=label, stage=STAGE_BUILDING, detail=detail
|
|
237
237
|
)
|
|
238
238
|
if state == "missing_model":
|
|
239
|
+
if health.semantic_source_revision:
|
|
240
|
+
return ReadinessItem(
|
|
241
|
+
key="embeddings",
|
|
242
|
+
label=label,
|
|
243
|
+
stage=STAGE_UNAVAILABLE,
|
|
244
|
+
detail=(
|
|
245
|
+
"embedding model missing — stored vectors intact · "
|
|
246
|
+
"repair: bin/search.sh reindex --embed"
|
|
247
|
+
),
|
|
248
|
+
)
|
|
239
249
|
return ReadinessItem(
|
|
240
250
|
key="embeddings",
|
|
241
251
|
label=label,
|
|
@@ -254,6 +264,16 @@ def _semantic_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
254
264
|
label = "Semantic build"
|
|
255
265
|
source = _short_revision(health.source_revision)
|
|
256
266
|
build = _short_revision(health.semantic_source_revision)
|
|
267
|
+
if health.embedding_state == "missing_model":
|
|
268
|
+
return ReadinessItem(
|
|
269
|
+
key="semantic",
|
|
270
|
+
label=label,
|
|
271
|
+
stage=STAGE_UNAVAILABLE,
|
|
272
|
+
detail=(
|
|
273
|
+
"embedding model missing — semantic queries unavailable · "
|
|
274
|
+
"repair: bin/search.sh reindex --embed"
|
|
275
|
+
),
|
|
276
|
+
)
|
|
257
277
|
if health.semantic_ready:
|
|
258
278
|
return ReadinessItem(
|
|
259
279
|
key="semantic",
|
|
@@ -282,6 +302,16 @@ def _search_item(health: RetrieverHealth) -> ReadinessItem:
|
|
|
282
302
|
return ReadinessItem(
|
|
283
303
|
key="search", label=label, stage=STAGE_READY, detail="hybrid current"
|
|
284
304
|
)
|
|
305
|
+
if health.bm25_ready and health.embedding_state == "missing_model":
|
|
306
|
+
return ReadinessItem(
|
|
307
|
+
key="search",
|
|
308
|
+
label=label,
|
|
309
|
+
stage=STAGE_READY,
|
|
310
|
+
detail=(
|
|
311
|
+
"lexical only — embedding model missing · "
|
|
312
|
+
"repair: bin/search.sh reindex --embed"
|
|
313
|
+
),
|
|
314
|
+
)
|
|
285
315
|
if health.bm25_ready and health.semantic_source_revision:
|
|
286
316
|
return ReadinessItem(
|
|
287
317
|
key="search",
|