create-egregore 0.21.0-runtime-mvp.2 → 0.21.0-runtime-mvp.4
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 +69 -7
- package/lib/setup.js +35 -4
- 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.4
|
|
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.4";
|
|
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.
|
|
@@ -2560,7 +2594,7 @@ function upgradeElapsed(st) {
|
|
|
2560
2594
|
// Plain-language body shared by the non-interactive `settings <slug> runtime`
|
|
2561
2595
|
// output and the interactive panel. Returns undecorated strings; callers add
|
|
2562
2596
|
// color. Never JSON, never internal paths beyond the instance's own root.
|
|
2563
|
-
function formatUpgradeLines(inst, st, { candidate = "" } = {}) {
|
|
2597
|
+
function formatUpgradeLines(inst, st, { candidate = "", retrieval = null } = {}) {
|
|
2564
2598
|
const lines = [];
|
|
2565
2599
|
const d = st || { status: "none" };
|
|
2566
2600
|
lines.push(`Selected Egregore: ${inst.name || inst.slug} (${inst.slug || "?"})`);
|
|
@@ -2581,6 +2615,17 @@ function formatUpgradeLines(inst, st, { candidate = "" } = {}) {
|
|
|
2581
2615
|
} else if (d.status !== "none") {
|
|
2582
2616
|
lines.push("Previous Runtime remains active until activation.");
|
|
2583
2617
|
}
|
|
2618
|
+
if (retrieval) {
|
|
2619
|
+
if (retrieval.repair) {
|
|
2620
|
+
lines.push("Local retrieval: repair needed");
|
|
2621
|
+
if (retrieval.embeddingsDetail) lines.push(` ✗ ${retrieval.embeddingsDetail}`);
|
|
2622
|
+
lines.push(" → run: bin/search.sh reindex --embed");
|
|
2623
|
+
} else if (retrieval.searchStage && retrieval.searchStage !== "ready") {
|
|
2624
|
+
lines.push(`Local retrieval: ${retrieval.searchStage}`);
|
|
2625
|
+
} else if (retrieval.searchStage === "ready") {
|
|
2626
|
+
lines.push("Local retrieval: ready");
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2584
2629
|
if (Array.isArray(d.stages) && d.status !== "none" && d.status !== "active") {
|
|
2585
2630
|
lines.push("");
|
|
2586
2631
|
for (const stage of d.stages) {
|
|
@@ -2984,7 +3029,7 @@ const SETTINGS_DOMAIN_NOUN = { repo: "repo name", admin: "github handle", people
|
|
|
2984
3029
|
// Runtime-update panel: renders the durable upgrade state from the Python
|
|
2985
3030
|
// engine. Same geometry as the settings panel; plain-language body from
|
|
2986
3031
|
// formatUpgradeLines with launcher glyph decoration only.
|
|
2987
|
-
function renderUpgradePanel({ inst, data, candidate, mode, notice, width }) {
|
|
3032
|
+
function renderUpgradePanel({ inst, data, candidate, retrieval, mode, notice, width }) {
|
|
2988
3033
|
const outerWidth = Math.min(Math.max(50, width - 2), 126);
|
|
2989
3034
|
const innerWidth = Math.max(48, outerWidth - 2);
|
|
2990
3035
|
const margin = " ".repeat(Math.max(0, Math.floor((width - outerWidth) / 2)));
|
|
@@ -2997,7 +3042,7 @@ function renderUpgradePanel({ inst, data, candidate, mode, notice, width }) {
|
|
|
2997
3042
|
? ["Checking this egregore's Runtime state…"]
|
|
2998
3043
|
: data === null
|
|
2999
3044
|
? ["Runtime update state is unavailable for this egregore."]
|
|
3000
|
-
: formatUpgradeLines(inst, data, { candidate });
|
|
3045
|
+
: formatUpgradeLines(inst, data, { candidate, retrieval });
|
|
3001
3046
|
for (const raw of body) {
|
|
3002
3047
|
let line = raw;
|
|
3003
3048
|
if (/^ ✓ /.test(raw)) line = ` ${GREEN}✓${RESET} ${raw.slice(4)}`;
|
|
@@ -3041,7 +3086,7 @@ function renderUpgradePanel({ inst, data, candidate, mode, notice, width }) {
|
|
|
3041
3086
|
return lines;
|
|
3042
3087
|
}
|
|
3043
3088
|
|
|
3044
|
-
function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCursor, mode, input, notice, width, upgrade }) {
|
|
3089
|
+
function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCursor, mode, input, notice, width, upgrade, retrieval }) {
|
|
3045
3090
|
const title = inst.slug || inst.name || path.basename(inst.path);
|
|
3046
3091
|
const outerWidth = Math.min(Math.max(50, width - 2), 126);
|
|
3047
3092
|
const innerWidth = Math.max(48, outerWidth - 2);
|
|
@@ -3196,7 +3241,12 @@ function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCurs
|
|
|
3196
3241
|
? `${YELLOW}${upgradeSummary(upgrade)}${RESET}`
|
|
3197
3242
|
: `${DIM}${upgradeSummary(upgrade)}${RESET}`;
|
|
3198
3243
|
const rows = [
|
|
3199
|
-
{
|
|
3244
|
+
{
|
|
3245
|
+
name: "Runtime update",
|
|
3246
|
+
value: retrieval && retrieval.repair
|
|
3247
|
+
? `${upgradeValue} ${YELLOW}· retrieval repair needed${RESET}`
|
|
3248
|
+
: upgradeValue,
|
|
3249
|
+
},
|
|
3200
3250
|
{ name: "Privacy & access", value: `${DIM}identity · boundary · consent${RESET}` },
|
|
3201
3251
|
{ name: "Telemetry", value: `${DIM}personal · opt-out${RESET}` },
|
|
3202
3252
|
{ name: "Hosted Artifacts", value: (d.hosting !== false) ? `${GREEN}ON${RESET}` : `${YELLOW}OFF${RESET}` },
|
|
@@ -3350,6 +3400,7 @@ async function browser(args = {}) {
|
|
|
3350
3400
|
inst: state.upgradeInst,
|
|
3351
3401
|
data: state.upgradeData,
|
|
3352
3402
|
candidate: state.upgradeCandidate,
|
|
3403
|
+
retrieval: state.upgradeRetrieval,
|
|
3353
3404
|
mode: state.upgradeMode,
|
|
3354
3405
|
notice: state.upgradeNotice,
|
|
3355
3406
|
width: w,
|
|
@@ -3375,6 +3426,7 @@ async function browser(args = {}) {
|
|
|
3375
3426
|
notice: state.settingsNotice,
|
|
3376
3427
|
width: w,
|
|
3377
3428
|
upgrade: state.settingsUpgrade,
|
|
3429
|
+
retrieval: state.settingsRetrieval,
|
|
3378
3430
|
});
|
|
3379
3431
|
for (const line of settingsLines) out.push(line + ANSI.clearLine + "\n");
|
|
3380
3432
|
const remainingRows = Math.max(0, h - settingsLines.length - 1);
|
|
@@ -3903,8 +3955,13 @@ async function browser(args = {}) {
|
|
|
3903
3955
|
if (S.settingsCursor === 0) {
|
|
3904
3956
|
state.upgradeInst = S.settingsInst;
|
|
3905
3957
|
state.upgradeData = S.settingsUpgrade;
|
|
3958
|
+
state.upgradeRetrieval = S.settingsRetrieval;
|
|
3906
3959
|
state.upgradeMode = "view";
|
|
3907
3960
|
state.upgradeNotice = "";
|
|
3961
|
+
retrievalHealthAsync(S.settingsInst, (health) => {
|
|
3962
|
+
state.upgradeRetrieval = health;
|
|
3963
|
+
if (state.upgradeInst) render();
|
|
3964
|
+
});
|
|
3908
3965
|
refreshUpgrade();
|
|
3909
3966
|
if (!state.upgradeCandidate) {
|
|
3910
3967
|
fetchRuntimeMvpCandidate().then((v) => {
|
|
@@ -4190,10 +4247,15 @@ async function browser(args = {}) {
|
|
|
4190
4247
|
state.settingsCursor = 0;
|
|
4191
4248
|
state.settingsView = "menu";
|
|
4192
4249
|
state.settingsUpgrade = undefined;
|
|
4250
|
+
state.settingsRetrieval = undefined;
|
|
4193
4251
|
upgradeStatusAsync(pick, (st) => {
|
|
4194
4252
|
state.settingsUpgrade = st;
|
|
4195
4253
|
if (state.settingsInst === pick && !state.upgradeInst) render();
|
|
4196
4254
|
});
|
|
4255
|
+
retrievalHealthAsync(pick, (health) => {
|
|
4256
|
+
state.settingsRetrieval = health;
|
|
4257
|
+
if (state.settingsInst === pick && !state.upgradeInst) render();
|
|
4258
|
+
});
|
|
4197
4259
|
state.settingsNotice = "";
|
|
4198
4260
|
render();
|
|
4199
4261
|
return;
|
package/lib/setup.js
CHANGED
|
@@ -1327,17 +1327,46 @@ function currentTemplatePath() {
|
|
|
1327
1327
|
// # egregore-launcher-version: X.Y.Z (bash)
|
|
1328
1328
|
// // egregore-launcher-version: X.Y.Z (node)
|
|
1329
1329
|
function launcherVersion(contents) {
|
|
1330
|
-
|
|
1330
|
+
// Capture the full stamp including any prerelease suffix. A prerelease-blind
|
|
1331
|
+
// regex made 0.21.0-runtime-mvp.1 and .2 both read as "0.21.0", so the
|
|
1332
|
+
// refresh treated the newer template as a local edit and refused to upgrade.
|
|
1333
|
+
const m = contents.match(/egregore-launcher-version:\s*(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/);
|
|
1331
1334
|
return m ? m[1] : null;
|
|
1332
1335
|
}
|
|
1333
1336
|
|
|
1334
|
-
// Return 1 if a > b, -1 if a < b, 0 if equal.
|
|
1337
|
+
// Return 1 if a > b, -1 if a < b, 0 if equal. Semver with prerelease rules:
|
|
1338
|
+
// a release outranks any prerelease of the same core; prerelease identifiers
|
|
1339
|
+
// compare dot-by-dot, numerically when both numeric.
|
|
1335
1340
|
function compareVersions(a, b) {
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1341
|
+
const splitOnce = (v) => {
|
|
1342
|
+
const i = v.indexOf("-");
|
|
1343
|
+
return i === -1 ? [v, ""] : [v.slice(0, i), v.slice(i + 1)];
|
|
1344
|
+
};
|
|
1345
|
+
const [coreA, preA] = splitOnce(a);
|
|
1346
|
+
const [coreB, preB] = splitOnce(b);
|
|
1347
|
+
const pa = coreA.split(".").map(Number);
|
|
1348
|
+
const pb = coreB.split(".").map(Number);
|
|
1338
1349
|
for (let i = 0; i < 3; i++) {
|
|
1339
1350
|
if (pa[i] !== pb[i]) return pa[i] > pb[i] ? 1 : -1;
|
|
1340
1351
|
}
|
|
1352
|
+
if (!preA && !preB) return 0;
|
|
1353
|
+
if (!preA) return 1;
|
|
1354
|
+
if (!preB) return -1;
|
|
1355
|
+
const ia = preA.split(".");
|
|
1356
|
+
const ib = preB.split(".");
|
|
1357
|
+
for (let i = 0; i < Math.max(ia.length, ib.length); i++) {
|
|
1358
|
+
if (ia[i] === undefined) return -1;
|
|
1359
|
+
if (ib[i] === undefined) return 1;
|
|
1360
|
+
const na = /^\d+$/.test(ia[i]) ? Number(ia[i]) : NaN;
|
|
1361
|
+
const nb = /^\d+$/.test(ib[i]) ? Number(ib[i]) : NaN;
|
|
1362
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb)) {
|
|
1363
|
+
if (na !== nb) return na > nb ? 1 : -1;
|
|
1364
|
+
} else if (Number.isNaN(na) !== Number.isNaN(nb)) {
|
|
1365
|
+
return Number.isNaN(na) ? 1 : -1;
|
|
1366
|
+
} else if (ia[i] !== ib[i]) {
|
|
1367
|
+
return ia[i] > ib[i] ? 1 : -1;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1341
1370
|
return 0;
|
|
1342
1371
|
}
|
|
1343
1372
|
|
|
@@ -1628,6 +1657,8 @@ async function installShellAlias(egregoreDir, ui) {
|
|
|
1628
1657
|
|
|
1629
1658
|
module.exports = {
|
|
1630
1659
|
install,
|
|
1660
|
+
launcherVersion,
|
|
1661
|
+
compareVersions,
|
|
1631
1662
|
ghApi,
|
|
1632
1663
|
acceptPendingInvitations,
|
|
1633
1664
|
registerInstance,
|
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.4",
|
|
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",
|