ygl-trees 0.75.1 → 0.76.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 +6 -0
- package/dist/apps/cli/src/bin.js +15 -4
- package/dist/bundle/bin.cjs +1242 -1147
- package/dist/packages/core/database/src/plugin.js +22 -0
- package/dist/packages/core/text/src/eventbus.js +35 -0
- package/dist/packages/ops/tools/src/webui.js +27 -1
- package/dist/packages/research/pipeline/src/writes.js +6 -0
- package/dist/packages/techtree/model/src/writes.js +19 -13
- package/package.json +4 -4
package/dist/bundle/bin.cjs
CHANGED
|
@@ -10,12 +10,13 @@ let node_fs = require("node:fs");
|
|
|
10
10
|
node_fs = require_rolldown_runtime.__toESM(node_fs);
|
|
11
11
|
let node_crypto = require("node:crypto");
|
|
12
12
|
let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
|
|
13
|
+
require("reflect-metadata");
|
|
14
|
+
let typeorm = require("typeorm");
|
|
13
15
|
let _deepseek_ai_cordis_plugin_timer = require("@deepseek-ai/cordis-plugin-timer");
|
|
14
16
|
_deepseek_ai_cordis_plugin_timer = require_rolldown_runtime.__toESM(_deepseek_ai_cordis_plugin_timer);
|
|
15
17
|
let node_child_process = require("node:child_process");
|
|
16
18
|
let _deepseek_ai_dsh_atomic_write = require("@deepseek-ai/dsh-atomic-write");
|
|
17
|
-
require("
|
|
18
|
-
let typeorm = require("typeorm");
|
|
19
|
+
let node_events = require("node:events");
|
|
19
20
|
let _deepseek_ai_schemastery = require("@deepseek-ai/schemastery");
|
|
20
21
|
_deepseek_ai_schemastery = require_rolldown_runtime.__toESM(_deepseek_ai_schemastery);
|
|
21
22
|
//#region packages/core/text/src/text.ts
|
|
@@ -971,507 +972,1027 @@ function bool(args, key) {
|
|
|
971
972
|
return v === true || v === "true" || v === "1";
|
|
972
973
|
}
|
|
973
974
|
//#endregion
|
|
974
|
-
//#region
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
* This is the entry point that apps compose via ctx.plugin().
|
|
978
|
-
*
|
|
979
|
-
* @module @ygl/core-database/plugin
|
|
980
|
-
*/
|
|
981
|
-
var DatabaseService = class extends _deepseek_ai_cordis.Service {
|
|
982
|
-
db;
|
|
983
|
-
constructor(ctx, dbFile) {
|
|
984
|
-
super(ctx, "database");
|
|
985
|
-
const lock = process.env.YGL_TEST !== "1" && process.env.YGL_SERVE !== "1";
|
|
986
|
-
this.db = new DB(dbFile, { lock });
|
|
987
|
-
}
|
|
988
|
-
};
|
|
989
|
-
function apply$3(ctx, config) {
|
|
990
|
-
new DatabaseService(ctx, config.file);
|
|
975
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorateMetadata.js
|
|
976
|
+
function __decorateMetadata(k, v) {
|
|
977
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
991
978
|
}
|
|
979
|
+
var init_decorateMetadata = require_rolldown_runtime.__esmMin((() => {}));
|
|
992
980
|
//#endregion
|
|
993
|
-
//#region
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
* hands static-file serving to whoever owns the HTTP surface.
|
|
1000
|
-
*
|
|
1001
|
-
* @module @ygl/web-app
|
|
1002
|
-
*/
|
|
1003
|
-
/** Resolve the built client dist (workspace knowledge). Pure: safe to call
|
|
1004
|
-
* from short-lived CLI processes without betting on async plugin timing.
|
|
1005
|
-
*
|
|
1006
|
-
* Probe by REPO MARKER, not by counting levels from __dirname: under the
|
|
1007
|
-
* tsdown single-file build __dirname collapses to dist/bundle, so any
|
|
1008
|
-
* fixed-depth relative path misses. Walking up to the repo root (the dir
|
|
1009
|
-
* holding ygl.bundle.json) works from every layout — tsc tree, bundle,
|
|
1010
|
-
* and a run straight from src/. */
|
|
1011
|
-
function resolveDistRoot(from = __dirname) {
|
|
1012
|
-
const candidates = [];
|
|
1013
|
-
let dir = from;
|
|
1014
|
-
for (let i = 0; i < 6; i++, dir = node_path.dirname(dir)) {
|
|
1015
|
-
candidates.push(node_path.join(dir, "apps", "web", "dist"));
|
|
1016
|
-
if (node_fs.existsSync(node_path.join(dir, "ygl.bundle.json"))) break;
|
|
1017
|
-
}
|
|
1018
|
-
candidates.push(node_path.resolve(process.cwd(), "apps", "web", "dist"));
|
|
1019
|
-
for (const candidate of candidates) if (node_fs.existsSync(node_path.join(candidate, "index.html"))) return candidate;
|
|
1020
|
-
return null;
|
|
1021
|
-
}
|
|
1022
|
-
const MIME = {
|
|
1023
|
-
".html": "text/html; charset=utf-8",
|
|
1024
|
-
".js": "text/javascript; charset=utf-8",
|
|
1025
|
-
".mjs": "text/javascript; charset=utf-8",
|
|
1026
|
-
".css": "text/css; charset=utf-8",
|
|
1027
|
-
".json": "application/json",
|
|
1028
|
-
".svg": "image/svg+xml",
|
|
1029
|
-
".png": "image/png",
|
|
1030
|
-
".ico": "image/x-icon",
|
|
1031
|
-
".woff2": "font/woff2",
|
|
1032
|
-
".map": "application/json"
|
|
1033
|
-
};
|
|
1034
|
-
/** URL → dist 静态文件(SPA 回落 index.html;路径遍历安全)。纯函数。 */
|
|
1035
|
-
function openStatic(urlPath) {
|
|
1036
|
-
const root = resolveDistRoot();
|
|
1037
|
-
if (!root) return null;
|
|
1038
|
-
const clean = urlPath.split("?")[0].replace(/^\/+/, "");
|
|
1039
|
-
let file = node_path.resolve(root, clean === "" ? "index.html" : clean);
|
|
1040
|
-
if (!file.startsWith(root)) file = node_path.join(root, "index.html");
|
|
1041
|
-
if (!node_fs.existsSync(file) || node_fs.statSync(file).isDirectory()) {
|
|
1042
|
-
file = node_path.join(root, "index.html");
|
|
1043
|
-
if (!node_fs.existsSync(file)) return null;
|
|
1044
|
-
}
|
|
1045
|
-
const ext = node_path.extname(file).toLowerCase();
|
|
1046
|
-
return {
|
|
1047
|
-
body: node_fs.readFileSync(file),
|
|
1048
|
-
mime: MIME[ext] || "application/octet-stream"
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
var WebAppService = class extends _deepseek_ai_cordis.Service {
|
|
1052
|
-
constructor(ctx) {
|
|
1053
|
-
super(ctx, "webApp");
|
|
1054
|
-
}
|
|
1055
|
-
/** Whether a built client exists to serve. */
|
|
1056
|
-
available() {
|
|
1057
|
-
return resolveDistRoot() !== null;
|
|
1058
|
-
}
|
|
1059
|
-
/** Delegates to the pure resolver — short-lived CLI callers may use the
|
|
1060
|
-
* standalone openStatic() without waiting on plugin timing. */
|
|
1061
|
-
open(urlPath) {
|
|
1062
|
-
return openStatic(urlPath);
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
function apply$2(ctx) {
|
|
1066
|
-
new WebAppService(ctx);
|
|
981
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorate.js
|
|
982
|
+
function __decorate(decorators, target, key, desc) {
|
|
983
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
984
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
985
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
986
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1067
987
|
}
|
|
988
|
+
var init_decorate = require_rolldown_runtime.__esmMin((() => {}));
|
|
1068
989
|
//#endregion
|
|
1069
|
-
//#region packages/
|
|
990
|
+
//#region packages/research/pipeline/src/entities.ts
|
|
1070
991
|
/**
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
* Speaks the Vercel AI Gateway v4 evaluation-model protocol (TypeSafe jev):
|
|
1074
|
-
* deterministic P(true) judgments, 1-2s/call, native CJK, collapses toward
|
|
1075
|
-
* the gray band instead of hard-answering on thin evidence. Material:
|
|
1076
|
-
* F:/Users/Administrator/Downloads/vercel-ai-gateway-eval-api.md.
|
|
992
|
+
* Research-domain entities — a typed snapshot of schema v5.
|
|
1077
993
|
*
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1080
|
-
*
|
|
994
|
+
* Contract with the schema authority:
|
|
995
|
+
* - `synchronize` is ALWAYS false; migrations stay on the PRAGMA
|
|
996
|
+
* user_version track (src/db.ts). If SCHEMA_SQL changes, these
|
|
997
|
+
* entities must be updated in the same commit.
|
|
998
|
+
* - Column names are explicit snake_case to match the database exactly.
|
|
999
|
+
* - FTS5 virtual tables and PRAGMA paths are OUT of entity scope; they
|
|
1000
|
+
* stay raw SQL in store.ts (see docs/plan.md TypeORM boundaries).
|
|
1081
1001
|
*/
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
try {
|
|
1099
|
-
const m = /^token\s*=\s*(\S+)/m.exec(node_fs.readFileSync(node_path.join(node_os.homedir(), ".vercel.ai-gateway.env"), "utf8"));
|
|
1100
|
-
if (m) return m[1];
|
|
1101
|
-
} catch {}
|
|
1102
|
-
return "";
|
|
1103
|
-
}
|
|
1104
|
-
/** One boolean question about a state. Retries 429/529 with exponential
|
|
1105
|
-
* backoff (<=3); 400/401 and malformed answers return without retry;
|
|
1106
|
-
* 60s timeout per attempt. `endpoint` defaults to the Vercel gateway —
|
|
1107
|
-
* callers pass the config-resolved value (decision_model_llm_api_base
|
|
1108
|
-
* has priority over the default). */
|
|
1109
|
-
async function evalBoolean(state, question, token, endpoint = GATEWAY_URL) {
|
|
1110
|
-
if (!token) return {
|
|
1111
|
-
ok: false,
|
|
1112
|
-
error: NO_TOKEN
|
|
1113
|
-
};
|
|
1114
|
-
const r = await postEval(state, { q: {
|
|
1115
|
-
type: "boolean",
|
|
1116
|
-
instructions: question
|
|
1117
|
-
} }, token, endpoint);
|
|
1118
|
-
if (!r.ok) return {
|
|
1119
|
-
ok: false,
|
|
1120
|
-
error: r.error
|
|
1121
|
-
};
|
|
1122
|
-
const p = r.answers.q?.probability;
|
|
1123
|
-
if (typeof p !== "number") return {
|
|
1124
|
-
ok: false,
|
|
1125
|
-
error: "malformed answer: no boolean probability"
|
|
1002
|
+
var Project, Query, Source, QuerySource, Evidence, Claim, ClaimEvidence, RESEARCH_ENTITIES;
|
|
1003
|
+
var init_entities$1 = require_rolldown_runtime.__esmMin((() => {
|
|
1004
|
+
init_decorateMetadata();
|
|
1005
|
+
init_decorate();
|
|
1006
|
+
Project = class Project {
|
|
1007
|
+
id;
|
|
1008
|
+
name;
|
|
1009
|
+
question;
|
|
1010
|
+
inclusion;
|
|
1011
|
+
exclusion;
|
|
1012
|
+
status;
|
|
1013
|
+
created;
|
|
1014
|
+
updated;
|
|
1015
|
+
queries;
|
|
1016
|
+
claims;
|
|
1017
|
+
sources;
|
|
1126
1018
|
};
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1019
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1020
|
+
name: "id",
|
|
1021
|
+
type: "text"
|
|
1022
|
+
}), __decorateMetadata("design:type", String)], Project.prototype, "id", void 0);
|
|
1023
|
+
__decorate([(0, typeorm.Column)({
|
|
1024
|
+
name: "name",
|
|
1025
|
+
type: "text"
|
|
1026
|
+
}), __decorateMetadata("design:type", String)], Project.prototype, "name", void 0);
|
|
1027
|
+
__decorate([(0, typeorm.Column)({
|
|
1028
|
+
name: "question",
|
|
1029
|
+
type: "text"
|
|
1030
|
+
}), __decorateMetadata("design:type", String)], Project.prototype, "question", void 0);
|
|
1031
|
+
__decorate([(0, typeorm.Column)({
|
|
1032
|
+
name: "inclusion",
|
|
1033
|
+
type: "text"
|
|
1034
|
+
}), __decorateMetadata("design:type", String)], Project.prototype, "inclusion", void 0);
|
|
1035
|
+
__decorate([(0, typeorm.Column)({
|
|
1036
|
+
name: "exclusion",
|
|
1037
|
+
type: "text"
|
|
1038
|
+
}), __decorateMetadata("design:type", String)], Project.prototype, "exclusion", void 0);
|
|
1039
|
+
__decorate([(0, typeorm.Column)({
|
|
1040
|
+
name: "status",
|
|
1041
|
+
type: "text"
|
|
1042
|
+
}), __decorateMetadata("design:type", String)], Project.prototype, "status", void 0);
|
|
1043
|
+
__decorate([(0, typeorm.Column)({
|
|
1044
|
+
name: "created",
|
|
1045
|
+
type: "text"
|
|
1046
|
+
}), __decorateMetadata("design:type", String)], Project.prototype, "created", void 0);
|
|
1047
|
+
__decorate([(0, typeorm.Column)({
|
|
1048
|
+
name: "updated",
|
|
1049
|
+
type: "text"
|
|
1050
|
+
}), __decorateMetadata("design:type", String)], Project.prototype, "updated", void 0);
|
|
1051
|
+
__decorate([(0, typeorm.OneToMany)(() => Query, (q) => q.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "queries", void 0);
|
|
1052
|
+
__decorate([(0, typeorm.OneToMany)(() => Claim, (c) => c.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "claims", void 0);
|
|
1053
|
+
__decorate([(0, typeorm.OneToMany)(() => Source, (s) => s.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "sources", void 0);
|
|
1054
|
+
Project = __decorate([(0, typeorm.Entity)({ name: "projects" })], Project);
|
|
1055
|
+
Query = class Query {
|
|
1056
|
+
id;
|
|
1057
|
+
projectId;
|
|
1058
|
+
text;
|
|
1059
|
+
engine;
|
|
1060
|
+
status;
|
|
1061
|
+
note;
|
|
1062
|
+
contentHash;
|
|
1063
|
+
created;
|
|
1064
|
+
project;
|
|
1065
|
+
querySources;
|
|
1131
1066
|
};
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
if (!j?.answers) return {
|
|
1189
|
-
ok: false,
|
|
1190
|
-
error: "malformed response: no answers"
|
|
1191
|
-
};
|
|
1192
|
-
return {
|
|
1193
|
-
ok: true,
|
|
1194
|
-
answers: j.answers,
|
|
1195
|
-
usage: j.usage ?? void 0
|
|
1196
|
-
};
|
|
1197
|
-
} catch (e) {
|
|
1198
|
-
const msg = String(e?.message || e);
|
|
1199
|
-
if (attempt < 3 && /timeout|abort|network|fetch failed|ECONN/i.test(msg)) {
|
|
1200
|
-
await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
|
|
1201
|
-
continue;
|
|
1202
|
-
}
|
|
1203
|
-
return {
|
|
1204
|
-
ok: false,
|
|
1205
|
-
error: msg
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
async function evalChoice(state, question, options, token, endpoint = GATEWAY_URL) {
|
|
1210
|
-
if (!token) return {
|
|
1211
|
-
ok: false,
|
|
1212
|
-
error: NO_TOKEN
|
|
1213
|
-
};
|
|
1214
|
-
const criteria = {};
|
|
1215
|
-
for (const [k, v] of Object.entries(options)) criteria[k] = v || null;
|
|
1216
|
-
const r = await postEval(state, { q: {
|
|
1217
|
-
type: "choice",
|
|
1218
|
-
instructions: question,
|
|
1219
|
-
criteria
|
|
1220
|
-
} }, token, endpoint);
|
|
1221
|
-
if (!r.ok) return {
|
|
1222
|
-
ok: false,
|
|
1223
|
-
error: r.error
|
|
1224
|
-
};
|
|
1225
|
-
const a = r.answers.q ?? {};
|
|
1226
|
-
if (typeof a.choice !== "string" || !a.probabilities) return {
|
|
1227
|
-
ok: false,
|
|
1228
|
-
error: "malformed answer: no choice/probabilities"
|
|
1067
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1068
|
+
name: "id",
|
|
1069
|
+
type: "text"
|
|
1070
|
+
}), __decorateMetadata("design:type", String)], Query.prototype, "id", void 0);
|
|
1071
|
+
__decorate([(0, typeorm.Column)({
|
|
1072
|
+
name: "project_id",
|
|
1073
|
+
type: "text"
|
|
1074
|
+
}), __decorateMetadata("design:type", String)], Query.prototype, "projectId", void 0);
|
|
1075
|
+
__decorate([(0, typeorm.Column)({
|
|
1076
|
+
name: "text",
|
|
1077
|
+
type: "text"
|
|
1078
|
+
}), __decorateMetadata("design:type", String)], Query.prototype, "text", void 0);
|
|
1079
|
+
__decorate([(0, typeorm.Column)({
|
|
1080
|
+
name: "engine",
|
|
1081
|
+
type: "text"
|
|
1082
|
+
}), __decorateMetadata("design:type", String)], Query.prototype, "engine", void 0);
|
|
1083
|
+
__decorate([(0, typeorm.Column)({
|
|
1084
|
+
name: "status",
|
|
1085
|
+
type: "text"
|
|
1086
|
+
}), __decorateMetadata("design:type", String)], Query.prototype, "status", void 0);
|
|
1087
|
+
__decorate([(0, typeorm.Column)({
|
|
1088
|
+
name: "note",
|
|
1089
|
+
type: "text"
|
|
1090
|
+
}), __decorateMetadata("design:type", String)], Query.prototype, "note", void 0);
|
|
1091
|
+
__decorate([(0, typeorm.Column)({
|
|
1092
|
+
name: "content_hash",
|
|
1093
|
+
type: "text"
|
|
1094
|
+
}), __decorateMetadata("design:type", String)], Query.prototype, "contentHash", void 0);
|
|
1095
|
+
__decorate([(0, typeorm.Column)({
|
|
1096
|
+
name: "created",
|
|
1097
|
+
type: "text"
|
|
1098
|
+
}), __decorateMetadata("design:type", String)], Query.prototype, "created", void 0);
|
|
1099
|
+
__decorate([
|
|
1100
|
+
(0, typeorm.ManyToOne)(() => Project, (p) => p.queries, { createForeignKeyConstraints: false }),
|
|
1101
|
+
(0, typeorm.JoinColumn)({ name: "project_id" }),
|
|
1102
|
+
__decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
|
|
1103
|
+
], Query.prototype, "project", void 0);
|
|
1104
|
+
__decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.query, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Query.prototype, "querySources", void 0);
|
|
1105
|
+
Query = __decorate([(0, typeorm.Entity)({ name: "queries" })], Query);
|
|
1106
|
+
Source = class Source {
|
|
1107
|
+
id;
|
|
1108
|
+
projectId;
|
|
1109
|
+
url;
|
|
1110
|
+
title;
|
|
1111
|
+
urlHash;
|
|
1112
|
+
contentHash;
|
|
1113
|
+
abstract;
|
|
1114
|
+
content;
|
|
1115
|
+
engine;
|
|
1116
|
+
relevance;
|
|
1117
|
+
confidence;
|
|
1118
|
+
tags;
|
|
1119
|
+
retrievedAt;
|
|
1120
|
+
created;
|
|
1121
|
+
project;
|
|
1122
|
+
querySources;
|
|
1229
1123
|
};
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1124
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1125
|
+
name: "id",
|
|
1126
|
+
type: "text"
|
|
1127
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "id", void 0);
|
|
1128
|
+
__decorate([(0, typeorm.Column)({
|
|
1129
|
+
name: "project_id",
|
|
1130
|
+
type: "text"
|
|
1131
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "projectId", void 0);
|
|
1132
|
+
__decorate([(0, typeorm.Column)({
|
|
1133
|
+
name: "url",
|
|
1134
|
+
type: "text"
|
|
1135
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "url", void 0);
|
|
1136
|
+
__decorate([(0, typeorm.Column)({
|
|
1137
|
+
name: "title",
|
|
1138
|
+
type: "text"
|
|
1139
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "title", void 0);
|
|
1140
|
+
__decorate([(0, typeorm.Column)({
|
|
1141
|
+
name: "url_hash",
|
|
1142
|
+
type: "text"
|
|
1143
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "urlHash", void 0);
|
|
1144
|
+
__decorate([(0, typeorm.Column)({
|
|
1145
|
+
name: "content_hash",
|
|
1146
|
+
type: "text"
|
|
1147
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "contentHash", void 0);
|
|
1148
|
+
__decorate([(0, typeorm.Column)({
|
|
1149
|
+
name: "abstract",
|
|
1150
|
+
type: "text"
|
|
1151
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "abstract", void 0);
|
|
1152
|
+
__decorate([(0, typeorm.Column)({
|
|
1153
|
+
name: "content",
|
|
1154
|
+
type: "text"
|
|
1155
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "content", void 0);
|
|
1156
|
+
__decorate([(0, typeorm.Column)({
|
|
1157
|
+
name: "engine",
|
|
1158
|
+
type: "text"
|
|
1159
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "engine", void 0);
|
|
1160
|
+
__decorate([(0, typeorm.Column)({
|
|
1161
|
+
name: "relevance",
|
|
1162
|
+
type: "integer"
|
|
1163
|
+
}), __decorateMetadata("design:type", Number)], Source.prototype, "relevance", void 0);
|
|
1164
|
+
__decorate([(0, typeorm.Column)({
|
|
1165
|
+
name: "confidence",
|
|
1166
|
+
type: "text"
|
|
1167
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "confidence", void 0);
|
|
1168
|
+
__decorate([(0, typeorm.Column)({
|
|
1169
|
+
name: "tags",
|
|
1170
|
+
type: "text"
|
|
1171
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "tags", void 0);
|
|
1172
|
+
__decorate([(0, typeorm.Column)({
|
|
1173
|
+
name: "retrieved_at",
|
|
1174
|
+
type: "text"
|
|
1175
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "retrievedAt", void 0);
|
|
1176
|
+
__decorate([(0, typeorm.Column)({
|
|
1177
|
+
name: "created",
|
|
1178
|
+
type: "text",
|
|
1179
|
+
nullable: true
|
|
1180
|
+
}), __decorateMetadata("design:type", String)], Source.prototype, "created", void 0);
|
|
1181
|
+
__decorate([
|
|
1182
|
+
(0, typeorm.ManyToOne)(() => Project, (p) => p.sources, { createForeignKeyConstraints: false }),
|
|
1183
|
+
(0, typeorm.JoinColumn)({ name: "project_id" }),
|
|
1184
|
+
__decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
|
|
1185
|
+
], Source.prototype, "project", void 0);
|
|
1186
|
+
__decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.source, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Source.prototype, "querySources", void 0);
|
|
1187
|
+
Source = __decorate([(0, typeorm.Entity)({ name: "sources" })], Source);
|
|
1188
|
+
QuerySource = class QuerySource {
|
|
1189
|
+
queryId;
|
|
1190
|
+
sourceId;
|
|
1191
|
+
note;
|
|
1192
|
+
created;
|
|
1193
|
+
query;
|
|
1194
|
+
source;
|
|
1235
1195
|
};
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1196
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1197
|
+
name: "query_id",
|
|
1198
|
+
type: "text"
|
|
1199
|
+
}), __decorateMetadata("design:type", String)], QuerySource.prototype, "queryId", void 0);
|
|
1200
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1201
|
+
name: "source_id",
|
|
1202
|
+
type: "text"
|
|
1203
|
+
}), __decorateMetadata("design:type", String)], QuerySource.prototype, "sourceId", void 0);
|
|
1204
|
+
__decorate([(0, typeorm.Column)({
|
|
1205
|
+
name: "note",
|
|
1206
|
+
type: "text"
|
|
1207
|
+
}), __decorateMetadata("design:type", String)], QuerySource.prototype, "note", void 0);
|
|
1208
|
+
__decorate([(0, typeorm.Column)({
|
|
1209
|
+
name: "created",
|
|
1210
|
+
type: "text"
|
|
1211
|
+
}), __decorateMetadata("design:type", String)], QuerySource.prototype, "created", void 0);
|
|
1212
|
+
__decorate([
|
|
1213
|
+
(0, typeorm.ManyToOne)(() => Query, (q) => q.querySources, { createForeignKeyConstraints: false }),
|
|
1214
|
+
(0, typeorm.JoinColumn)({ name: "query_id" }),
|
|
1215
|
+
__decorateMetadata("design:type", typeof Query === "undefined" ? Object : Query)
|
|
1216
|
+
], QuerySource.prototype, "query", void 0);
|
|
1217
|
+
__decorate([
|
|
1218
|
+
(0, typeorm.ManyToOne)(() => Source, (s) => s.querySources, { createForeignKeyConstraints: false }),
|
|
1219
|
+
(0, typeorm.JoinColumn)({ name: "source_id" }),
|
|
1220
|
+
__decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
|
|
1221
|
+
], QuerySource.prototype, "source", void 0);
|
|
1222
|
+
QuerySource = __decorate([(0, typeorm.Entity)({ name: "query_sources" })], QuerySource);
|
|
1223
|
+
Evidence = class Evidence {
|
|
1224
|
+
id;
|
|
1225
|
+
projectId;
|
|
1226
|
+
sourceId;
|
|
1227
|
+
quote;
|
|
1228
|
+
note;
|
|
1229
|
+
locator;
|
|
1230
|
+
contentHash;
|
|
1231
|
+
created;
|
|
1232
|
+
project;
|
|
1233
|
+
source;
|
|
1234
|
+
claimEvidence;
|
|
1243
1235
|
};
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1236
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1237
|
+
name: "id",
|
|
1238
|
+
type: "text"
|
|
1239
|
+
}), __decorateMetadata("design:type", String)], Evidence.prototype, "id", void 0);
|
|
1240
|
+
__decorate([(0, typeorm.Column)({
|
|
1241
|
+
name: "project_id",
|
|
1242
|
+
type: "text"
|
|
1243
|
+
}), __decorateMetadata("design:type", String)], Evidence.prototype, "projectId", void 0);
|
|
1244
|
+
__decorate([(0, typeorm.Column)({
|
|
1245
|
+
name: "source_id",
|
|
1246
|
+
type: "text"
|
|
1247
|
+
}), __decorateMetadata("design:type", String)], Evidence.prototype, "sourceId", void 0);
|
|
1248
|
+
__decorate([(0, typeorm.Column)({
|
|
1249
|
+
name: "quote",
|
|
1250
|
+
type: "text"
|
|
1251
|
+
}), __decorateMetadata("design:type", String)], Evidence.prototype, "quote", void 0);
|
|
1252
|
+
__decorate([(0, typeorm.Column)({
|
|
1253
|
+
name: "note",
|
|
1254
|
+
type: "text"
|
|
1255
|
+
}), __decorateMetadata("design:type", String)], Evidence.prototype, "note", void 0);
|
|
1256
|
+
__decorate([(0, typeorm.Column)({
|
|
1257
|
+
name: "locator",
|
|
1258
|
+
type: "text"
|
|
1259
|
+
}), __decorateMetadata("design:type", String)], Evidence.prototype, "locator", void 0);
|
|
1260
|
+
__decorate([(0, typeorm.Column)({
|
|
1261
|
+
name: "content_hash",
|
|
1262
|
+
type: "text"
|
|
1263
|
+
}), __decorateMetadata("design:type", String)], Evidence.prototype, "contentHash", void 0);
|
|
1264
|
+
__decorate([(0, typeorm.Column)({
|
|
1265
|
+
name: "created",
|
|
1266
|
+
type: "text"
|
|
1267
|
+
}), __decorateMetadata("design:type", String)], Evidence.prototype, "created", void 0);
|
|
1268
|
+
__decorate([
|
|
1269
|
+
(0, typeorm.ManyToOne)(() => Project, void 0, { createForeignKeyConstraints: false }),
|
|
1270
|
+
(0, typeorm.JoinColumn)({ name: "project_id" }),
|
|
1271
|
+
__decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
|
|
1272
|
+
], Evidence.prototype, "project", void 0);
|
|
1273
|
+
__decorate([
|
|
1274
|
+
(0, typeorm.ManyToOne)(() => Source, void 0, { createForeignKeyConstraints: false }),
|
|
1275
|
+
(0, typeorm.JoinColumn)({ name: "source_id" }),
|
|
1276
|
+
__decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
|
|
1277
|
+
], Evidence.prototype, "source", void 0);
|
|
1278
|
+
__decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.evidence, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Evidence.prototype, "claimEvidence", void 0);
|
|
1279
|
+
Evidence = __decorate([(0, typeorm.Entity)({ name: "evidence" })], Evidence);
|
|
1280
|
+
Claim = class Claim {
|
|
1281
|
+
id;
|
|
1282
|
+
projectId;
|
|
1283
|
+
text;
|
|
1284
|
+
status;
|
|
1285
|
+
confidence;
|
|
1286
|
+
contentHash;
|
|
1287
|
+
created;
|
|
1288
|
+
updated;
|
|
1289
|
+
project;
|
|
1290
|
+
claimEvidence;
|
|
1252
1291
|
};
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1292
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1293
|
+
name: "id",
|
|
1294
|
+
type: "text"
|
|
1295
|
+
}), __decorateMetadata("design:type", String)], Claim.prototype, "id", void 0);
|
|
1296
|
+
__decorate([(0, typeorm.Column)({
|
|
1297
|
+
name: "project_id",
|
|
1298
|
+
type: "text"
|
|
1299
|
+
}), __decorateMetadata("design:type", String)], Claim.prototype, "projectId", void 0);
|
|
1300
|
+
__decorate([(0, typeorm.Column)({
|
|
1301
|
+
name: "text",
|
|
1302
|
+
type: "text"
|
|
1303
|
+
}), __decorateMetadata("design:type", String)], Claim.prototype, "text", void 0);
|
|
1304
|
+
__decorate([(0, typeorm.Column)({
|
|
1305
|
+
name: "status",
|
|
1306
|
+
type: "text"
|
|
1307
|
+
}), __decorateMetadata("design:type", String)], Claim.prototype, "status", void 0);
|
|
1308
|
+
__decorate([(0, typeorm.Column)({
|
|
1309
|
+
name: "confidence",
|
|
1310
|
+
type: "text"
|
|
1311
|
+
}), __decorateMetadata("design:type", String)], Claim.prototype, "confidence", void 0);
|
|
1312
|
+
__decorate([(0, typeorm.Column)({
|
|
1313
|
+
name: "content_hash",
|
|
1314
|
+
type: "text"
|
|
1315
|
+
}), __decorateMetadata("design:type", String)], Claim.prototype, "contentHash", void 0);
|
|
1316
|
+
__decorate([(0, typeorm.Column)({
|
|
1317
|
+
name: "created",
|
|
1318
|
+
type: "text"
|
|
1319
|
+
}), __decorateMetadata("design:type", String)], Claim.prototype, "created", void 0);
|
|
1320
|
+
__decorate([(0, typeorm.Column)({
|
|
1321
|
+
name: "updated",
|
|
1322
|
+
type: "text",
|
|
1323
|
+
nullable: true
|
|
1324
|
+
}), __decorateMetadata("design:type", String)], Claim.prototype, "updated", void 0);
|
|
1325
|
+
__decorate([
|
|
1326
|
+
(0, typeorm.ManyToOne)(() => Project, (p) => p.claims, { createForeignKeyConstraints: false }),
|
|
1327
|
+
(0, typeorm.JoinColumn)({ name: "project_id" }),
|
|
1328
|
+
__decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
|
|
1329
|
+
], Claim.prototype, "project", void 0);
|
|
1330
|
+
__decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.claim, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Claim.prototype, "claimEvidence", void 0);
|
|
1331
|
+
Claim = __decorate([(0, typeorm.Entity)({ name: "claims" })], Claim);
|
|
1332
|
+
ClaimEvidence = class ClaimEvidence {
|
|
1333
|
+
claimId;
|
|
1334
|
+
evidenceId;
|
|
1335
|
+
stance;
|
|
1336
|
+
claim;
|
|
1337
|
+
evidence;
|
|
1256
1338
|
};
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1339
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1340
|
+
name: "claim_id",
|
|
1341
|
+
type: "text"
|
|
1342
|
+
}), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "claimId", void 0);
|
|
1343
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1344
|
+
name: "evidence_id",
|
|
1345
|
+
type: "text"
|
|
1346
|
+
}), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "evidenceId", void 0);
|
|
1347
|
+
__decorate([(0, typeorm.Column)({
|
|
1348
|
+
name: "stance",
|
|
1349
|
+
type: "text"
|
|
1350
|
+
}), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "stance", void 0);
|
|
1351
|
+
__decorate([
|
|
1352
|
+
(0, typeorm.ManyToOne)(() => Claim, (c) => c.claimEvidence, { createForeignKeyConstraints: false }),
|
|
1353
|
+
(0, typeorm.JoinColumn)({ name: "claim_id" }),
|
|
1354
|
+
__decorateMetadata("design:type", typeof Claim === "undefined" ? Object : Claim)
|
|
1355
|
+
], ClaimEvidence.prototype, "claim", void 0);
|
|
1356
|
+
__decorate([
|
|
1357
|
+
(0, typeorm.ManyToOne)(() => Evidence, void 0, { createForeignKeyConstraints: false }),
|
|
1358
|
+
(0, typeorm.JoinColumn)({ name: "evidence_id" }),
|
|
1359
|
+
__decorateMetadata("design:type", typeof Evidence === "undefined" ? Object : Evidence)
|
|
1360
|
+
], ClaimEvidence.prototype, "evidence", void 0);
|
|
1361
|
+
ClaimEvidence = __decorate([(0, typeorm.Entity)({ name: "claim_evidence" })], ClaimEvidence);
|
|
1362
|
+
RESEARCH_ENTITIES = [
|
|
1363
|
+
Project,
|
|
1364
|
+
Query,
|
|
1365
|
+
Source,
|
|
1366
|
+
QuerySource,
|
|
1367
|
+
Evidence,
|
|
1368
|
+
Claim,
|
|
1369
|
+
ClaimEvidence
|
|
1370
|
+
];
|
|
1371
|
+
}));
|
|
1372
|
+
//#endregion
|
|
1373
|
+
//#region packages/core/database/src/entities.ts
|
|
1374
|
+
/** Cross-cutting entities (audit trail). Research-domain entities live in
|
|
1375
|
+
* research.ts; both are registered in src/orm.ts. */
|
|
1376
|
+
var Event, CORE_ENTITIES;
|
|
1377
|
+
var init_entities = require_rolldown_runtime.__esmMin((() => {
|
|
1378
|
+
init_decorateMetadata();
|
|
1379
|
+
init_decorate();
|
|
1380
|
+
Event = class Event {
|
|
1381
|
+
id;
|
|
1382
|
+
ts;
|
|
1383
|
+
actor;
|
|
1384
|
+
kind;
|
|
1385
|
+
entity;
|
|
1386
|
+
entityId;
|
|
1387
|
+
payload;
|
|
1388
|
+
/** v7 telemetry severity: info | warn | error (spec R10.2) */
|
|
1389
|
+
level;
|
|
1260
1390
|
};
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1391
|
+
__decorate([(0, typeorm.PrimaryColumn)({
|
|
1392
|
+
name: "id",
|
|
1393
|
+
type: "text"
|
|
1394
|
+
}), __decorateMetadata("design:type", String)], Event.prototype, "id", void 0);
|
|
1395
|
+
__decorate([(0, typeorm.Column)({
|
|
1396
|
+
name: "ts",
|
|
1397
|
+
type: "text"
|
|
1398
|
+
}), __decorateMetadata("design:type", String)], Event.prototype, "ts", void 0);
|
|
1399
|
+
__decorate([(0, typeorm.Column)({
|
|
1400
|
+
name: "actor",
|
|
1401
|
+
type: "text"
|
|
1402
|
+
}), __decorateMetadata("design:type", String)], Event.prototype, "actor", void 0);
|
|
1403
|
+
__decorate([(0, typeorm.Column)({
|
|
1404
|
+
name: "kind",
|
|
1405
|
+
type: "text"
|
|
1406
|
+
}), __decorateMetadata("design:type", String)], Event.prototype, "kind", void 0);
|
|
1407
|
+
__decorate([(0, typeorm.Column)({
|
|
1408
|
+
name: "entity",
|
|
1409
|
+
type: "text"
|
|
1410
|
+
}), __decorateMetadata("design:type", String)], Event.prototype, "entity", void 0);
|
|
1411
|
+
__decorate([(0, typeorm.Column)({
|
|
1412
|
+
name: "entity_id",
|
|
1413
|
+
type: "text"
|
|
1414
|
+
}), __decorateMetadata("design:type", String)], Event.prototype, "entityId", void 0);
|
|
1415
|
+
__decorate([(0, typeorm.Column)({
|
|
1416
|
+
name: "payload",
|
|
1417
|
+
type: "text"
|
|
1418
|
+
}), __decorateMetadata("design:type", String)], Event.prototype, "payload", void 0);
|
|
1419
|
+
__decorate([(0, typeorm.Column)({
|
|
1420
|
+
name: "level",
|
|
1421
|
+
type: "text",
|
|
1422
|
+
default: "info"
|
|
1423
|
+
}), __decorateMetadata("design:type", String)], Event.prototype, "level", void 0);
|
|
1424
|
+
Event = __decorate([(0, typeorm.Entity)({ name: "events" })], Event);
|
|
1425
|
+
CORE_ENTITIES = [Event];
|
|
1426
|
+
}));
|
|
1427
|
+
//#endregion
|
|
1428
|
+
//#region packages/core/database/src/orm.ts
|
|
1429
|
+
/**
|
|
1430
|
+
* Lazy TypeORM DataSource over the SAME SQLite file the CLI works on.
|
|
1431
|
+
*
|
|
1432
|
+
* Boundaries (see docs/plan.md "TypeORM boundaries"):
|
|
1433
|
+
* - `synchronize` is permanently false — schema authority stays with
|
|
1434
|
+
* SCHEMA_SQL + PRAGMA user_version migrations in src/db.ts. Entities in
|
|
1435
|
+
* src/entities/ are a typed snapshot; change them in the same commit as
|
|
1436
|
+
* SCHEMA_SQL.
|
|
1437
|
+
* - TypeORM is a QUERY layer here, not a migration layer. FTS5 MATCH /
|
|
1438
|
+
* bm25 loops, PRAGMA paths and the PG sync/migrate machinery stay raw
|
|
1439
|
+
* SQL on the DB class.
|
|
1440
|
+
* - CLI commands using the ORM must return a Promise (main() awaits) so
|
|
1441
|
+
* the DataSource settles before the wasm DB closes.
|
|
1442
|
+
*/
|
|
1443
|
+
var orm_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
1444
|
+
closeOrm: () => closeOrm,
|
|
1445
|
+
getOrm: () => getOrm
|
|
1446
|
+
});
|
|
1447
|
+
async function getOrm(dbFile) {
|
|
1448
|
+
if (cached && cached.file === dbFile) return cached.ds;
|
|
1449
|
+
const ds = new typeorm.DataSource({
|
|
1450
|
+
type: "better-sqlite3",
|
|
1451
|
+
database: dbFile,
|
|
1452
|
+
synchronize: false,
|
|
1453
|
+
entities: [...RESEARCH_ENTITIES, ...CORE_ENTITIES]
|
|
1454
|
+
});
|
|
1455
|
+
await ds.initialize();
|
|
1456
|
+
await ds.query("PRAGMA synchronous=NORMAL");
|
|
1457
|
+
cached = {
|
|
1458
|
+
file: dbFile,
|
|
1459
|
+
ds
|
|
1265
1460
|
};
|
|
1461
|
+
return ds;
|
|
1266
1462
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
} }, token, endpoint);
|
|
1277
|
-
if (!r.ok) return {
|
|
1278
|
-
ok: false,
|
|
1279
|
-
error: r.error
|
|
1280
|
-
};
|
|
1281
|
-
const a = r.answers.q ?? {};
|
|
1282
|
-
if (typeof a.score !== "number" || !a.probabilities) return {
|
|
1283
|
-
ok: false,
|
|
1284
|
-
error: "malformed answer: no score/probabilities"
|
|
1285
|
-
};
|
|
1286
|
-
return {
|
|
1287
|
-
ok: true,
|
|
1288
|
-
score: a.score,
|
|
1289
|
-
probabilities: a.probabilities,
|
|
1290
|
-
usage: r.usage
|
|
1291
|
-
};
|
|
1463
|
+
/** Destroy the cached DataSource (tests/scratch dirs cannot be removed on
|
|
1464
|
+
* Windows while a better-sqlite3 handle keeps the file open — EPERM). */
|
|
1465
|
+
async function closeOrm() {
|
|
1466
|
+
if (cached) {
|
|
1467
|
+
try {
|
|
1468
|
+
await cached.ds.destroy();
|
|
1469
|
+
} catch {}
|
|
1470
|
+
cached = null;
|
|
1471
|
+
}
|
|
1292
1472
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1473
|
+
var cached;
|
|
1474
|
+
var init_orm = require_rolldown_runtime.__esmMin((() => {
|
|
1475
|
+
init_entities$1();
|
|
1476
|
+
init_entities();
|
|
1477
|
+
cached = null;
|
|
1478
|
+
}));
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region packages/core/database/src/plugin.ts
|
|
1481
|
+
/**
|
|
1482
|
+
* DatabasePlugin — provides ctx.database (DatabaseService).
|
|
1483
|
+
* This is the entry point that apps compose via ctx.plugin().
|
|
1484
|
+
*
|
|
1485
|
+
* @module @ygl/core-database/plugin
|
|
1486
|
+
*/
|
|
1487
|
+
var DatabaseService = class extends _deepseek_ai_cordis.Service {
|
|
1488
|
+
db;
|
|
1489
|
+
disposed = false;
|
|
1490
|
+
constructor(ctx, dbFile) {
|
|
1491
|
+
super(ctx, "database");
|
|
1492
|
+
const lock = process.env.YGL_TEST !== "1" && process.env.YGL_SERVE !== "1";
|
|
1493
|
+
this.db = new DB(dbFile, { lock });
|
|
1494
|
+
}
|
|
1495
|
+
/** Lifecycle: Cordis calls this when the Context is disposed — the
|
|
1496
|
+
* "Services stop with Context, resources auto-clean" promise made real.
|
|
1497
|
+
* Closes the SQLite handle AND the cached ORM DataSource. */
|
|
1498
|
+
stop() {
|
|
1499
|
+
if (this.disposed) return;
|
|
1500
|
+
this.disposed = true;
|
|
1501
|
+
try {
|
|
1502
|
+
const { closeOrm } = (init_orm(), require_rolldown_runtime.__toCommonJS(orm_exports));
|
|
1503
|
+
closeOrm().catch(() => {});
|
|
1504
|
+
} catch {}
|
|
1505
|
+
try {
|
|
1506
|
+
this.db.close();
|
|
1507
|
+
} catch {}
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
function apply$3(ctx, config) {
|
|
1511
|
+
new DatabaseService(ctx, config.file);
|
|
1512
|
+
}
|
|
1513
|
+
//#endregion
|
|
1514
|
+
//#region packages/web/app/src/index.ts
|
|
1515
|
+
/**
|
|
1516
|
+
* WebAppPlugin — provides ctx.webApp: serves the built React client
|
|
1517
|
+
* (apps/web/dist, workspace knowledge — never user config) over the
|
|
1518
|
+
* dashboard server. Pattern follows DSH packages/bundle/web-app: the app
|
|
1519
|
+
* entry builds with vite, this runtime-glue plugin resolves the dist and
|
|
1520
|
+
* hands static-file serving to whoever owns the HTTP surface.
|
|
1521
|
+
*
|
|
1522
|
+
* @module @ygl/web-app
|
|
1523
|
+
*/
|
|
1524
|
+
/** Resolve the built client dist (workspace knowledge). Pure: safe to call
|
|
1525
|
+
* from short-lived CLI processes without betting on async plugin timing.
|
|
1526
|
+
*
|
|
1527
|
+
* Probe by REPO MARKER, not by counting levels from __dirname: under the
|
|
1528
|
+
* tsdown single-file build __dirname collapses to dist/bundle, so any
|
|
1529
|
+
* fixed-depth relative path misses. Walking up to the repo root (the dir
|
|
1530
|
+
* holding ygl.bundle.json) works from every layout — tsc tree, bundle,
|
|
1531
|
+
* and a run straight from src/. */
|
|
1532
|
+
function resolveDistRoot(from = __dirname) {
|
|
1533
|
+
const candidates = [];
|
|
1534
|
+
let dir = from;
|
|
1535
|
+
for (let i = 0; i < 6; i++, dir = node_path.dirname(dir)) {
|
|
1536
|
+
candidates.push(node_path.join(dir, "apps", "web", "dist"));
|
|
1537
|
+
if (node_fs.existsSync(node_path.join(dir, "ygl.bundle.json"))) break;
|
|
1538
|
+
}
|
|
1539
|
+
candidates.push(node_path.resolve(process.cwd(), "apps", "web", "dist"));
|
|
1540
|
+
for (const candidate of candidates) if (node_fs.existsSync(node_path.join(candidate, "index.html"))) return candidate;
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
const MIME = {
|
|
1544
|
+
".html": "text/html; charset=utf-8",
|
|
1545
|
+
".js": "text/javascript; charset=utf-8",
|
|
1546
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
1547
|
+
".css": "text/css; charset=utf-8",
|
|
1548
|
+
".json": "application/json",
|
|
1549
|
+
".svg": "image/svg+xml",
|
|
1550
|
+
".png": "image/png",
|
|
1551
|
+
".ico": "image/x-icon",
|
|
1552
|
+
".woff2": "font/woff2",
|
|
1553
|
+
".map": "application/json"
|
|
1554
|
+
};
|
|
1555
|
+
/** URL → dist 静态文件(SPA 回落 index.html;路径遍历安全)。纯函数。 */
|
|
1556
|
+
function openStatic(urlPath) {
|
|
1557
|
+
const root = resolveDistRoot();
|
|
1558
|
+
if (!root) return null;
|
|
1559
|
+
const clean = urlPath.split("?")[0].replace(/^\/+/, "");
|
|
1560
|
+
let file = node_path.resolve(root, clean === "" ? "index.html" : clean);
|
|
1561
|
+
if (!file.startsWith(root)) file = node_path.join(root, "index.html");
|
|
1562
|
+
if (!node_fs.existsSync(file) || node_fs.statSync(file).isDirectory()) {
|
|
1563
|
+
file = node_path.join(root, "index.html");
|
|
1564
|
+
if (!node_fs.existsSync(file)) return null;
|
|
1565
|
+
}
|
|
1566
|
+
const ext = node_path.extname(file).toLowerCase();
|
|
1310
1567
|
return {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
reason: `gray band [${min - 1}, ${min}) around expected level ${min}`
|
|
1568
|
+
body: node_fs.readFileSync(file),
|
|
1569
|
+
mime: MIME[ext] || "application/octet-stream"
|
|
1314
1570
|
};
|
|
1315
1571
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1572
|
+
var WebAppService = class extends _deepseek_ai_cordis.Service {
|
|
1573
|
+
constructor(ctx) {
|
|
1574
|
+
super(ctx, "webApp");
|
|
1575
|
+
}
|
|
1576
|
+
/** Whether a built client exists to serve. */
|
|
1577
|
+
available() {
|
|
1578
|
+
return resolveDistRoot() !== null;
|
|
1579
|
+
}
|
|
1580
|
+
/** Delegates to the pure resolver — short-lived CLI callers may use the
|
|
1581
|
+
* standalone openStatic() without waiting on plugin timing. */
|
|
1582
|
+
open(urlPath) {
|
|
1583
|
+
return openStatic(urlPath);
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
function apply$2(ctx) {
|
|
1587
|
+
new WebAppService(ctx);
|
|
1588
|
+
}
|
|
1589
|
+
//#endregion
|
|
1590
|
+
//#region packages/techtree/eval/src/eval.ts
|
|
1591
|
+
/**
|
|
1592
|
+
* Decision-model boolean evaluator — spec R8 (docs/spec.md).
|
|
1593
|
+
*
|
|
1594
|
+
* Speaks the Vercel AI Gateway v4 evaluation-model protocol (TypeSafe jev):
|
|
1595
|
+
* deterministic P(true) judgments, 1-2s/call, native CJK, collapses toward
|
|
1596
|
+
* the gray band instead of hard-answering on thin evidence. Material:
|
|
1597
|
+
* F:/Users/Administrator/Downloads/vercel-ai-gateway-eval-api.md.
|
|
1598
|
+
*
|
|
1599
|
+
* Offline-safe by design: callers map "no token / network failure" to a
|
|
1600
|
+
* PENDING gate outcome (never a hard fail) — the evaluator is an
|
|
1601
|
+
* enhancement, not a dependency.
|
|
1602
|
+
*/
|
|
1603
|
+
/** Normalize a configured api_base: a bare host gets the standard v4
|
|
1604
|
+
* evaluation-model path appended; a full endpoint is used as-is.
|
|
1605
|
+
* Config priority is composed by the CALLER (cmds layer):
|
|
1606
|
+
* config(decision_model_llm_api_base) > this default. */
|
|
1607
|
+
function resolveEndpoint(base) {
|
|
1608
|
+
const v = String(base || "").trim().replace(/\/+$/, "");
|
|
1609
|
+
if (!v) return GATEWAY_URL;
|
|
1610
|
+
if (/\/v4\/ai\/evaluation-model$/.test(v)) return v;
|
|
1611
|
+
return `${v}/v4/ai/evaluation-model`;
|
|
1612
|
+
}
|
|
1613
|
+
/** Token resolution: env first, then ~/.vercel.ai-gateway.env (token=...).
|
|
1614
|
+
* Set YGL_EVAL_OFFLINE=1 to force the offline path (tests/demos on
|
|
1615
|
+
* machines that DO have a token file — keeps them deterministic). */
|
|
1616
|
+
function evalToken() {
|
|
1617
|
+
if (process.env.YGL_EVAL_OFFLINE) return "";
|
|
1618
|
+
if (process.env.AI_GATEWAY_API_KEY) return process.env.AI_GATEWAY_API_KEY;
|
|
1619
|
+
try {
|
|
1620
|
+
const m = /^token\s*=\s*(\S+)/m.exec(node_fs.readFileSync(node_path.join(node_os.homedir(), ".vercel.ai-gateway.env"), "utf8"));
|
|
1621
|
+
if (m) return m[1];
|
|
1622
|
+
} catch {}
|
|
1623
|
+
return "";
|
|
1624
|
+
}
|
|
1625
|
+
/** One boolean question about a state. Retries 429/529 with exponential
|
|
1626
|
+
* backoff (<=3); 400/401 and malformed answers return without retry;
|
|
1627
|
+
* 60s timeout per attempt. `endpoint` defaults to the Vercel gateway —
|
|
1628
|
+
* callers pass the config-resolved value (decision_model_llm_api_base
|
|
1629
|
+
* has priority over the default). */
|
|
1630
|
+
async function evalBoolean(state, question, token, endpoint = GATEWAY_URL) {
|
|
1321
1631
|
if (!token) return {
|
|
1322
1632
|
ok: false,
|
|
1323
1633
|
error: NO_TOKEN
|
|
1324
1634
|
};
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
instructions: q.question
|
|
1330
|
-
};
|
|
1331
|
-
else if (q.type === "choice") {
|
|
1332
|
-
const criteria = {};
|
|
1333
|
-
for (const [k, v] of Object.entries(q.options)) criteria[k] = v || null;
|
|
1334
|
-
qmap[`q${i}`] = {
|
|
1335
|
-
type: "choice",
|
|
1336
|
-
instructions: q.question,
|
|
1337
|
-
criteria
|
|
1338
|
-
};
|
|
1339
|
-
} else qmap[`q${i}`] = {
|
|
1340
|
-
type: "score",
|
|
1341
|
-
instructions: q.question,
|
|
1342
|
-
criteria: q.levels
|
|
1343
|
-
};
|
|
1344
|
-
});
|
|
1345
|
-
const r = await postEval(state, qmap, token, endpoint);
|
|
1635
|
+
const r = await postEval(state, { q: {
|
|
1636
|
+
type: "boolean",
|
|
1637
|
+
instructions: question
|
|
1638
|
+
} }, token, endpoint);
|
|
1346
1639
|
if (!r.ok) return {
|
|
1347
1640
|
ok: false,
|
|
1348
1641
|
error: r.error
|
|
1349
1642
|
};
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
if (typeof a.probability !== "number") return {
|
|
1356
|
-
ok: false,
|
|
1357
|
-
error: `malformed answer q${i}: no boolean probability`
|
|
1358
|
-
};
|
|
1359
|
-
results.push({
|
|
1360
|
-
ok: true,
|
|
1361
|
-
type: "boolean",
|
|
1362
|
-
p: a.probability
|
|
1363
|
-
});
|
|
1364
|
-
} else if (t === "choice") {
|
|
1365
|
-
if (typeof a.choice !== "string" || !a.probabilities) return {
|
|
1366
|
-
ok: false,
|
|
1367
|
-
error: `malformed answer q${i}: no choice/probabilities`
|
|
1368
|
-
};
|
|
1369
|
-
results.push({
|
|
1370
|
-
ok: true,
|
|
1371
|
-
type: "choice",
|
|
1372
|
-
choice: a.choice,
|
|
1373
|
-
probabilities: a.probabilities
|
|
1374
|
-
});
|
|
1375
|
-
} else {
|
|
1376
|
-
if (typeof a.score !== "number" || !a.probabilities) return {
|
|
1377
|
-
ok: false,
|
|
1378
|
-
error: `malformed answer q${i}: no score/probabilities`
|
|
1379
|
-
};
|
|
1380
|
-
results.push({
|
|
1381
|
-
ok: true,
|
|
1382
|
-
type: "score",
|
|
1383
|
-
score: a.score,
|
|
1384
|
-
probabilities: a.probabilities
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1643
|
+
const p = r.answers.q?.probability;
|
|
1644
|
+
if (typeof p !== "number") return {
|
|
1645
|
+
ok: false,
|
|
1646
|
+
error: "malformed answer: no boolean probability"
|
|
1647
|
+
};
|
|
1388
1648
|
return {
|
|
1389
1649
|
ok: true,
|
|
1390
|
-
|
|
1650
|
+
p,
|
|
1391
1651
|
usage: r.usage
|
|
1392
1652
|
};
|
|
1393
1653
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1654
|
+
function parseEvalSpec(value) {
|
|
1655
|
+
const s = String(value ?? "").trim();
|
|
1656
|
+
if (s.startsWith("{")) try {
|
|
1657
|
+
const j = JSON.parse(s);
|
|
1658
|
+
if (j && typeof j.q === "string") return j;
|
|
1659
|
+
} catch {}
|
|
1660
|
+
return { q: s };
|
|
1661
|
+
}
|
|
1662
|
+
function packEvalChoiceSpec(q, options, answer) {
|
|
1663
|
+
return JSON.stringify({
|
|
1664
|
+
q,
|
|
1665
|
+
options,
|
|
1666
|
+
answer
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
function packEvalScoreSpec(q, levels, min) {
|
|
1670
|
+
return JSON.stringify({
|
|
1671
|
+
q,
|
|
1672
|
+
levels,
|
|
1673
|
+
min
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
async function postEval(state, questions, token, endpoint) {
|
|
1677
|
+
const url = resolveEndpoint(endpoint);
|
|
1678
|
+
const body = JSON.stringify({
|
|
1679
|
+
state,
|
|
1680
|
+
questions
|
|
1681
|
+
});
|
|
1682
|
+
for (let attempt = 0;; attempt++) try {
|
|
1683
|
+
const res = await fetch(url, {
|
|
1684
|
+
method: "POST",
|
|
1685
|
+
headers: {
|
|
1686
|
+
Authorization: `Bearer ${token}`,
|
|
1687
|
+
"Content-Type": "application/json",
|
|
1688
|
+
"ai-model-id": MODEL_ID,
|
|
1689
|
+
"ai-evaluation-model-specification-version": "4",
|
|
1690
|
+
"ai-gateway-protocol-version": "0.0.1",
|
|
1691
|
+
"ai-gateway-auth-method": "api-key"
|
|
1692
|
+
},
|
|
1693
|
+
body,
|
|
1694
|
+
signal: AbortSignal.timeout(6e4)
|
|
1695
|
+
});
|
|
1696
|
+
if (res.status === 429 || res.status === 529) {
|
|
1697
|
+
if (attempt >= 3) return {
|
|
1698
|
+
ok: false,
|
|
1699
|
+
error: `HTTP ${res.status} after ${attempt + 1} attempts`
|
|
1700
|
+
};
|
|
1701
|
+
await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
if (!res.ok) return {
|
|
1705
|
+
ok: false,
|
|
1706
|
+
error: `HTTP ${res.status} (protocol/auth error — no retry)`
|
|
1707
|
+
};
|
|
1708
|
+
const j = await res.json();
|
|
1709
|
+
if (!j?.answers) return {
|
|
1710
|
+
ok: false,
|
|
1711
|
+
error: "malformed response: no answers"
|
|
1712
|
+
};
|
|
1713
|
+
return {
|
|
1714
|
+
ok: true,
|
|
1715
|
+
answers: j.answers,
|
|
1716
|
+
usage: j.usage ?? void 0
|
|
1717
|
+
};
|
|
1718
|
+
} catch (e) {
|
|
1719
|
+
const msg = String(e?.message || e);
|
|
1720
|
+
if (attempt < 3 && /timeout|abort|network|fetch failed|ECONN/i.test(msg)) {
|
|
1721
|
+
await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
return {
|
|
1725
|
+
ok: false,
|
|
1726
|
+
error: msg
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
async function evalChoice(state, question, options, token, endpoint = GATEWAY_URL) {
|
|
1731
|
+
if (!token) return {
|
|
1732
|
+
ok: false,
|
|
1733
|
+
error: NO_TOKEN
|
|
1734
|
+
};
|
|
1735
|
+
const criteria = {};
|
|
1736
|
+
for (const [k, v] of Object.entries(options)) criteria[k] = v || null;
|
|
1737
|
+
const r = await postEval(state, { q: {
|
|
1738
|
+
type: "choice",
|
|
1739
|
+
instructions: question,
|
|
1740
|
+
criteria
|
|
1741
|
+
} }, token, endpoint);
|
|
1742
|
+
if (!r.ok) return {
|
|
1743
|
+
ok: false,
|
|
1744
|
+
error: r.error
|
|
1745
|
+
};
|
|
1746
|
+
const a = r.answers.q ?? {};
|
|
1747
|
+
if (typeof a.choice !== "string" || !a.probabilities) return {
|
|
1748
|
+
ok: false,
|
|
1749
|
+
error: "malformed answer: no choice/probabilities"
|
|
1750
|
+
};
|
|
1751
|
+
return {
|
|
1752
|
+
ok: true,
|
|
1753
|
+
choice: a.choice,
|
|
1754
|
+
probabilities: a.probabilities,
|
|
1755
|
+
usage: r.usage
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
/** Tri-state on the EXPECTED answer's probability — same thresholds and
|
|
1759
|
+
* gray-band philosophy as boolean P(true). */
|
|
1760
|
+
function mapChoiceOutcome(r, answer, high = .7, low = .4) {
|
|
1761
|
+
if (!r.ok || !r.probabilities) return {
|
|
1398
1762
|
outcome: "pending",
|
|
1399
|
-
reason: r.error || "no
|
|
1763
|
+
reason: r.error || "no probabilities"
|
|
1400
1764
|
};
|
|
1401
|
-
|
|
1765
|
+
const p = r.probabilities[answer];
|
|
1766
|
+
if (typeof p !== "number") return {
|
|
1767
|
+
outcome: "pending",
|
|
1768
|
+
reason: `expected answer '${answer}' not in distribution`
|
|
1769
|
+
};
|
|
1770
|
+
const base = {
|
|
1771
|
+
p,
|
|
1772
|
+
choice: r.choice
|
|
1773
|
+
};
|
|
1774
|
+
if (p >= high) return {
|
|
1402
1775
|
outcome: "pass",
|
|
1403
|
-
|
|
1776
|
+
...base
|
|
1404
1777
|
};
|
|
1405
|
-
if (
|
|
1778
|
+
if (p <= low) return {
|
|
1406
1779
|
outcome: "fail",
|
|
1407
|
-
|
|
1780
|
+
...base
|
|
1408
1781
|
};
|
|
1409
1782
|
return {
|
|
1410
1783
|
outcome: "pending",
|
|
1411
|
-
|
|
1784
|
+
...base,
|
|
1412
1785
|
reason: `gray band ${low}<P<${high} (process state)`
|
|
1413
1786
|
};
|
|
1414
1787
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
]
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1788
|
+
async function evalScore(state, question, levels, token, endpoint = GATEWAY_URL) {
|
|
1789
|
+
if (!token) return {
|
|
1790
|
+
ok: false,
|
|
1791
|
+
error: NO_TOKEN
|
|
1792
|
+
};
|
|
1793
|
+
const r = await postEval(state, { q: {
|
|
1794
|
+
type: "score",
|
|
1795
|
+
instructions: question,
|
|
1796
|
+
criteria: levels
|
|
1797
|
+
} }, token, endpoint);
|
|
1798
|
+
if (!r.ok) return {
|
|
1799
|
+
ok: false,
|
|
1800
|
+
error: r.error
|
|
1801
|
+
};
|
|
1802
|
+
const a = r.answers.q ?? {};
|
|
1803
|
+
if (typeof a.score !== "number" || !a.probabilities) return {
|
|
1804
|
+
ok: false,
|
|
1805
|
+
error: "malformed answer: no score/probabilities"
|
|
1806
|
+
};
|
|
1807
|
+
return {
|
|
1808
|
+
ok: true,
|
|
1809
|
+
score: a.score,
|
|
1810
|
+
probabilities: a.probabilities,
|
|
1811
|
+
usage: r.usage
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
/** Tri-state on the ordinal scale: score >= min → pass; score <= min-1 →
|
|
1815
|
+
* fail; the ±1 band around the threshold is the gray zone (the score
|
|
1816
|
+
* counterpart of boolean's (0.4, 0.7) process state). */
|
|
1817
|
+
function mapScoreOutcome(r, min) {
|
|
1818
|
+
if (!r.ok || r.score === void 0) return {
|
|
1819
|
+
outcome: "pending",
|
|
1820
|
+
reason: r.error || "no score"
|
|
1821
|
+
};
|
|
1822
|
+
const s = r.score;
|
|
1823
|
+
if (s >= min) return {
|
|
1824
|
+
outcome: "pass",
|
|
1825
|
+
score: s
|
|
1826
|
+
};
|
|
1827
|
+
if (s <= min - 1) return {
|
|
1828
|
+
outcome: "fail",
|
|
1829
|
+
score: s
|
|
1830
|
+
};
|
|
1831
|
+
return {
|
|
1832
|
+
outcome: "pending",
|
|
1833
|
+
score: s,
|
|
1834
|
+
reason: `gray band [${min - 1}, ${min}) around expected level ${min}`
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
async function evalMixed(state, questions, token, endpoint = GATEWAY_URL) {
|
|
1838
|
+
if (!questions.length) return {
|
|
1839
|
+
ok: true,
|
|
1840
|
+
results: []
|
|
1841
|
+
};
|
|
1842
|
+
if (!token) return {
|
|
1843
|
+
ok: false,
|
|
1844
|
+
error: NO_TOKEN
|
|
1845
|
+
};
|
|
1846
|
+
const qmap = {};
|
|
1847
|
+
questions.forEach((q, i) => {
|
|
1848
|
+
if (q.type === "boolean") qmap[`q${i}`] = {
|
|
1849
|
+
type: "boolean",
|
|
1850
|
+
instructions: q.question
|
|
1471
1851
|
};
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1852
|
+
else if (q.type === "choice") {
|
|
1853
|
+
const criteria = {};
|
|
1854
|
+
for (const [k, v] of Object.entries(q.options)) criteria[k] = v || null;
|
|
1855
|
+
qmap[`q${i}`] = {
|
|
1856
|
+
type: "choice",
|
|
1857
|
+
instructions: q.question,
|
|
1858
|
+
criteria
|
|
1859
|
+
};
|
|
1860
|
+
} else qmap[`q${i}`] = {
|
|
1861
|
+
type: "score",
|
|
1862
|
+
instructions: q.question,
|
|
1863
|
+
criteria: q.levels
|
|
1864
|
+
};
|
|
1865
|
+
});
|
|
1866
|
+
const r = await postEval(state, qmap, token, endpoint);
|
|
1867
|
+
if (!r.ok) return {
|
|
1868
|
+
ok: false,
|
|
1869
|
+
error: r.error
|
|
1870
|
+
};
|
|
1871
|
+
const results = [];
|
|
1872
|
+
for (let i = 0; i < questions.length; i++) {
|
|
1873
|
+
const a = r.answers[`q${i}`] ?? {};
|
|
1874
|
+
const t = questions[i].type;
|
|
1875
|
+
if (t === "boolean") {
|
|
1876
|
+
if (typeof a.probability !== "number") return {
|
|
1877
|
+
ok: false,
|
|
1878
|
+
error: `malformed answer q${i}: no boolean probability`
|
|
1879
|
+
};
|
|
1880
|
+
results.push({
|
|
1881
|
+
ok: true,
|
|
1882
|
+
type: "boolean",
|
|
1883
|
+
p: a.probability
|
|
1884
|
+
});
|
|
1885
|
+
} else if (t === "choice") {
|
|
1886
|
+
if (typeof a.choice !== "string" || !a.probabilities) return {
|
|
1887
|
+
ok: false,
|
|
1888
|
+
error: `malformed answer q${i}: no choice/probabilities`
|
|
1889
|
+
};
|
|
1890
|
+
results.push({
|
|
1891
|
+
ok: true,
|
|
1892
|
+
type: "choice",
|
|
1893
|
+
choice: a.choice,
|
|
1894
|
+
probabilities: a.probabilities
|
|
1895
|
+
});
|
|
1896
|
+
} else {
|
|
1897
|
+
if (typeof a.score !== "number" || !a.probabilities) return {
|
|
1898
|
+
ok: false,
|
|
1899
|
+
error: `malformed answer q${i}: no score/probabilities`
|
|
1900
|
+
};
|
|
1901
|
+
results.push({
|
|
1902
|
+
ok: true,
|
|
1903
|
+
type: "score",
|
|
1904
|
+
score: a.score,
|
|
1905
|
+
probabilities: a.probabilities
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
return {
|
|
1910
|
+
ok: true,
|
|
1911
|
+
results,
|
|
1912
|
+
usage: r.usage
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
/** R8.4 tri-state mapping. Thresholds come from config (eval.pass_threshold
|
|
1916
|
+
* / eval.fail_threshold) so callers pass the resolved values. */
|
|
1917
|
+
function mapOutcome(r, high = .7, low = .4) {
|
|
1918
|
+
if (!r.ok || r.p === void 0) return {
|
|
1919
|
+
outcome: "pending",
|
|
1920
|
+
reason: r.error || "no probability"
|
|
1921
|
+
};
|
|
1922
|
+
if (r.p >= high) return {
|
|
1923
|
+
outcome: "pass",
|
|
1924
|
+
p: r.p
|
|
1925
|
+
};
|
|
1926
|
+
if (r.p <= low) return {
|
|
1927
|
+
outcome: "fail",
|
|
1928
|
+
p: r.p
|
|
1929
|
+
};
|
|
1930
|
+
return {
|
|
1931
|
+
outcome: "pending",
|
|
1932
|
+
p: r.p,
|
|
1933
|
+
reason: `gray band ${low}<P<${high} (process state)`
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
var GATEWAY_URL, MODEL_ID, NO_TOKEN;
|
|
1937
|
+
var init_eval = require_rolldown_runtime.__esmMin((() => {
|
|
1938
|
+
GATEWAY_URL = "https://ai-gateway.vercel.sh/v4/ai/evaluation-model";
|
|
1939
|
+
MODEL_ID = "typesafe-ai/jev";
|
|
1940
|
+
NO_TOKEN = "no evaluator configured (set config decision_model_llm_token, or AI_GATEWAY_API_KEY / ~/.vercel.ai-gateway.env)";
|
|
1941
|
+
}));
|
|
1942
|
+
//#endregion
|
|
1943
|
+
//#region packages/techtree/model/src/reads.ts
|
|
1944
|
+
init_db();
|
|
1945
|
+
init_text();
|
|
1946
|
+
init_eval();
|
|
1947
|
+
var TreeStore = class {
|
|
1948
|
+
db;
|
|
1949
|
+
constructor(db) {
|
|
1950
|
+
this.db = db;
|
|
1951
|
+
}
|
|
1952
|
+
treeByName(name) {
|
|
1953
|
+
return this.db.get("SELECT * FROM trees WHERE name=? OR id=?", [name, name]);
|
|
1954
|
+
}
|
|
1955
|
+
treeResolve(name) {
|
|
1956
|
+
const key = name || this.db.metaGet("current_tree");
|
|
1957
|
+
if (!key) throw new Error("no tree selected; run: ygl-trees tree new <name>");
|
|
1958
|
+
const t = this.treeByName(key);
|
|
1959
|
+
if (!t) throw new Error(`tree not found: ${key}`);
|
|
1960
|
+
return t;
|
|
1961
|
+
}
|
|
1962
|
+
treeList() {
|
|
1963
|
+
return this.db.all(`SELECT t.name, t.note,
|
|
1964
|
+
(SELECT COUNT(*) FROM nodes n WHERE n.tree_id=t.id) AS nodes,
|
|
1965
|
+
(SELECT COUNT(*) FROM node_edges e WHERE e.tree_id=t.id) AS edges,
|
|
1966
|
+
(SELECT dirty FROM node_index i WHERE i.tree_id=t.id) AS dirty,
|
|
1967
|
+
t.created
|
|
1968
|
+
FROM trees t ORDER BY t.created DESC`);
|
|
1969
|
+
}
|
|
1970
|
+
nodeGet(treeId, keyOrId) {
|
|
1971
|
+
return this.db.get("SELECT * FROM nodes WHERE tree_id=? AND (key=? OR id=?)", [
|
|
1972
|
+
treeId,
|
|
1973
|
+
keyOrId,
|
|
1974
|
+
keyOrId
|
|
1975
|
+
]);
|
|
1976
|
+
}
|
|
1977
|
+
/** Runtime state for one node (default context). Separate from nodeGet on
|
|
1978
|
+
* purpose: nodeGet feeds WRITE paths, and a context join there could return
|
|
1979
|
+
* one row per context — silently wrong for link/unlink/rm. */
|
|
1980
|
+
nodeStateOf(treeId, keyOrId, context = "default") {
|
|
1981
|
+
const r = this.db.get(`SELECT st.status AS state, st.updated AS unlocked_at
|
|
1982
|
+
FROM nodes n LEFT JOIN node_state st ON st.node_id=n.id AND st.context=?
|
|
1983
|
+
WHERE n.tree_id=? AND (n.key=? OR n.id=?)`, [
|
|
1984
|
+
context,
|
|
1985
|
+
treeId,
|
|
1986
|
+
keyOrId,
|
|
1987
|
+
keyOrId
|
|
1988
|
+
]);
|
|
1989
|
+
return {
|
|
1990
|
+
state: r?.state || "locked",
|
|
1991
|
+
unlocked_at: r?.unlocked_at || null
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
nodeList(treeId, context = "default") {
|
|
1995
|
+
return this.db.all(`SELECT n.key, n.name, n.kind, n.tier, n.cost, n.tags,
|
|
1475
1996
|
(SELECT COUNT(*) FROM node_edges e WHERE e.child_id=n.id AND e.required=1) AS req_parents,
|
|
1476
1997
|
(SELECT COUNT(*) FROM node_edges e WHERE e.parent_id=n.id) AS children,
|
|
1477
1998
|
COALESCE(st.status,'locked') AS state,
|
|
@@ -3277,700 +3798,234 @@ CREATE TABLE claim_evidence (
|
|
|
3277
3798
|
stance text NOT NULL DEFAULT 'supports', PRIMARY KEY (claim_id, evidence_id));
|
|
3278
3799
|
CREATE TABLE artifacts (
|
|
3279
3800
|
id text PRIMARY KEY, project_id text NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
3280
|
-
kind text NOT NULL DEFAULT 'section', title text NOT NULL DEFAULT '',
|
|
3281
|
-
format text NOT NULL DEFAULT 'markdown', content text NOT NULL DEFAULT '',
|
|
3282
|
-
version integer NOT NULL DEFAULT 1, created text NOT NULL,
|
|
3283
|
-
meta text NOT NULL DEFAULT '');
|
|
3284
|
-
CREATE TABLE kb_notes (
|
|
3285
|
-
id text PRIMARY KEY, title text NOT NULL, body text NOT NULL DEFAULT '',
|
|
3286
|
-
source_url text DEFAULT '', origin text DEFAULT '', tags text DEFAULT '',
|
|
3287
|
-
confidence text NOT NULL DEFAULT 'B', reuse integer NOT NULL DEFAULT 0,
|
|
3288
|
-
content_hash text NOT NULL DEFAULT '', created text NOT NULL, updated text NOT NULL,
|
|
3289
|
-
UNIQUE (content_hash));
|
|
3290
|
-
CREATE TABLE kb_links (
|
|
3291
|
-
from_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
|
|
3292
|
-
to_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
|
|
3293
|
-
rel text NOT NULL DEFAULT 'related', PRIMARY KEY (from_id, to_id, rel));
|
|
3294
|
-
CREATE TABLE trees (
|
|
3295
|
-
id text PRIMARY KEY, project_id text REFERENCES projects(id) ON DELETE CASCADE,
|
|
3296
|
-
name text NOT NULL UNIQUE, note text DEFAULT '', created text NOT NULL);
|
|
3297
|
-
CREATE TABLE nodes (
|
|
3298
|
-
id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
|
|
3299
|
-
key text NOT NULL, name text NOT NULL, kind text NOT NULL DEFAULT 'tech',
|
|
3300
|
-
tier integer NOT NULL DEFAULT 0, cost double precision NOT NULL DEFAULT 0,
|
|
3301
|
-
cost_unit text DEFAULT '', payload text DEFAULT '', tags text DEFAULT '',
|
|
3302
|
-
created text NOT NULL, UNIQUE (tree_id, key));
|
|
3303
|
-
CREATE TABLE node_edges (
|
|
3304
|
-
id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
|
|
3305
|
-
parent_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3306
|
-
child_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3307
|
-
rel text NOT NULL DEFAULT 'prereq', mode text NOT NULL DEFAULT 'AND',
|
|
3308
|
-
required integer NOT NULL DEFAULT 1, min_level integer NOT NULL DEFAULT 1,
|
|
3309
|
-
weight double precision NOT NULL DEFAULT 1, note text DEFAULT '',
|
|
3310
|
-
UNIQUE (parent_id, child_id, rel));
|
|
3311
|
-
CREATE TABLE node_closure (
|
|
3312
|
-
tree_id text NOT NULL, ancestor text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3313
|
-
descendant text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3314
|
-
depth integer NOT NULL, PRIMARY KEY (ancestor, descendant));
|
|
3315
|
-
CREATE TABLE node_conditions (
|
|
3316
|
-
id text PRIMARY KEY, node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3317
|
-
name text NOT NULL, kind text NOT NULL DEFAULT 'flag', expr text NOT NULL DEFAULT '',
|
|
3318
|
-
op text NOT NULL DEFAULT '>=', value text NOT NULL DEFAULT '',
|
|
3319
|
-
required integer NOT NULL DEFAULT 1, effect text NOT NULL DEFAULT 'gate',
|
|
3320
|
-
weight double precision NOT NULL DEFAULT 1, note text DEFAULT '');
|
|
3321
|
-
CREATE TABLE node_state (
|
|
3322
|
-
tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
|
|
3323
|
-
context text NOT NULL DEFAULT 'default', node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3324
|
-
status text NOT NULL DEFAULT 'locked', level integer NOT NULL DEFAULT 0,
|
|
3325
|
-
progress double precision NOT NULL DEFAULT 0, updated text NOT NULL,
|
|
3326
|
-
PRIMARY KEY (tree_id, context, node_id));
|
|
3327
|
-
CREATE TABLE node_index (
|
|
3328
|
-
tree_id text PRIMARY KEY REFERENCES trees(id) ON DELETE CASCADE,
|
|
3329
|
-
topo_order text NOT NULL DEFAULT '', tiers text NOT NULL DEFAULT '',
|
|
3330
|
-
in_degree text NOT NULL DEFAULT '', built_at text NOT NULL, dirty integer NOT NULL DEFAULT 1);
|
|
3331
|
-
CREATE TABLE relations (
|
|
3332
|
-
id text PRIMARY KEY, src_type text NOT NULL, src_id text NOT NULL,
|
|
3333
|
-
dst_type text NOT NULL, dst_id text NOT NULL, rel text NOT NULL DEFAULT 'related',
|
|
3334
|
-
weight double precision NOT NULL DEFAULT 1,
|
|
3335
|
-
UNIQUE (src_type, src_id, dst_type, dst_id, rel));
|
|
3336
|
-
CREATE TABLE jobs (
|
|
3337
|
-
id text PRIMARY KEY, title text NOT NULL, command text NOT NULL,
|
|
3338
|
-
every_minutes integer DEFAULT 0, at text DEFAULT '', enabled integer NOT NULL DEFAULT 1,
|
|
3339
|
-
runs integer NOT NULL DEFAULT 0, last_run text DEFAULT '',
|
|
3340
|
-
status text NOT NULL DEFAULT 'pending', created text NOT NULL);
|
|
3341
|
-
CREATE TABLE runs (
|
|
3342
|
-
id text PRIMARY KEY, job_id text NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
3343
|
-
started text NOT NULL, finished text DEFAULT '', rc integer, output text DEFAULT '');
|
|
3344
|
-
CREATE TABLE events (
|
|
3345
|
-
id text PRIMARY KEY, ts text NOT NULL, actor text NOT NULL DEFAULT 'cli', kind text NOT NULL,
|
|
3346
|
-
entity text DEFAULT '', entity_id text DEFAULT '', payload text DEFAULT '',
|
|
3347
|
-
level text NOT NULL DEFAULT 'info');
|
|
3348
|
-
`;
|
|
3349
|
-
const MIGRATE_INDEXES = `
|
|
3350
|
-
CREATE INDEX IF NOT EXISTS idx_sources_project ON sources(project_id);
|
|
3351
|
-
CREATE INDEX IF NOT EXISTS idx_evidence_project ON evidence(project_id);
|
|
3352
|
-
CREATE INDEX IF NOT EXISTS idx_claims_project ON claims(project_id);
|
|
3353
|
-
CREATE INDEX IF NOT EXISTS idx_artifacts_proj ON artifacts(project_id);
|
|
3354
|
-
CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
|
|
3355
|
-
CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON jobs(enabled);
|
|
3356
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_tree ON nodes(tree_id);
|
|
3357
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_tier ON nodes(tree_id, tier);
|
|
3358
|
-
CREATE INDEX IF NOT EXISTS idx_edge_child ON node_edges(child_id);
|
|
3359
|
-
CREATE INDEX IF NOT EXISTS idx_edge_parent ON node_edges(parent_id);
|
|
3360
|
-
CREATE INDEX IF NOT EXISTS idx_edge_req ON node_edges(child_id, required);
|
|
3361
|
-
CREATE INDEX IF NOT EXISTS idx_closure_desc ON node_closure(descendant);
|
|
3362
|
-
CREATE INDEX IF NOT EXISTS idx_closure_anc ON node_closure(ancestor);
|
|
3363
|
-
CREATE INDEX IF NOT EXISTS idx_cond_node ON node_conditions(node_id);
|
|
3364
|
-
CREATE INDEX IF NOT EXISTS idx_cond_req ON node_conditions(node_id, required);
|
|
3365
|
-
CREATE INDEX IF NOT EXISTS idx_state_ctx ON node_state(tree_id, context);
|
|
3366
|
-
CREATE INDEX IF NOT EXISTS idx_state_status ON node_state(tree_id, context, status);
|
|
3367
|
-
`;
|
|
3368
|
-
/**
|
|
3369
|
-
* One-time migration of the whole library into a standalone PG database.
|
|
3370
|
-
* Atomic (single transaction: DDL + data + verification), refuses a non-
|
|
3371
|
-
* empty target unless `fresh` drops the existing tables first. After
|
|
3372
|
-
* migration the CLI keeps working on SQLite only — no dual writes.
|
|
3373
|
-
*/
|
|
3374
|
-
async function migrateToPg(db, url, fresh = false) {
|
|
3375
|
-
let pg;
|
|
3376
|
-
try {
|
|
3377
|
-
pg = require("pg");
|
|
3378
|
-
} catch {
|
|
3379
|
-
throw new Error("pg driver not installed — run: pnpm add pg (optional dependency)");
|
|
3380
|
-
}
|
|
3381
|
-
const typeErrors = preflightTypeCheck(db);
|
|
3382
|
-
if (typeErrors.length) {
|
|
3383
|
-
const lines = typeErrors.slice(0, 5).map((e) => ` ${e.table}.${e.column} rowid=${e.rowid}: value "${e.value}" is not a valid INTEGER`);
|
|
3384
|
-
throw new Error(`pre-flight type check failed (${typeErrors.length} issue(s)) — fix these before migrating:\n${lines.join("\n")}${typeErrors.length > 5 ? `\n ...and ${typeErrors.length - 5} more` : ""}\n hint: ygl-trees db sql "UPDATE <table> SET <column>=<int> WHERE rowid=<rowid>"`);
|
|
3385
|
-
}
|
|
3386
|
-
const client = new pg.Client({ connectionString: url });
|
|
3387
|
-
await client.connect();
|
|
3388
|
-
try {
|
|
3389
|
-
const verNum = Number((await client.query("SHOW server_version_num")).rows[0].server_version_num);
|
|
3390
|
-
const ver = (await client.query("SELECT version()")).rows[0].version;
|
|
3391
|
-
const fulltext = verNum >= 12e4 ? "tsvector+GIN" : "ngram-only(pg<12)";
|
|
3392
|
-
if (!fresh) {
|
|
3393
|
-
for (const t of MIRROR_TABLES) if ((await client.query(`SELECT 1 AS hit FROM information_schema.tables WHERE table_name=$1`, [t])).rowCount) {
|
|
3394
|
-
const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
|
|
3395
|
-
if (n > 0) throw new Error(`target already contains data (table ${t}, ${n} rows) — migration is one-time; use --fresh to drop and recreate, or 'db sync pg' for a refreshable mirror`);
|
|
3396
|
-
}
|
|
3397
|
-
} else for (const t of [...MIRROR_TABLES].reverse()) await client.query(`DROP TABLE IF EXISTS "${t}" CASCADE`);
|
|
3398
|
-
await client.query("BEGIN");
|
|
3399
|
-
try {
|
|
3400
|
-
await client.query(MIGRATE_DDL);
|
|
3401
|
-
await client.query(MIGRATE_INDEXES);
|
|
3402
|
-
for (const t of Object.keys(NGRAM_INPUT)) {
|
|
3403
|
-
await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram text NOT NULL DEFAULT ''`);
|
|
3404
|
-
if (verNum >= 12e4) {
|
|
3405
|
-
await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram_tsv tsvector
|
|
3406
|
-
GENERATED ALWAYS AS (to_tsvector('simple', regexp_replace(coalesce(ngram,''), '[/\\._:,&+()]+', ' ', 'g'))) STORED`);
|
|
3407
|
-
await client.query(`CREATE INDEX IF NOT EXISTS "${t}_tsv" ON "${t}" USING GIN (ngram_tsv)`);
|
|
3408
|
-
}
|
|
3409
|
-
}
|
|
3410
|
-
for (const t of MIRROR_TABLES) {
|
|
3411
|
-
const src = db.all(`PRAGMA table_info(${t})`).map((c) => c.name).sort();
|
|
3412
|
-
const dst = (await client.query(`SELECT column_name FROM information_schema.columns WHERE table_name=$1`, [t])).rows.map((r) => r.column_name);
|
|
3413
|
-
const want = new Set(src);
|
|
3414
|
-
if (Object.keys(NGRAM_INPUT).includes(t)) {
|
|
3415
|
-
want.add("ngram");
|
|
3416
|
-
if (verNum >= 12e4) want.add("ngram_tsv");
|
|
3417
|
-
}
|
|
3418
|
-
const got = new Set(dst);
|
|
3419
|
-
if ([...want].filter((c) => !got.has(c)).length || got.size !== want.size) throw new Error(`schema drift on ${t}: migrate DDL out of date (want [${[...want]}]; got [${dst}]) — update MIGRATE_DDL`);
|
|
3420
|
-
}
|
|
3421
|
-
const result = [];
|
|
3422
|
-
for (const t of MIRROR_TABLES) {
|
|
3423
|
-
const rows = await copyTable(client, db, t);
|
|
3424
|
-
const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
|
|
3425
|
-
if (n !== rows) throw new Error(`count mismatch on ${t}: copied ${rows}, found ${n}`);
|
|
3426
|
-
result.push({
|
|
3427
|
-
table: t,
|
|
3428
|
-
rows
|
|
3429
|
-
});
|
|
3430
|
-
}
|
|
3431
|
-
await client.query("COMMIT");
|
|
3432
|
-
return {
|
|
3433
|
-
tables: result,
|
|
3434
|
-
server_version: ver,
|
|
3435
|
-
constraints: {
|
|
3436
|
-
fk: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='FOREIGN KEY'`)).rows[0].n,
|
|
3437
|
-
unique: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='UNIQUE'`)).rows[0].n
|
|
3438
|
-
},
|
|
3439
|
-
fulltext
|
|
3440
|
-
};
|
|
3441
|
-
} catch (e) {
|
|
3442
|
-
await client.query("ROLLBACK");
|
|
3443
|
-
throw e;
|
|
3444
|
-
}
|
|
3445
|
-
} finally {
|
|
3446
|
-
await client.end();
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
/** Pre-flight: scan INTEGER columns for non-integer values (SQLite weak
|
|
3450
|
-
* typing allows REAL into INTEGER; PG rejects at migration time with no
|
|
3451
|
-
* context). Returns actionable errors with table.column+rowid. */
|
|
3452
|
-
function preflightTypeCheck(db) {
|
|
3453
|
-
const issues = [];
|
|
3454
|
-
const tables = db.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '%_fts%' AND name NOT LIKE '%_config%' AND name NOT LIKE '%_content%' AND name NOT LIKE '%_data%' AND name NOT LIKE '%_docsize%' AND name NOT LIKE '%_idx%' AND name != 'meta'");
|
|
3455
|
-
for (const t of tables) {
|
|
3456
|
-
const cols = db.all(`PRAGMA table_info(${t.name})`);
|
|
3457
|
-
for (const c of cols) {
|
|
3458
|
-
if (c.type !== "INTEGER") continue;
|
|
3459
|
-
const bad = db.all(`SELECT rowid, ${c.name} AS v FROM ${t.name} WHERE typeof(${c.name}) NOT IN ('integer','null') LIMIT 5`);
|
|
3460
|
-
for (const row of bad) issues.push({
|
|
3461
|
-
table: t.name,
|
|
3462
|
-
column: c.name,
|
|
3463
|
-
rowid: row.rowid,
|
|
3464
|
-
value: String(row.v)
|
|
3465
|
-
});
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
return issues;
|
|
3469
|
-
}
|
|
3470
|
-
//#endregion
|
|
3471
|
-
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorateMetadata.js
|
|
3472
|
-
function __decorateMetadata(k, v) {
|
|
3473
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
3474
|
-
}
|
|
3475
|
-
var init_decorateMetadata = require_rolldown_runtime.__esmMin((() => {}));
|
|
3476
|
-
//#endregion
|
|
3477
|
-
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorate.js
|
|
3478
|
-
function __decorate(decorators, target, key, desc) {
|
|
3479
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3480
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3481
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
3482
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3483
|
-
}
|
|
3484
|
-
var init_decorate = require_rolldown_runtime.__esmMin((() => {}));
|
|
3485
|
-
//#endregion
|
|
3486
|
-
//#region packages/research/pipeline/src/entities.ts
|
|
3487
|
-
/**
|
|
3488
|
-
* Research-domain entities — a typed snapshot of schema v5.
|
|
3489
|
-
*
|
|
3490
|
-
* Contract with the schema authority:
|
|
3491
|
-
* - `synchronize` is ALWAYS false; migrations stay on the PRAGMA
|
|
3492
|
-
* user_version track (src/db.ts). If SCHEMA_SQL changes, these
|
|
3493
|
-
* entities must be updated in the same commit.
|
|
3494
|
-
* - Column names are explicit snake_case to match the database exactly.
|
|
3495
|
-
* - FTS5 virtual tables and PRAGMA paths are OUT of entity scope; they
|
|
3496
|
-
* stay raw SQL in store.ts (see docs/plan.md TypeORM boundaries).
|
|
3497
|
-
*/
|
|
3498
|
-
var Project, Query, Source, QuerySource, Evidence, Claim, ClaimEvidence, RESEARCH_ENTITIES;
|
|
3499
|
-
var init_entities$1 = require_rolldown_runtime.__esmMin((() => {
|
|
3500
|
-
init_decorateMetadata();
|
|
3501
|
-
init_decorate();
|
|
3502
|
-
Project = class Project {
|
|
3503
|
-
id;
|
|
3504
|
-
name;
|
|
3505
|
-
question;
|
|
3506
|
-
inclusion;
|
|
3507
|
-
exclusion;
|
|
3508
|
-
status;
|
|
3509
|
-
created;
|
|
3510
|
-
updated;
|
|
3511
|
-
queries;
|
|
3512
|
-
claims;
|
|
3513
|
-
sources;
|
|
3514
|
-
};
|
|
3515
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3516
|
-
name: "id",
|
|
3517
|
-
type: "text"
|
|
3518
|
-
}), __decorateMetadata("design:type", String)], Project.prototype, "id", void 0);
|
|
3519
|
-
__decorate([(0, typeorm.Column)({
|
|
3520
|
-
name: "name",
|
|
3521
|
-
type: "text"
|
|
3522
|
-
}), __decorateMetadata("design:type", String)], Project.prototype, "name", void 0);
|
|
3523
|
-
__decorate([(0, typeorm.Column)({
|
|
3524
|
-
name: "question",
|
|
3525
|
-
type: "text"
|
|
3526
|
-
}), __decorateMetadata("design:type", String)], Project.prototype, "question", void 0);
|
|
3527
|
-
__decorate([(0, typeorm.Column)({
|
|
3528
|
-
name: "inclusion",
|
|
3529
|
-
type: "text"
|
|
3530
|
-
}), __decorateMetadata("design:type", String)], Project.prototype, "inclusion", void 0);
|
|
3531
|
-
__decorate([(0, typeorm.Column)({
|
|
3532
|
-
name: "exclusion",
|
|
3533
|
-
type: "text"
|
|
3534
|
-
}), __decorateMetadata("design:type", String)], Project.prototype, "exclusion", void 0);
|
|
3535
|
-
__decorate([(0, typeorm.Column)({
|
|
3536
|
-
name: "status",
|
|
3537
|
-
type: "text"
|
|
3538
|
-
}), __decorateMetadata("design:type", String)], Project.prototype, "status", void 0);
|
|
3539
|
-
__decorate([(0, typeorm.Column)({
|
|
3540
|
-
name: "created",
|
|
3541
|
-
type: "text"
|
|
3542
|
-
}), __decorateMetadata("design:type", String)], Project.prototype, "created", void 0);
|
|
3543
|
-
__decorate([(0, typeorm.Column)({
|
|
3544
|
-
name: "updated",
|
|
3545
|
-
type: "text"
|
|
3546
|
-
}), __decorateMetadata("design:type", String)], Project.prototype, "updated", void 0);
|
|
3547
|
-
__decorate([(0, typeorm.OneToMany)(() => Query, (q) => q.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "queries", void 0);
|
|
3548
|
-
__decorate([(0, typeorm.OneToMany)(() => Claim, (c) => c.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "claims", void 0);
|
|
3549
|
-
__decorate([(0, typeorm.OneToMany)(() => Source, (s) => s.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "sources", void 0);
|
|
3550
|
-
Project = __decorate([(0, typeorm.Entity)({ name: "projects" })], Project);
|
|
3551
|
-
Query = class Query {
|
|
3552
|
-
id;
|
|
3553
|
-
projectId;
|
|
3554
|
-
text;
|
|
3555
|
-
engine;
|
|
3556
|
-
status;
|
|
3557
|
-
note;
|
|
3558
|
-
contentHash;
|
|
3559
|
-
created;
|
|
3560
|
-
project;
|
|
3561
|
-
querySources;
|
|
3562
|
-
};
|
|
3563
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3564
|
-
name: "id",
|
|
3565
|
-
type: "text"
|
|
3566
|
-
}), __decorateMetadata("design:type", String)], Query.prototype, "id", void 0);
|
|
3567
|
-
__decorate([(0, typeorm.Column)({
|
|
3568
|
-
name: "project_id",
|
|
3569
|
-
type: "text"
|
|
3570
|
-
}), __decorateMetadata("design:type", String)], Query.prototype, "projectId", void 0);
|
|
3571
|
-
__decorate([(0, typeorm.Column)({
|
|
3572
|
-
name: "text",
|
|
3573
|
-
type: "text"
|
|
3574
|
-
}), __decorateMetadata("design:type", String)], Query.prototype, "text", void 0);
|
|
3575
|
-
__decorate([(0, typeorm.Column)({
|
|
3576
|
-
name: "engine",
|
|
3577
|
-
type: "text"
|
|
3578
|
-
}), __decorateMetadata("design:type", String)], Query.prototype, "engine", void 0);
|
|
3579
|
-
__decorate([(0, typeorm.Column)({
|
|
3580
|
-
name: "status",
|
|
3581
|
-
type: "text"
|
|
3582
|
-
}), __decorateMetadata("design:type", String)], Query.prototype, "status", void 0);
|
|
3583
|
-
__decorate([(0, typeorm.Column)({
|
|
3584
|
-
name: "note",
|
|
3585
|
-
type: "text"
|
|
3586
|
-
}), __decorateMetadata("design:type", String)], Query.prototype, "note", void 0);
|
|
3587
|
-
__decorate([(0, typeorm.Column)({
|
|
3588
|
-
name: "content_hash",
|
|
3589
|
-
type: "text"
|
|
3590
|
-
}), __decorateMetadata("design:type", String)], Query.prototype, "contentHash", void 0);
|
|
3591
|
-
__decorate([(0, typeorm.Column)({
|
|
3592
|
-
name: "created",
|
|
3593
|
-
type: "text"
|
|
3594
|
-
}), __decorateMetadata("design:type", String)], Query.prototype, "created", void 0);
|
|
3595
|
-
__decorate([
|
|
3596
|
-
(0, typeorm.ManyToOne)(() => Project, (p) => p.queries, { createForeignKeyConstraints: false }),
|
|
3597
|
-
(0, typeorm.JoinColumn)({ name: "project_id" }),
|
|
3598
|
-
__decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
|
|
3599
|
-
], Query.prototype, "project", void 0);
|
|
3600
|
-
__decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.query, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Query.prototype, "querySources", void 0);
|
|
3601
|
-
Query = __decorate([(0, typeorm.Entity)({ name: "queries" })], Query);
|
|
3602
|
-
Source = class Source {
|
|
3603
|
-
id;
|
|
3604
|
-
projectId;
|
|
3605
|
-
url;
|
|
3606
|
-
title;
|
|
3607
|
-
urlHash;
|
|
3608
|
-
contentHash;
|
|
3609
|
-
abstract;
|
|
3610
|
-
content;
|
|
3611
|
-
engine;
|
|
3612
|
-
relevance;
|
|
3613
|
-
confidence;
|
|
3614
|
-
tags;
|
|
3615
|
-
retrievedAt;
|
|
3616
|
-
created;
|
|
3617
|
-
project;
|
|
3618
|
-
querySources;
|
|
3619
|
-
};
|
|
3620
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3621
|
-
name: "id",
|
|
3622
|
-
type: "text"
|
|
3623
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "id", void 0);
|
|
3624
|
-
__decorate([(0, typeorm.Column)({
|
|
3625
|
-
name: "project_id",
|
|
3626
|
-
type: "text"
|
|
3627
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "projectId", void 0);
|
|
3628
|
-
__decorate([(0, typeorm.Column)({
|
|
3629
|
-
name: "url",
|
|
3630
|
-
type: "text"
|
|
3631
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "url", void 0);
|
|
3632
|
-
__decorate([(0, typeorm.Column)({
|
|
3633
|
-
name: "title",
|
|
3634
|
-
type: "text"
|
|
3635
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "title", void 0);
|
|
3636
|
-
__decorate([(0, typeorm.Column)({
|
|
3637
|
-
name: "url_hash",
|
|
3638
|
-
type: "text"
|
|
3639
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "urlHash", void 0);
|
|
3640
|
-
__decorate([(0, typeorm.Column)({
|
|
3641
|
-
name: "content_hash",
|
|
3642
|
-
type: "text"
|
|
3643
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "contentHash", void 0);
|
|
3644
|
-
__decorate([(0, typeorm.Column)({
|
|
3645
|
-
name: "abstract",
|
|
3646
|
-
type: "text"
|
|
3647
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "abstract", void 0);
|
|
3648
|
-
__decorate([(0, typeorm.Column)({
|
|
3649
|
-
name: "content",
|
|
3650
|
-
type: "text"
|
|
3651
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "content", void 0);
|
|
3652
|
-
__decorate([(0, typeorm.Column)({
|
|
3653
|
-
name: "engine",
|
|
3654
|
-
type: "text"
|
|
3655
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "engine", void 0);
|
|
3656
|
-
__decorate([(0, typeorm.Column)({
|
|
3657
|
-
name: "relevance",
|
|
3658
|
-
type: "integer"
|
|
3659
|
-
}), __decorateMetadata("design:type", Number)], Source.prototype, "relevance", void 0);
|
|
3660
|
-
__decorate([(0, typeorm.Column)({
|
|
3661
|
-
name: "confidence",
|
|
3662
|
-
type: "text"
|
|
3663
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "confidence", void 0);
|
|
3664
|
-
__decorate([(0, typeorm.Column)({
|
|
3665
|
-
name: "tags",
|
|
3666
|
-
type: "text"
|
|
3667
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "tags", void 0);
|
|
3668
|
-
__decorate([(0, typeorm.Column)({
|
|
3669
|
-
name: "retrieved_at",
|
|
3670
|
-
type: "text"
|
|
3671
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "retrievedAt", void 0);
|
|
3672
|
-
__decorate([(0, typeorm.Column)({
|
|
3673
|
-
name: "created",
|
|
3674
|
-
type: "text",
|
|
3675
|
-
nullable: true
|
|
3676
|
-
}), __decorateMetadata("design:type", String)], Source.prototype, "created", void 0);
|
|
3677
|
-
__decorate([
|
|
3678
|
-
(0, typeorm.ManyToOne)(() => Project, (p) => p.sources, { createForeignKeyConstraints: false }),
|
|
3679
|
-
(0, typeorm.JoinColumn)({ name: "project_id" }),
|
|
3680
|
-
__decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
|
|
3681
|
-
], Source.prototype, "project", void 0);
|
|
3682
|
-
__decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.source, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Source.prototype, "querySources", void 0);
|
|
3683
|
-
Source = __decorate([(0, typeorm.Entity)({ name: "sources" })], Source);
|
|
3684
|
-
QuerySource = class QuerySource {
|
|
3685
|
-
queryId;
|
|
3686
|
-
sourceId;
|
|
3687
|
-
note;
|
|
3688
|
-
created;
|
|
3689
|
-
query;
|
|
3690
|
-
source;
|
|
3691
|
-
};
|
|
3692
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3693
|
-
name: "query_id",
|
|
3694
|
-
type: "text"
|
|
3695
|
-
}), __decorateMetadata("design:type", String)], QuerySource.prototype, "queryId", void 0);
|
|
3696
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3697
|
-
name: "source_id",
|
|
3698
|
-
type: "text"
|
|
3699
|
-
}), __decorateMetadata("design:type", String)], QuerySource.prototype, "sourceId", void 0);
|
|
3700
|
-
__decorate([(0, typeorm.Column)({
|
|
3701
|
-
name: "note",
|
|
3702
|
-
type: "text"
|
|
3703
|
-
}), __decorateMetadata("design:type", String)], QuerySource.prototype, "note", void 0);
|
|
3704
|
-
__decorate([(0, typeorm.Column)({
|
|
3705
|
-
name: "created",
|
|
3706
|
-
type: "text"
|
|
3707
|
-
}), __decorateMetadata("design:type", String)], QuerySource.prototype, "created", void 0);
|
|
3708
|
-
__decorate([
|
|
3709
|
-
(0, typeorm.ManyToOne)(() => Query, (q) => q.querySources, { createForeignKeyConstraints: false }),
|
|
3710
|
-
(0, typeorm.JoinColumn)({ name: "query_id" }),
|
|
3711
|
-
__decorateMetadata("design:type", typeof Query === "undefined" ? Object : Query)
|
|
3712
|
-
], QuerySource.prototype, "query", void 0);
|
|
3713
|
-
__decorate([
|
|
3714
|
-
(0, typeorm.ManyToOne)(() => Source, (s) => s.querySources, { createForeignKeyConstraints: false }),
|
|
3715
|
-
(0, typeorm.JoinColumn)({ name: "source_id" }),
|
|
3716
|
-
__decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
|
|
3717
|
-
], QuerySource.prototype, "source", void 0);
|
|
3718
|
-
QuerySource = __decorate([(0, typeorm.Entity)({ name: "query_sources" })], QuerySource);
|
|
3719
|
-
Evidence = class Evidence {
|
|
3720
|
-
id;
|
|
3721
|
-
projectId;
|
|
3722
|
-
sourceId;
|
|
3723
|
-
quote;
|
|
3724
|
-
note;
|
|
3725
|
-
locator;
|
|
3726
|
-
contentHash;
|
|
3727
|
-
created;
|
|
3728
|
-
project;
|
|
3729
|
-
source;
|
|
3730
|
-
claimEvidence;
|
|
3731
|
-
};
|
|
3732
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3733
|
-
name: "id",
|
|
3734
|
-
type: "text"
|
|
3735
|
-
}), __decorateMetadata("design:type", String)], Evidence.prototype, "id", void 0);
|
|
3736
|
-
__decorate([(0, typeorm.Column)({
|
|
3737
|
-
name: "project_id",
|
|
3738
|
-
type: "text"
|
|
3739
|
-
}), __decorateMetadata("design:type", String)], Evidence.prototype, "projectId", void 0);
|
|
3740
|
-
__decorate([(0, typeorm.Column)({
|
|
3741
|
-
name: "source_id",
|
|
3742
|
-
type: "text"
|
|
3743
|
-
}), __decorateMetadata("design:type", String)], Evidence.prototype, "sourceId", void 0);
|
|
3744
|
-
__decorate([(0, typeorm.Column)({
|
|
3745
|
-
name: "quote",
|
|
3746
|
-
type: "text"
|
|
3747
|
-
}), __decorateMetadata("design:type", String)], Evidence.prototype, "quote", void 0);
|
|
3748
|
-
__decorate([(0, typeorm.Column)({
|
|
3749
|
-
name: "note",
|
|
3750
|
-
type: "text"
|
|
3751
|
-
}), __decorateMetadata("design:type", String)], Evidence.prototype, "note", void 0);
|
|
3752
|
-
__decorate([(0, typeorm.Column)({
|
|
3753
|
-
name: "locator",
|
|
3754
|
-
type: "text"
|
|
3755
|
-
}), __decorateMetadata("design:type", String)], Evidence.prototype, "locator", void 0);
|
|
3756
|
-
__decorate([(0, typeorm.Column)({
|
|
3757
|
-
name: "content_hash",
|
|
3758
|
-
type: "text"
|
|
3759
|
-
}), __decorateMetadata("design:type", String)], Evidence.prototype, "contentHash", void 0);
|
|
3760
|
-
__decorate([(0, typeorm.Column)({
|
|
3761
|
-
name: "created",
|
|
3762
|
-
type: "text"
|
|
3763
|
-
}), __decorateMetadata("design:type", String)], Evidence.prototype, "created", void 0);
|
|
3764
|
-
__decorate([
|
|
3765
|
-
(0, typeorm.ManyToOne)(() => Project, void 0, { createForeignKeyConstraints: false }),
|
|
3766
|
-
(0, typeorm.JoinColumn)({ name: "project_id" }),
|
|
3767
|
-
__decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
|
|
3768
|
-
], Evidence.prototype, "project", void 0);
|
|
3769
|
-
__decorate([
|
|
3770
|
-
(0, typeorm.ManyToOne)(() => Source, void 0, { createForeignKeyConstraints: false }),
|
|
3771
|
-
(0, typeorm.JoinColumn)({ name: "source_id" }),
|
|
3772
|
-
__decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
|
|
3773
|
-
], Evidence.prototype, "source", void 0);
|
|
3774
|
-
__decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.evidence, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Evidence.prototype, "claimEvidence", void 0);
|
|
3775
|
-
Evidence = __decorate([(0, typeorm.Entity)({ name: "evidence" })], Evidence);
|
|
3776
|
-
Claim = class Claim {
|
|
3777
|
-
id;
|
|
3778
|
-
projectId;
|
|
3779
|
-
text;
|
|
3780
|
-
status;
|
|
3781
|
-
confidence;
|
|
3782
|
-
contentHash;
|
|
3783
|
-
created;
|
|
3784
|
-
updated;
|
|
3785
|
-
project;
|
|
3786
|
-
claimEvidence;
|
|
3787
|
-
};
|
|
3788
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3789
|
-
name: "id",
|
|
3790
|
-
type: "text"
|
|
3791
|
-
}), __decorateMetadata("design:type", String)], Claim.prototype, "id", void 0);
|
|
3792
|
-
__decorate([(0, typeorm.Column)({
|
|
3793
|
-
name: "project_id",
|
|
3794
|
-
type: "text"
|
|
3795
|
-
}), __decorateMetadata("design:type", String)], Claim.prototype, "projectId", void 0);
|
|
3796
|
-
__decorate([(0, typeorm.Column)({
|
|
3797
|
-
name: "text",
|
|
3798
|
-
type: "text"
|
|
3799
|
-
}), __decorateMetadata("design:type", String)], Claim.prototype, "text", void 0);
|
|
3800
|
-
__decorate([(0, typeorm.Column)({
|
|
3801
|
-
name: "status",
|
|
3802
|
-
type: "text"
|
|
3803
|
-
}), __decorateMetadata("design:type", String)], Claim.prototype, "status", void 0);
|
|
3804
|
-
__decorate([(0, typeorm.Column)({
|
|
3805
|
-
name: "confidence",
|
|
3806
|
-
type: "text"
|
|
3807
|
-
}), __decorateMetadata("design:type", String)], Claim.prototype, "confidence", void 0);
|
|
3808
|
-
__decorate([(0, typeorm.Column)({
|
|
3809
|
-
name: "content_hash",
|
|
3810
|
-
type: "text"
|
|
3811
|
-
}), __decorateMetadata("design:type", String)], Claim.prototype, "contentHash", void 0);
|
|
3812
|
-
__decorate([(0, typeorm.Column)({
|
|
3813
|
-
name: "created",
|
|
3814
|
-
type: "text"
|
|
3815
|
-
}), __decorateMetadata("design:type", String)], Claim.prototype, "created", void 0);
|
|
3816
|
-
__decorate([(0, typeorm.Column)({
|
|
3817
|
-
name: "updated",
|
|
3818
|
-
type: "text",
|
|
3819
|
-
nullable: true
|
|
3820
|
-
}), __decorateMetadata("design:type", String)], Claim.prototype, "updated", void 0);
|
|
3821
|
-
__decorate([
|
|
3822
|
-
(0, typeorm.ManyToOne)(() => Project, (p) => p.claims, { createForeignKeyConstraints: false }),
|
|
3823
|
-
(0, typeorm.JoinColumn)({ name: "project_id" }),
|
|
3824
|
-
__decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
|
|
3825
|
-
], Claim.prototype, "project", void 0);
|
|
3826
|
-
__decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.claim, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Claim.prototype, "claimEvidence", void 0);
|
|
3827
|
-
Claim = __decorate([(0, typeorm.Entity)({ name: "claims" })], Claim);
|
|
3828
|
-
ClaimEvidence = class ClaimEvidence {
|
|
3829
|
-
claimId;
|
|
3830
|
-
evidenceId;
|
|
3831
|
-
stance;
|
|
3832
|
-
claim;
|
|
3833
|
-
evidence;
|
|
3834
|
-
};
|
|
3835
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3836
|
-
name: "claim_id",
|
|
3837
|
-
type: "text"
|
|
3838
|
-
}), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "claimId", void 0);
|
|
3839
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3840
|
-
name: "evidence_id",
|
|
3841
|
-
type: "text"
|
|
3842
|
-
}), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "evidenceId", void 0);
|
|
3843
|
-
__decorate([(0, typeorm.Column)({
|
|
3844
|
-
name: "stance",
|
|
3845
|
-
type: "text"
|
|
3846
|
-
}), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "stance", void 0);
|
|
3847
|
-
__decorate([
|
|
3848
|
-
(0, typeorm.ManyToOne)(() => Claim, (c) => c.claimEvidence, { createForeignKeyConstraints: false }),
|
|
3849
|
-
(0, typeorm.JoinColumn)({ name: "claim_id" }),
|
|
3850
|
-
__decorateMetadata("design:type", typeof Claim === "undefined" ? Object : Claim)
|
|
3851
|
-
], ClaimEvidence.prototype, "claim", void 0);
|
|
3852
|
-
__decorate([
|
|
3853
|
-
(0, typeorm.ManyToOne)(() => Evidence, void 0, { createForeignKeyConstraints: false }),
|
|
3854
|
-
(0, typeorm.JoinColumn)({ name: "evidence_id" }),
|
|
3855
|
-
__decorateMetadata("design:type", typeof Evidence === "undefined" ? Object : Evidence)
|
|
3856
|
-
], ClaimEvidence.prototype, "evidence", void 0);
|
|
3857
|
-
ClaimEvidence = __decorate([(0, typeorm.Entity)({ name: "claim_evidence" })], ClaimEvidence);
|
|
3858
|
-
RESEARCH_ENTITIES = [
|
|
3859
|
-
Project,
|
|
3860
|
-
Query,
|
|
3861
|
-
Source,
|
|
3862
|
-
QuerySource,
|
|
3863
|
-
Evidence,
|
|
3864
|
-
Claim,
|
|
3865
|
-
ClaimEvidence
|
|
3866
|
-
];
|
|
3867
|
-
}));
|
|
3868
|
-
//#endregion
|
|
3869
|
-
//#region packages/core/database/src/entities.ts
|
|
3870
|
-
/** Cross-cutting entities (audit trail). Research-domain entities live in
|
|
3871
|
-
* research.ts; both are registered in src/orm.ts. */
|
|
3872
|
-
var Event, CORE_ENTITIES;
|
|
3873
|
-
var init_entities = require_rolldown_runtime.__esmMin((() => {
|
|
3874
|
-
init_decorateMetadata();
|
|
3875
|
-
init_decorate();
|
|
3876
|
-
Event = class Event {
|
|
3877
|
-
id;
|
|
3878
|
-
ts;
|
|
3879
|
-
actor;
|
|
3880
|
-
kind;
|
|
3881
|
-
entity;
|
|
3882
|
-
entityId;
|
|
3883
|
-
payload;
|
|
3884
|
-
/** v7 telemetry severity: info | warn | error (spec R10.2) */
|
|
3885
|
-
level;
|
|
3886
|
-
};
|
|
3887
|
-
__decorate([(0, typeorm.PrimaryColumn)({
|
|
3888
|
-
name: "id",
|
|
3889
|
-
type: "text"
|
|
3890
|
-
}), __decorateMetadata("design:type", String)], Event.prototype, "id", void 0);
|
|
3891
|
-
__decorate([(0, typeorm.Column)({
|
|
3892
|
-
name: "ts",
|
|
3893
|
-
type: "text"
|
|
3894
|
-
}), __decorateMetadata("design:type", String)], Event.prototype, "ts", void 0);
|
|
3895
|
-
__decorate([(0, typeorm.Column)({
|
|
3896
|
-
name: "actor",
|
|
3897
|
-
type: "text"
|
|
3898
|
-
}), __decorateMetadata("design:type", String)], Event.prototype, "actor", void 0);
|
|
3899
|
-
__decorate([(0, typeorm.Column)({
|
|
3900
|
-
name: "kind",
|
|
3901
|
-
type: "text"
|
|
3902
|
-
}), __decorateMetadata("design:type", String)], Event.prototype, "kind", void 0);
|
|
3903
|
-
__decorate([(0, typeorm.Column)({
|
|
3904
|
-
name: "entity",
|
|
3905
|
-
type: "text"
|
|
3906
|
-
}), __decorateMetadata("design:type", String)], Event.prototype, "entity", void 0);
|
|
3907
|
-
__decorate([(0, typeorm.Column)({
|
|
3908
|
-
name: "entity_id",
|
|
3909
|
-
type: "text"
|
|
3910
|
-
}), __decorateMetadata("design:type", String)], Event.prototype, "entityId", void 0);
|
|
3911
|
-
__decorate([(0, typeorm.Column)({
|
|
3912
|
-
name: "payload",
|
|
3913
|
-
type: "text"
|
|
3914
|
-
}), __decorateMetadata("design:type", String)], Event.prototype, "payload", void 0);
|
|
3915
|
-
__decorate([(0, typeorm.Column)({
|
|
3916
|
-
name: "level",
|
|
3917
|
-
type: "text",
|
|
3918
|
-
default: "info"
|
|
3919
|
-
}), __decorateMetadata("design:type", String)], Event.prototype, "level", void 0);
|
|
3920
|
-
Event = __decorate([(0, typeorm.Entity)({ name: "events" })], Event);
|
|
3921
|
-
CORE_ENTITIES = [Event];
|
|
3922
|
-
}));
|
|
3923
|
-
//#endregion
|
|
3924
|
-
//#region packages/core/database/src/orm.ts
|
|
3801
|
+
kind text NOT NULL DEFAULT 'section', title text NOT NULL DEFAULT '',
|
|
3802
|
+
format text NOT NULL DEFAULT 'markdown', content text NOT NULL DEFAULT '',
|
|
3803
|
+
version integer NOT NULL DEFAULT 1, created text NOT NULL,
|
|
3804
|
+
meta text NOT NULL DEFAULT '');
|
|
3805
|
+
CREATE TABLE kb_notes (
|
|
3806
|
+
id text PRIMARY KEY, title text NOT NULL, body text NOT NULL DEFAULT '',
|
|
3807
|
+
source_url text DEFAULT '', origin text DEFAULT '', tags text DEFAULT '',
|
|
3808
|
+
confidence text NOT NULL DEFAULT 'B', reuse integer NOT NULL DEFAULT 0,
|
|
3809
|
+
content_hash text NOT NULL DEFAULT '', created text NOT NULL, updated text NOT NULL,
|
|
3810
|
+
UNIQUE (content_hash));
|
|
3811
|
+
CREATE TABLE kb_links (
|
|
3812
|
+
from_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
|
|
3813
|
+
to_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
|
|
3814
|
+
rel text NOT NULL DEFAULT 'related', PRIMARY KEY (from_id, to_id, rel));
|
|
3815
|
+
CREATE TABLE trees (
|
|
3816
|
+
id text PRIMARY KEY, project_id text REFERENCES projects(id) ON DELETE CASCADE,
|
|
3817
|
+
name text NOT NULL UNIQUE, note text DEFAULT '', created text NOT NULL);
|
|
3818
|
+
CREATE TABLE nodes (
|
|
3819
|
+
id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
|
|
3820
|
+
key text NOT NULL, name text NOT NULL, kind text NOT NULL DEFAULT 'tech',
|
|
3821
|
+
tier integer NOT NULL DEFAULT 0, cost double precision NOT NULL DEFAULT 0,
|
|
3822
|
+
cost_unit text DEFAULT '', payload text DEFAULT '', tags text DEFAULT '',
|
|
3823
|
+
created text NOT NULL, UNIQUE (tree_id, key));
|
|
3824
|
+
CREATE TABLE node_edges (
|
|
3825
|
+
id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
|
|
3826
|
+
parent_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3827
|
+
child_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3828
|
+
rel text NOT NULL DEFAULT 'prereq', mode text NOT NULL DEFAULT 'AND',
|
|
3829
|
+
required integer NOT NULL DEFAULT 1, min_level integer NOT NULL DEFAULT 1,
|
|
3830
|
+
weight double precision NOT NULL DEFAULT 1, note text DEFAULT '',
|
|
3831
|
+
UNIQUE (parent_id, child_id, rel));
|
|
3832
|
+
CREATE TABLE node_closure (
|
|
3833
|
+
tree_id text NOT NULL, ancestor text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3834
|
+
descendant text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3835
|
+
depth integer NOT NULL, PRIMARY KEY (ancestor, descendant));
|
|
3836
|
+
CREATE TABLE node_conditions (
|
|
3837
|
+
id text PRIMARY KEY, node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3838
|
+
name text NOT NULL, kind text NOT NULL DEFAULT 'flag', expr text NOT NULL DEFAULT '',
|
|
3839
|
+
op text NOT NULL DEFAULT '>=', value text NOT NULL DEFAULT '',
|
|
3840
|
+
required integer NOT NULL DEFAULT 1, effect text NOT NULL DEFAULT 'gate',
|
|
3841
|
+
weight double precision NOT NULL DEFAULT 1, note text DEFAULT '');
|
|
3842
|
+
CREATE TABLE node_state (
|
|
3843
|
+
tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
|
|
3844
|
+
context text NOT NULL DEFAULT 'default', node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
3845
|
+
status text NOT NULL DEFAULT 'locked', level integer NOT NULL DEFAULT 0,
|
|
3846
|
+
progress double precision NOT NULL DEFAULT 0, updated text NOT NULL,
|
|
3847
|
+
PRIMARY KEY (tree_id, context, node_id));
|
|
3848
|
+
CREATE TABLE node_index (
|
|
3849
|
+
tree_id text PRIMARY KEY REFERENCES trees(id) ON DELETE CASCADE,
|
|
3850
|
+
topo_order text NOT NULL DEFAULT '', tiers text NOT NULL DEFAULT '',
|
|
3851
|
+
in_degree text NOT NULL DEFAULT '', built_at text NOT NULL, dirty integer NOT NULL DEFAULT 1);
|
|
3852
|
+
CREATE TABLE relations (
|
|
3853
|
+
id text PRIMARY KEY, src_type text NOT NULL, src_id text NOT NULL,
|
|
3854
|
+
dst_type text NOT NULL, dst_id text NOT NULL, rel text NOT NULL DEFAULT 'related',
|
|
3855
|
+
weight double precision NOT NULL DEFAULT 1,
|
|
3856
|
+
UNIQUE (src_type, src_id, dst_type, dst_id, rel));
|
|
3857
|
+
CREATE TABLE jobs (
|
|
3858
|
+
id text PRIMARY KEY, title text NOT NULL, command text NOT NULL,
|
|
3859
|
+
every_minutes integer DEFAULT 0, at text DEFAULT '', enabled integer NOT NULL DEFAULT 1,
|
|
3860
|
+
runs integer NOT NULL DEFAULT 0, last_run text DEFAULT '',
|
|
3861
|
+
status text NOT NULL DEFAULT 'pending', created text NOT NULL);
|
|
3862
|
+
CREATE TABLE runs (
|
|
3863
|
+
id text PRIMARY KEY, job_id text NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
3864
|
+
started text NOT NULL, finished text DEFAULT '', rc integer, output text DEFAULT '');
|
|
3865
|
+
CREATE TABLE events (
|
|
3866
|
+
id text PRIMARY KEY, ts text NOT NULL, actor text NOT NULL DEFAULT 'cli', kind text NOT NULL,
|
|
3867
|
+
entity text DEFAULT '', entity_id text DEFAULT '', payload text DEFAULT '',
|
|
3868
|
+
level text NOT NULL DEFAULT 'info');
|
|
3869
|
+
`;
|
|
3870
|
+
const MIGRATE_INDEXES = `
|
|
3871
|
+
CREATE INDEX IF NOT EXISTS idx_sources_project ON sources(project_id);
|
|
3872
|
+
CREATE INDEX IF NOT EXISTS idx_evidence_project ON evidence(project_id);
|
|
3873
|
+
CREATE INDEX IF NOT EXISTS idx_claims_project ON claims(project_id);
|
|
3874
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_proj ON artifacts(project_id);
|
|
3875
|
+
CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
|
|
3876
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON jobs(enabled);
|
|
3877
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_tree ON nodes(tree_id);
|
|
3878
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_tier ON nodes(tree_id, tier);
|
|
3879
|
+
CREATE INDEX IF NOT EXISTS idx_edge_child ON node_edges(child_id);
|
|
3880
|
+
CREATE INDEX IF NOT EXISTS idx_edge_parent ON node_edges(parent_id);
|
|
3881
|
+
CREATE INDEX IF NOT EXISTS idx_edge_req ON node_edges(child_id, required);
|
|
3882
|
+
CREATE INDEX IF NOT EXISTS idx_closure_desc ON node_closure(descendant);
|
|
3883
|
+
CREATE INDEX IF NOT EXISTS idx_closure_anc ON node_closure(ancestor);
|
|
3884
|
+
CREATE INDEX IF NOT EXISTS idx_cond_node ON node_conditions(node_id);
|
|
3885
|
+
CREATE INDEX IF NOT EXISTS idx_cond_req ON node_conditions(node_id, required);
|
|
3886
|
+
CREATE INDEX IF NOT EXISTS idx_state_ctx ON node_state(tree_id, context);
|
|
3887
|
+
CREATE INDEX IF NOT EXISTS idx_state_status ON node_state(tree_id, context, status);
|
|
3888
|
+
`;
|
|
3925
3889
|
/**
|
|
3926
|
-
*
|
|
3927
|
-
*
|
|
3928
|
-
*
|
|
3929
|
-
*
|
|
3930
|
-
* SCHEMA_SQL + PRAGMA user_version migrations in src/db.ts. Entities in
|
|
3931
|
-
* src/entities/ are a typed snapshot; change them in the same commit as
|
|
3932
|
-
* SCHEMA_SQL.
|
|
3933
|
-
* - TypeORM is a QUERY layer here, not a migration layer. FTS5 MATCH /
|
|
3934
|
-
* bm25 loops, PRAGMA paths and the PG sync/migrate machinery stay raw
|
|
3935
|
-
* SQL on the DB class.
|
|
3936
|
-
* - CLI commands using the ORM must return a Promise (main() awaits) so
|
|
3937
|
-
* the DataSource settles before the wasm DB closes.
|
|
3890
|
+
* One-time migration of the whole library into a standalone PG database.
|
|
3891
|
+
* Atomic (single transaction: DDL + data + verification), refuses a non-
|
|
3892
|
+
* empty target unless `fresh` drops the existing tables first. After
|
|
3893
|
+
* migration the CLI keeps working on SQLite only — no dual writes.
|
|
3938
3894
|
*/
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
}
|
|
3951
|
-
|
|
3952
|
-
await
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3895
|
+
async function migrateToPg(db, url, fresh = false) {
|
|
3896
|
+
let pg;
|
|
3897
|
+
try {
|
|
3898
|
+
pg = require("pg");
|
|
3899
|
+
} catch {
|
|
3900
|
+
throw new Error("pg driver not installed — run: pnpm add pg (optional dependency)");
|
|
3901
|
+
}
|
|
3902
|
+
const typeErrors = preflightTypeCheck(db);
|
|
3903
|
+
if (typeErrors.length) {
|
|
3904
|
+
const lines = typeErrors.slice(0, 5).map((e) => ` ${e.table}.${e.column} rowid=${e.rowid}: value "${e.value}" is not a valid INTEGER`);
|
|
3905
|
+
throw new Error(`pre-flight type check failed (${typeErrors.length} issue(s)) — fix these before migrating:\n${lines.join("\n")}${typeErrors.length > 5 ? `\n ...and ${typeErrors.length - 5} more` : ""}\n hint: ygl-trees db sql "UPDATE <table> SET <column>=<int> WHERE rowid=<rowid>"`);
|
|
3906
|
+
}
|
|
3907
|
+
const client = new pg.Client({ connectionString: url });
|
|
3908
|
+
await client.connect();
|
|
3909
|
+
try {
|
|
3910
|
+
const verNum = Number((await client.query("SHOW server_version_num")).rows[0].server_version_num);
|
|
3911
|
+
const ver = (await client.query("SELECT version()")).rows[0].version;
|
|
3912
|
+
const fulltext = verNum >= 12e4 ? "tsvector+GIN" : "ngram-only(pg<12)";
|
|
3913
|
+
if (!fresh) {
|
|
3914
|
+
for (const t of MIRROR_TABLES) if ((await client.query(`SELECT 1 AS hit FROM information_schema.tables WHERE table_name=$1`, [t])).rowCount) {
|
|
3915
|
+
const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
|
|
3916
|
+
if (n > 0) throw new Error(`target already contains data (table ${t}, ${n} rows) — migration is one-time; use --fresh to drop and recreate, or 'db sync pg' for a refreshable mirror`);
|
|
3917
|
+
}
|
|
3918
|
+
} else for (const t of [...MIRROR_TABLES].reverse()) await client.query(`DROP TABLE IF EXISTS "${t}" CASCADE`);
|
|
3919
|
+
await client.query("BEGIN");
|
|
3963
3920
|
try {
|
|
3964
|
-
await
|
|
3965
|
-
|
|
3966
|
-
|
|
3921
|
+
await client.query(MIGRATE_DDL);
|
|
3922
|
+
await client.query(MIGRATE_INDEXES);
|
|
3923
|
+
for (const t of Object.keys(NGRAM_INPUT)) {
|
|
3924
|
+
await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram text NOT NULL DEFAULT ''`);
|
|
3925
|
+
if (verNum >= 12e4) {
|
|
3926
|
+
await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram_tsv tsvector
|
|
3927
|
+
GENERATED ALWAYS AS (to_tsvector('simple', regexp_replace(coalesce(ngram,''), '[/\\._:,&+()]+', ' ', 'g'))) STORED`);
|
|
3928
|
+
await client.query(`CREATE INDEX IF NOT EXISTS "${t}_tsv" ON "${t}" USING GIN (ngram_tsv)`);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
for (const t of MIRROR_TABLES) {
|
|
3932
|
+
const src = db.all(`PRAGMA table_info(${t})`).map((c) => c.name).sort();
|
|
3933
|
+
const dst = (await client.query(`SELECT column_name FROM information_schema.columns WHERE table_name=$1`, [t])).rows.map((r) => r.column_name);
|
|
3934
|
+
const want = new Set(src);
|
|
3935
|
+
if (Object.keys(NGRAM_INPUT).includes(t)) {
|
|
3936
|
+
want.add("ngram");
|
|
3937
|
+
if (verNum >= 12e4) want.add("ngram_tsv");
|
|
3938
|
+
}
|
|
3939
|
+
const got = new Set(dst);
|
|
3940
|
+
if ([...want].filter((c) => !got.has(c)).length || got.size !== want.size) throw new Error(`schema drift on ${t}: migrate DDL out of date (want [${[...want]}]; got [${dst}]) — update MIGRATE_DDL`);
|
|
3941
|
+
}
|
|
3942
|
+
const result = [];
|
|
3943
|
+
for (const t of MIRROR_TABLES) {
|
|
3944
|
+
const rows = await copyTable(client, db, t);
|
|
3945
|
+
const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
|
|
3946
|
+
if (n !== rows) throw new Error(`count mismatch on ${t}: copied ${rows}, found ${n}`);
|
|
3947
|
+
result.push({
|
|
3948
|
+
table: t,
|
|
3949
|
+
rows
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
await client.query("COMMIT");
|
|
3953
|
+
return {
|
|
3954
|
+
tables: result,
|
|
3955
|
+
server_version: ver,
|
|
3956
|
+
constraints: {
|
|
3957
|
+
fk: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='FOREIGN KEY'`)).rows[0].n,
|
|
3958
|
+
unique: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='UNIQUE'`)).rows[0].n
|
|
3959
|
+
},
|
|
3960
|
+
fulltext
|
|
3961
|
+
};
|
|
3962
|
+
} catch (e) {
|
|
3963
|
+
await client.query("ROLLBACK");
|
|
3964
|
+
throw e;
|
|
3965
|
+
}
|
|
3966
|
+
} finally {
|
|
3967
|
+
await client.end();
|
|
3967
3968
|
}
|
|
3968
3969
|
}
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3970
|
+
/** Pre-flight: scan INTEGER columns for non-integer values (SQLite weak
|
|
3971
|
+
* typing allows REAL into INTEGER; PG rejects at migration time with no
|
|
3972
|
+
* context). Returns actionable errors with table.column+rowid. */
|
|
3973
|
+
function preflightTypeCheck(db) {
|
|
3974
|
+
const issues = [];
|
|
3975
|
+
const tables = db.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '%_fts%' AND name NOT LIKE '%_config%' AND name NOT LIKE '%_content%' AND name NOT LIKE '%_data%' AND name NOT LIKE '%_docsize%' AND name NOT LIKE '%_idx%' AND name != 'meta'");
|
|
3976
|
+
for (const t of tables) {
|
|
3977
|
+
const cols = db.all(`PRAGMA table_info(${t.name})`);
|
|
3978
|
+
for (const c of cols) {
|
|
3979
|
+
if (c.type !== "INTEGER") continue;
|
|
3980
|
+
const bad = db.all(`SELECT rowid, ${c.name} AS v FROM ${t.name} WHERE typeof(${c.name}) NOT IN ('integer','null') LIMIT 5`);
|
|
3981
|
+
for (const row of bad) issues.push({
|
|
3982
|
+
table: t.name,
|
|
3983
|
+
column: c.name,
|
|
3984
|
+
rowid: row.rowid,
|
|
3985
|
+
value: String(row.v)
|
|
3986
|
+
});
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
return issues;
|
|
3990
|
+
}
|
|
3991
|
+
//#endregion
|
|
3992
|
+
//#region packages/core/text/src/eventbus.ts
|
|
3993
|
+
/**
|
|
3994
|
+
* Cross-domain event bus — connects logEvent writes to real-time consumers.
|
|
3995
|
+
*
|
|
3996
|
+
* Architecture: a process-wide EventEmitter singleton. Write services call
|
|
3997
|
+
* `emitEvent()` after logEvent persists to the events table; consumers
|
|
3998
|
+
* (webui SSE, user plugins, future watchers) subscribe with `onEvent()`.
|
|
3999
|
+
*
|
|
4000
|
+
* This makes "ctx.on reactivity" real without requiring a full Cordis
|
|
4001
|
+
* event system — a lightweight bridge that the webui and plugins can use.
|
|
4002
|
+
*
|
|
4003
|
+
* @module @ygl/core-text/eventbus
|
|
4004
|
+
*/
|
|
4005
|
+
var eventbus_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
4006
|
+
emitEvent: () => emitEvent,
|
|
4007
|
+
onEvent: () => onEvent,
|
|
4008
|
+
onEventKind: () => onEventKind
|
|
4009
|
+
});
|
|
4010
|
+
/** Publish a cross-domain event (called after events table write). */
|
|
4011
|
+
function emitEvent(evt) {
|
|
4012
|
+
bus.emit("ygl", evt);
|
|
4013
|
+
bus.emit(`ygl:${evt.kind}`, evt);
|
|
4014
|
+
}
|
|
4015
|
+
/** Subscribe to all events. Returns an unsubscribe function. */
|
|
4016
|
+
function onEvent(handler) {
|
|
4017
|
+
bus.on("ygl", handler);
|
|
4018
|
+
return () => bus.off("ygl", handler);
|
|
4019
|
+
}
|
|
4020
|
+
/** Subscribe to a specific kind (e.g. "node.unlock"). Returns unsubscribe. */
|
|
4021
|
+
function onEventKind(kind, handler) {
|
|
4022
|
+
bus.on(`ygl:${kind}`, handler);
|
|
4023
|
+
return () => bus.off(`ygl:${kind}`, handler);
|
|
4024
|
+
}
|
|
4025
|
+
var bus;
|
|
4026
|
+
var init_eventbus = require_rolldown_runtime.__esmMin((() => {
|
|
4027
|
+
bus = new node_events.EventEmitter();
|
|
4028
|
+
bus.setMaxListeners(50);
|
|
3974
4029
|
}));
|
|
3975
4030
|
//#endregion
|
|
3976
4031
|
//#region packages/research/pipeline/src/writes.ts
|
|
@@ -4013,6 +4068,16 @@ async function logEvent$1(mgr, kind, entity, entityId, payload, level = "info")
|
|
|
4013
4068
|
payload: p,
|
|
4014
4069
|
level
|
|
4015
4070
|
});
|
|
4071
|
+
try {
|
|
4072
|
+
const { emitEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
|
|
4073
|
+
emitEvent({
|
|
4074
|
+
kind,
|
|
4075
|
+
entity,
|
|
4076
|
+
entityId,
|
|
4077
|
+
payload: String(payload),
|
|
4078
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
4079
|
+
});
|
|
4080
|
+
} catch {}
|
|
4016
4081
|
}
|
|
4017
4082
|
async function queryAdd(ds, projectId, text, engine = "", force = false) {
|
|
4018
4083
|
const hash = contentHash(text, force);
|
|
@@ -7187,6 +7252,16 @@ async function logEvent(mgr, kind, entityId, payload, level = "info") {
|
|
|
7187
7252
|
body,
|
|
7188
7253
|
level
|
|
7189
7254
|
]);
|
|
7255
|
+
try {
|
|
7256
|
+
const { emitEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
|
|
7257
|
+
emitEvent({
|
|
7258
|
+
kind,
|
|
7259
|
+
entity: "node",
|
|
7260
|
+
entityId,
|
|
7261
|
+
payload: String(body),
|
|
7262
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
7263
|
+
});
|
|
7264
|
+
} catch {}
|
|
7190
7265
|
}
|
|
7191
7266
|
async function reaches(mgr, treeId, fromId, toId) {
|
|
7192
7267
|
return (await mgr.query(`SELECT 1 AS hit FROM node_closure WHERE tree_id=? AND ancestor=? AND descendant=? AND depth>0`, [
|
|
@@ -10360,6 +10435,15 @@ function createDashboardServer(env) {
|
|
|
10360
10435
|
const h = setInterval(tick, 1500);
|
|
10361
10436
|
if (typeof h.unref === "function") h.unref();
|
|
10362
10437
|
}
|
|
10438
|
+
let unsubBus = null;
|
|
10439
|
+
try {
|
|
10440
|
+
const { onEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
|
|
10441
|
+
unsubBus = onEvent((evt) => {
|
|
10442
|
+
if (!sseClients.size) return;
|
|
10443
|
+
push("data", gatherData(env));
|
|
10444
|
+
push(evt.kind.replace(/\//g, "-"), evt);
|
|
10445
|
+
});
|
|
10446
|
+
} catch {}
|
|
10363
10447
|
const server = http.createServer((req, res) => {
|
|
10364
10448
|
const u = new URL(req.url || "/", "http://127.0.0.1");
|
|
10365
10449
|
const url = u.pathname;
|
|
@@ -10457,7 +10541,15 @@ function createDashboardServer(env) {
|
|
|
10457
10541
|
sseClients.clear();
|
|
10458
10542
|
};
|
|
10459
10543
|
server.on("close", dropLive);
|
|
10460
|
-
server.closeLive =
|
|
10544
|
+
server.closeLive = () => {
|
|
10545
|
+
dropLive();
|
|
10546
|
+
if (unsubBus) {
|
|
10547
|
+
try {
|
|
10548
|
+
unsubBus();
|
|
10549
|
+
} catch {}
|
|
10550
|
+
unsubBus = null;
|
|
10551
|
+
}
|
|
10552
|
+
};
|
|
10461
10553
|
return server;
|
|
10462
10554
|
}
|
|
10463
10555
|
/** Live dashboard: blocks until SIGINT/SIGTERM, then resolves so the CLI can
|
|
@@ -11217,6 +11309,9 @@ async function main(argv) {
|
|
|
11217
11309
|
return 1;
|
|
11218
11310
|
}).finally(() => {
|
|
11219
11311
|
try {
|
|
11312
|
+
if (booted?.ctx?.database?.stop) booted.ctx.database.stop();
|
|
11313
|
+
} catch {}
|
|
11314
|
+
if (!booted?.ctx?.database) try {
|
|
11220
11315
|
db?.close();
|
|
11221
11316
|
} catch {}
|
|
11222
11317
|
});
|