lurqrun 0.1.7 → 0.1.8

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.
Files changed (122) hide show
  1. package/README.md +312 -148
  2. package/dist/{auditHandler-JCOJSUYW.js → auditHandler-VWCXSDVO.js} +12 -11
  3. package/dist/autopilotInit-Q62MX5M6.js +178 -0
  4. package/dist/bin/lurq.js +235 -83
  5. package/dist/{browserAuth-B6G27USY.js → browserAuth-J7APSD4P.js} +4 -3
  6. package/dist/{cache-DKI34SVS.js → cache-AEAKCV4I.js} +2 -2
  7. package/dist/check-2JTQDQA6.js +2613 -0
  8. package/dist/{check-2FDGUOV7.js → check-PQSQ2MMA.js} +1 -1
  9. package/dist/checkUpgrade-5YW77OBL.js +24 -0
  10. package/dist/{chunk-BCANRN5B.js → chunk-2GAYNIHG.js} +8 -1
  11. package/dist/{chunk-EA3DP5HG.js → chunk-2O6Z37IT.js} +155 -25
  12. package/dist/{chunk-J5FZYN4A.js → chunk-2WD4QAH4.js} +1 -1
  13. package/dist/{chunk-CUP36ND3.js → chunk-3676XOBU.js} +145 -36
  14. package/dist/chunk-3D2MPGOQ.js +181 -0
  15. package/dist/{chunk-LYMPYETA.js → chunk-4JHDSVIN.js} +270 -6
  16. package/dist/chunk-53SG6XWA.js +141 -0
  17. package/dist/chunk-5ABNDB3I.js +37 -0
  18. package/dist/chunk-5ALIJFDA.js +114 -0
  19. package/dist/{chunk-BULZAX2U.js → chunk-64ZF7KO4.js} +55 -38
  20. package/dist/{chunk-2VMOWSRY.js → chunk-6DM6UQFD.js} +19 -1
  21. package/dist/{chunk-HWBBVLOT.js → chunk-6L67QMCP.js} +1 -1
  22. package/dist/{chunk-GPTKSNR6.js → chunk-72Y2FDVS.js} +2 -1
  23. package/dist/{chunk-B43LCXBM.js → chunk-A3TYI2TF.js} +81 -16
  24. package/dist/{chunk-IQ4SDKKY.js → chunk-B2UUYRBX.js} +31 -228
  25. package/dist/{chunk-DB2VF5UD.js → chunk-BJT2LHAR.js} +16 -11
  26. package/dist/chunk-BM3LF5T6.js +132 -0
  27. package/dist/{chunk-3N3Q3DSW.js → chunk-BN3NM47Q.js} +8 -8
  28. package/dist/chunk-D3K6IZVR.js +12845 -0
  29. package/dist/{chunk-AHBP533R.js → chunk-D3VTZS7V.js} +7 -5
  30. package/dist/{chunk-UDVNA7EA.js → chunk-DM37MTOO.js} +161 -8
  31. package/dist/chunk-DONTW3NE.js +171 -0
  32. package/dist/{chunk-M6NYEXCW.js → chunk-DSROTZ3Z.js} +1 -1
  33. package/dist/{chunk-4F7ZESPO.js → chunk-EQCXENKZ.js} +3 -5
  34. package/dist/{chunk-CW5HJ3LX.js → chunk-EYAKM6YN.js} +1 -1
  35. package/dist/{chunk-VL2E5T2A.js → chunk-FDFPMBCA.js} +6 -3
  36. package/dist/{chunk-BV32UNLL.js → chunk-FVYFV5HA.js} +2 -2
  37. package/dist/{chunk-DWAGCNEL.js → chunk-GM5Y555O.js} +36 -103
  38. package/dist/{chunk-PSX3GIAN.js → chunk-HWFX4BC4.js} +2 -1
  39. package/dist/chunk-IFNONGRQ.js +603 -0
  40. package/dist/chunk-J3U2CPSK.js +136 -0
  41. package/dist/{chunk-Y5GI7RL3.js → chunk-J6XQ7OPZ.js} +9 -1
  42. package/dist/{chunk-LBFGSSF2.js → chunk-KMV4Y6R3.js} +2 -1
  43. package/dist/{chunk-L6N4JFW2.js → chunk-KP5ZDDPE.js} +81 -4
  44. package/dist/{chunk-SJOVFF7B.js → chunk-KYKSZ3XZ.js} +3 -2
  45. package/dist/chunk-NEZNAPPN.js +41 -0
  46. package/dist/{chunk-NH5I4KHH.js → chunk-NPVN2THM.js} +1 -5
  47. package/dist/{chunk-6L3U7DZ5.js → chunk-NYZU5NCW.js} +1 -1
  48. package/dist/chunk-O4KHCV6I.js +320 -0
  49. package/dist/{chunk-LO35HLQZ.js → chunk-ONDVTXUO.js} +25 -4
  50. package/dist/{chunk-4ZYCIMY4.js → chunk-OTJFHESY.js} +25 -4
  51. package/dist/chunk-P6MIKSPY.js +243 -0
  52. package/dist/{checkUpgrade-F2WESBPM.js → chunk-QCRBX3LV.js} +72 -10
  53. package/dist/{chunk-LRD4GBYE.js → chunk-QUIMQ7YL.js} +1 -1
  54. package/dist/chunk-RG3YLU6E.js +49 -0
  55. package/dist/{chunk-ST2X2GYN.js → chunk-SHZFOSXC.js} +109 -20
  56. package/dist/{chunk-CIDWNUAP.js → chunk-SOXFMMXC.js} +8 -2
  57. package/dist/chunk-TDTYV6QI.js +119 -0
  58. package/dist/{chunk-FW6BD2NO.js → chunk-TUXQ32WE.js} +457 -121
  59. package/dist/{chunk-WQ23REYT.js → chunk-VGCDRK3S.js} +1 -1
  60. package/dist/{chunk-U5AT3TD4.js → chunk-X4W64MHU.js} +1735 -1858
  61. package/dist/chunk-XBQA4I36.js +92 -0
  62. package/dist/chunk-XGOV6KV4.js +413 -0
  63. package/dist/{chunk-OQS4GUSI.js → chunk-XIFQIFSZ.js} +210 -661
  64. package/dist/{chunk-XBIDMHSK.js → chunk-XLXPVN3Y.js} +2 -34
  65. package/dist/chunk-ZJMLQ37G.js +90 -0
  66. package/dist/{client-IRKAMDYZ.js → client-MU4EUDZV.js} +3 -3
  67. package/dist/{commands-FIL5ET6P.js → commands-UOOTMBKA.js} +145 -33
  68. package/dist/{compat-R3UTFQ5Q.js → compat-Q5DVPITN.js} +6 -6
  69. package/dist/constants-DJFOFZ44.js +24 -0
  70. package/dist/{dashboardIssue-WYYMBFIB.js → dashboardIssue-KBKLQMGC.js} +105 -23
  71. package/dist/{diff-GZA2774U.js → diff-I4SNUHU3.js} +1 -1
  72. package/dist/domains-VRT4BRVZ.js +14 -0
  73. package/dist/env-UN6KGZ7S.js +15 -0
  74. package/dist/envCheck-KUPTORFQ.js +71 -0
  75. package/dist/{extract-NKGKLIVT.js → extract-6WG265BL.js} +14 -3
  76. package/dist/{fetch-QYTUJVG2.js → fetch-PJ3SC32M.js} +4 -4
  77. package/dist/{firstTouch-QQTJH4QX.js → firstTouch-COJCMOB3.js} +1 -1
  78. package/dist/fix-3EDWNKW3.js +220 -0
  79. package/dist/{handlers-GEJAVZDK.js → handlers-4Q37WIFT.js} +19 -17
  80. package/dist/hook-NA66XJLC.js +412 -0
  81. package/dist/{http-ETPYMBGD.js → http-25GJ6SKK.js} +2078 -510
  82. package/dist/{ingestQueue-VKN4KBIF.js → ingestQueue-FSVJC3AA.js} +14 -12
  83. package/dist/{install-EK3W2COX.js → install-G2HHXYJ2.js} +6 -6
  84. package/dist/{installSkill-VQJWJVCL.js → installSkill-5TIMYIJZ.js} +21 -3
  85. package/dist/manifest-ENMIC54B.js +13 -0
  86. package/dist/{mcp-3JHD5SUZ.js → mcp-PENLL2PK.js} +60 -32
  87. package/dist/mcpConfig-3LYPRY5Q.js +80 -0
  88. package/dist/{mcpHandlers-QFN4MCFL.js → mcpHandlers-PO7GENA7.js} +7 -7
  89. package/dist/{mcpScan-QZHWENIP.js → mcpScan-ULP626TQ.js} +306 -442
  90. package/dist/{mcpScanWorkflow-JPYCBGC4.js → mcpScanWorkflow-27O44FOE.js} +29 -12
  91. package/dist/{mcpStack-BYJCZNIV.js → mcpStack-ZNYIFXZ5.js} +1 -1
  92. package/dist/model-ASMZWLM7.js +214 -0
  93. package/dist/{packages-THVFMWFP.js → packages-RWF2LXST.js} +3 -5
  94. package/dist/{plan-V4OEI3J4.js → plan-SDILIDZT.js} +23 -21
  95. package/dist/{planView-JRXGCG2N.js → planView-ZAKRTGVS.js} +4 -1
  96. package/dist/{policy-3P6BGRDN.js → policy-JAGNMQJI.js} +5 -5
  97. package/dist/probe-3IK646WN.js +610 -0
  98. package/dist/publicUpgrades-WTBGLDIK.js +42 -0
  99. package/dist/{references-6N7VEPHT.js → references-I6Y4BSRR.js} +3 -1
  100. package/dist/{release-4U4ABNSJ.js → release-7IU2R4YG.js} +5 -5
  101. package/dist/{remote-YGCXYI4V.js → remote-FN6225AH.js} +13 -3
  102. package/dist/rename-OS6JYBZQ.js +204 -0
  103. package/dist/{reportRuns-CKQQYLY4.js → reportRuns-O4X7ZFTX.js} +15 -2
  104. package/dist/safeFetch-COERXGFN.js +19 -0
  105. package/dist/{sandbox-MEZLAODF.js → sandbox-SAPSRKEI.js} +4 -4
  106. package/dist/sarif-WLVJFJ7U.js +125 -0
  107. package/dist/server-OLTY3NYT.js +59 -0
  108. package/dist/{surface-V62GE554.js → surface-7MHE3RL7.js} +18 -16
  109. package/dist/{surface-YL6O67W3.js → surface-JS2XU3U3.js} +5 -3
  110. package/dist/{surfaceHandlers-X2IZKA3E.js → surfaceHandlers-4B6TMZ34.js} +5 -5
  111. package/dist/{typecheck-X7U5ELMR.js → typecheck-NGOKNAJY.js} +23 -9
  112. package/dist/{uninstall-P7RGBX6G.js → uninstall-I2IJP5JZ.js} +30 -4
  113. package/dist/{upgrade-NQ5IWFZB.js → upgrade-FS26DJGG.js} +86 -32
  114. package/dist/{upgradePlan-A4LRKLEG.js → upgradePlan-ZS5IOVMZ.js} +46 -11
  115. package/dist/upkeepHandler-VFGYXJKB.js +13 -0
  116. package/dist/{userConfig-Z3GQE776.js → userConfig-ICASHI47.js} +1 -1
  117. package/dist/{watch-DGFMWUDL.js → watch-OCNEUXG6.js} +24 -34
  118. package/package.json +3 -2
  119. package/templates/skill-instructions.md +22 -3
  120. package/dist/chunk-6AAEVQ7I.js +0 -6336
  121. package/dist/chunk-I2JUKN6Z.js +0 -24
  122. package/dist/server-KFUD5X57.js +0 -55
@@ -0,0 +1,181 @@
1
+ import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
2
+ import {
3
+ init_esm_shims
4
+ } from "./chunk-I3M2KJXB.js";
5
+
6
+ // src/notify/safeHttp.ts
7
+ init_esm_shims();
8
+ import { lookup as dnsLookup } from "dns";
9
+ import https from "https";
10
+ import { isIP } from "net";
11
+ var MAX_URL = 2e3;
12
+ var TIMEOUT_MS = 1e4;
13
+ var MAX_RESPONSE = 4e3;
14
+ function v4Private(ip) {
15
+ const [a = 0, b = 0] = ip.split(".").map(Number);
16
+ return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || // carrier-grade NAT
17
+ a === 169 && b === 254 || // link-local, cloud metadata
18
+ a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 192 && b === 0 || a === 198 && (b === 18 || b === 19) || // benchmarking
19
+ a >= 224;
20
+ }
21
+ function isPrivateAddress(ip) {
22
+ const kind = isIP(ip);
23
+ if (kind === 4) return v4Private(ip);
24
+ if (kind !== 6) return true;
25
+ const v = ip.toLowerCase();
26
+ if (v === "::" || v === "::1") return true;
27
+ const mapped = /^::ffff:(.+)$/.exec(v);
28
+ if (mapped) {
29
+ const rest = mapped[1];
30
+ if (isIP(rest) === 4) return v4Private(rest);
31
+ const [hi = "0", lo = "0"] = rest.split(":");
32
+ const n = parseInt(hi, 16) << 16 | parseInt(lo, 16);
33
+ return v4Private([n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255].join("."));
34
+ }
35
+ return /^f[cd]/.test(v) || /^fe[89ab]/.test(v) || v.startsWith("ff");
36
+ }
37
+ var SLACK_HOSTS = /* @__PURE__ */ new Set(["hooks.slack.com"]);
38
+ var DISCORD_HOSTS = /* @__PURE__ */ new Set([
39
+ "discord.com",
40
+ "discordapp.com",
41
+ "ptb.discord.com",
42
+ "canary.discord.com"
43
+ ]);
44
+ var TEAMS_SUFFIXES = [".logic.azure.com", ".powerplatform.com", ".powerautomate.com"];
45
+ function validateChannelUrl(kind, raw) {
46
+ if (typeof raw !== "string" || !raw.trim() || raw.length > MAX_URL)
47
+ return { ok: false, error: "Paste the full webhook URL." };
48
+ let url;
49
+ try {
50
+ url = new URL(raw.trim());
51
+ } catch {
52
+ return { ok: false, error: "That is not a valid URL." };
53
+ }
54
+ if (url.protocol !== "https:") return { ok: false, error: "The URL must start with https://." };
55
+ if (url.username || url.password)
56
+ return { ok: false, error: "The URL must not contain a username or password." };
57
+ const host = url.hostname.toLowerCase();
58
+ if (kind === "slack") {
59
+ if (!SLACK_HOSTS.has(host) || !/^\/(services|triggers)\//.test(url.pathname)) {
60
+ return {
61
+ ok: false,
62
+ error: "A Slack incoming webhook URL starts with https://hooks.slack.com/services/."
63
+ };
64
+ }
65
+ } else if (kind === "discord") {
66
+ if (!DISCORD_HOSTS.has(host) || !url.pathname.startsWith("/api/webhooks/")) {
67
+ return {
68
+ ok: false,
69
+ error: "A Discord webhook URL starts with https://discord.com/api/webhooks/."
70
+ };
71
+ }
72
+ } else if (kind === "teams") {
73
+ if (host.endsWith(".webhook.office.com") || host === "outlook.office.com") {
74
+ return {
75
+ ok: false,
76
+ error: "Teams retired Office 365 connector webhooks in May 2026. Create one with the Workflows app instead."
77
+ };
78
+ }
79
+ if (!TEAMS_SUFFIXES.some((s) => host.endsWith(s))) {
80
+ return {
81
+ ok: false,
82
+ error: "Use the webhook URL a Teams Workflows flow gives you (a logic.azure.com or powerplatform.com address)."
83
+ };
84
+ }
85
+ } else {
86
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) {
87
+ return { ok: false, error: "The URL must be reachable on the public internet." };
88
+ }
89
+ if (isIP(host.replace(/^\[|\]$/g, "")) && isPrivateAddress(host.replace(/^\[|\]$/g, ""))) {
90
+ return { ok: false, error: "The URL must be reachable on the public internet." };
91
+ }
92
+ }
93
+ return { ok: true, url };
94
+ }
95
+ function safeLookup(resolve = dnsLookup) {
96
+ return (hostname, options, callback) => {
97
+ resolve(hostname, { all: true }, (err, addresses) => {
98
+ if (err) return callback(err, []);
99
+ const list = addresses;
100
+ if (!list.length || list.some((a) => isPrivateAddress(a.address))) {
101
+ const e = Object.assign(new Error(`${hostname} resolves to a private network address`), {
102
+ code: "EPRIVATE"
103
+ });
104
+ return callback(e, []);
105
+ }
106
+ if (options?.all) return callback(null, list);
107
+ callback(null, list[0].address, list[0].family);
108
+ });
109
+ };
110
+ }
111
+ function postJson(url, payload, headers, opts = {}) {
112
+ return new Promise((resolvePost) => {
113
+ let settled = false;
114
+ const done = (o) => {
115
+ if (!settled) {
116
+ settled = true;
117
+ resolvePost(o);
118
+ }
119
+ };
120
+ let req;
121
+ try {
122
+ req = https.request(
123
+ url,
124
+ {
125
+ method: "POST",
126
+ headers: {
127
+ "Content-Type": "application/json",
128
+ "User-Agent": "lurq-alerts",
129
+ "Content-Length": Buffer.byteLength(payload),
130
+ ...headers
131
+ },
132
+ lookup: opts.lookup ?? safeLookup(),
133
+ timeout: opts.timeoutMs ?? TIMEOUT_MS
134
+ },
135
+ (res) => {
136
+ let body = "";
137
+ res.setEncoding("utf8");
138
+ res.on("data", (chunk) => {
139
+ if (body.length < MAX_RESPONSE) body += chunk;
140
+ });
141
+ res.on("end", () => {
142
+ const status = res.statusCode ?? 0;
143
+ done({
144
+ status,
145
+ error: status >= 200 && status < 300 ? null : `HTTP ${status}${body ? `: ${body.slice(0, 120).replace(/\s+/g, " ")}` : ""}`
146
+ });
147
+ });
148
+ res.on(
149
+ "error",
150
+ () => done({ status: res.statusCode ?? 0, error: "response interrupted" })
151
+ );
152
+ }
153
+ );
154
+ } catch (err) {
155
+ done({
156
+ status: 0,
157
+ error: err instanceof Error ? err.message : "request failed",
158
+ code: "EINVAL"
159
+ });
160
+ return;
161
+ }
162
+ req.on("timeout", () => {
163
+ req.destroy(Object.assign(new Error("timed out"), { code: "ETIMEDOUT" }));
164
+ });
165
+ req.on("error", (err) => {
166
+ done({
167
+ status: 0,
168
+ error: err.code === "EPRIVATE" ? err.message : `could not connect (${err.code ?? err.message})`,
169
+ code: err.code ?? "ECONNECT"
170
+ });
171
+ });
172
+ req.end(payload);
173
+ });
174
+ }
175
+
176
+ export {
177
+ isPrivateAddress,
178
+ validateChannelUrl,
179
+ safeLookup,
180
+ postJson
181
+ };
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
2
2
  import {
3
3
  EMBEDDING_DIM
4
- } from "./chunk-GPTKSNR6.js";
4
+ } from "./chunk-72Y2FDVS.js";
5
5
  import {
6
6
  __export,
7
7
  init_esm_shims
@@ -14,6 +14,7 @@ __export(schema_exports, {
14
14
  apiKeys: () => apiKeys,
15
15
  apiSurfaces: () => apiSurfaces,
16
16
  askSpendDaily: () => askSpendDaily,
17
+ builderScans: () => builderScans,
17
18
  claims: () => claims,
18
19
  compatEdges: () => compatEdges,
19
20
  compatVerifyQueue: () => compatVerifyQueue,
@@ -23,8 +24,15 @@ __export(schema_exports, {
23
24
  mcpChangeEvents: () => mcpChangeEvents,
24
25
  mcpContracts: () => mcpContracts,
25
26
  mcpDeployments: () => mcpDeployments,
27
+ mcpEndpointChangeAcks: () => mcpEndpointChangeAcks,
28
+ mcpEndpointChanges: () => mcpEndpointChanges,
29
+ mcpEndpointObservations: () => mcpEndpointObservations,
30
+ mcpEndpointServers: () => mcpEndpointServers,
26
31
  mcpObservations: () => mcpObservations,
32
+ mcpPins: () => mcpPins,
27
33
  mcpPublicReports: () => mcpPublicReports,
34
+ mcpRegistryServers: () => mcpRegistryServers,
35
+ mcpRemoteEndpoints: () => mcpRemoteEndpoints,
28
36
  notificationChannels: () => notificationChannels,
29
37
  notificationDeliveries: () => notificationDeliveries,
30
38
  notificationItems: () => notificationItems,
@@ -36,6 +44,7 @@ __export(schema_exports, {
36
44
  policyDecisions: () => policyDecisions,
37
45
  recommendationOutcomes: () => recommendationOutcomes,
38
46
  repoAlerts: () => repoAlerts,
47
+ repoPolicyDefaults: () => repoPolicyDefaults,
39
48
  repos: () => repos,
40
49
  resolvedClosures: () => resolvedClosures,
41
50
  seedPackages: () => seedPackages,
@@ -161,7 +170,17 @@ var packages = pgTable(
161
170
  * Null for crawler/_changes-created packages, or ones ingested before
162
171
  * dashboard accounts existed. Stamped once via a standalone WHERE ... IS NULL
163
172
  * update kept out of upsertPackage, so re-syncs can never clobber it. */
164
- firstRequestedByOwnerId: text("first_requested_by_owner_id")
173
+ firstRequestedByOwnerId: text("first_requested_by_owner_id"),
174
+ /**
175
+ * Failed API-surface extractions for `surface_attempted_version`. The worker
176
+ * stops asking once a version has failed SURFACE_MAX_ATTEMPTS times; a new
177
+ * latest version starts the count again, since it may ship types the last
178
+ * one did not. Without this, a package with no extractable surface was
179
+ * re-attempted at random every hour, forever. Not in upsertPackage's row,
180
+ * so re-syncs never reset it.
181
+ */
182
+ surfaceAttempts: integer("surface_attempts").notNull().default(0),
183
+ surfaceAttemptedVersion: text("surface_attempted_version")
165
184
  },
166
185
  (table) => [
167
186
  uniqueIndex("packages_ecosystem_name_idx").on(table.ecosystem, table.name),
@@ -211,6 +230,13 @@ var discoveryQueue = pgTable(
211
230
  * and never reaching the write that would take it off the queue. Same
212
231
  * treatment `compat_verify_queue.attempts` already gives a failing set. */
213
232
  attempts: integer("attempts").notNull().default(0),
233
+ /**
234
+ * The account whose query asked for this package (`reactive` rows only),
235
+ * credited as first requester when it is ingested. Stored here because the
236
+ * on-demand ingest used to live only in the API process's memory, and a
237
+ * deploy mid-backlog dropped both the work and the attribution.
238
+ */
239
+ requestedByOwnerId: text("requested_by_owner_id"),
214
240
  discoveredAt: ts("discovered_at").notNull().defaultNow()
215
241
  },
216
242
  (table) => [index("discovery_queue_status_idx").on(table.status)]
@@ -392,6 +418,11 @@ var ownerUsageDaily = pgTable(
392
418
  index("owner_usage_daily_owner_date_idx").on(table.ownerId, table.date)
393
419
  ]
394
420
  );
421
+ var repoPolicyDefaults = pgTable("repo_policy_defaults", {
422
+ ownerId: text("owner_id").primaryKey(),
423
+ policy: jsonb("policy").$type().notNull(),
424
+ updatedAt: ts("updated_at").notNull().defaultNow()
425
+ });
395
426
  var repos = pgTable(
396
427
  "repos",
397
428
  {
@@ -458,6 +489,21 @@ var upgradeRuns = pgTable(
458
489
  * row per package+target and updates in place.
459
490
  */
460
491
  runUrl: text("run_url").notNull().default(""),
492
+ /**
493
+ * What started the run, from GITHUB_EVENT_NAME. See `RunTrigger`.
494
+ *
495
+ * NULLABLE, and deliberately NOT in the dedup index below. Every row
496
+ * written before this column existed has no trigger, and back-filling them
497
+ * with 'schedule' would invent evidence the log then presents as fact — the
498
+ * same rule `unverified` and `analysedOnly` follow. The dashboard renders
499
+ * null as "not recorded".
500
+ *
501
+ * Keeping it out of the dedup key is not a style choice: `runUrl` is in
502
+ * that key and is NOT NULL precisely because Postgres treats NULLs as
503
+ * distinct, so a nullable column there would let every re-post insert a
504
+ * duplicate instead of updating in place.
505
+ */
506
+ trigger: text("trigger").$type(),
461
507
  createdAt: ts("created_at").notNull().defaultNow()
462
508
  },
463
509
  (table) => [
@@ -480,7 +526,10 @@ var repoAlerts = pgTable(
480
526
  id: serial("id").primaryKey(),
481
527
  /** Clerk user id, copied from the repo — alerts are read owner-scoped. */
482
528
  ownerId: text("owner_id").notNull(),
483
- repoId: integer("repo_id").notNull(),
529
+ /** Null for an account that runs `check-upgrade` on a repo lurq has no
530
+ * GitHub App installation for: the alert is keyed by `repo_full_name` from
531
+ * its upgrade runs instead, and there is no repo page to link to. */
532
+ repoId: integer("repo_id"),
484
533
  /** Denormalized so the feed renders without a join, as in `upgrade_runs`. */
485
534
  repoFullName: text("repo_full_name").notNull(),
486
535
  packageName: text("package_name").notNull(),
@@ -507,7 +556,11 @@ var repoAlerts = pgTable(
507
556
  // One alert per repo per release. A re-sync of the same version — and the
508
557
  // watcher re-syncs on every publish, including non-latest backports — must
509
558
  // not re-notify.
510
- uniqueIndex("repo_alerts_dedup_idx").on(table.repoId, table.packageName, table.toVersion)
559
+ uniqueIndex("repo_alerts_dedup_idx").on(table.repoId, table.packageName, table.toVersion),
560
+ // The same rule for alerts with no connected repo. Postgres treats NULLs as
561
+ // distinct, so the index above can never dedupe those rows; this one covers
562
+ // exactly them. Partial, so adding it cannot fail on existing rows.
563
+ uniqueIndex("repo_alerts_cli_dedup_idx").on(table.ownerId, table.repoFullName, table.packageName, table.toVersion).where(sql`repo_id is null`)
511
564
  ]
512
565
  );
513
566
  var surfaceQueue = pgTable(
@@ -532,6 +585,12 @@ var surfaceQueue = pgTable(
532
585
  specKey: text("spec_key").notNull().unique(),
533
586
  /** Failed drains bump this; the worker drops a spec that keeps failing. */
534
587
  attempts: integer("attempts").notNull().default(0),
588
+ /**
589
+ * Not eligible before this. Set on every retry with an exponential delay, so
590
+ * a spec that just failed waits instead of being retried the next cycle and
591
+ * — oldest-first — blocking every newer spec behind it. Null = ready now.
592
+ */
593
+ nextAttemptAt: ts("next_attempt_at"),
535
594
  requestedAt: ts("requested_at").notNull().defaultNow()
536
595
  },
537
596
  (table) => [index("surface_queue_requested_idx").on(table.kind, table.requestedAt)]
@@ -801,7 +860,11 @@ var mcpDeployments = pgTable(
801
860
  lastChangedAt: ts("last_changed_at")
802
861
  },
803
862
  (table) => [
804
- uniqueIndex("mcp_deployments_identity_idx").on(table.ownerId, table.serverKey, table.configFingerprint),
863
+ uniqueIndex("mcp_deployments_identity_idx").on(
864
+ table.ownerId,
865
+ table.serverKey,
866
+ table.configFingerprint
867
+ ),
805
868
  index("mcp_deployments_owner_idx").on(table.ownerId, table.lastScannedAt)
806
869
  ]
807
870
  );
@@ -854,7 +917,9 @@ var mcpPublicReports = pgTable(
854
917
  reportedAt: ts("reported_at").notNull().defaultNow()
855
918
  },
856
919
  (table) => [
857
- primaryKey({ columns: [table.registry, table.packageName, table.version, table.contentHash, table.ownerId] }),
920
+ primaryKey({
921
+ columns: [table.registry, table.packageName, table.version, table.contentHash, table.ownerId]
922
+ }),
858
923
  index("mcp_public_reports_version_idx").on(table.registry, table.packageName, table.version)
859
924
  ]
860
925
  );
@@ -924,6 +989,196 @@ var notificationChannels = pgTable(
924
989
  },
925
990
  (table) => [index("notification_channels_owner_idx").on(table.ownerId)]
926
991
  );
992
+ var builderScans = pgTable(
993
+ "builder_scans",
994
+ {
995
+ ownerId: text("owner_id").notNull(),
996
+ target: text("target").notNull(),
997
+ login: text("login").notNull(),
998
+ archetype: text("archetype").$type().notNull(),
999
+ avatarUrl: text("avatar_url").notNull(),
1000
+ // What builderStanding.ts ranks on, copied out of `profile` at save, so
1001
+ // ranking reads seven integers per builder instead of every whole report.
1002
+ repos: integer("repos").notNull(),
1003
+ active90: integer("active_90").notNull(),
1004
+ stars: integer("stars").notNull(),
1005
+ depsTracked: integer("deps_tracked").notNull(),
1006
+ depsBehind: integer("deps_behind").notNull(),
1007
+ depsMajor: integer("deps_major").notNull(),
1008
+ advisories: integer("advisories").notNull(),
1009
+ profile: jsonb("profile").$type().notNull(),
1010
+ scannedAt: ts("scanned_at").notNull().defaultNow(),
1011
+ createdAt: ts("created_at").notNull().defaultNow()
1012
+ },
1013
+ (table) => [
1014
+ primaryKey({ columns: [table.ownerId, table.target] }),
1015
+ index("builder_scans_owner_recent_idx").on(table.ownerId, table.scannedAt)
1016
+ ]
1017
+ );
1018
+ var mcpRegistryServers = pgTable(
1019
+ "mcp_registry_servers",
1020
+ {
1021
+ name: text("name").notNull(),
1022
+ version: text("version").notNull(),
1023
+ title: text("title"),
1024
+ description: text("description"),
1025
+ websiteUrl: text("website_url"),
1026
+ repositoryUrl: text("repository_url"),
1027
+ /** `active`, `deprecated`, `deleted`, … as the registry reports it. */
1028
+ status: text("status").notNull(),
1029
+ isLatest: boolean("is_latest").notNull(),
1030
+ remotes: jsonb("remotes").$type().notNull(),
1031
+ packages: jsonb("packages").$type().notNull(),
1032
+ publishedAt: ts("published_at"),
1033
+ registryUpdatedAt: ts("registry_updated_at"),
1034
+ syncedAt: ts("synced_at").notNull().defaultNow()
1035
+ },
1036
+ (table) => [
1037
+ primaryKey({ columns: [table.name, table.version] }),
1038
+ index("mcp_registry_servers_latest_idx").on(table.name).where(sql`${table.isLatest}`),
1039
+ index("mcp_registry_servers_updated_idx").on(table.registryUpdatedAt)
1040
+ ]
1041
+ );
1042
+ var mcpRemoteEndpoints = pgTable(
1043
+ "mcp_remote_endpoints",
1044
+ {
1045
+ id: serial("id").primaryKey(),
1046
+ url: text("url").notNull().unique(),
1047
+ host: text("host").notNull(),
1048
+ /** Transport the registry declares: `streamable-http` or `sse`. */
1049
+ transport: text("transport").notNull(),
1050
+ /** URL carries `{placeholders}`: never probed, answered as setup-dependent. */
1051
+ templated: boolean("templated").notNull().default(false),
1052
+ /** A maintainer asked not to be probed. Never scheduled again. */
1053
+ optedOut: boolean("opted_out").notNull().default(false),
1054
+ lastStatus: text("last_status").$type(),
1055
+ lastHttpStatus: integer("last_http_status"),
1056
+ /** Content hash of the last contract read (a key into `mcp_contracts`). */
1057
+ lastContentHash: text("last_content_hash"),
1058
+ /** Hash of the auth profile, so an auth change is detectable without a diff. */
1059
+ lastAuthHash: text("last_auth_hash"),
1060
+ auth: jsonb("auth").$type(),
1061
+ violations: jsonb("violations").$type().notNull().default([]),
1062
+ protocolMode: text("protocol_mode").$type(),
1063
+ protocolVersion: text("protocol_version"),
1064
+ serverName: text("server_name"),
1065
+ serverVersion: text("server_version"),
1066
+ finalUrl: text("final_url"),
1067
+ latencyMs: integer("latency_ms"),
1068
+ lastError: text("last_error"),
1069
+ consecutiveFailures: integer("consecutive_failures").notNull().default(0),
1070
+ probeCount: integer("probe_count").notNull().default(0),
1071
+ firstSeenAt: ts("first_seen_at").notNull().defaultNow(),
1072
+ lastProbedAt: ts("last_probed_at"),
1073
+ lastChangedAt: ts("last_changed_at"),
1074
+ nextProbeAt: ts("next_probe_at").notNull().defaultNow(),
1075
+ leaseUntil: ts("lease_until"),
1076
+ /** No live registry entry names this URL any more. Kept for history. */
1077
+ removedAt: ts("removed_at"),
1078
+ /**
1079
+ * The key an account's own `lurq mcp-scan` gives this server (`remote:<host><path>`,
1080
+ * query dropped). Joining on it is how a change the public probe sees reaches
1081
+ * every account already running the server, without anyone rescanning.
1082
+ */
1083
+ scanKey: text("scan_key")
1084
+ },
1085
+ (table) => [
1086
+ index("mcp_remote_endpoints_scan_key_idx").on(table.scanKey),
1087
+ index("mcp_remote_endpoints_due_idx").on(table.nextProbeAt).where(
1088
+ sql`${table.optedOut} = false and ${table.templated} = false and ${table.removedAt} is null`
1089
+ ),
1090
+ index("mcp_remote_endpoints_host_idx").on(table.host)
1091
+ ]
1092
+ );
1093
+ var mcpEndpointServers = pgTable(
1094
+ "mcp_endpoint_servers",
1095
+ {
1096
+ endpointId: integer("endpoint_id").notNull().references(() => mcpRemoteEndpoints.id),
1097
+ serverName: text("server_name").notNull(),
1098
+ headers: jsonb("headers").$type().notNull().default([]),
1099
+ lastSeenAt: ts("last_seen_at").notNull().defaultNow(),
1100
+ /**
1101
+ * The server's latest registry version stopped naming this URL, or the
1102
+ * server was deleted. Marked, never deleted: rows in this schema are history.
1103
+ */
1104
+ removedAt: ts("removed_at")
1105
+ },
1106
+ (table) => [
1107
+ primaryKey({ columns: [table.endpointId, table.serverName] }),
1108
+ index("mcp_endpoint_servers_name_idx").on(table.serverName)
1109
+ ]
1110
+ );
1111
+ var mcpEndpointObservations = pgTable(
1112
+ "mcp_endpoint_observations",
1113
+ {
1114
+ id: serial("id").primaryKey(),
1115
+ endpointId: integer("endpoint_id").notNull().references(() => mcpRemoteEndpoints.id),
1116
+ status: text("status").$type().notNull(),
1117
+ httpStatus: integer("http_status"),
1118
+ contentHash: text("content_hash"),
1119
+ authHash: text("auth_hash"),
1120
+ violations: jsonb("violations").$type().notNull().default([]),
1121
+ protocolVersion: text("protocol_version"),
1122
+ error: text("error"),
1123
+ probeCount: integer("probe_count").notNull().default(1),
1124
+ firstSeenAt: ts("first_seen_at").notNull().defaultNow(),
1125
+ lastSeenAt: ts("last_seen_at").notNull().defaultNow()
1126
+ },
1127
+ (table) => [
1128
+ index("mcp_endpoint_observations_endpoint_idx").on(table.endpointId, table.firstSeenAt)
1129
+ ]
1130
+ );
1131
+ var mcpEndpointChanges = pgTable(
1132
+ "mcp_endpoint_changes",
1133
+ {
1134
+ id: serial("id").primaryKey(),
1135
+ endpointId: integer("endpoint_id").notNull().references(() => mcpRemoteEndpoints.id),
1136
+ kind: text("kind").$type().notNull(),
1137
+ fromKey: text("from_key").notNull(),
1138
+ toKey: text("to_key").notNull(),
1139
+ severity: text("severity").$type().notNull(),
1140
+ summary: text("summary").notNull(),
1141
+ diff: jsonb("diff").$type(),
1142
+ createdAt: ts("created_at").notNull().defaultNow()
1143
+ },
1144
+ (table) => [
1145
+ uniqueIndex("mcp_endpoint_changes_pair_idx").on(
1146
+ table.endpointId,
1147
+ table.kind,
1148
+ table.fromKey,
1149
+ table.toKey
1150
+ ),
1151
+ index("mcp_endpoint_changes_endpoint_idx").on(table.endpointId, table.createdAt),
1152
+ index("mcp_endpoint_changes_created_idx").on(table.createdAt)
1153
+ ]
1154
+ );
1155
+ var mcpPins = pgTable(
1156
+ "mcp_pins",
1157
+ {
1158
+ id: serial("id").primaryKey(),
1159
+ ownerId: text("owner_id").notNull(),
1160
+ endpointId: integer("endpoint_id").notNull().references(() => mcpRemoteEndpoints.id),
1161
+ /** Null when the contract was not readable without credentials at pin time. */
1162
+ contentHash: text("content_hash"),
1163
+ authHash: text("auth_hash"),
1164
+ note: text("note"),
1165
+ pinnedAt: ts("pinned_at").notNull().defaultNow(),
1166
+ removedAt: ts("removed_at")
1167
+ },
1168
+ (table) => [
1169
+ uniqueIndex("mcp_pins_owner_endpoint_idx").on(table.ownerId, table.endpointId),
1170
+ index("mcp_pins_endpoint_idx").on(table.endpointId)
1171
+ ]
1172
+ );
1173
+ var mcpEndpointChangeAcks = pgTable(
1174
+ "mcp_endpoint_change_acks",
1175
+ {
1176
+ ownerId: text("owner_id").notNull(),
1177
+ changeId: integer("change_id").notNull().references(() => mcpEndpointChanges.id),
1178
+ acknowledgedAt: ts("acknowledged_at").notNull().defaultNow()
1179
+ },
1180
+ (table) => [primaryKey({ columns: [table.ownerId, table.changeId] })]
1181
+ );
927
1182
 
928
1183
  export {
929
1184
  packages,
@@ -940,6 +1195,7 @@ export {
940
1195
  recommendationOutcomes,
941
1196
  askSpendDaily,
942
1197
  ownerUsageDaily,
1198
+ repoPolicyDefaults,
943
1199
  repos,
944
1200
  upgradeRuns,
945
1201
  repoAlerts,
@@ -961,5 +1217,13 @@ export {
961
1217
  mcpPublicReports,
962
1218
  notificationPreferences,
963
1219
  notificationChannels,
1220
+ builderScans,
1221
+ mcpRegistryServers,
1222
+ mcpRemoteEndpoints,
1223
+ mcpEndpointServers,
1224
+ mcpEndpointObservations,
1225
+ mcpEndpointChanges,
1226
+ mcpPins,
1227
+ mcpEndpointChangeAcks,
964
1228
  schema_exports
965
1229
  };