@sleep2agi/commhub-server 0.9.0-preview.4 → 0.9.0-preview.41

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 +125 -7
  2. package/bin/commhub.ts +10 -2
  3. package/package.json +5 -5
  4. package/src/ack-create-request-transport.test.ts +159 -0
  5. package/src/ack-create-request.test.ts +331 -0
  6. package/src/admin-networks-http.test.ts +84 -0
  7. package/src/alias-filter.test.ts +72 -0
  8. package/src/alias-filter.ts +32 -0
  9. package/src/api-host-supervisors-fallback.test.ts +267 -0
  10. package/src/api-nodes-shape.test.ts +107 -13
  11. package/src/auth.ts +93 -14
  12. package/src/avatar-validate.test.ts +163 -0
  13. package/src/avatar-validate.ts +103 -0
  14. package/src/boot-side-effect.test.ts +124 -0
  15. package/src/config-apply-sec1.test.ts +107 -0
  16. package/src/config-apply-validate.test.ts +92 -0
  17. package/src/config-apply-validate.ts +81 -3
  18. package/src/create-node-tool-schema.test.ts +39 -0
  19. package/src/create-node-validate.test.ts +80 -1
  20. package/src/create-node-validate.ts +49 -5
  21. package/src/create-node.ts +51 -1
  22. package/src/dashboard-slash-routing-http.test.ts +103 -0
  23. package/src/db-adapter-guard.test.ts +85 -0
  24. package/src/db-adapter.ts +64 -23
  25. package/src/db-timestamp.test.ts +38 -0
  26. package/src/db-timestamp.ts +15 -0
  27. package/src/db.ts +654 -33
  28. package/src/delivered-without-push.test.ts +140 -0
  29. package/src/desktop-message-seam.test.ts +150 -0
  30. package/src/desktop-message.test.ts +368 -0
  31. package/src/ext-token-shared.test.ts +60 -0
  32. package/src/ext-validation-regression.test.ts +131 -0
  33. package/src/external-schedule-edits-http.test.ts +316 -0
  34. package/src/external-schedule-edits.ts +314 -0
  35. package/src/file-download-authz-dev-open.test.ts +135 -0
  36. package/src/file-download-authz.test.ts +403 -0
  37. package/src/file-download-date-bucket.test.ts +436 -0
  38. package/src/file-network-scope-dev-open.test.ts +97 -0
  39. package/src/file-network-scope.test.ts +731 -0
  40. package/src/health-redaction.test.ts +637 -0
  41. package/src/inbox-count-sse.test.ts +310 -0
  42. package/src/inbox-count.ts +29 -0
  43. package/src/index.ts +15 -2187
  44. package/src/lifecycle-guard.ts +50 -0
  45. package/src/list-host-supervisors.test.ts +314 -0
  46. package/src/mcp-write-network-resolution.test.ts +429 -0
  47. package/src/network-name-http.test.ts +71 -0
  48. package/src/network-scope.ts +101 -0
  49. package/src/node-attrs-validate.ts +88 -0
  50. package/src/node-attrs.test.ts +213 -0
  51. package/src/node-id-alias.test.ts +147 -0
  52. package/src/node-lifecycle-controllable.test.ts +64 -0
  53. package/src/node-lifecycle-controllable.ts +51 -0
  54. package/src/observer-avatar-http.test.ts +424 -0
  55. package/src/observer-push.test.ts +252 -0
  56. package/src/package-version-consistency.test.ts +25 -0
  57. package/src/peer-reply-atomic.test.ts +455 -0
  58. package/src/peer-reply-capability.test.ts +70 -0
  59. package/src/probe-validate.test.ts +221 -0
  60. package/src/probe-validate.ts +104 -0
  61. package/src/probe.test.ts +80 -0
  62. package/src/probe.ts +200 -0
  63. package/src/push.ts +163 -7
  64. package/src/redact-tokens.test.ts +65 -0
  65. package/src/redact-tokens.ts +40 -0
  66. package/src/reply-target-derivation.test.ts +96 -0
  67. package/src/require-explicit-test-db.ts +9 -0
  68. package/src/resolve-port.test.ts +57 -0
  69. package/src/resolve-port.ts +44 -0
  70. package/src/response-charset.test.ts +130 -0
  71. package/src/rest-explicit-columns-http.test.ts +212 -0
  72. package/src/rest-identity.test.ts +129 -0
  73. package/src/rest-identity.ts +100 -0
  74. package/src/rest-projections.ts +67 -0
  75. package/src/rest-write-network-resolution.test.ts +133 -0
  76. package/src/runtime-label.test.ts +22 -0
  77. package/src/runtime-label.ts +21 -0
  78. package/src/scheduled-run-terminal.test.ts +268 -0
  79. package/src/scheduled-tasks-http.test.ts +479 -0
  80. package/src/scheduled-tasks.ts +549 -0
  81. package/src/send-reply-agent-warning.test.ts +202 -0
  82. package/src/send-reply-alias-optional.test.ts +131 -0
  83. package/src/send-reply-attachments.test.ts +437 -0
  84. package/src/server.ts +3664 -0
  85. package/src/shared/external-schedule-contract.ts +60 -0
  86. package/src/shared/probe-host-allowlist-drift.test.ts +54 -0
  87. package/src/shared/probe-host-allowlist.ts +128 -0
  88. package/src/side-thread-command-transport.test.ts +302 -0
  89. package/src/side-thread-command-transport.ts +318 -0
  90. package/src/side-thread-http-integration.test.ts +229 -0
  91. package/src/side-thread-http.ts +504 -0
  92. package/src/side-thread-production.test.ts +72 -0
  93. package/src/side-thread-production.ts +118 -0
  94. package/src/side-thread.test.ts +1067 -0
  95. package/src/side-thread.ts +2495 -0
  96. package/src/skillhub-http.test.ts +88 -0
  97. package/src/start-node.test.ts +109 -0
  98. package/src/stop-delete-node.test.ts +952 -0
  99. package/src/task-auth-origin-http.test.ts +87 -0
  100. package/src/task-auth-origin.test.ts +24 -0
  101. package/src/task-auth-origin.ts +7 -0
  102. package/src/task-consumption.test.ts +471 -0
  103. package/src/task-diagnostic-http.test.ts +145 -0
  104. package/src/task-diagnostic.test.ts +115 -0
  105. package/src/task-diagnostic.ts +118 -0
  106. package/src/task-idempotency-mcp.test.ts +142 -0
  107. package/src/task-idempotency.test.ts +60 -0
  108. package/src/task-idempotency.ts +75 -0
  109. package/src/task-lifecycle-watcher.test.ts +200 -0
  110. package/src/task-lifecycle-watcher.ts +49 -0
  111. package/src/task-network-resolution-http.test.ts +137 -0
  112. package/src/tasks-pagination-http.test.ts +137 -0
  113. package/src/tools.ts +2994 -191
  114. package/src/update-provider.test.ts +234 -0
  115. package/src/upload-early-reject-guard.test.ts +155 -0
  116. package/src/uploads-http.test.ts +92 -14
  117. package/src/uploads.test.ts +66 -0
  118. package/src/uploads.ts +130 -5
  119. package/src/user-inbox-read-http.test.ts +220 -0
  120. package/src/user-inbox-schema.test.ts +53 -0
  121. package/src/vault.test.ts +200 -0
  122. package/src/vault.ts +156 -0
package/src/db-adapter.ts CHANGED
@@ -212,10 +212,28 @@ export class PgAdapter implements DbAdapter {
212
212
  // Factory
213
213
  // ════════════════════════════════════════════
214
214
 
215
+ export type DbTarget =
216
+ | { kind: "postgres"; url: string }
217
+ | { kind: "sqlite"; path: string };
218
+
215
219
  /**
216
- * Create the appropriate adapter based on environment.
217
- * - DATABASE_URL starts with "postgres://" → PgAdapter
218
- * - Otherwise → SQLiteAdapter with COMMHUB_DB or default path
220
+ * Refuse every inherited DATABASE_URL under the Bun test environment before
221
+ * adapter selection, logging, DNS, or construction. A test that intentionally
222
+ * exercises PostgreSQL needs a separately reviewed isolated harness; accepting
223
+ * a shell/CI DATABASE_URL here could connect a unit test to production.
224
+ */
225
+ export function assertSafeTestDatabaseEnv(env: NodeJS.ProcessEnv = process.env): void {
226
+ if (env.NODE_ENV === "test" && env.DATABASE_URL) {
227
+ throw new Error(
228
+ "[commhub] REFUSING to honor inherited DATABASE_URL under NODE_ENV=test.\n" +
229
+ " Tests must unset DATABASE_URL and set COMMHUB_DB to an isolated path.\n" +
230
+ " This guard is fail-closed and has no opt-in bypass."
231
+ );
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Pure target selection after the inherited-DATABASE_URL safety gate.
219
237
  *
220
238
  * Test-default-prod GUARD: when `NODE_ENV === "test"` (set automatically by
221
239
  * `bun test`) AND `COMMHUB_DB` is unset, we REFUSE to fall through to the
@@ -227,27 +245,20 @@ export class PgAdapter implements DbAdapter {
227
245
  * pre-existing history of test pollution in the same DB). The guard makes
228
246
  * the failure mode loud + actionable instead of silent + destructive.
229
247
  *
230
- * The guard does NOT fire when:
231
- * - COMMHUB_DB is set (the test runner's documented contract); or
232
- * - NODE_ENV is unset (production server) or any non-"test" value.
233
- *
234
- * Operators who run `bun -e` snippets that touch the DB are expected to
235
- * `COMMHUB_DB=/tmp/...` themselves — `bun -e` doesn't set NODE_ENV, so the
236
- * guard can't catch that case. The rule (never read or write the
237
- * production hub database) is documented in CLAUDE.md.
248
+ * The production default path is a capability, not a generic fallback:
249
+ * only canonical server entrypoints set COMMHUB_SERVER=1 before importing
250
+ * the database graph. Tests, scripts, and `bun -e` probes must name an
251
+ * explicit COMMHUB_DB (or a reviewed PostgreSQL DATABASE_URL), so forgetting
252
+ * one fails before mkdir/open/write instead of touching the live Hub.
238
253
  */
239
- export function createAdapter(): DbAdapter {
240
- const dbUrl = process.env.DATABASE_URL;
254
+ function resolveDatabaseTargetAfterGuard(env: NodeJS.ProcessEnv): DbTarget {
255
+ const dbUrl = env.DATABASE_URL;
241
256
  if (dbUrl && (dbUrl.startsWith("postgres://") || dbUrl.startsWith("postgresql://"))) {
242
- console.log("[commhub] database: PostgreSQL");
243
- return new PgAdapter(dbUrl);
257
+ return { kind: "postgres", url: dbUrl };
244
258
  }
245
- // Default: SQLite
246
- const { mkdirSync } = require("fs");
247
- const { dirname } = require("path");
248
259
 
249
260
  // Test-default-prod GUARD (see docblock above).
250
- if (process.env.NODE_ENV === "test" && !process.env.COMMHUB_DB) {
261
+ if (env.NODE_ENV === "test" && !env.COMMHUB_DB) {
251
262
  throw new Error(
252
263
  "[commhub] REFUSING to open the default SQLite database under NODE_ENV=test.\n" +
253
264
  " Tests must explicitly set COMMHUB_DB to a throwaway path so they don't\n" +
@@ -257,10 +268,40 @@ export function createAdapter(): DbAdapter {
257
268
  );
258
269
  }
259
270
 
260
- const dbPath = process.env.COMMHUB_DB || `${process.env.HOME}/.commhub/commhub.db`;
261
- mkdirSync(dirname(dbPath), { recursive: true });
262
- console.log(`[commhub] database: ${dbPath}`);
263
- const rawDb = new Database(dbPath);
271
+ if (!env.COMMHUB_DB && env.COMMHUB_SERVER !== "1") {
272
+ throw new Error(
273
+ "[commhub] REFUSING to open the default production SQLite database from a non-server entrypoint.\n" +
274
+ " Scripts and probes must set an explicit COMMHUB_DB path. The canonical\n" +
275
+ " Hub entrypoints set COMMHUB_SERVER=1 before loading the database."
276
+ );
277
+ }
278
+
279
+ return { kind: "sqlite", path: env.COMMHUB_DB || `${env.HOME}/.commhub/commhub.db` };
280
+ }
281
+
282
+ /** Pure public resolver used by tests without constructing an adapter. */
283
+ export function resolveDatabaseTarget(env: NodeJS.ProcessEnv = process.env): DbTarget {
284
+ assertSafeTestDatabaseEnv(env);
285
+ return resolveDatabaseTargetAfterGuard(env);
286
+ }
287
+
288
+ /**
289
+ * Create the appropriate adapter based on environment.
290
+ * The inherited-DATABASE_URL guard is deliberately the first executable line.
291
+ */
292
+ export function createAdapter(): DbAdapter {
293
+ assertSafeTestDatabaseEnv(process.env);
294
+ const target = resolveDatabaseTargetAfterGuard(process.env);
295
+ if (target.kind === "postgres") {
296
+ console.log("[commhub] database: PostgreSQL");
297
+ return new PgAdapter(target.url);
298
+ }
299
+
300
+ const { mkdirSync } = require("fs");
301
+ const { dirname } = require("path");
302
+ mkdirSync(dirname(target.path), { recursive: true });
303
+ console.log(`[commhub] database: ${target.path}`);
304
+ const rawDb = new Database(target.path);
264
305
  rawDb.exec("PRAGMA journal_mode=WAL");
265
306
  rawDb.exec("PRAGMA busy_timeout=5000");
266
307
  return new SQLiteAdapter(rawDb);
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { parseDbTimestampMs } from "./db-timestamp.js";
3
+
4
+ const NAIVE = "2026-08-27 11:23:54";
5
+ const EXPECTED = Date.UTC(2026, 7, 27, 11, 23, 54);
6
+
7
+ describe("parseDbTimestampMs", () => {
8
+ // ⚠ 这条在 **TZ=UTC 的环境**下没有分辨力:裸 Date.parse 在 UTC 下恰好正确。
9
+ // CI runner 与容器默认就是 UTC(本仓 Docker 套件亦然),所以缺陷在常规跑法里
10
+ // 恒绿。(更正:Bun 是尊重 TZ 的,不存在"bun test 固定 UTC"——通信评审牛 #1279
11
+ // 独审③ 独立复跑证伪了我最初的说法。)真正稳定见红的门是下面那条强制子进程。
12
+ it("treats a naive SQLite timestamp as UTC", () => {
13
+ expect(parseDbTimestampMs(NAIVE)).toBe(EXPECTED);
14
+ });
15
+
16
+ // 分辨力关键:宿主是 UTC 时,裸 Date.parse 与正确写法结果相同,断言恒绿。
17
+ // 强制子进程 TZ=Asia/Shanghai,让缺陷在任何 CI 时区下都显形。
18
+ it("is unaffected by a non-UTC host timezone (child process TZ=Asia/Shanghai)", () => {
19
+ const r = Bun.spawnSync({
20
+ cmd: ["bun", "-e",
21
+ `const {parseDbTimestampMs} = await import("${import.meta.dir}/db-timestamp.ts");` +
22
+ `console.log(parseDbTimestampMs("${NAIVE}"));`],
23
+ env: { ...process.env, TZ: "Asia/Shanghai" },
24
+ });
25
+ expect(Number(r.stdout.toString().trim())).toBe(EXPECTED);
26
+ });
27
+
28
+ it("keeps explicit-offset timestamps intact", () => {
29
+ expect(parseDbTimestampMs("2026-08-27T11:23:54Z")).toBe(EXPECTED);
30
+ expect(parseDbTimestampMs("2026-08-27T19:23:54+08:00")).toBe(EXPECTED);
31
+ });
32
+
33
+ it("an online window of 5min holds for a heartbeat written 1s ago", () => {
34
+ const now = Date.now();
35
+ const iso = new Date(now - 1000).toISOString().slice(0, 19).replace("T", " ");
36
+ expect(now - parseDbTimestampMs(iso)).toBeLessThanOrEqual(5 * 60_000);
37
+ });
38
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SQLite 里的时间戳列(sessions.last_seen_at、tasks.created_at 等)存的是
3
+ * **无时区标记的 UTC 串**(`YYYY-MM-DD HH:MM:SS`,由 SQLite `datetime('now')` 写入)。
4
+ *
5
+ * 裸 `Date.parse("2026-08-27 11:23:54")` 会按**宿主机本地时区**解释这个串。
6
+ * 生产机 TZ=CST+0800 时,它比真实 UTC 时刻早 8 小时——所以 `now - parsed`
7
+ * 恒为 +480 分钟,任何「最近 N 秒内算在线」的判据都会恒判离线。
8
+ * 在 TZ=UTC 的机器上两种写法结果相同,缺陷不会显形。
9
+ */
10
+ export function parseDbTimestampMs(value: string): number {
11
+ const s = String(value).trim();
12
+ // 已带时区标记(ISO 的 Z / ±HH:MM)就原样交给 Date
13
+ if (/[Zz]$|[+-]\d{2}:?\d{2}$/.test(s)) return Date.parse(s);
14
+ return Date.parse(s.replace(" ", "T") + "Z");
15
+ }